首先,它是函数的一个办法,咱们须要将其--

1.挂载到Function的原型链上

Function.prototype.mybind =...//这样,所有继承自Function的函数就可能应用.操作符来拜访mybind了!//PS:因为JS原型式继承
而后,让咱们先看看原生JS的bind办法有哪些行为--

2.调用函数时扭转this指向

让调用该办法的函数的this指向传入的第一个参数

咱们能够借助apply办法实现

Function.prototype.mybind = function (context) {  this.apply(context);};let obj = {  name: "Crushdada",};let fn = function (params) {  console.log(this.name);};fn.mybind(obj); //Crushdada

3.返回一个匿名的绑定函数

留神两点:
  • 因为咱们返回了一个绑定函数(匿名函数),则在调用时须要在调用语句前面再加一个圆括号
  • 与此同时,因为匿名函数中的this指向window/global,咱们须要应用箭头函数或者手动保留一下指向mybind中指向调用者fn的this

    • 此处应用箭头函数
    Function.prototype.mybind = function (context) {  return () => this.apply(context);};let obj = {  name: "Crushdada",};let fn = function (params) {  console.log(this.name);};fn.mybind(obj)(); //Crushdada

    4.反对柯里化传递参数

集体了解:相比“容许传入参数”这种说法,形容为“传递参数”更贴切,bind办法作为一个两头办法,会代收参数后再传递给它返回的匿名绑定函数,其返回一个匿名函数这一点,人造反对柯里化(可能是ES6引入它的初衷之一),因为这样就容许咱们在调用bind时传入一部分参数,在调用其绑定函数时再传入剩下的参数。而后它会在接管完第二次传参后再apply执行调用bind的那个办法

  • 实现柯里化的逻辑很简略,仅仅须要在mybind中接管一次参数,而后在绑定函数中接管一次参数,并将二者拼接后一起传给mybind的调用办法应用即可

    上面,实现传参&柯里化!
  • 若应用的是一般函数,要解决参数,因为arguments为类数组,slice为Array办法,故先在原型链上调用而后call一下

    • 第一个参数为this新的指向,不是属性,故slice掉它
    应用箭头函数能极大简化代码
    上面咱们改亿点点细节!
  • 应用箭头函数(Array Function)没有arguments属性,因而应用rest运算符代替解决
  • 在拼接args和bindArgs时应用扩大运算符代替concat
  • 不得不说ES6引入的rest运算符、扩大运算符在解决参数这一点上提供了极大的便当

    Function.prototype.mybind = function (context, ...args) {  return (...bindArgs) => {  //拼接柯里化的两次传参    let all_args = [...args, ...bindArgs];   //执行调用bind办法的那个函数    let call_fn = this.apply(context, all_args);     return call_fn;  };};let person = {  name: "Crushdada",};let getInfo = function (like, fav) {  let info = `${this.name} likes ${like},but his favorite is ${fav}`;  return info;};//anonymous_bind:mybind返回的那个匿名的绑定函数let anonymous_bind = getInfo.mybind(person, "南瓜子豆腐");let info = anonymous_bind("皂角仁甜菜"); //执行绑定函数console.log(info);//Crushdada likes 南瓜子豆腐,but his favorite is 皂角仁甜菜

    箭头函数不能作为构造函数!

须要用一般函数重写mybind

写到反对柯里化这一步,bind办法还是能够应用箭头函数实现的,而且比一般函数更加简洁

然而想要持续欠缺它的的行为,就不能用持续用Arrow Function了,因为箭头函数不能被new!,要是尝试去new它会报错:

anonymous_bind is not a constructor
笔者也是写到这才想起箭头函数这个机制的。那么上面咱们须要用一般函数重写mybind
不过也很简略,只须要手动保留一下this即可。就不再贴出改变后的代码了。间接看下一步

5.反对new绑定函数

bind的一个隐式行为:

  • 它返回的绑定函数容许被new 关键字调用,然而,理论被作为结构器的是调用bind的那个函数!!!
  • 且new调用时传入的参数照常被传递给调用函数。

    逻辑

实现这一步的逻辑也较为简单,咱们类比一下和个别调用new时的区别--

  • new一个一般函数:按理来说生成的实例对象的构造函数是那个一般函数
  • new一个绑定函数:生成的实例对象的构造函数调用bind的那个函数

次要须要咱们写的逻辑有:

  1. 判断是否是new调用
  2. getInfo函数中的this指向--new中创立的实例对象obj

    1. 就是把getInfo函数里的this换成obj,以使obj获取到其中的属性
    2. 能够借助apply办法
  3. 判断getInfo函数是否返回一个对象,若是,则返回该对象,否则返回new生成的obj

    至于为什么这么写,就须要你先弄懂new关键字实现的机制了,我的笔记链接附在文末
    上面,实现它!
    Function.prototype.mybind= function (context, ...args) {  let self = this;  return function (...bindArgs) {    //拼接柯里化的两次传参    let all_args = [...args, ...bindArgs];    // new.target 用来检测是否是被 new 调用    if (new.target !== undefined) {      // 让调用mybind的那个函数的this指向new中创立的空对象      var result = self.apply(this, all_args);      // 判断调用mybind办法的那个理论的构造函数是否返回对象,没有返回对象就返回new生成的实例对象obj      return result instanceof Object ? result : this;    }    //如果不是 new 就原来的逻辑    //执行调用bind办法的那个函数    let call_fn = self.apply(context, all_args);    return call_fn;  };};let person = {  name: "Crushdada",};let getInfo = function (like, fav) {  this.dear = "Bravetata";  let info = `${this.name} likes ${like},but his favorite is ${fav}`;  return info;};//anonymous_bind:mybind返回的那个匿名的绑定函数let anonymous_bind = getInfo.mybind(person, "南瓜子豆腐");let obj = new anonymous_bind("皂角仁甜菜"); //执行绑定函数console.log(obj);       //{ dear: 'Bravetata' }console.log(obj.name);  //undefined
    解释一下以上代码:

第一个逻辑

  • new外部有相似这样一条语句:Con.apply(obj, args)
  • 其中Con是new 的那个构造函数,obj是最初要返回的实例对象
  • 当咱们new下面mybind中return的那个绑定函数时
  • Con就是该绑定函数
  • 当Con.apply(obj, args)执行,
  • 调用绑定函数并将其中的this换成obj
  • 而后程序就进入到了该绑定函数中--
  • 判断的确是new调用的
  • 执行self.apply(this, all_args);
  • 这条语句就相当于getInfo.apply(obj, all_args)
  • 这样就达成咱们的目标了!--让getInfo成为new生成的实例对象的理论结构器

第二个逻辑

  • new关键字会判断构造函数自身会不会返回一个对象
  • 如果会,则间接返回这个对象当做实例,否则失常是返回那个new生成的obj当做实例对象
  • 那么--咱们在第一个逻辑里曾经调用了理论结构器--getInfo
  • 接下来咱们直接判断一下调用的后果,即它是否return一个对象,而后return给new做最终的return即可

    此外:能够看到,当new mybind返回的绑定函数时,obj没有获取到person.name属性,为undefined。也就是说--

    此时,bind扭转this指向的行为会生效

看个栗子,这样分明一点
var value = 2;var foo = {    value: 1};function bar(name, age) {    this.habit = 'shopping';    console.log(this.value);    console.log(name);    console.log(age);}bar.prototype.friend = 'kevin';var bindFoo = bar.bind(foo, 'daisy');var obj = new bindFoo('18');// undefined// daisy// 18console.log(obj.habit);console.log(obj.friend);// shopping// kevin

只管在全局和 foo 中都申明了 value 值,最初仍然返回了 undefind,阐明绑定的this 生效了,

这是为什么呢?

如果大家理解 new 的模仿实现,就会晓得了--

new是JS模仿面向对象的一个关键字,它的目标之一是实现继承,它要去继承构造函数(类)之中的属性,那么new关键字是怎么去实现的呢?它在外部利用了相似这样一条语句:

Con.apply(obj, args) //Con是new 的那个构造函数

new 关键字会先申明一个空对象obj,而后将构造函数的this指向这个对象

这样做会产生什么--
  • 如果构造函数中设置了一些属性,如:this.name = xx;
  • 那么就相当于将this换成了obj,变成:obj.name = xx;
  • obj就继承到了构造函数的属性!!
  • obj就是最初会返回的实例对象

详见:《JS中new操作符做了什么?》--Crushdada's Notes

让咱们回到为什么this会生效这一问题上
理解完new关键字的相干实现,咱们曾经失去答案了--

new完绑定函数后,绑定函数外部的this 曾经指向了 obj,而obj中没有value这个属性,当然就返回undefined了

6.反对原型链继承

实际上这一步是对绑定函数内重写new办法的一个补充--

因为new办法原本就反对原型链继承

逻辑

那么咱们只须要--

让new的实例对象obj的原型指向理论结构器getInfo的prototype即可

Object.setPrototypeOf(this, self.prototype);

更加规范化

能够为mybind办法加上一个判断,调用者必须是一个函数,否则抛出TypeError--

if (typeof this !== 'function' || Object.prototype.toString.call(this) !== '[object Function]') {    throw new TypeError(this + ' must be a function');  }

最终后果

Function.prototype.mybind = function (context, ...args) {  if (    typeof this !== "function" ||    Object.prototype.toString.call(this) !== "[object Function]"  ) {    throw new TypeError(this + " must be a function");  }  let self = this;  return function (...bindArgs) {    let all_args = [...args, ...bindArgs];    if (new.target !== undefined) {      var result = self.apply(this, all_args);      Object.setPrototypeOf(this, self.prototype);      return result instanceof Object ? result : this;    }    let call_fn = self.apply(context, all_args);    return call_fn;  };};

其余知识点:

Array.prototype.slice.call()

  • 接管一个字符串或有length属性对象
  • 该办法可能将有length属性对象或字符串转换为数组

    因而像是arguments对象这样领有length属性的类数组就能够应用该办法转换为真正的数组
    JS中,只有String和Array领有.slice办法,对象没有。
    let slice = (arrlike) => Array.prototype.slice.call(arrlike);var b = "123456";let arr = slice(b);console.log(arr);// ["1", "2", "3", "4", "5", "6"]

    arr.slice()办法

  • 返回一个新的数组对象,这一对象是一个由beginend决定的原数组的浅拷贝包含begin不包含end)。

    • 接管的参数--begin、end是数组index
    • 原始数组不会被扭转。
    const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];console.log(animals.slice(2));// expected output: Array ["camel", "duck", "elephant"]console.log(animals.slice(2, 4));// expected output: Array ["camel", "duck"]

    逻辑或“||”

a || b :

  • 若运算符后面的值为false,则返回前面的值
  • 若true,返回后面的值

    js中逻辑值为false的6种状况

当一个函数领有形参,但调用时没有传实参时,形参是undefined,会被按false解决
function name(params) {  console.log(params); //undefined}name();console.log(undefined == false);  //falseconsole.log(undefined || "undefined was reated as false");//undefined was reated as false
会被逻辑或运算符当做false解决的总共6个--

0、null、""、false、undefined 或者 NaN

参考:

JavaScript深刻之bind的模仿实现--掘金

js 实现 bind 的这五层,你在第几层?--蓝色的秋风 | 思否

《JS中new操作符做了什么?》--Crushdada's Notes