Несколько распространенных рукописных кодов в интервью

JavaScript опрос
Несколько распространенных рукописных кодов в интервью

Реализуйте новую процедуру:

Ключевые моменты:

  1. Первым параметром функции является конструктор
  2. __proto__ экземпляра указывает на прототип свойства прототипа конструктора.
  3. Остальные параметры функции должны быть смонтированы на объекте-экземпляре
  4. Когда конструктор имеет возвращаемое значение, возвращаемое значение возвращается
const createObj = function () {
  let obj = {}
  let Constructor = [].shift.call(arguments) // 1
  obj.__proto__ = Constructor.prototype // 2
  let ret = Constructor.apply(obj, arguments) // 3
  return typeof ret === 'object' ? ret: obj // 4
}

// 使用
const Fun = function (name) {
  this.name = name
}
Fun.prototype.getName = function() {
  alert(this.name)
}
let fun = createObj(Fun, 'gim')
fun.getName() // gim

Стоит отметить, что класс es6 должен вызываться с new, иначе будет сообщено об ошибке, как показано ниже:

class Fun {
  constructor(name) {
    this.name = name
  }
  getName() {
    alert(this.name)
  }
}
let fun = createObj(Fun, 'gim')
fun.getName() // Uncaught TypeError: Class constructor Fun cannot be invoked without 'new'

Рукописные функции вызова, применения и привязки

Общая основа:

  1. Первый параметр для привязки
  2. Это внутри функции на самом деле является функцией, которую нужно связать (потому что все три являются точечными вызовами)

bind

Здесь реализована простая версия (результат нового вызова другой), а по сложной версии рекомендуется обратиться кэта статья

  1. После выполнения функции связывания она возвращает копию исходной функции.
  2. Контекст, переданный привязке fn внутри возвращаемой функции
Function.prototype.myBind = function(context, ...args) {
  if (typeof this !== 'function') throw 'caller must be a function'
  const fn = this
  return function() {
    return fn.call(context, ...args, ...arguments)
  }
}

call,applyРеализация функции фактически зависит от вызова точки. Используйте первый параметр для передачи и удалите его после вызова.

call

Function.prototype.myCall = function(context = windows, ...args) {
  context._fn = this
  const result = context._fn(...args)
  delete context._fn
  return result
}

apply

Function.prototype.myApply = function(context = windows, args) {
  context._fn = this
  const result = context._fn(args)
  delete context._fn
  return result
}

троттлинг и стабилизация

Когда я впервые столкнулся с этими двумя понятиями, я был глупо сбит с толку.

Некоторые события браузера, такие как: изменение размера, прокрутка, нажатие клавиши, нажатие клавиши, перемещение мыши и т. д. Эти события запускаются слишком часто, и функции обратного вызова, привязанные к этим событиям, будут вызываться постоянно. Это увеличит нагрузку на браузер, что приведет к очень плохому пользовательскому опыту.

Дросселирование и защита от сотрясений в основном используют затворы.

дросселирование

Регулировка функции, чтобы функция срабатывала каждые n миллисекунд.

// 节流
function throttle (f, wait = 200) {
  let last = 0
  return function (...args) { // 以下 内部匿名函数 均是指这个匿名函数
    let now = Date.now()
    if (now - last > wait) {
      last = now
      f.apply(this, args) // 注意此时 f 函数的 this 被绑定成了内部匿名函数的 this,这是很有用的
    }
  }
}
// 未节流
input.onkeyup = funciton () {
  $.ajax(url, this.value)
}
// 节流
input.onkeyup = throttle(function () { // throttle() 返回内部匿名函数,所以 input 被绑定到了内部匿名函数的 this 上
  $.ajax(url, this.value) // 注意这个 this 在执行时被 apply 到了内部匿名函数上的 this ,也就是 input
})

Стабилизатор

Функция debounce заставляет функцию срабатывать только в последний раз в течение n миллисекунд.

// 防抖
function debounce (f, wait = 200) {
  let timer = 0
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      f.apply(this, args)
    }, wait)
  }
}
// 未防抖
input.onkeyup = funciton () {
  $.ajax(url, this.value)
}
// 防抖
input.onkeyup = debounce(function () { // debounce() 返回内部匿名函数,所以 input 被绑定到了内部匿名函数的 this 上
  $.ajax(url, this.value) // 注意这个 this 在执行时被 apply 到了内部匿名函数上的 this ,也就是 input
})

Карри функция

Каррирование может использовать функции и различные параметры для формирования более конкретных функций.

Каррирование на самом деле использует технологию замыканий для кэширования функций и параметров снова и снова и выполнения функции, когда параметров достаточно.

function curry(fn, ...rest) {
  const length = fn.length
  return function() {
    const args = [...rest, ...arguments]
    if (args.length < length) {
      return curry.call(this, fn, ...args)
    } else {
      return fn.apply(this, args)
    }
  }
}
function add(m, n) {
  return m + n
}
const add5 = curry(add, 5)

Promise

Ключевые моменты:

  1. Три изменения состояния:pending fulfilled rejected
  2. resolve() reject()Реализация функции
  3. ключевой моментthenРеализация связанных вызовов
class MyPromise {
  constructor(fn) {
    this.status = 'pending'
    this.value = null
    this.resolve = this._resolve.bind(this)
    this.reject = this._reject.bind(this)
    this.resolvedFns = []
    this.rejectedFns = []
    try {
      fn(this.resolve, this.reject)
    } catch (e) {
      this.catch(e)
    }
  }
  _resolve(res) {
    setTimeout(() => {
      this.status = 'fulfilled'
      this.value = res
      this.resolvedFns.forEach(fn => {
        fn(res)
      })
    })
  }
  _reject(res) {
    setTimeout(() => {
      this.status = 'rejected'
      this.value = res
      this.rejectedFns.forEach(fn => {
        fn(res)
      })
    })
  }
  then(resolvedFn, rejecetedFn) {
    return new MyPromise(function(resolve, reject) {
      this.resolveFns.push(function(value) {
        try {
          const res = resolvedFn(value)
          if (res instanceof MyPromise) {
            res.then(resolve, reject)
          } else {
            resolve(res)
          }
        } catch (err) {
          reject(err)
        }
      })
      this.rejectedFns.push(function(value){
        try {
          const res = rejectedFn(value)
          if (res instanceof MyPromise) {
            res.then(resolve, reject)
          } else {
            reject(res)
          }
        } catch (err) {
          reject(err)
        }
      })
    })
  }
  catch(rejectedFn) {
    return this.then(null, rejectedFn)
  }
}

this.resolvedFnsа такжеthis.rejectedFnsХранится вthenЛогика обработки параметров функции будет выполнена, когда операция Promise будет иметь результат.

thenФункции, возвращающие Promise, реализуют цепочку.

На самом деле, я в основном полагаюсь на механическое запоминание во время собеседования, потому что однажды попросил меня написать 5 реализаций (включая промисы) за 20 минут, ,кто дал вам время подумать. . .

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

нищенская версия

function deepCopy(obj) {
  //判断是否是简单数据类型,
  if (typeof obj == "object") {
    //复杂数据类型
    var result = obj.constructor == Array ? [] : {};
    for (let i in obj) {
      result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
    }
  } else {
    //简单数据类型 直接 == 赋值
    var result = obj;
  }
  return result;
}

Шаблон наблюдателя и шаблон публикации-подписки

Режим наблюдателя Наблюдатель Наблюдатель и субъект Тема относительно четкие, тогда как публикацией и подпиской в ​​режиме публикации-подписки занимается диспетчерский центр, а границы между издателями и подписчиками размыты.

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

Важно: Должны быть реализованы все три события добавления/удаления/отправки обновления.

Шаблон наблюдателя

class Subject {
  constructor() {
    this.observers = []
  }
  add(observer) {
    this.observers.push(observer)
    this.observers = [...new Set(this.observers)]
  }
  notify(...args) {
    this.observers.forEach(observer => observer.update(...args))
  }
  remove(observer) {
    let observers = this.observers
    for (let i = 0, len = observers.length; i < len; i++) {
      if (observers[i] === observer) observers.splice(i, 1)
    }
  }
}

class Observer {
  update(...args) {
    console.log(...args)
  }
}

let observer_1 = new Observer() // 创建观察者1
let observer_2 = new Observer()
let sub = new Subject() // 创建主体
sub.add(observer_1) // 添加观察者1
sub.add(observer_2)
sub.notify('I changed !')

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

Здесь мы используем те, которые все еще находятся на стадии предложенияclassчастная собственность#handlers, но основные браузеры уже поддерживают его.

class Event {
  // 首先定义一个事件容器,用来装事件数组(因为订阅者可以是多个)
  #handlers = {}

  // 事件添加方法,参数有事件名和事件方法
  addEventListener(type, handler) {
    // 首先判断handlers内有没有type事件容器,没有则创建一个新数组容器
    if (!(type in this.#handlers)) {
      this.#handlers[type] = []
    }
    // 将事件存入
    this.#handlers[type].push(handler)
  }

  // 触发事件两个参数(事件名,参数)
  dispatchEvent(type, ...params) {
    // 若没有注册该事件则抛出错误
    if (!(type in this.#handlers)) {
      return new Error('未注册该事件')
    }
    // 便利触发
    this.#handlers[type].forEach(handler => {
      handler(...params)
    })
  }

  // 事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)
  removeEventListener(type, handler) {
    // 无效事件抛出
    if (!(type in this.#handlers)) {
      return new Error('无效事件')
    }
    if (!handler) {
      // 直接移除事件
      delete this.#handlers[type]
    } else {
      const idx = this.#handlers[type].findIndex(ele => ele === handler)
      // 抛出异常事件
      if (idx === -1) {
        return new Error('无该绑定事件')
      }
      // 移除事件
      this.#handlers[type].splice(idx, 1)
      if (this.#handlers[type].length === 0) {
        delete this.#handlers[type]
      }
    }
  }
}

рекомендовать

Недавно у меня брал интервью рукописный Promise.Изначально я хотел написать эту статью о рукописных функциях, но нашел хорошую статью, когда писал ее.Прочитав ее, я чувствую, что мне нечего писать...

портал