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