JS 手写bind 我写bug的能给你看生代码段子?

今天看完手写bind真的是寄了。
callapply说实话还好,这bind到底是什么寄吧。
上一段完整代码:
__proto__被不推荐使用的情况下,支持检测new的手写bind代码如下:
先别看这部分代码,建议先看最下面的几种实现。

Function.prototype.my_bind = function (thisArg) {
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1)
  function Fun() { }
  var fnBound = function () {
    var _this = this instanceof self ? this : thisArg
    return self.apply(_this, args.concat(Array.prototype.slice.call(arguments)));
  }
  Fun.prototype = this.prototype
  fnBound.prototype = new Fun()
  return fnBound
}

function foo(name) {
  this.name = name;
}

var obj = {};

// 解决 上下文
var bar = foo.my_bind(obj);
bar('jack')
console.log(obj.name); // jack
// 解决 参数问题
var tar = foo.my_bind(obj, 'rose')
tar()
console.log(obj.name); // rose
// 解决 new问题
var alice = new bar('alice')
console.log(obj.name); // rose
console.log(alice.name); // alice

为什么中间要加一层原型链关系,不是说加一层,这里其实是加了两层,你不要听我解释,先看最底下的代码实现,先别看这个!!!!:

  • 仔细看一下代码,bar其实是手写的bind返回出来的一个闭包fnBound,然后bar作为构造函数new了一个alice
  • 请回答:alice__proto__指向哪里?——答案是bar.prototype,实际上是fnBound.prototype,然后fnBound.prototype.__proto__指向哪里?——答案是他的构造函数的原型对象,即Fun.prototype,好,到这里仔细看上面的这一行代码,Fun.prototype = this.prototype,请问这么做的目的到底是为什么?——答案是为var _this = this instanceof self ? this : thisArg这一行做铺垫,重点是this instanceof self的实现。
  • 如果我们不做任何处理,上面这行的thisself不会有任何关系,instanceof的值一定是false,就没有效果了对吧。
  • 实际支持var alice = new bar('alice') 以及 var _this = this instanceof self ? this : thisArg的那一部分代码(instanceof)到底怎么运作的呢,听我解释:
  • new出来的实例alice就是this,不懂为什么的话就去看我那篇《手写new》,然后这个this往上找自己的__proto__,先找到了this.__proto__,这个的值===fnBound.prototype,因为fnBound就是this实例的构造函数,但是仍不等于self.prototype,所以继续找下一层__proto__,找到了fnBound.prototype.__proto__ === self.prototype,因为FunfnBound.prototype的构造函数,然后Fun.prototype不是原生的值,而是被我们手动赋值为了self.prototype,不知道为什么这样找的话,就去看看instanceof的实现原理。
  • 好了,现在这个instanceof的值为true了,我们需要的_this指定为了当前实例的this,这样执行的代码才满足我们的要求
  • 加入值为false_this的值为thisArg,因为闭包,thisArg没有被销毁,而是一支存活,每次调用都能取到,就会又给obj执行相关代码,结果就会是obj.name === 'alice'

我脑子有病才像上面这么写。

我为什么不这么写:

Function.prototype.my_bind = function (thisArg) {
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1)
  function Fun() { }
  var fnBound = function () {
    //                         !!!!!!!看这里
    var _this = this instanceof fnBound ? this : thisArg
    return self.apply(_this, args.concat(Array.prototype.slice.call(arguments)));
  }
  Fun.prototype = this.prototype
  fnBound.prototype = new Fun()
  return fnBound
}

我都知道self.prototypefnBound的原型链上了,如果alice实例是fnBound创建的,那fnBound一定在alice的原型链上啊,那self也就是foo肯定更在啊,为什么要非得找两层呢,寄。看博客都能看傻。

但是究其本质,看过new实现原理的应该都懂,这都是对__proto__这个属性的操作,其直接就影响到原型链。
我们完全可以这么写:

Function.prototype.my_bind = function (thisArg) {
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1)
  function Fun() { }
  var fnBound = function () {
    var _this = this instanceof self ? this : thisArg
    return self.apply(_this, args.concat(Array.prototype.slice.call(arguments)));
  }
  // 看这里
  fnBound.prototype.__proto__ = this.prototype
  return fnBound
}

self.prototype既然在fnBound.prototype的原型链上了,那也就在实例对象的原型链上。
但是现在不推荐直接写__proto__,所以采用new Fun()这种不明显的方法来实现。

其实可以不操作__proto__

Function.prototype.my_bind = function (thisArg) {
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1)
  function Fun() { }
  var fnBound = function () {
    var _this = this instanceof self ? this : thisArg
    return self.apply(_this, args.concat(Array.prototype.slice.call(arguments)));
  }
  // 看这里 据说这样写有问题
  fnBound.prototype = this.prototype
  // 而网上的博客会这样写 效果上没有什么区别 这两种写法暂时都没看出问题
  fnBound.prototype = Object.create(this.prototype)
  return fnBound
}

instanceof的第一次寻找就可以返回true

手写instanceofnew以及原型链相关的解释都在我的博客里面。

寄了。

然后解释每一段代码:

  • if (typeof this !== 'function') { throw new TypeError('not a function') }
    如果不是函数那就直接抛出错误,不是函数绑定啥绑定
  • var self = this; 将调用的函数保存一下this,会在下面的闭包里当做常量使用
    var args = Array.prototype.slice.call(arguments, 1) 将绑定目标以外的参数都取出来,后面要和调用时得到的参数列表进行拼接
  • var fnBound = function () {
    var _this = this instanceof self ? this : thisArg 判定new操作,如果是new出来的实例,那就直接用当前this,否则用之前引用的外层函数的变量thisArg,也就是绑定目标
    return self.apply(_this, args.concat(Array.prototype.slice.call(arguments))); 返回一个函数,可以在外部手动执行,绑定目标和参数都已经处理好了
    }
  • function Fun() { } 声明一个空函数,用于链接原型链
    fnBound.prototype = Object.create(this.prototype) 赋值一个selfprototypefnBound.prototype,把原型链链接起来
    return fnBound 返回可执行函数
上一篇:C++11 标准库 bind 函数


下一篇:[Vue]直接使用 v-bind 指令到