说到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() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
手写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);
};
};