Daily silicon step, apply/call/bind self-realization

Keywords: Javascript Attribute REST github encoding

call/apply/bind is used in daily encoding by developers to implement "object impersonation", i.e. "display binding this".

The interview question, "call/apply/bind source implementation", is actually a comprehensive assessment of JavaScript fundamentals.

Relevant knowledge points:

  1. Scope;
  2. this points to;
  3. Curitization of functions;
  4. Prototype and prototype chain;

Differences between call/apply/bind

  1. All three can be used to display the binding this;
  2. The difference between call/apply is the way in which the parameters are passed.

    • fn.call(obj, arg1, arg2,...), passes a list of parameters separated by commas;
    • fn.call(obj, [arg1, arg2,...]), passing an array of parameters;
  3. bind returns a function to be executed, an application of the Curitized function, while call/apply executes the function immediately

A Preliminary Study of Ideas

Function.prototype.myCall = function(context) {
    // This in the prototype points to an instance object, so this points to [Function: bar]
    console.log(this);  // [Function: bar]
    // In the incoming context object, create an attribute with a value pointing to the method bar
    context.fn = this;  // foo.fn = [Function: bar]
    // Call this method, where the caller is Foo and this points to foo
    context.fn();
    // Delete it after execution and use it only once to avoid it being used elsewhere (traversal)
    delete context.fn;
};

let foo = {
    value: 2
};

function bar() {
    console.log(this.value);
}
// The declaration of the bar function is equivalent to: var bar = new Function("console.log(this.value)));

bar.call(foo);   // 2;

Source implementation of call

I have a general idea, the rest is to improve the code.

// ES6 Version
Function.prototype.myCall = function(context, ...params) {
  // ES6 function Rest parameter so that it can specify an object, receive the remaining parameters of the function, and synthesize an array
  if (typeof context === 'object') {
    context = context || window;
  } else {
    context = Object.create(null);
  }

  // Use Symbol as attribute key value to keep uniqueness and avoid conflicts
  let fn = Symbol();
  context[fn] = this;
  // Expand the parameter array and pass in as multiple parameters
  const result = context[fn](...params);
  // Delete to avoid persistence
  delete(context[fn]);
  // Functions can have return values
  return result;            
}

// test
var mine = {
    name: 'In the name of pleasure'
}

var person = {
  name: 'Anonymous person',
  sayHi: function(msg) {
    console.log('My name:' + this.name + ',', msg);
  }
}

person.sayHi.myCall(mine, 'Nice to meet you!');  
// My name: It's a pleasure to meet you!

Knowledge point supplement:

  1. ES6 The new original data type, Symbol, represents unique values;
  2. Object.create(null) Creates an empty object
// How to create an empty object

// eg.A 
let emptyObj = {};

// eg.B
let emptyObj = new Object();

// eg.C
let emptyObj = Object.create(null);

Empty objects created with Object.create(null) are not interfered with by the prototype chain.The end of the prototype chain points to null, has no constructor, and has no attributes such as toString, hasOwnProperty, valueOf, which come from Object.prototype.Partners with a prototype chain base should know that the prototype chain of all common objects points to Object.prototype.

So Object.create(null) creates empty objects that are cleaner than the other two methods and do not have attributes on the chain of Object prototypes.

ES5 Version:

  1. Processing parameters by oneself;
  2. Self-Implementing Symobo
// ES5 Version

// Simulate Symbol
function getSymbol(obj) {
  var uniqAttr = '00' + Math.random();
  if (obj.hasOwnProperty(uniqAttr)) {
    // If it already exists, it recursively calls the function
    arguments.callee(obj);
  } else {
    return uniqAttr;
  }
}

Function.prototype.myCall = function() {
  var args = arguments;
  if (!args.length) return;

  var context = [].shift.apply(args);
  context = context || window;

  var fn = getSymbol(context);
  context[fn] = this;

  // No other parameters passed in
  if (!arguments.length) {
    return context[fn];
  }

  var param = args[i];
  // Type judgment, otherwise eval will run in error
  var paramType = typeof param;
  switch(paramType) {
    case 'string':
      param = '"' + param + '"'
    break;
    case 'object':
      param = JSON.stringify(param);
    break;
  } 

  fnStr += i == args.length - 1 ? param : param + ',';

  // Execute with eval
  var result = eval(fnStr);
  delete context[fn];
  return result;
}

// test
var mine = {
    name: 'In the name of pleasure'
}

var person = {
  name: 'Anonymous person',
  sayHi: function(msg) {
    console.log('My name:' + this.name + ',', msg);
  }
}

person.sayHi.myCall(mine, 'Nice to meet you!');  
// My name: Happy to know!

Source implementation of apply

call's source code implementation, then apply is simple, the two are just different ways of passing parameters.

Function.prototype.myApply = function(context, params) {
    // apply differs from call in that the second parameter is an array and there is no third parameter
    if (typeof context === 'object') {
        context = context || window;
    } else {
        context = Object.create(null);
    }

    let fn = Symbol();
    context[fn] = this;
    const result context[fn](...params);
    delete context[fn];
    return result;
}

Source implementation of bind

  1. The difference between bind and call/apply is that it returns a function to be executed, not the result of its execution.
  2. The function returned by bind is used with new as a constructor, and the binding this needs to be ignored;

The value passed to the target function as the this parameter when the bound function is called.If a binding function is constructed using the new operator, the value is ignored.- MDN

Function.prototype.bind = function(context, ...initArgs) {
    // The method called by bind must be a function
    if (typeof this !== 'function') {
      throw new TypeError('not a function');
    }
    let self = this;  
    let F = function() {};
    F.prototype = this.prototype;
    let bound = function(...finnalyArgs) {
      // Merge forward and backward parameters into
      return self.call(this instanceof F ? this : context || this, ...initArgs, ...finnalyArgs);
    }
    bound.prototype = new F();
    return bound;
}

Many partners will also encounter the question of how to bind without using call/apply?

Don't panic in Sao Nian. Without call/apply, it's equivalent to replacing call/apply with a corresponding self-realization method. It's a lazy trick.

This call/apply/bind source implementation is a further consolidation of previous article series knowledge points.

"Don't panic if you have your code in mind."

Reference documents:

More front-end cornerstones to build, in Github, look forward to Star!
https://github.com/ZengLingYong/blog

Author: In the name of Happy
This article is original, there are inappropriate places to welcome pointing out.Please indicate the source for reprinting.

Posted by rawisjp on Sat, 10 Aug 2019 19:16:16 -0700