几点基础知识:
- Function.prototype.call 和 Function.prototype.apply 方法用于改变内部函数 this 的指向
func.apply(thisArg[, argsArray])
接受两个参数,第一个参数指定了 func 函数运行时的this,第二个参数是一个数组或者类数组对象func.call(thisArg, arg1, arg2, ...)
方法的作用和 apply() 方法类似,只有一个区别,就是 call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组
举个栗子:
var hero = {
name:'奥特曼'
};
function beatMonster() {
console.log(this.name);
}
// call把this由window指向hero对象,并执行了beatMonster方法
beatMonster.call(hero); // 输出:奥特曼
模拟实现call
思路:
1. 将函数设置为对象的属性
2. 执行该函数
3. 删除对象中的该函数
Function.prototype.myCall = function(context) {
// context即为hero
// this为被调用的方法 因为myCall在beatMonster的上下文中执行,所以this指向beatMonster
context.fn = this;
context.fn();
delete context.fn;
}
beatMonster.myCall(hero); // 输出:奥特曼 没毛病(・。・)
// 接下来考虑有参数的情况
function beatMonster(weapon, level) {
console.log(this.name);
console.log(weapon);
console.log(level);
}
beatMonster.call(hero,'动感光波',10); // 输出:奥特曼 动感光波 10
// 因为参数的实际情况不明,所以使用函数内部的arguments(类数组对象)来获取实参,先凭本能搞一波
Function.prototype.myCall = function(context) {
// 借用 Array.prototype的方法拿到实参数组
var args = [].slice.apply(arguments,[1]);
context.fn = this;
context.fn.apply(context, args);
//这里可以用es6的...操作符 context.fn(...args);但call是es3的方法
delete context.fn;
}
beatMonster.myCall(hero,'巴拉拉能量',8); // 输出:奥特曼 巴拉拉能量 8
// 以上的实现从结果来看没毛病,但是感觉怪怪的,模拟call的时候用他兄弟apply,那还模拟个啥
// 改下改下 用个eval方法
Function.prototype.myCall = function(context) {
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args[i-1] = "arguments["+i+"]";
}
context.fn = this;
// 这里 args 会自动调用 Array.toString() 这个方法
eval('context.fn('+ args +')');
delete context.fn;
}
看起来好像成功了 (´ω`★)
然而 还有一种情况 beatMonster.myCall(null);
报错 Cannot set property 'fn' of null
再加上两点必须注意:
- 凡是方法中使用到上下文语境的,一定要考虑默认的上下文(this)
- 凡是编码对象是function的,一定要考虑返回值
于是代码改为:
Function.prototype.myCall = function(context) {
context = context || window;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args[i-1] = "arguments["+i+"]";
}
context.fn = this;
// 这里 args 会自动调用 Array.toString() 这个方法
var result = eval('context.fn('+ args +')');
delete context.fn;
return result;
}
(๑•̀ㅂ•́)و✧
模拟实现apply
直接上代码:
Function.prototype.myApply = function(context, arr) {
context = context || window;
context.fn = this;
var result;
if(!arr) {
result = context.fn();
} else {
var args = [];
for(var i = 0, len = arr.length; i < len; i++) {
args[i] = "arr["+i+"]";
}
result = eval('context.fn('+ args +')');
}
delete context.fn;
return result;
}