004-call的用法和实现原理

002-判断JS数据类型中我们使用了Object.prototype.toString.call()方法来判断数据类型,那么这个call方法是怎么使用的呢,这篇文章我们来探究这个问题。

call的用法

MDN定义:使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
语法:fun.call(thisArg, arg1, arg2, ...)

应用:

  1. 使用call实现继承
  2. 获取数组中的最大值和最小值
    var num = [3, 4, 1, 5];
    console.log(Math.max.call(Math, ...num));
    
  3. 调用函数并且指定上下文的 this
    var foo = {
      value: 1
    };
    
    function bar() {
      console.log(this.value);
    }
    
    bar.call(foo); // 1
    
    

2.实现原理

第一步

我们观察栗子3知道,call做了两件事情:

  1. 执行了函数bar()
  2. 改变了bar的this,指向了foo

我们抛开call,使用其他方式来做这两件事情:

执行了函数bar()

bar()

改变了bar的this,指向了foo

如果bar()是对象foo中的一个方法,那么会是怎样的呢

foo = {
  value: 1,
  bar: function() {
    console.log(this.value)
  }
}
foo.bar()  // 1

我们得到的结果跟使用call是一样的,所以我们按照这个思路来实现call

  1. 将bar函数设置为foo的属性
  2. 执行bar函数
  3. 删除bar函数(因为此处只是借用该函数,使用之后删除)

代码实现

Function.prototype.myCall = function(context) {  // 此处context传入的是foo
	console.log(this)  // 在这里可以看到this是bar()
	context.fn = this;  //  1.将bar()当做是foo的一个属性
	context.fn();  //  2.执行foo.bar()
	delete context.fn;  //  3.删除foo.bar()
}


// 测试一下
var foo = {
	value: 1
};

function bar() {
	console.log(this.value);
}

bar.myCall(foo)  // 1

第二步

第一步我们解决了函数执行和this的问题,但是由call的语法可知,call还能传其他参数,来看看原本的call是怎么使用的吧

var foo = {
	value: 1
};

function bar(name, age) {
    console.log(this.value);  // 1
    console.log(name);  // 'James'
    console.log(age)  // 20
}

bar.call(foo, 'James', 20);

由此可见,我们需要添加如下两个步骤

  1. 收集bar中的参数(在这里我们想到了arguments)
  2. 在执行bar函数的时候把参数传进去
Function.prototype.myCall = function(context) {  // 此处context传入的是foo
	console.log(this)  // 在这里可以看到this是bar()
	context.fn = this;  //  1.将bar()当做是foo的一个属性
	var args = [];
	for(var i = 1; i < arguments.length; i++) {
		args.push('arguments[' + i + ']');  // 2.收集bar的参数
	}
	eval('context.fn('+args+')');  // 3.将参数传入并执行foo.bar()
	delete context.fn;  //  4.删除foo.bar()
}


// 测试一下
var foo = {
	value: 1
};

function bar(name, age) {
    console.log(this.value);  // 1
    console.log(name);  // 'James'
    console.log(age)  // 20
}

bar.myCall(foo, 'James', 20);

第三步

还有两个小问题需要解决

  1. this 参数可以传 null,当为 null 的时候,视为指向 window
var value = 1;

function bar() {
    console.log(this.value);
}

bar.call(null); // 1
  1. 函数是可以有返回值的
var obj = {
    value: 1
}

function bar(name, age) {
    return {
        value: this.value,
        name: name,
        age: age
    }
}

console.log(bar.call(obj, 'James', 20));  // { value: 1, name: 'James', age: 20 }

现在我们来一举攻克这两个问题

Function.prototype.myCall = function(context) {  // 此处context传入的是foo
    var context = context || window;  // 解决问题1,参数传null
	context.fn = this;  //  1.将bar()当做是foo的一个属性
	var args = [];
	for(var i = 1; i < arguments.length; i++) {
		args.push('arguments[' + i + ']');  // 2.收集bar的参数
	}
	var result = eval('context.fn('+args+')');  // 3.将参数传入并执行foo.bar()
    delete context.fn;  //  4.删除foo.bar()
    return result;  //  解决问题2,有返回值
}


// 测试一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);  // 2
    return {
        value: this.value,
        name: name,
        age: age
    }
}

bar.myCall(null); // 1

console.log(bar.myCall(obj, 'James', 20));  // {value: 1, name: "James", age: 20}

参考:
https://github.com/mqyqingfeng/Blog/issues/11
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值