说到call、apply、bind首先是改变this指向的问题(ES5中),其次它们之间的区别,最后光说不练假把式,手写call、apply、bind!
this指向
在 ES5 中,其实 this 的指向,始终坚持一个原理: this 永远指向最后调用它的那个对象(也就是说:this取什么值是在函数执行时确定的,不是在函数定义时确定的)
- 看个例子:
var name = "如来佛祖";
function a() {
var name = "孙悟空";
console.log(this.name); // 如来佛祖
console.log("inner:" + this); //inner:[object Window]
}
a();
console.log("outer:" + this); //outer:[object Window]
很简单,像上面说的 this取什么值是在函数执行时确定的,最后k看调用 a 的地方 a(),前面没有调用的对象那么就是全局对象 window,这就相当于是 window.a()这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是 undefined,那么就会报错 Uncaught TypeError: Cannot read property ‘name’ of undefined。
- 再来一个:
var name = "如来佛祖";
var a = {
name: "孙悟空",
fn: function() {
name: "猪八戒",
console.log(this.name); //孙悟空
}
};
window.a.fn();
为什么,这里会是孙悟空,而不是猪八戒呢,或者为什么不是如来佛祖呢,因为如上面的那句话:this取什么值是在函数执行时确定的,因为,window.a.fn()就表示,调用 fn 的是 a 对象,也就是说 fn 的内部的 this 是对象 a,所以name是孙悟空。
改变this 的指向
- 使用 ES6 的箭头函数
- 在函数内部使用 _this = this
- 使用 apply、call、bind
使用 ES6 的箭头函数
var name = "如来佛祖";
var a = {
name: "孙悟空",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(() => {
this.func1();
}, 100);
}
};
a.func2(); //孙悟空
在函数内部使用 _this = this
var name = "如来佛祖";
var a = {
name: "孙悟空",
func1: function() {
console.log(this.name);
},
func2: function() {
var _this = this;
setTimeout(function() {
_this.func1();
}, 100);
}
};
a.func2(); //孙悟空
使用 apply、call、bind
使用 apply
var a = {
name: "孙悟空",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(
function() {
this.func1();
}.apply(a),
100
);
}
};
a.func2();
使用 call
var a = {
name: "孙悟空",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(
function() {
this.func1();
}.apply(a),
100
);
}
};
a.func2(); //孙悟空
使用bind
var a = {
name: "孙悟空",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(
function() {
this.func1();
}.bind(a)(),
100
);
}
};
a.func2(); //孙悟空
call、apply、bind的区别
- call语法
fun.call(thisArg[, arg1[, arg2[, …]]]) - apply语法
fun.apply(thisArg, [argsArray]) - bind语法
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
apply,call,bind三者的区别
- 三者都可以改变函数的this对象指向。
- 三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。
- 三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。
- bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行
bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。
小技巧:改变参数传入方式
示例:
求数组中的最大值:
var arr=[1,10,5,8,3]; console.log(Math.max.apply(null, arr)); //10
其中Math.max函数的参数是以参数列表,如:Math.max(1,10,5,8,3)的形式传入的,因此我们没法直接把数组当做参数,但是apply方法可以将数组参数转换成列表参数传入,从而直接求数组的最大值。
call方法
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
示例:
var arr=[1,10,5,8,3]; console.log(Math.max.call(null,arr[0],arr[1],arr[2],arr[3],arr[4])); //10
采纳以参数列表的形式传入,而apply以参数数组的形式传入。
bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
示例:
var arr=[1,10,5,8,12]; var max=Math.max.bind(null,arr[0],arr[1],arr[2],arr[3]) console.log(max(arr[4])); //12,分两次传参
手写call、apply、bind
以下内容部分来自来自 Medium
- 手写call
Function.prototype.myOwnCall = function(someOtherThis) {
someOtherThis = someOtherThis || global;
var uniqueID = "00" + Math.random();
while (someOtherThis.hasOwnProperty(uniqueID)) {
uniqueID = "00" + Math.random();
}
someOtherThis[uniqueID] = this;
const args = [];
// arguments are saved in strings, using args
for (var i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
}
// strings are reparsed into statements in the eval method
// Here args automatically calls the Array.toString() method.
var result = eval("someOtherThis[uniqueID](" + args + ")");
delete someOtherThis[uniqueID];
return result;
};
- 手写apply
Function.prototype.myOwnApply = function(someOtherThis, arr) {
someOtherThis = someOtherThis || global;
var uniqueID = "00" + Math.random();
while (someOtherThis.hasOwnProperty(uniqueID)) {
uniqueID = "00" + Math.random();
}
someOtherThis[uniqueID] = this;
var args = [];
var result = null;
if (!arr) {
result = someOtherThis[uniqueID]();
} else {
for (let i = 1, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("someOtherThis[uniqueID](" + args + ")");
}
delete someOtherThis[uniqueID];
return result;
};
- 手写bind
// 模拟 bind
Function.prototype.bind1 = function() {
//arguments获取一个函数所有的参数,它是一个列表
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
// 获取 this(数组第一项)
const t = args.shift();
// 好比fn1.bind(...) 中的 fn1 或者下面的_this=this
// const self = this;
_this=this
// 返回一个函数(bind本来是返回一个函数)
return function() {
// apply的第一个参数就是this
return _this.apply(t, args);
};
};