写在前面
在面试中常被问到的一个问题:apply和call有什么区别,能手动实现他们吗?
它们都是用来改变函数中 this 的指向的,区别只在于传参,apply传入一个数组,而call传入多个参数。
至于bind也是用来改变 this指向的,但是它不会立即执行,同时,它还会将所有传入bind()方法中的实参(第一个参数之后的参数)与this一起绑定。
1. 实现call
Funciton.prototype.myCall = function(context){
// 先判断调用call的对象(this)是不是函数,如果不是,则抛出类型错误
if(typeof this !== 'function'){
throw new TypeError('not a function')
}
// 当第一个参数,也就是新的上下文对象传入的 !!context === false 时,将上下文置为 window
context = context || window
/**
* 定义参数,已知第一个参数是新的上下文对象,那么就需要将第一个参数剥离出去,
* 由于arguments是类数组,不能直接用数组原型上的slice方法操作,所以先要转换成数组。还可选:
* var args = Array.prototype.slice.call(arguments, 1)
* var args = Array.from(arguments).slice(1)
* var args = [].shift.call(arguments)
*/
var args = [...arguments].slice(1)
/**
* 这一步很多人都忽略掉了,而在这里直接context.fn = this, 那么如果 context 上本来就有一个属性叫做 fn 呢?
* 明显会被覆盖掉,导致原来的属性丢失,这个时候可以用ES6中的Symbol解决,它一定是唯一的。
*/
var fn = Symbol()
// 将call的调用者 赋值给 新的上下文对象并(传入参数)执行,将执行结果赋值给result
context[fn] = this
var result = context[fn](...args)
// 这个时候新的对象被新添了一个叫做Symbol() 的属性,要删掉它
delete context[fn]
// 返回执行结果
return result
}
2. 实现apply
apply与call的主要区别在于传参方式不同,所以实现方式大致相同, 不再逐行分析
Function.prototype.myApply = function(context){
if(typeof this !== 'function'){
throw new TypeError('not a function')
}
context = context || window
var fn = Symbol()
context[fn] = this
// 先定义result
var result
// 判断是否传入了另外的参数,若有,则传入执行;若无,则直接执行, 将执行结果赋值给result
if(arguments[1]){
result = context[fn](...arguments[1])
}else{
result = conetxt[fn]()
}
delete context[fn]
return result
}
3.实现bind
bind是一个典型的 函数柯里化。查看函数柯里化相关,可以查看 www.zhangxinxu.com/wordpress/2…
Function.prototype.myBind = function(context){
if(typeof this !== 'function'){
throw new TypeError('not a function')
}
context = context || window
var args = Array.from(arguments).slice(1)
// 这里要先将当前 this 赋值给 that,因为 function 中 this 的作用域决定于被谁调用,若直接在return的函数中调用,则this指向谁不确定
var that = this
// 返回call函数
return function(F){
// 判断是否被当做构造函数实例化
if(this instanceof F){
return that.apply(this, [...args, ...arguments])
}
return that.apply(context,[...args, ...arguments])
}
}
以上就是这三个函数的实现以及个人理解了,欢迎指正。