前言
本文主要是讲下call、apply、bind 的实现原理,我们都知道 call、apply、bind 三个方法是用来改变 this 指向的,但是也只局限于会用状态。具体的实现原理,却没有思考过。所以有必要研究一下。在明白起原理的情况下,才能用的更好。
call、apply 和 bind 是挂在 Function 对象上的三个方法,所以调用这三个方法的必须是一个函数。
调用示例
// thisArg 在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格
//模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
fn.call(thisArg,arg1,arg2,....)
fn.apply(thisArg,[arg1,arg2,....])
fn.bind(thisArg,arg1,arg2,....)()
在这里先补充点this指向的相关知识
关于This指向补充
- 在浏览器里,在全局范围内this 指向window对象
- 在函数中,this永远指向最后调用他的那个对象;
- 构造函数中,this指向new出来的那个新的对象;
- call、apply、bind 中的this被强绑定在指定的那个对象上;
- 箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前几种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,指向是不可更改的
总结:谁调用了该函数,函数作用域就指向谁。箭头函数永远指向它的父级作用域
注意:因为这call、apply、bind这几个方法底层的源码都是通过c++来实现,我们这里只是用js来模拟实现
实现call方法
-
call方法的使用是通过函数调用,第一个参数是我们需要指定的this指向,后面可以是单独给出的一个或多个参数。
-
call方法的作用无非就是执行一个函数的时候,把这个函数的作用域对象指向新的作用域对象,接下来就可以围绕着这段话去实现一个自己的call方法
Function.prototype.mycall = function (thisArg, ...params) {
//定义对象的唯一属性值
let fn=Symbol('fn')
//传进来的作用域如果是null或者undefined自动替换为指向全局对象
//传进来的作用域可能是Number、String等类型,但是我们又要它是对象,所以我们利用Object强制转换下
let newArg =(thisArg !== undefined && thisArg !== null) ? Object(thisArg) : window;
//this指向的就是调用mycall的那个函数,把this添加到新作用域的属性fn上。在新作用域对象上调用fn,fn的this
//指向就会指向newArg
newArg[fn] = this;
//上面...params的意思是es6用的剩余参数,这时params是个数组,然后我们需要展开来传到fn里面,下面...params
//的作用是展开参数
let result=newArg[fn](...params);
//防止作用域污染,删除该属性
delete newArg[fn];
return result;
};
let newObj={
name:'小张'
}
let obj={
name:'小王',
fn:function(num){
console.log(this.name)
console.log(num)
}
}
obj.fn.mycall(newObj,1)
上面打印的name就是小张,num是1
实现apply方法
- apply 和 call 的功能完全一致,唯一不同就是参数形式不同而已,只是apply方法后面的参数只能是一个数组。
Function.prototype.myapply = function (thisArg, params) {
let fn=Symbol('fn')
let newArg =(thisArg !== undefined && thisArg !== null) ? Object(thisArg) : window;
newArg[fn] = this;
//必须为数组类型,否则抛出错误
if(!Array.isArray(params)){
throw new TypeError('params not Array')
}
let result=newArg[fn](...params);
delete newArg[fn];
return result;
};
let newObj={
name:'小张'
}
let obj={
name:'小王',
fn:function(num){
console.log(this.name)
console.log(num)
}
}
obj.fn.mycall(newObj,[1])
上面打印的name就是小张,num是1
实现bind方法
- bind与call的不同是它先返回的是一个函数,再让我们调用这个函数。
- bind() 方法创建一个新的函数作为返回值,在 bind() 被调用时,这个新函数的 this 被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用
Function.prototype.mybind=function(thisArg,...params){
let fn=Symbol('fn')
let newArg=(thisArg!==undefined && thisArg !==null)?Object(thisArg):window
newArg[fn]=this
return function(...args){
//拼接bind的参数列表和新函数的参数列表
let newArr=[...params,...args]
let proxy= newArg[fn](...newArr)
delete newArg[fn]
return proxy
}
}
let newObj={
name:'小张'
}
let obj={
name:'小王',
fn:function(num1,num2){
console.log(this.name)
console.log(num1,num2)
}
}
obj.fn.mybind(newObj,1)(2)
上面打印的name就是小张,num1是1 num2是2
总结
call、apply 和 bind 的功能非常相似,那什么时候该使用 call、apply,什么时候使用 bind 呢?其实这个也没有明确的规定,只要知其原理,相互转化何其简单,主要的区别就是 call、apply 绑定后是立即执行,而 bind 绑定后是返回待调用的。