- Написать новую функцию вручную
- Рукописная функция привязки/вызова/применения
- Написанные от руки функции защиты от сотрясений и дросселирования
- рукописная функция каррированная функция
- Написание обещания от руки
- Напишите функцию глубокого копирования вручную
- Напишите модель публикации-подписки
Реализуйте новую процедуру:
Ключевые моменты:
- Первым параметром функции является конструктор
- __proto__ экземпляра указывает на прототип свойства прототипа конструктора.
- Остальные параметры функции должны быть смонтированы на объекте-экземпляре
- Когда конструктор имеет возвращаемое значение, возвращаемое значение возвращается
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'
Рукописные функции вызова, применения и привязки
Общая основа:
- Первый параметр для привязки
- Это внутри функции на самом деле является функцией, которую нужно связать (потому что все три являются точечными вызовами)
bind
Здесь реализована простая версия (результат нового вызова другой), а по сложной версии рекомендуется обратиться кэта статья
- После выполнения функции связывания она возвращает копию исходной функции.
- Контекст, переданный привязке 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
Ключевые моменты:
- Три изменения состояния:
pending
fulfilled
rejected
-
resolve()
reject()
Реализация функции - ключевой момент
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.Изначально я хотел написать эту статью о рукописных функциях, но нашел хорошую статью, когда писал ее.Прочитав ее, я чувствую, что мне нечего писать...