The realization of several handwritten codes in the front end

Keywords: Javascript Attribute

Preface

Now the front-end threshold is higher and higher, and it's not as simple as writing pages. Modularity, automation, cross end development and so on are gradually becoming requirements, but these need to be built on our solid foundation. No matter how the framework and model change, only by laying down the basic principles can we quickly adapt to the changes in the market. Here are some common source code implementations:

  • call implementation
  • bind implementation
  • new implementation
  • instanceof implementation
  • Object.create implementation
  • Deep copy implementation
  • Publish and subscribe mode

call

call is used to change the direction of this function and execute the function

Generally, whoever calls a function points to this. Using this feature, we can change the direction of this function by calling the function as the attribute of the object, which is called implicit binding. apply implements the same principle by changing the input parameter form.

let obj = {
  name: 'JoJo'
}
function foo(){
  console.log(this.name)
}
obj.fn = foo
obj.fn() // log: JOJO

Realization

Function.prototype.mycall = function () {
  if(typeof this !== 'function'){
    throw 'caller must be a function'
  }
  let othis = arguments[0] || window
  othis._fn = this
  let arg = [...arguments].slice(1)
  let res = othis._fn(...arg)
  Reflect.deleteProperty(othis, '_fn') //Remove the fn attribute
  return res
}

Use

let obj = {
  name: 'JoJo'
}
function foo(){
  console.log(this.name)
}
foo.mycall(obj) // JoJo

bind

bind is used to change the direction of this function and return a function

Note:

  • this as a constructor call points to
  • Maintain prototype chain
Function.prototype.mybind = function (oThis) {
  if(typeof this != 'function'){
    throw 'caller must be a function'
  }
  let fThis = this
  //Array.prototype.slice.call converts a class array to an array
  let arg = Array.prototype.slice.call(arguments,1)
  let NOP = function(){}
  let fBound = function(){
    let arg_ = Array.prototype.slice.call(arguments)
    // new binding level is higher than explicit binding
    // When called as a constructor, keep the pointer unchanged
    // Use instanceof to determine whether it is a constructor call
    return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_))
  }
  // Maintenance prototype
  if(this.prototype){
    NOP.prototype = this.prototype
  }
  fBound.prototype = new NOP()
  return fBound
}

Use

let obj = {
  msg: 'JoJo'
}
function foo(msg){
  console.log(msg + '' + this.msg)
}
let f = foo.mybind(obj)
f('hello') // hello JoJo

new

new uses the constructor to create an instance object and add this property and method to the instance object

new process:

  1. create new object
  2. New object? proto? Points to constructor prototype
  3. New object add attribute method (this points to)
  4. Returns the new object this points to
function new_(){
  let fn = Array.prototype.shift.call(arguments)
  if(typeof fn != 'function'){
    throw fn + ' is not a constructor'
  }
  let obj = {}
  obj.__proto__ = fn.prototype
  let res = fn.apply(obj, arguments)
  return typeof res === 'object' ? res : obj
}

instanceof

instanceof determines whether the prototype on the left exists in the prototype chain on the right.

Implementation idea: look up the prototype layer by layer. If the final prototype is null, it is proved that it does not exist in the prototype chain, otherwise it exists.

function instanceof_(left, right){
  left = left.__proto__
  while(left !== right.prototype){
    left = left.__proto__ // Find the prototype and judge again while
    if(left === null){
      return false
    }
  }
  return true
}

Object.create

Object.create creates a new object, and uses the existing object to provide the "proto" of the newly created object. The second optional parameter is the property description object

function objectCreate_(proto, propertiesObject = {}){
  if(typeof proto !== 'object' || typeof proto !== 'function' || proto !== null){
    throw('Object prototype may only be an Object or null:'+proto)
  }
  let res = {}
  res.__proto__ = proto
  Object.defineProperties(res, propertiesObject)
  return res
}

Deep copy

A deep copy creates an identical copy of an object with different reference addresses. When you want to use an object, but do not want to modify the original object, deep copy is a good choice. A basic version is implemented here, which only makes deep copies of objects and arrays.

Implementation idea: traverse the object, use recursion to copy the reference type, and assign the basic type directly

function deepClone(origin) {
  let toStr = Object.prototype.toString
  let isInvalid = toStr.call(origin) !== '[object Object]' && toStr.call(origin) !== '[object Array]'
  if (isInvalid) {
    return origin
  }
  let target = toStr.call(origin) === '[object Object]' ? {} : []
  for (const key in origin) {
    if (origin.hasOwnProperty(key)) {
      const item = origin[key];
      if (typeof item === 'object' && item !== null) {
        target[key] = deepClone(item)
      } else {
        target[key] = item
      }
    }
  }
  return target
}

Publish and subscribe mode

Publish and subscribe mode can realize complete decoupling between modules in actual development, and modules only need to pay attention to event registration and triggering.

The publish and subscribe mode implements EventBus:

class EventBus{
  constructor(){
    this.task = {}
  }

  on(name, cb){
    if(!this.task[name]){
      this.task[name] = []
    }
    typeof cb === 'function' && this.task[name].push(cb)
  }

  emit(name, ...arg){
    let taskQueen = this.task[name]
    if(taskQueen && taskQueen.length > 0){
      taskQueen.forEach(cb=>{
        cb(...arg)
      })
    }
  }

  off(name, cb){
    let taskQueen = this.task[name]
    if(taskQueen && taskQueen.length > 0){
      let index = taskQueen.indexOf(cb)
      index != -1 && taskQueen.splice(index, 1)
    }
  }

  once(name, cb){
    function callback(...arg){
      this.off(name, cb)
      cb(...arg)
    }
    typeof cb === 'function' && this.on(name, callback)
  }
}

Use

let bus = new EventBus()
bus.on('add', function(a,b){
  console.log(a+b)
})
bus.emit('add', 10, 20) //30

Posted by tr0gd0rr on Fri, 14 Feb 2020 23:41:40 -0800