您好,欢迎访问一九零五行业门户网

一行有趣的JS代码

javascript栏目详细介绍一句代码。
我们经常在框架级的源码中看到类似如下的一句代码,比如:
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(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(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(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;}// 模拟实现bindfunction.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-
相关免费学习推荐:javascript(视频)
以上就是一行有趣的js代码的详细内容。
其它类似信息

推荐信息