本文首发于知乎专栏——前端面试题汇总,大家可以通过文章底部的阅读原来来访问原文地址
什么是手写源码
平时面试时经常会遇到让手写一个已有方法的实现,其实面试官是想考察你对于JS底层逻辑是否熟悉,经常面试会出的会在下面:
- call、apply、bind
- promise
- requireJS
- vue-router
- Array.prototype.indexOf()
回顾一下call、apply、bind的用法
代码语言:javascript复制function sayHelloTo (to) {
console.log(`${this.name} say hello to ${to}`)
}
var Jerry = {
name: 'Jerry'
}
sayHelloTo.call(Jerry, 'Tom')
//Jerry say hello to Tom.
var Foo = {
name: 'Foo'
}
sayHelloTo.apply(Foo, ['Bar'])
//Foo say hello to Bar.
var XYZ = {
name: 'XYZ'
}
var say = sayHelloTo.bind(XYZ)
say('ABC')
//XYZ say hello to ABC.
先看call的实现
做一个myCall
,来模拟call
。
Function.prototype.myCall = function(context, ...args) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
let fnSymbol = Symbol()
context[fnSymbol] = this
let fn = context[fnSymbol] (...args)
delete context[fnSymbol]
return fn
}
核心思路是:
- 为传入的
context
扩展一个属性,将原函数指向这个属性 - 将
context
之外的所有参数全部传递给这个新属性,并将运行结果返回。
一些细节:
- 利用rest 参数(
…args
)可以存储函数多余的参数 - 为传入的
context
扩展参数扩展新属性使用了`Symbol()`数据类型,这样确保不会影响到传入的context
,因为Symbol值一定是独一无二的。 - 用扩展运算符(
…
)将原来是数组的args
转发为逗号分隔一个个参数传入到函数中
为什么能找到this.name
呢?因为context[fnSymbol]
中的this
指向的是context
。
手写apply方法
代码语言:javascript复制Function.prototype.myApply = function(context, args) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
let fnSymbol = Symbol()
context[fnSymbol] = this
let fn = context[fnSymbol] (...args)
return fn
}
思路和call
是一样的只是传参不同方式而已
手写bind方法
代码语言:javascript复制Function.prototype.myBind = function(context) {
// 判断是否是undefined和null
if (typeof context === "undefined" || context === null) {
context = window;
}
self = this;
return function(...args) {
return self.apply(context, args);
}
}