Реализация принципа рукописного кода во внешнем интерфейсе

JavaScript

предисловие

Сейчас порог фронтенда становится все выше и выше, и это уже не так просто, как просто написание страниц. Модульность, автоматизация, кросс-энд разработка и т. д. постепенно становятся требованиями, но они должны быть построены на нашем прочном фундаменте. Как бы ни менялись рамки и модели, только заложив прочный фундамент в основных принципах, можно быстро адаптироваться к изменениям на рынке. Вот некоторые часто используемые реализации исходного кода:

  • реализация вызова
  • привязать реализацию
  • новая реализация
  • экземпляр реализации
  • Реализация Object.create
  • реализация глубокого копирования
  • модель публикации-подписки

call

callдля изменения функцииthisуказать и выполнить функцию

В общем, кто вызывает функцию, тотthisуказать кому. Воспользовавшись этой возможностью, функцию можно изменить, вызвав функцию как атрибут объекта.thisуказать, это называется неявным связыванием.applyЧтобы добиться того же, просто измените форму входного параметра.

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

выполнить

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') //删除_fn属性
  return res
}

использовать

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

bind

bindдля изменения функцииthisуказывает и возвращает функцию

будь осторожен:

  • это указывает на вызов конструктора
  • Поддерживать цепочку прототипов
Function.prototype.mybind = function (oThis) {
  if(typeof this != 'function'){
    throw 'caller must be a function'
  }
  let fThis = this
  //Array.prototype.slice.call 将类数组转为数组
  let arg = Array.prototype.slice.call(arguments,1)
  let NOP = function(){}
  let fBound = function(){
    let arg_ = Array.prototype.slice.call(arguments)
    // new 绑定等级高于显式绑定
    // 作为构造函数调用时,保留指向不做修改
    // 使用 instanceof 判断是否为构造函数调用
    return fThis.apply(this instanceof fBound ? this : oThis, arg.concat(arg_))
  }
  // 维护原型
  if(this.prototype){
    NOP.prototype = this.prototype
    fBound.prototype = new NOP()
  }
  return fBound
}

использовать

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

new

newИспользуйте конструктор для создания объекта экземпляра, добавьте к объекту экземпляраthisсвойства и методы

newпроцесс:

  1. создать новый объект
  2. Новый объект __proto__ указывает на прототип конструктора
  3. Новый метод добавления свойства объекта (это указывает на)
  4. Возвращает новый объект, на который указывает this
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
}

В ES5 мы можем использоватьObject.createЧтобы упростить процесс:

function new_(){
  let fn = Array.prototype.shift.call(arguments)
  if(typeof fn != 'function'){
    throw fn + ' is not a constructor'
  }
  let obj = Object.create(fn.prototype)
  let res = fn.apply(obj, arguments)
  return typeof res === 'object' ? res : obj
}

instanceof

instanceofОсуществляется, присутствует ли прототип слева в цепочке прототипа справа.

Идея реализации: Найти прототип слой за слоем, если окончательный прототипnull, доказательство не существует в цепочке прототипов, иначе оно существует.

function instanceof_(left, right){
  left = left.__proto__
  while(left !== right.prototype){
    left = left.__proto__ // 查找原型,再次while判断
    if(left === null){
      return false
    }
  }
  return true
}

Object.create

Object.createСоздайте новый объект, используйте существующий объект, чтобы предоставить __proto__ вновь созданного объекта, вторым необязательным параметром является объект описания свойства.

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
}

глубокая копия

Глубокая копия создает идентичную копию объекта с другими ссылочными адресами. Глубокое копирование — хороший выбор, когда вы хотите использовать объект, но не хотите изменять исходный объект. Здесь реализована базовая версия, которая делает только глубокие копии объектов и массивов.

Идея реализации: обход объектов, использование рекурсии для продолжения копирования ссылочных типов и непосредственное назначение базовых типов

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
}

модель публикации-подписки

Модель «публикация-подписка» может обеспечить полное разделение между модулями в фактической разработке, и модулям нужно только обращать внимание на регистрацию и инициирование событий.

Модель публикации-подписки реализует 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 taskQueue = this.task[name]
    if(taskQueue && taskQueue.length > 0){
      taskQueue.forEach(cb=>{
        cb(...arg)
      })
    }
  }

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

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

использовать

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