Simple simulation of call, apply and bind methods in JavaScript

Time:2021-6-5

Introduction

After reading the introduction and in-depth chapter about this in the JavaScript you don’t know Volume I, the direction of thisI use this article to summarize. Then I wonder if I can use this knowledge to simulate the call, apply and bind methods commonly used in JavaScript?

So there is this article, no more nonsense, the full text begins!

Implicit loss

Since implicit loss is used in simulation implementation, I’d like to introduce it first.

Implicit loss is a common problem of this binding, which means that the function that is implicitly bound will lose the bound object and eventually apply to the default binding. In other words, it belongs to implicit binding(obj.xxxIn the case of this pointing to obj, the default binding (this pointing to global object) is applied.

Common implicit loss case 1: reference passing

var a = 'window'
function foo() {
    console.log(this.a)
}
var obj = {
    a: 'obj',
    foo: foo
}

Obj. Foo() //'obj'this = > obj
var lose = obj.foo
Lose() // 'window' this = > window

Common implicit loss case 2: it is passed in as a callback function

var a = 'window'
function foo() {
    console.log(this.a)
}
var obj = {
    a: 'obj',
    foo: foo
}

function lose(callback) {
    callback()
}

Lose (obj. Foo) //'window'this = > window


//= = = = = = = split line===============
var t = 'window'
function bar() {
    console.log(this.t)
}

setTimeout(bar, 1000)   // 'window'

I conclude that(I don’t know right or wrong): after excluding explicit binding, no matter how to pass value, as long as it is called without any modification, it will be applied to the default binding

Further, the key principle of the whole implementation is obtained No matter how to pass the value, the way of calling finally determines the direction of this

Hard binding

The intuitive description of hard binding is as follows:Once this is explicitly specified for a function, no matter how it is called later, the direction of this will not be changed

The implementation of hard binding solves the problem of implicit loss. The implementation of bind function is based on the principle of hard binding

//Solving implicit loss
var a = 'window'
function foo() {
    console.log(this.a)
}
var obj = {
    a: 'obj',
    foo: foo
}

function lose(callback) {
    callback()
}

lose(obj.foo)   // 'window'

var fixTheProblem = obj.foo.bind(obj)
lose(fixTheProblem) // 'obj'

Realization and principle analysis

Simulate call

//Simulate call
Function.prototype._ Call = function ($this,... Parms) {//... Parms is the rest operator, which is used to receive all the incoming arguments and return an array containing them
    /* 
        This will point to the call_ The function object this of call method must be a function
        **This step is very critical * * = > and then temporarily store this object in our specified $this (context) object
    */
    $this['caller'] = this
    //$this['caller'](...parms)

    //This writing will be clearer than the one above
    $this. Caller (... Parms) //... Parms is now the spread operator, which is used to deconstruct the elements in the array and pass in arguments to the caller function
    /* 
        For clarity, use the following more explicit writing instead of commenting out
            1. $this.caller is the original function that we want to change this point to
            2. However, since it is now called by $this.caller, the implicit binding rules are applied
            3. So this success points to $this
    */
    Delete $this ['caller '] // this is a temporary attribute. It cannot destroy the original structure of the artificially bound object, so it needs to be deleted after it is used up
}

Simulation Implementation of apply

//Simulation Implementation of apply * * and_ The implementation of call is almost the same, and the main difference is only in the method / type of parameter transfer**
Function.prototype._ Apply = function ($this, parmsarr) {// according to the second parameter of the original apply, an array is passed in
    $this['caller'] = this
    $this ['caller '] (... Parmsarr) //... Parmsarr is now the spread operator, which is used to deconstruct the elements in the array and pass in arguments to the caller function
    delete $this['caller']
}

Since_ Call and_ If the similarity (coupling degree) before apply is so high, we can further extract them

function interface4CallAndApply(caller, $this, parmsOrParmArr) {
    $this['caller'] = caller
    $this['caller'](...parmsOrParmArr)
    delete $this['caller']
}


Function.prototype._call = function ($this, ...parms) {
    var funcCaller = this
    interface4CallAndApply(funcCaller, $this, parms)
}


Function.prototype._apply = function ($this, parmsArr) {
    var funcCaller = this
    interface4CallAndApply(funcCaller, $this, parmsArr)
}

One I think can be better displayed_ Call and_ An example of applying principle

var myName = 'window'
var obj = {
    myName: 'Fitz',
    sayName() {
        console.log(this.myName)
    }
}

var foo = obj.sayName

var bar = {
    myName: 'bar',
    foo
}

bar.foo()

Simulation Implementation of bind

//Using the principle of hard binding to simulate the implementation of bind
Function.prototype._bind = function ($this, ...parms) {
    $bindcaller = this // save call_ The object of the bind function. Note: this object is a function
    //According to the return value of the native bind function: it is a function
    Return function () {// use the rest operator instead of arguments to collect the incoming arguments
        return $bindCaller._apply($this, parms)
    }
}

An example showing the principle of hard binding

function hardBind(fn) {
    var caller = this
    var parms = [].slice.call(arguments, 1)
    return function bound() {
        parms = [...parms, ...arguments]
        FN. Apply (caller, parms) // apply can accept a pseudo array instead of an array
    }
}


var myName = 'window'
function foo() {
    console.log(this.myName)
}
var obj = {
    myName: 'obj',
    foo: foo,
    hardBind: hardBind
}

//Under normal circumstances
foo()   // 'window'
obj.foo()   // 'obj'

var hb = hardBind(foo)
//You can see that once hard bound, no matter how you call it, you can't change this point
hb()    // 'window'
Obj. HB = Hb // add this method to obj for testing
obj.hb()    // 'window'

//Let's make a deeper impression
var hb2 = obj.hardBind(foo)
Hb2() //'obj '// this call should point to window

Overall implementation (pure version / no comments)

function interface4CallAndApply(caller, $this, parmsOrParmArr) {
    $this['caller'] = caller
    $this['caller'](...parmsOrParmArr)
    delete $this['caller']
}


Function.prototype._call = function ($this, ...parms) {
    var funcCaller = this
    interface4CallAndApply(funcCaller, $this, parms)
}


Function.prototype._apply = function ($this, parmsArr) {
    var funcCaller = this
    interface4CallAndApply(funcCaller, $this, parmsArr)
}


Function.prototype._bind = function ($this, ...parms) {
    $bindCaller = this
    return function () {
        return $bindCaller._apply($this, parms)
    }
}



//= = = = = = Test===============
var foo = {
    name: 'foo',
    sayHello: function (a, b) {
        console.log(`hello, get the parms => ${a} and ${b}`)
    }
}

var bar = {
    name: 'bar'
}

foo.sayHello._call(bar, 'Fitz', 'smart')
foo.sayHello._apply(bar, ['Fitz', 'smart'])

var baz = foo.sayHello._bind(bar, 'Fitz', 'smart')
baz()

var testHardBind = foo.sayHello._bind(bar, 'hard', 'bind')
testHardBind._ Call (object. Create (null)) // Hello, get the parms = > hard and bind test_ Hard binding of bind

Write at the end

I am just a little white who is learning the front end. Please correct me if there is something wrong

If you feel inspired or helpful, please leave a message or give me a concern, thank you!