Function 做为 JavaScript 的内置对象,拥有如下两个方法:javascript
- Function.call();
- Function.apply();
这两个方法所实现的功能都是相同的:将函数做为对想象的方法调用。(引用自《JavaScript 权威指南》P.768 Function.call())。这二者的不一样之处在于参数的类型不同。java
具体两者不一样之处不是本文的重点,读者若想了解能够自行搜索。node
言归正传,本文将经过实现相似 Function.call()的功能函数来深刻解释其函数内部的运行机制!git
在研究该函数内部的运行机制以前,咱们先来了解如下该函数具体是要实现什么样的功能?github
根据《JavaScript 权威指南》P.768 有关 Function.call(thisobj, args...)的描述,其详细解释以下:bash
call()将指定的函数 function 做为对象 thisobj 的方法来调用,并传入参数列表中 thisobj 以后的参数。返回的是调用 function 的返回值。在函数体内,关键字 this 指代 thisobj 对象,若是 thisobj 位 null,则使用全局对象。app
也就是说 call()函数将会用 thisobj 来调用指定的函数 function,并返回结果。函数
在有了上述描述以后,咱们就能够着手设计方法了!咱们先写一下伪代码:测试
Function.prototype.myCall = function(obj) {
let object = 若是有传入对象obj传入则保留obj,不然为全局对象
在object上添加一个临时的属性fn
将this赋值给fn //让this所指向的对象(函数的调用者)成为object的一个属性。
传参给object.fn(),而且执行该方法,将返回值保留。
删除object上的临时属性fn。
返回以前保留的保留值。
}
复制代码
有了以前的设计以后,就是依葫芦画瓢,将伪代码进行实现。具体实现以下:ui
Function.prototype.myCall = function(obj) {
let object = obj || global; // node环境中的全局对象
object.fn = this;
var args = [];
for (let i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
}
let result = eval("object.fn(" + args + ")");
delete object.fn;
return result;
};
复制代码
这里将会用列出两个例子,一方面是为了测试 myCall()的功能实现,另外一方面是为了加深对于 call()函数的理解!
案例一是一个简单的函数调用,即用 fn2()这个函数对象,调用 fn1(a)这个方法,具体代码以下:
function fn1(a) {
console.log(a);
}
function fn2() {
console.log("*");
}
fn1.myCall(fn2, "HelloWorld!"); //输出HelloWorld!
复制代码
在这段代码 中,最终咱们是要运行 fn1.myCall(fn2, "HelloWorld!")。
在运行这段代码的时候,myCall()函数中的 this 指向调用者 fn1。myCall()的第一个参数接收了 arguments 的第一个参数,也就是 fn2,而且赋给了 object 变量。咱们将 this 所指向的对象添加为 objec 的临时属性 fn。以后执行 object.fn()函数,实际上就是执行了临时添加在 object 上的 this 对象,也就是这里的 fn1。咱们将传入的 arguments 从第二个开始传入到 object.fn()中而且执行,将返回值保留。最后销毁临时属性 fn,返回保留值。
案例二是 myCall 的连环调用,具体代码以下:
function fn1() {
console.log(1);
}
function fn2() {
console.log(2);
}
fn1.myCall.myCall(fn2); //输出2
复制代码
这段代码理解起来讲复杂也不复杂,关键是要理解是谁在调用谁的关系。
首先函数从左往右进行执行,先查找fn1.myCall.myCall(fn2);中的粗体部分。
在执行前半段的时候,其实是一个对象属性查找的过程,最终依据原型链(在这里默认读者明白什么是原型链了,若是不清楚原型链,请自行查找资料)查找到存在在原型链中的 Function.prototype.myCall 属性,其实际就是一个方法。因此上述代码实际上能够与另外一句代码相等:
//这两句代码的意思是同样的!
fn1.myCall.myCall(fn2);
Function.prototype.myCall.myCall(fn2);
复制代码
这里咱们就直接用 Function.prototype.myCall.**myCall(fn2)**进行解释。
第二步将执行后半段粗体部分。
这里咱们将一些帮助理解的测试代码给你们粘贴一下:
fn1.myCall.myCall(fn2); //输出2
Function.prototype.myCall.myCall(fn2); //输出2
fn2.myCall(); //输出2
复制代码
2019/03/29