Handwritten new, call, apply, bind, reduce, currying, anti shake and throttle source code, with detailed analysis

These are mainly written for review and precipitation. In the process of re writing, I will also put forward my own understanding, and I hope to point out some inappropriate points.
I recommend an online IDE here. I will use it when I type some code demo StackBlitz , which is easier to use, but it is recommended to update when saving instead of setting. Otherwise, problems often occur when updating too often.

Implement new

First, describe the implementation process of new with words

  • Define a json object
  • Object inherits the prototype chain of the constructor
  • Point this of the constructor to this json object
  • Returns a result based on the return value type of the constructor,
  function myNew(fn) {
    let obj = {}
    obj.__proto__ = Object.create(fn.prototype) 
    let args = Array.prototype.slice.call(arguments, 1) // Get parameters other than fn
    let result = fn.call(obj, ...args)
    return typeof result === 'object' ? result : obj;
  }
  function foo() {
    this.name = 'ciel'
    this.arg = arguments[0]
  }
  foo.prototype.callName = function() {
    console.log(this.name)
  }
  // test
  let test = myNew(foo, 'hhh', '123', 'saf')
  test.callName()
  console.log(test.arg)
Copy code

Here we explain the return type of result = = = 'object'? Result: object; this Code:
In the JavaScript constructor: if the return value type has no effect on the constructor, the instantiated object returns an empty object; if the return reference type (array, function, object), the instantiated object returns the reference type; you can test the values returned by the following two constructors after new to understand the meaning of this sentence

 function foo() {
   this.name = 'ciel'
   return function() {

   }
 }
 new foo() //  fn(){}
 function bar() {
   this.name = 'ciel'
   return 1
 }
 new bar() // {name: ciel}
Copy code

Implement call

First, let's see how the pseudo code uses fn.myCall(obj, args) of myCall to analyze how the code should be implemented

  • myCall should be on Function.prototype
  • this of fn points to obj
  • args of myCall is transmitted to fn
Function.prototype.myCall = function(target, ...args) {
  // this points to the object calling the myCall function
  if (typeof this !== "function") {
    throw new TypeError("not a function")
  }
  target = target || window
  target.fn = this // Implicit binding, changing the caller of the constructor indirectly changes this point
  let result = target.fn(...args)
  return result
};
// test
var obj = { name: 123 }
function foo(...args) {
  console.log(this.name, args)
}
var s = foo.myCall(obj, '111', '222')
Copy code

Implement apply

Recall the difference between apply and call: the apply parameter should be an array. Others are the same as call implementation

Function.prototype.myApply = function(target) {
  if (typeof this !== "function") {
    throw new TypeError("not a function");
  }
  if (!Array.isArray(arguments[1])) {
    throw new Error('arg not a array')
  }
  target = target || window
  target.fn = this
  let args = arguments[1]
  let result = target.fn(args)
  return result
};

var obj = { name: 123 };
function foo(...args) {
  console.log(this.name, args);
}
foo.prototype.name = 123;
var s1 = [1, 2, 3, 4, 5];
var s = foo.myApply(obj,s1);
Copy code

Implement bind

  • The difference between call and apply: fn.bind(obj) does not execute FN function immediately, while call and apply execute immediately
  • The new function returned by bind can be called either in the normal way or in the constructor way. When it is a constructor, this refers to the instance
  • The parameter of bind() method has a feature, that is, function currification, which is simply to keep the position of a parameter, and then automatically store the parameter in this position when the parameter is passed for the second time
Function.prototype.mybind = function(thisArg) {
  if (typeof this !== 'function') {
    throw TypeError("Bind must be called on a function");
  }
  // Get the parameters to pass to the caller
  const args = Array.prototype.slice.call(arguments, 1),
    self = this,
    // Build a clean function to save the prototype of the original function
    nop = function() {},
    // Bound function
    bound = function() {
      // this instanceof nop, judge whether to use new to call the bound
      // If it is called by new, the point of this is its instance,
      // If it is not a new call, change this to the specified object o
      return self.apply(
        this instanceof nop ? this : thisArg,
        args.concat(Array.prototype.slice.call(arguments)) 
      );
    };
  // Arrow function has no prototype, and arrow function this always points to its scope
  if (this.prototype) {
    nop.prototype = this.prototype;
  }
  // Modify the prototype point of the binding function
  bound.prototype = new nop();
  return bound;
}

// test

 let obj = { name: "ciel" }
    function test(x,y,z) {
      console.log(this.name) // ciel
      console.log(x+y+z) // 6
    }
    var Bound = test.mybind(obj, 1, 2)
    Bound(3) // 6
Copy code

Implement reduce

arr.reduce((res,cur, index, arr) => res+cur, 0)

  • Parameter: a callback function, an initialization parameter (not required)
  • The callback function parameter has four values (res: stands for cumulative value, cur: current value, index: the number of values, array where arr calls reduce)
  • Overall return res accumulation value
Array.prototype.myReduce = function(cb, initValue) {
  if (!Array.isArray(this)) {
    throw new TypeError("not a array")
  }
  // Array is empty and has initial value. Error is reported
  if (this.length === 0 && arguments.length < 2) {
    throw new TypeError('Reduce of empty array with no initial value')
  }
  let arr = this
  let res = null
  // Judge whether there is initial value
  if (arguments.length > 1) {
    res = initValue
  } else {
    res = arr.splice(0,1)[0] //Take the first value if not
  }
  arr.forEach((item, index) => {
    res = cb(res, item, index, arr) // cb will return a new res value after each execution, overwriting the previous res
  })
  return res
};

// test result
let arr = [1,2,3,4]
let result = arr.myReduce((res, cur) => {
  return res + cur
})
console.log(result) // 10
Copy code

tip: usually, reduce is often used when processing data in the work. To implement a data processing, you need to traverse it many times. To implement it by reduce, you may only need to traverse it once

Implementing Currying

What is Coriolis? The complex problem is decomposed into many small programmable problems, and the realization of multi parameter function provides a realization idea of recursive degradation -- transforming the function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function), and returning the new function that accepts the remaining parameters and returns the result, combined with an example, to achieve the following effect

sum(1,2) // 3
sum(1,2)(3) // 6
sum(4,5)(10) // 19
 Copy code

Implementation code

function sum() {
  let allArgs = Array.prototype.slice.call(arguments);
  let add = function(){
    allArgs.push(...arguments) // Parameters are collected every time the sum function is called
    return add
  }
  // Rewrite the toString method. When the function is executed, the toString() method will be called automatically, and all parameter results will be returned after calculation
  add.toString = function () {
    return allArgs.reduce((a, b) => a+b)
  }
  return add
}

Copy code

test result

Anti shake

Anti shake: the function will only be executed once within n seconds after triggering the high frequency event. If the high frequency event is triggered again within n seconds, the time will be recalculated (take the last time). The idea is to cancel the previous delay call method before each trigger

  function debounce(fn, delay) {
   let timer = null
   return function() {
     let self = this // This gets this because debounce() returns an internal function, where this can be captured.
     let args = Array.prototype.slice.call(arguments)
     if (timer) clearTimeout(timer) // Cancel previous timer
     setTimeout(function () {
       fn.call(self, ...args) // Prevent this point from changing, ensure that the context is the current this, and pass parameters
     }, delay)
   }
 }
 function testFn() {
  console.log('It's clicked', this)
 }
 // test
document.addEventListener('click', debounce(testFn, 1000)) 
Copy code

Achieve throttling

Throttling: triggered by high-frequency events, but only once in n seconds, so throttling will dilute the frequency of function execution thinking: each time an event is triggered, it is necessary to determine whether there is a delay function waiting to be executed, and a mark is needed

function throtting(fn, delay) {
   let timer = null
   let isCancel = false
   return function() {
     if (isCancel) return
     isCancel = true
     clearTimeout(timer)
     let self = this;
     let args = Array.prototype.slice.call(arguments)
     if (timer) clearTimeout(timer)
     setTimeout(function () {
       fn.call(self, ...args)
       isCancel = false
     }, delay)
   }
 }
 function testFn() {
  console.log('Entered', this)
 }
document.addEventListener('input', throtting(testFn, 1000)) 
Copy code

Execute only once in a certain period of time, judge whether there is a delay function waiting for execution, and return if there is one

Tags: JSON Javascript

Posted on Fri, 08 May 2020 23:00:55 -0700 by jponte