关于javascript:JavaScript深入理解系列call与apply

32次阅读

共计 4611 个字符,预计需要花费 12 分钟才能阅读完成。

定义

call 和 apply:函数调动 call() 办法在执行的时候,函数的外面的 this 会指向第一个参数值,除第一个参数值前面的若干支都是传进该函数,简而言之就是扭转函数运行时的 this 指向。

 应用示例:fn.call(obj, args1, args2...), fn.apply(obj, [arg1, arg2 ...]), call 与 apply 调用除了第二个传参形式不一样,其余一样。

举个例子 1:

var  obj = {name: '程序员米粉'};
function fn() {console.log(this.name)
}
fn() // this.name => undefined
fn.call(obj) // this.name => '程序员米粉'

总结:

1、fn 函数调动 call 办法执行时候,函数的 this 指向了 obj
2、call 办法和 fn 函数都执行了。

如果还看不明确,fn.call(obj) 执行的时候,能够看作在 obj 对象外面有个 fn 函数执行。那么 fn 函数外面的 this 指向就是 obj

再举个例子 2:

var  obj = {
    name: '程序员米粉',
    fn: function() {console.log(this.name)
    }
};
obj.fn(); // this.name => '程序员米粉'

总结:

1、例子 1 中的 fn.call(obj) 执行的时候,相当于例子 2 obj.fn(); call 办法被调用的时候,会扭转以后调用函数的执行上下文(就是扭转 this 指向)。

模仿步骤 1

例子 1:

var  obj = {name: '程序员米粉'};
function fn() {console.log(this.name)
}
fn.call(obj) // this.name => '程序员米粉'

例子 2:

var  obj = {
    name: '程序员米粉',
    fn: function() {console.log(this.name)
    }
};
obj.fn(); // this.name => '程序员米粉'

要实现本人的 myCall 办法,首先察看例子 1 和例子 2 中测 obj 对象,有什么不一样。

不一样:

1、例子 2 中 obj 增加了 fn 函数办法。2、例子 2 执行办法是 obj.fn() 执行,而例子 1 中执行办法是 fn.call(obj)。

这就好办了,咱们如何把例子 1 革新成例子 2 呢?
咱们总结一下步骤:

1、把 fn 函数设置为对象的一个属性
2、调用 fn 函数执行。3、调用结束之后,从对象中删除 fn 属性(函数)。(给对象减少了属性,咱们调用完就能够删掉)

思路有了,那代码咱们能够写成这样:

obj.fn = fn;
obj.fn();
delete obj.fn; // 删除属性 

根据上述思路,那依照这个思路,写一个咱们本人的 myCall 办法:

Function.prototype.myCall = function(context) {
    context.fn = this;
    context.fn();
    delete context.fn;
};
var  obj = {name: '程序员米粉'};
function fn() {console.log(this.name);
}
fn.myCall(obj) // this.name => '程序员米粉'
 ````

执行一下 myCall 办法,能够得进去,this 指向 obj 对象,并打印了冀望的值,所以这个办法第一步实现了!# 模仿步骤 2

持续欠缺一下 myCall 办法,把 fn 办法改一下,减少 2 个参数, 再执行一下办法。

function fn(index, value) {

console.log(this.name);
console.log(index, value);

}

fn.myCall(obj, 111, ‘ 我是一个值 ’);
// this.name => ‘ 程序员米粉 ’
// undefined, undefined


执行一个 fn 函数,只打印一个值进去,传进入的参数,没有打印进去。那须要革新一下办法,使参数也能打印进去。咱们能够从 arguments 对象中取值,arguments 对像代表函数传进来的对象,打印看看就晓得了。

Function.prototype.myCall = function(context) {

console.log(arguments);
context.fn = this;
context.fn();
delete context.fn;

};

 打印进去的 arguments 对象:![](/img/bVcY9cV)

咱们能够看到 arguments 对象构造是:

{

'0': {name: '程序员米粉'}, 
'1': 111, 
'2': '我是一个值' 

}

 0 代表传进来的第 1 个参数,1 代表第 2 个参数,以此类推。咱们看到 arguments 是一个类数组,那就能够用数组办法,存储起来,因为咱们只是获取参数,所以从 1 开始取值。

var args = [];
for (var i = 1; i < arguments.length; i++) {

args.push('arguments[' + i + ']');

}
// args 为 [“arguments[1]”, “arguments[2]”, “arguments[3]”]

 取值的参数的问题解决了,咱们下一步解决一下,如何把这些参数传给函数执行。

// 思考一下,怎样才能这样传参给 fn 函数执行
context.fn(arguments[1], arguments[2]);


可能有人想到如下办法:

// 把数组里的元素通过 join 办法,传进函数的形参里
context.fn(args.join(‘,’))

// 这样的话,其实 context.fn(args.join(‘,’)) 执行变成:context.fn(“arguments[1]”, “arguments[2]”);
// 变成了一个字符串,变成了一个死值了,无论传什么,都变成字符串 “arguments[1]”, “arguments[2]”。。。。


能够有人又想到用 ES6 办法来解决。这个 call 是 ES3 的办法,所以还是不必 ES6 办法解决。其实咱们能够用 eval 办法来解决。查文档得悉 eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。这到底是什么意思?eval() 的参数是一个字符串。如果字符串示意的是表达式,eval() 会对表达式进行求值。如果参数示意一个或多个 JavaScript 语句,那么 eval() 就会执行这些语句。举个例子:console.log(eval('2 + 2')); // 2 + 2 => 4, 最初输入是 2
    console.log(eval('context.fn(' + args +')')) // 相当于运行 context.fn(arguments[1], arguments[2], ...),应用 eval 执行一串 JavaScript 语句。+ [eval 更具体链接;](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval)
  
args 会主动调用 Array.toString() 这个办法。通过 eval 主动执行变成 context.fn(arguments[1], arguments[2]) 这样执行。代码如下:

var obj = {

name: '程序员米粉'

};

function fn(index, value) {

console.log(this.name); // 程序员米粉
console.log(index, value); // 111 我是一个值 

}

Function.prototype.myCall = function(context) {

context.fn = this;
var args = [];
for (var i = 1; i < arguments.length; i++) {args.push('arguments[' + i + ']');
}
eval('context.fn(' + args + ')');
delete context.fn;

};

fn.myCall(obj, 111, ‘ 我是一个值 ’); // this.name => ‘ 程序员米粉 ’


执行一下办法,输出完全符合咱们的预期,咱们终于搞定了!# 模仿步骤 3

## 第一个参数传 null 或 undefined,this 指向为 window

举个例子:

var name = ‘ 程序员米粉 ’

function fn() {

console.log(this.name); // 程序员米粉 

}

fn.call(null); // this.name => ‘ 程序员米粉 ’


fn 执行的时候,还是输入 this.name = '程序员米粉',阐明了什么,只有第一个参数传 null 或者 undefined,那函数调用 call 办法,this 指向 window

## 函数执行 call 办法,有返回值,那就返回。举个例子:

var name = ‘ 程序员米粉 ’

function fn() {

console.log(this.name); // 程序员米粉
return {name: this.name}

}

fn.call(null); // this.name => ‘ 程序员米粉 ’
// 执行 fn 函数间接返回对象
// {
// name: ‘ 程序员米粉 ’
// }


## 最终版实现 call 代码

var obj = {

name: '程序员米粉'

};

function fn(index, value) {

console.log(this.name); // 程序员米粉
console.log(index, value); // 111 我是一个值
return {
    name: this.name,
    index: index,
    value: value
};

}

Function.prototype.myCall = function(context) {

var context = context || window;
context.fn = this;
var args = [];
for (var i = 1; i < arguments.length; i++) {args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args + ')');
delete context.fn;
return result;

};

fn.myCall(obj, 111, ‘ 我是一个值 ’); // this.name => ‘ 程序员米粉 ’
// 最终输入
// {
// name: “ 程序员米粉 ”
// index: 111
// value: “ 我是一个值 ”
// }


# 实现 apply 代码

因为 apply 与 call 实现原理根本一样,那就不具体叙述了,间接上代码:

Function.prototype.myApply = function(context, arr) {

var context = context || window;
context.fn = this;
var result;
if (!arr) {result = context.fn();
} else {var args = [];
    for (var i = 0; i < arr.length; i++) {args.push('arr[' + i + ']');
    }
    result = eval('context.fn(' + args + ')');
}
delete context.fn;
return result;

};


# 结语

心愿看完这篇文章对你有帮忙:+ 了解 call 和 apply 原理
+ 实际本人写一个 call 和 apply 办法

文中如有谬误,欢送在评论区斧正,如果这篇文章帮忙到了你,欢送点赞和关注,后续会输入更好的分享。欢送关注公众号:【程序员米粉】

正文完
 0