共计 3433 个字符,预计需要花费 9 分钟才能阅读完成。
难度:中等
咱们常常在框架级的源码中看到相似如下的一句代码,比方:
var toStr1 = Function.prototype.call.bind(Object.prototype.toString);
在这一句代码中既应用 call 办法,同时也应用 bind 办法,乍看之下,有点晕!这到底是想干嘛?
不妨,咱们调用看看,传入不同的类型试试,成果如下:
console.log(toStr1({})); // "[object Object]"
console.log(toStr1([])); // "[object Array]"
console.log(toStr1(123)); // "[object Number]"
console.log(toStr1("abc")); // "[object String]"
console.log(toStr1(new Date));// "[object Date]"
从后果中能够看到该办法的次要性能是用于检测对象的类型。但通常类型检测,咱们可能更多地看到如下代码实现:
var toStr2 = obj => Object.prototype.toString.call(obj);
console.log(toStr2({})); // "[object Object]"
console.log(toStr2([])); // "[object Array]"
console.log(toStr2(123)); // "[object Number]"
console.log(toStr2("abc")); // "[object String]"
console.log(toStr2(new Date));// "[object Date]"
相熟 bind 和 call 的同学应该晓得,两种办法实质上是雷同的,而第二种办法更简洁,仅仅应用一次 call 就能取得咱们想要的性能,且代码逻辑清晰,了解起来更加容易,可在泛滥框架中为何更多应用第一种呢?
其实次要的起因是 避免原型净化
,比方咱们在业务代码中覆写了Object.prototype.toString
办法,第二种写法将得不到正确的后果,而第一种写法依然能够。咱们用代码来来试试:
var toStr1 = Function.prototype.call.bind(Object.prototype.toString);
var toStr2 = obj => Object.prototype.toString.call(obj);
Object.prototype.toString = function(){return'toString 办法被笼罩!';}
// 接着咱们再调用上述办法
// toStr1 调用后果如下:console.log(toStr1({})); // "[object Object]"
console.log(toStr1([])); // "[object Array]"
console.log(toStr1(123)); // "[object Number]"
console.log(toStr1("abc")); // "[object String]"
console.log(toStr1("abc")); // "[object String]"
console.log(toStr1(new Date));// "[object Date]"
// toStr2 调用后果如下:console.log(toStr2({})); // "toString 办法被笼罩!"
console.log(toStr2([])); // "toString 办法被笼罩!"
console.log(toStr2(123)); // "toString 办法被笼罩!"
console.log(toStr2("abc")); // "toString 办法被笼罩!"
console.log(toStr2(new Date));// "toString 办法被笼罩!"
后果很显著。第一种办法依然能正确失去后果,而第二种则不行!那么为什么会这样呢?咱们晓得 bind 函数返回后果是一个函数,这个函数是函数外部的函数,会被提早执行,那么很天然联想到这里可能存在闭包!因为闭包能够放弃外部函数执行时的上下文状态
。不过在现代版浏览器中 call 和 bind 都曾经被 js 引擎外部实现了,咱们没有方法调试!然而咱们能够通过 polly-fill 提供的近似实现的源码来了解引擎外部的逻辑,上面是本文调试的 demo,大家能够尝试下:
// 模仿实现 call
// ES6 实现
Function.prototype.mycall = function (context) {context = context ? Object(context) : window;
var fn = Symbol();
context[fn] = this;
let args = [...arguments].slice(1);
let result = context[fn](...args);
delete context[fn]
return result;
}
// 模仿实现 bind
Function.prototype.mybind = function (context) {if (typeof this !== "function") {throw new Error("请应用函数对象调用我,谢谢!");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {var bindArgs = Array.prototype.slice.call(arguments);
return self.myapply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
// 模仿实现 apply
// ES6 实现
Function.prototype.myapply = function (context, arr) {context = context ? Object(context) : window;
var fn = Symbol();
context[fn] = this;
let result;
if (!arr) {result = context[fn]();} else {result = context[fn](...arr);
}
delete context[fn]
return result;
}
var toStr1 = Function.prototype.mycall.mybind(Object.prototype.toString);
console.log(toStr1({})); // "[object Object]"
console.log(toStr1([])); // "[object Array]"
console.log(toStr1(123)); // "[object Number]"
console.log(toStr1("abc")); // "[object String]"
console.log(toStr1(new Date));// "[object Date]"
上述的实现略去一些健壮性的代码,仅保留外围逻辑,具体的实现细节这里不做解释,有趣味的同学能够本人钻研,从 devtools 咱们看到 mybind
造成的闭包的确在函数对象 toStr1 的作用域上!
当然如果你对原型链有深刻理解的话,其实这句乏味的代码还能够写成如下形式:
var toStr3 = Function.call.bind(Object.prototype.toString);
var toStr4 = Function.call.call.bind(Object.prototype.toString);
var toStr5 = Function.call.call.call.bind(Object.prototype.toString);
// 甚至能够这么写。。。var toStr6 = (()=>{}).call.bind(Object.prototype.toString);
-END-