MDN官方文档
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
注意:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
个人概述
这几个函数的共同点就在于,它们改变了函数的作用域,也就是this的指向。那什么是改变作用域呢?
打个比方。
同样的一件女装,若是穿在漂亮小姐姐的身上,你可能会肾上腺素狂飙。
那么,如果穿在我身上呢?没错,穿在一个大汉的身上。
你会为我而芳心暗动吗?
说笑而已,不要当真,我们来看一个直观的例子吧。
Demo
const obj1 = {
num: 1,
handle: function(num1, num2) {
return this.num + this.num1 + this.num2;
}
};
const obj2 = {
num: 2,
handle: function(num1, num2) {
return this.num + this.num1 + this.num2;
}
};
obj1.handle(1,2); // 1+1+2 = 4
obj2.handle(1,2); // 1+1+2 = 5
不难发现,两个对象中的handle方法在逻辑上是一模一样的,唯一不同的就是,调用它们的对象不同,致使它们的this指向不同,在obj1中,this.num = 1,在obj2中,this.num = 2。
明明是一样的代码,却需要写两次,哪怕CV工程师能忍,简洁爱好者都忍不了。
别急,这时候就轮到三位少侠上场了。
const handle = function(num1,num2) {
return this.num + num1 + num2;
}
const obj1 = { num:1 }
const obj2 = { num:2 }
console.log(handle.bind(obj1,1,2)()) // 4
console.log(handle.bind(obj2,1,2)()) // 5
console.log(handle.apply(obj1,[1,2]))// 4
console.log(handle.apply(obj2,[1,2])) // 5
console.log(handle.call(obj1,1,2)) // 4
console.log(handle.call(obj2,1,2)) // 5
就如我们之前所的,这三位少侠可以改变函数的作用域,this会指向函数内的第一个参数。
三兄弟的殊途同归
这三个函数是十分相似的,但是也有一些细节上的区分,观察上面的官方介绍以及我们的Demo不难发现。
与apply,call不同的是,bind返回的是被改变了作用域的函数。这也就是为什么上面的代码中,handle在bind之后还需要进行调用。
与bind,call不同的是,apply在接收了作为函数作用域的对象后,接下来的参数需要以数组形式传入,而不是一个一个传进去。但最终效果是一样的。
基于ES6简易实现
const fn = function(num1,num2) {
return this.num + num1 + num2;
}
const obj = { num:1 }
const obj2 = { num:2 }
Function.prototype.selfCall = function(context) {
//指定作用域
context = context || window;
//获取参数
let args = [...arguments].splice(1);
//将方法挂载到这个函数上,函数作用域自然就是在对象内了,然后进行调用
context.handle = this;
let res = context.handle(...args)
//获取到结果之后,就没必要在对象上保存这个方法了。
delete context.handle;
//返回我们获得的最终值。
return res;
}
Function.prototype.selfApply = function(context) {
//指定作用域
context = context || window;
//获取参数
let args = arguments[1];
//将方法挂载到这个函数上,函数作用域自然就是在对象内了,然后进行调用
context.handle = this;
let res = context.handle(...args)
//获取到结果之后,就没必要在对象上保存这个方法了。
delete context.handle;
//返回我们获得的最终值。
return res;
}
Function.prototype.selfBind = function(context) {
//获取函数本体
let self = this;
//指定作用域
context = context || window;
//获取参数
let args = [...arguments].splice(1);
//这里引用我们前面自己写的call函数
return function(){
return self.selfCall(context,...args)
}
}
console.log(fn.selfBind(obj,1,2)()) // 4
console.log(fn.selfBind(obj2,1,2)()) // 5
console.log(fn.selfApply(obj,[1,2]))// 4
console.log(fn.selfApply(obj2,[1,2])) // 5
console.log(fn.selfCall(obj,1,2)) // 4
console.log(fn.selfCall(obj2,1,2)) // 5js
该说的都写在注释里了,耐下心看一下吧~
题外话
既然都基于ES6了,为什么不用箭头函数去酷酷地实现呢?
千万别干这种事!
let attr = "Hello";
const fn = () => {
console.log(this.attr)
}
const obj = {
attr: "World!"
}
fn() // window环境下:Hello node环境下:undefined
fn.call(obj) // undefined
箭头函数的作用域在它声明时就已经是确定了的,不能再改变,不要踩坑咯。
第一次写文,还是能感觉到各方面的不足的,但是我实在想去睡觉了啊!就这样吧,祝安好!