2
头图
First of all, it is a method of the function, we need to add it -

1. Mount to the prototype chain of Function

Function.prototype.mybind =...
//这样,所有继承自Function的函数就能够使用.操作符来访问mybind了!
//PS:因为JS原型式继承
Then, let us first look at the behavior of the bind method of native JS -

2. Change this point when calling a function

Let the this of the function calling the method point to the first parameter passed in

We can with the apply method

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. Return an anonymous binding function

pay attention to two points:
  • Since we returned a binding function (anonymous function), you need to call again later when the call statement add a parenthesis
  • At the same time, due to the anonymous function in this point window / global, we need or use the arrow functions manually save what point mybind point to the caller fn of this

    • Arrow function
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. Support Currying to pass parameters

personally understand : Compared with the argument of "allowing incoming parameters", it is passing parameters ". As an intermediate method, the bind method will to the anonymous binding returned after receiving the parameters. Function, which returns an anonymous function, naturally supports currying ( may be one of the original intentions for ES6 to introduce it), because this allows us to pass in some parameters when calling bind, and then call its binding When defining the function, pass in the remaining parameters. Then it will apply and execute the method that calls bind after receiving the second pass.

  • The logic to implement currying is very simple. You only need to receive the parameter once in mybind, and then receive the parameter once in the binding function, and pass the two together to the calling method of mybind.
Next, realize parameter transfer & currying!
  • If you are using a normal function, you need to process the parameters. Since arguments is an array-like method and slice is an Array method, call it on the prototype chain first and then call it.

    • The first parameter is the new point of this, not an attribute, so slice it out
Using arrow functions can greatly simplify the code
Let's change a little bit of details!
  • Use arrow function (Array Function) has no arguments property, so use rest operator instead of processing
  • Use spread operator instead of concat when splicing args and bindArgs
  • have to say that the rest operator and spread operator introduced by ES6 provide great convenience in processing parameters
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 皂角仁甜菜

Arrow functions cannot be used as constructors!

Need to rewrite mybind with ordinary functions

When it comes to the step of supporting currying, the bind method can still be implemented using arrow functions, and it is more concise than ordinary functions

But if you want to continue to improve its behavior, you can't continue to use Arrow Function, because arrow functions cannot be new! , If you try to go to new, it will report an error:

anonymous_bind is not a constructor
The author also wrote about this mechanism of arrow functions. Then we need to rewrite mybind with a normal function
But it's also very simple, just save this manually. No longer post the changed code. See the next step

5. Support new binding function

An implicit behavior of bind:

  • to be called by the new keyword. However, actually used as the constructor to call the bind function! ! !
  • and the parameters passed in when calling new are passed to the calling function as usual.
logic

The logic to implement this step is also relatively simple. Let's compare the difference with the general call of new -

  • a new normal function : Logically generated constructor object instance it is that ordinary function
  • a new binding function : generated constructor instance of an object is call bind that function

The main logic we need to write is:

  1. Determine whether it is a new call
  2. Let this in function point to the instance object obj created in --new

    1. Is to replace this in the getInfo function with obj, so that obj can get the attributes
    2. You can use the apply method
  3. Determine whether function returns an object, if so, return the object, otherwise return the obj generated by new
As for why you write this, you need to understand the mechanism of the new keyword first. The link to my notes is attached at the end of the article.
Next, realize it!
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
Explain the above code:

first logic

  • There is a statement like this inside new: Con.apply(obj, args)
  • Where Con is the constructor of new, and obj is the last instance object to be returned
  • When the binding function of return in mybind above our new
  • Con is the binding function
  • When Con.apply(obj, args) is executed,
  • Call the binding function and replace this with obj
  • Then the program enters the binding function--
  • Judgment is indeed called by new
  • Execute self.apply(this, all_args);
  • This statement is equivalent to getInfo.apply(obj, all_args)
  • This will achieve our goal! -Let getInfo be the actual constructor of the instance object generated by new

second logic

  • The new keyword will determine whether the constructor itself will return a object
  • If so, then directly return this object as an instance, otherwise it is normal to return the obj generated by new as an instance object
  • So-we have called the actual constructor in the first logic-getInfo
  • Next, we directly judge the result of the call, that is, whether it returns an object, and then return to new as the final return.
In addition: it can be seen that when the binding function returned by new mybind, obj does not get the person.name property, which is undefined. That is to say--

At this point, the behavior of bind changing this point will be invalid

Look at the chestnuts, make it clearer
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
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

Although the value value is declared in the global and foo, it still returns undefind at the end, indicating that the bound this is invalid.

Why is this?

If you understand the simulation implementation of new, you will know--

New is a keyword of JS simulation object-oriented. One of its purposes is to achieve inheritance. It needs to inherit the properties in the constructor (class). So how does the new keyword achieve it? It internally applied similar such a statement:

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

The new keyword will first declare an empty object obj, and then point the this of the constructor to this object

What will happen if you do this--
  • If some properties are set in the constructor, such as: this.name = xx;
  • Then it is equivalent to replacing this with obj, which becomes: obj.name = xx;
  • obj inherits the properties of the constructor! !
  • obj is the instance object that will be returned at the end

See: "What does the new operator in JS do?" 》--Crushdada's Notes

Let's go back to why this is invalid
After understanding the relevant implementation of the new keyword, we have already got the answer--

After the new binding function is completed, the this inside the binding function already points to obj, and there is no value attribute in obj, and of course it returns undefined

6. Support prototype chain inheritance

In fact, this step is a supplement to the overriding new method in the binding function--

Because the new method originally supports prototype chain inheritance

logic

Then we only need--

Let the prototype of the new instance object obj point to the prototype of the actual constructor getInfo

Object.setPrototypeOf(this, self.prototype);
Standardization / rigor

You can add a judgment to the mybind method, the caller must be a function, otherwise a TypeError--

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

We simulate the bind method, which is achieved through apply after all. And how its source code is implemented is like a black box to me. In other words: without apply, how is it achieved?

7. Ultimate version-without applying apply

Most of Baidu's various versions are implemented with the help of apply, but I am lucky to find the answer in thinking about it - JS bind method how to achieve?

The alternative to apply given by the respondent is very simple:

  • The function that calls the bind method--that is, the function pointed to by this to be changed: caller
  • Let the caller's this point to: context

Then we only need--

  • Mount the caller as an object method on the context: context.callerFn = caller

    • the code above, the attribute name "callerFn" is a custom

In this way, when the sentence is executed, it is equivalent to the context calling the caller function, so this in the caller function naturally points to the context of the caller.

Above, it replaces the core function of apply in this example- and changing this point to

In addition, in order to improve the performance of the code, delete it after using up callerFn

context.__INTERNAL_SECRETS = func
  try {
  return context.__INTERNAL_SECRETS(...args)
} finally {
  delete context.__INTERNAL_SECRETS
}
Replace apply with the above code to get the final version

final version

FFunction.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; //这里的this和self即:调用mybind的方法--fn()
  context.caller2 = self;
  return function (...bindArgs) {
    let all_args = [...args, ...bindArgs];
    //new调用时,this被换成new方法最后要返回的实例对象obj
    if (new.target !== undefined) {
      try {
        this.caller = self;
        var result = this.caller(...all_args);
      } finally {
        delete this.caller;
      }
      Object.setPrototypeOf(this, self.prototype);
      return result instanceof Object ? result : this;
    }
    //当不是new调用时,this指向global/window(因为匿名函数返回后由全局调用)
    try {
      var final_res = context.caller2(...all_args);
    } finally {
      delete context.caller2;
    }
    return final_res; //调用mybind的那个函数[可能]有返回
  };
};

Other knowledge points:

Array.prototype.slice.call()

  • Receiving a string or have length property of objects
  • The method can be have a length property of objects or string into an array
Therefore, a class array with a length attribute such as arguments object can be converted into a real array using this method
In JS, only String and Array have .slice methods, but objects do not.
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() method

  • returns a new array object , that the object is a begin and end decisions original array shallow copy ( including begin , not include end ).

    • Received parameter - begin、end is the array index
    • The original array will not be changed.
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"]

Logical or "||"

a || b :

  • If the value in front of the operator is false, the following value is returned
  • If true, return the previous value

6 situations where the logical value is false in js

When a function has formal parameters, but no actual parameters are passed when calling, the formal parameter is undefined and will be processed as false
function name(params) {
  console.log(params); //undefined
}
name();
console.log(undefined == false);  //false
console.log(undefined || "undefined was reated as false");
//undefined was reated as false
A total of 6 will be treated as false by the logical OR operator--

0, null, "", false, undefined or NaN

Reference:

JavaScript in-depth bind simulation implementation-Nuggets

js implements these five layers of bind, which layer are you on? --The blue autumn

"What does the new operator do in JS?" 》--Crushdada's Notes


Crushdada
146 声望3 粉丝