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