作用:借用某个对象的方法同时调用执行。【修改this指向同时执行】
知识储备
- call,apply,bind
属于显示绑定,这三个方法都能直接修改this指向。
- 其中call与apply比较特殊,在修改this的同时还会执行方法,而bind只是返回一个修改完this的boundFunction
并未执行
- call与apply唯一区别在于参数不同
fn.call(this, arg1, arg2); // 参数散列
fn.apply(this, [arg1, arg2]) // 参数使用数组包裹
实现步骤
-
_call
方法挂载到函数原型,让所有函数都可以直接访问_call
。 -
obj 是我们方法fn 要借给的对象。在借用的对象身上临时挂载上这个方法,然后调用,调用完,从这个对象身上删除。
-
fn._call
属于this隐式绑定,所以在执行时_call
时内部this
指向fn
,这里的obj.fn = this
就是将方法fn
赋予成了obj
的一条属性,属性名字 也叫‘fn’。obj 是我们方法要接给的对象。
代码实现
第一步:改变this指向
//模拟call方法
Function.prototype._call = function (obj) {
obj.fn = this; // 此时this就是函数fn
obj.fn(); // 执行fn
delete obj.fn; //删除fn
};
fn._call(obj); // obj的name
测试代码
var name = '我是Window 的name';
var obj = {
name: '我是对象obj 的name'
};
function fn() {
console.log(this.name);
};
fn(); //我是Window 的name
fn.call(obj); //我是对象obj 的name
第二步:接受参数
函数有一个arguments
属性,代指函数接收的所有参数,它是一个类数组。
你可能想到Array.prototype.slice.call(arguments)
,转成数组再用。很遗憾,我们现在是在模拟call
方法,也不行。那就用最基础的for
循环。如下:
Function.prototype._call= function (obj) {
var args = [];
// 注意i从1开始
for (var i = 1, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
};
console.log(args);// [1, 2, 3]
};
fn._call(obj, 1, 2, 3);
最后的代码实现
Function.prototype._call = function (obj) {
//判断是否为null或者undefined,同时考虑传递参数不是对象情况
obj = obj ? Object(obj) : window;
var args = [];
// i从1开始
for (var i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
};
obj.fn = this; // 此时this就是函数fn
let res = eval("obj.fn(" + args + ")"); // 执行fn并丢入参数
delete obj.fn; //删除fn
return res;
};
用ES6实现,更简洁。使用拓展运算符处理参数这里
Function.prototype._call = function (obj) {
obj = obj ? Object(obj) : window;
obj.fn = this;
// 利用拓展运算符直接将arguments转为数组
let args = [...arguments].slice(1);
let result = obj.fn(...args);
delete obj.fn
return result;
};
测试代码
var name = 'window';
var obj = {
name: 'Macrolam'
};
function fn() {
console.log(this.name);
fn._call(obj, "我的", "名字", "是"); // 我的名字是Macrolam
fn._call(null, "我的", "名字", "是"); // 我的名字是window
fn._call(undefined, "我的", "名字", "是"); // 我的名字是window
注意:
- 容错
参数容错处理,防止传入,string、number等简单类型时候报错。用Object包装下。
- 参数的处理上:
在`args.push(arguments[i])`这一步我们提前将字符串进行了解析,这就导致`eval`在执行时,
表达式变成了eval("obj.fn(我的,名字,是)");
正常调用函数的形式应该是这样的obj.fn("我的","名字","是")
,所以对于eval而言就像传递了三个没加引号的字符串,无法进行解析进而报错。
于是:
把 args.push(arguments[i])
改写:
args.push("arguments[" + i + "]");
args最终就是这个样子["arguments[1]","arguments[2]","arguments[3]"]
,当执行eval
时,arguments[1]
此时确实是作为一个变量存在,于是被eval
解析成了一个真正的字符传递给了函数。