call和apply的模拟实现
分类:javascript
一句话介绍call
call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
2、看看他是怎么工作的
let foo = {
value: 'foo.value'
}
var value = 'window.value'
function bar() {
console.log(this.value)
}
bar() // window.value
bar.call(foo) // foo.value
总结: 说白了就是要改变 this 的指向
注意:
- call 改变了this 的指向, 指向了foo
- bar函数执行了
3、 先实现第一种
先试试水
foo = {
value: 'foo.value',
bar: function() {
console.log(this.value)
}
}
foo.bar(); // foo.value
哈哈哈。。。试水成功了,是我们要的效果,但是 foo 多了一个属性,这不是我们想要的,那就直接 delete 不就好了吗
是时候展示一波真正的技术了
Function.prototype.call1 = function(context) {
context.fn = this;
context.fn();
delete context.fn;
}
bar.call1(foo); // foo.value
效果还不错吧,但是还有不少缺陷,比如 call1 无法传入参数
模拟实现第二步
这一步的目标就是可以传入参数,而且参数的个数是不确定的
那么我们可以
Function.prototype.call2 = function(context, ...args) {
context.fn = this;
context.fn(...args);
delete context.fn;
}
function bar2(name) {
console.log(name, this.value);
}
bar2.call2(foo, 'tutu') // tutu foo.value
嗯,效果还可以啊,可是 call 是 ES3 的产物,我们用 ES6 的语法有点不太好,所以我们可以。。。
Function.prototype.call2 = function(context) {
context.fn = this;
var args = [];
for(var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
//args 长这个样子的 ["arguments[1]", "arguments[2]", "arguments[3]"]
// context.fn(arguments[1],arguments[2],arguments[3])
eval('context.fn(' + args +')');
delete context.fn;
}
function bar3(a, b, c) {
console.log(this.value, a + b + c)
}
// bar3.call2(foo, 1, 2, 3)
这里有一个不常用的函数eval, 他是这么用的。
到这里模拟代码已经完成了 80% 了,距离成功已经不远了
最后一步了,已经离成功不远了
这次需要解决两个问题
- this 参数可以传 null,当为 null 的时候,视为指向 window
- 函数是可以有返回值的
举个例子
function barTest() {
return this.value;
}
console.log(barTest.call(null)) // window.value
开始最后的操作吧
Function.prototype.call3 = function(context) {
context = context || window;
context.fn = this;
var args = [];
for(let i = 1;i < arguments.length; i++) {
args.push('arguments[' + i + ']')
}
var result = eval('context.fn(' + args +')');
delete context.fn;
return result;
}
function bar3(a, b, c) {
console.log(this.value)
return a + b + c;
}
var sum = bar3.call3(null, 1, 2, 3);
console.log(sum)
// window.value
// 6
再来实现一个 apply
原理和 call 是一样的,区别是参数的形式不一样,call 的入参形式正常的入参,apply 的参数是以数组的形式传入的。
写个例子:
function bar(a, b){
console.log(a + b)
}
bar.apply(null, [1, 2]) // 3
bar.call(null, 1, 2) // 3
Function.prototype.apply = function (context, arr) {
var context = Object(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.push('arr[' + i + ']');
}
// 'context.fn(' + args + ')' ==> context.fn(arr[0],arr[1],arr[2])
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
写在最后
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。
最后感谢冴羽 大神的 JavaScript深入之call和apply的模拟实现
作者:掘金-call和apply的模拟实现