call() 方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法
如:
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call(foo); // 1
注意两点:
- call 改变了this的指向, 指向到foo
- bar函数执行了
模拟实现第一步
试想当调用call的时候, 把foo对象改造如下:
var foo = {
value: 1,
bar: function() {
console.log(this.value)
}
};
foo.bar(); // 1
这个时候this就指向了 foo,
这样给foo对象本身添加了一个属性bar, 所以最后执行完函数,我们得把属性删除
模拟步骤:
1.将函数设为对象的属性
2.执行该函数
3.删除该函数
// 第一步
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn
fn 是属性名, 可随意取,因为最后会删除掉
// 第一版
var foo = {
value: 1,
fn: bar // 新增属性指向 函数bar
};
function bar() {
console.log(this.value);
}
console.log(foo);
foo.fn(); // 执行函数
delete foo.fn; // 删除函数
console.log(foo);
image.png
根据这个思路, 试着写一个call2方法
// 第二版
Function.prototype.call2 = function(obj){
console.log(this); // this指向调用该方法的函数bar
console.log(obj); // 传入的foo对象
obj.fn = this; // 往foo对象新增属性fn, 指向函数bar
obj.fn(); // 执行函数fn(bar), 成功输出1
delete obj.fn; // 删除属性fn
console.log(obj); // 最后没有改变foo对象本身(里面的属性)
}
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar.call2(foo);
image.png
模拟实现第二步
call 方法还能给定参数执行函数
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1
注意:传入的参数并不确定。怎么办? 我们可以从 Arguments 对象中取值, 取出第二个到最后一个参数, 放在一个数组里。
// 第三版
Function.prototype.call2 = function(obj){
console.log(arguments);
var args = [];
for(var i=1; i<arguments.length; i++){
args.push(arguments[i]);
}
console.log(args); // ["Ailse", "18"]
console.log(args.join(',')); // "Ailse,18"
obj.fn = this;
obj.fn(args.join(','));
delete obj.fn;
}
var foo = {
value: 1
};
function bar(name,age) {
console.log(arguments);
console.log(name,age);
}
bar.call2(foo, "Ailse","18");
但是这样是不行的,看控制台输出,call2函数里的3个console:
image.png
这样执行fn传入的参数是一个字符串“Ailse,18”, 函数bar接收的参数永远只能接收到一个字符串参数了,如下打印:
image.png
怎么正确传参呢,
通过eval方法拼成一个函数,类似这样:
这里简单说一下eval()方法:
eval 是全局对象上的一个函数,会把传入的字符串当做 JavaScript 代码执行。如果传入的参数不是字符串,它会原封不动地将其返回。eval 分为直接调用和间接调用两种,通常间接调用的性能会好于直接调用。
进一步了解eval方法:https://juejin.im/post/5bead276e51d452ceb51e027
eval('obj.fn('+args+')');
args 还得改一下,不然会报错
args.push('arguments['+i+']');
image.png
代码改一下
// 第三版
Function.prototype.call2 = function(obj){
var args = [];
for(var i=1; i<arguments.length; i++){
args.push('arguments['+i+']');
}
obj.fn = this;
eval('obj.fn('+args+')');
delete obj.fn;
}
var foo = {
value: 1
};
function bar(name,age) {
console.log(arguments);
console.log(name,age);
}
bar.call2(foo, "Ailse","18");
这样就成功输出了
image.png
模拟实现第三步
模拟代码已经完成80%, 还有两个小点要注意:
1.this参数可以传null, 当为null的时候,视为指向 window
var value = 1;
function bar() {
console.log(this.value);
}
bar.call(null); // 1
2.函数是可以有返回值的
var obj = {
value: 1
}
function bar(name, age) {
return {
value: this.value,
name: name,
age: age
}
}
console.log(bar.call(obj, 'kevin', 18));
// Object {
// value: 1,
// name: 'kevin',
// age: 18
// }
都好解决, 最后代码:
// 第四版
Function.prototype.call2 = function(obj){
var obj = obj || window;
var args = [];
for(var i=1; i<arguments.length; i++){
args.push('arguments[' + i + ']');
}
obj.fn = this;
var result = eval('obj.fn('+args+')'); // eval方法拼成一个函数
delete obj.fn;
return result;
}
var foo = {
value: 1
};
function bar(name,age) {
console.log(name,age);
return {
name: name
}
}
console.log(bar.call2(null, "Ailse","18"));
apply的模拟实现
apply的实现跟call类似
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 + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
参考:https://juejin.im/post/5907eb99570c3500582ca23c
面试题:call与apply的模拟思路
关键点:
- call 改变了this的指向, 指向到foo
- bar函数执行了
1.将函数设为对象的属性
2.执行该函数
3.删除该函数
传入的参数并不确定怎么办?(通过从Arguments对象取值)
如何正确传参?(通过eval方法拼成一个函数)
网友评论