Зачем писать такие статьи
Для программиста умение кодировать, несомненно, очень и очень важно, точно так же, как то, почему интервью на больших фабриках в основном спрашивают, какой API, как его реализовать, показывает его важность. Что я хочу сказать, так это то, что почерк настолько важен, поэтому мы должны его освоить, поэтому заголовок статьи совсем не является чрезмерным, и я надеюсь, что он не считается заглавной партией.
Как обычный фронтенд, я действительно не могу написать спецификацию Promise A+, но это не беда, мы можем стоять на плечах гигантов, мы должны верить, что дорога, по которой мы собираемся идти сейчас, пройдена наши предшественники, так что мы можем искать текущее сообщество Те отличные статьи, которые существуют, например, написанные промышленными эрудитами100 строк кода для реализации спецификации Promises/A+, После нахождения этих статей, это не фаворит, но вы должны найти время, чтобы изучить его приземленно, и отшлифовать его строка за строкой, пока вы не поймете. Это то, что я делаю сейчас.
что можно получить
Эта статья обычно делится на 2 типа рукописных вопросов.Первая половина может быть резюмирована как общие требования, а вторая половина — это реализация существующих технологий;
- Рукописная реализация часто используемых требований, таких как функции определения типа данных, глубокое копирование и т. д., может быть непосредственно использована в будущих проектах, что повысит эффективность разработки проекта;
- Реализация существующих ключевых слов и API может потребовать использования других знаний или API.Например, при написании forEach используется операция беззнакового сдвига вправо, которая обычно не очень доступна.Сейчас встречается.Можно легко разжиться этого. Таким образом, написанные от руки эти реализации могут слегка расширить и укрепить вашу собственную основу JS;
- Написав различные тестовые примеры, вы узнаете граничные условия различных API, таких как Promise.all, вы должны учитывать различные условия входящих параметров, тем самым углубляя свое понимание и использование их;
что делать во время чтения
При чтении нужно понимать каждую строчку кода, знать, что она делает, зачем писать так, можно ли написать лучше? Например, при написании ленивой загрузки картинки мы вообще судим загружать ли картинку по положению текущего элемента и вьюпорта.Обычные программисты так пишут и это почти закончено. А большие программисты рассмотрят больше деталей, например, как повысить производительность? Как можно упростить код? Например, в ленивой загрузке картинок, написанной yeyan1996, учитываются еще два момента: например, когда все картинки загружены, прослушиватель событий должен быть удален, например, при загрузке картинки текущий img должен быть удалено из imgList.Роль оптимизации памяти.
Помимо чтения кода, вы также можете открыть фрагмент сценария Chrome, чтобы написать тестовые примеры и запустить код для лучшего понимания и использования.
Прочитав несколько статей и написав множество тестовых примеров, попробуйте реализовать это вручную, чтобы увидеть, насколько вы освоились. Все дороги ведут в Рим, как иначе? Или ты умеешь писать лучше других?
Ну что ты еще делаешь, давай начнем работать.
оценка типа данных
typeof может правильно идентифицировать: Undefined, Boolean, Number, String, Symbol, Function и другие типы данных, но другие будут рассматриваться как объекты, такие как Null, Date и т. д., поэтому будет неточным судить о типе данных по тип. Но это можно реализовать с помощью Object.prototype.toString.
function typeOf(obj) {
- let res = Object.prototype.toString.call(obj).split(' ')[1]
- res = res.substring(0, res.length - 1).toLowerCase()
- return res
// 评论区里提到的更好的写法
+ return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()
}
typeOf([]) // 'array'
typeOf({}) // 'object'
typeOf(new Date) // 'date'
наследовать
Наследование цепочки прототипов
function Animal() {
this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
return this.colors
}
function Dog() {}
Dog.prototype = new Animal()
let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors) // ['black', 'white', 'brown']
Проблемы с наследованием цепочки прототипов:
- Проблема 1: свойства ссылочного типа, содержащиеся в прототипе, будут общими для всех экземпляров;
- Вопрос 2. Подкласс не может передавать параметры конструктору родительского класса при его создании;
Наследование через конструкторы
function Animal(name) {
this.name = name
this.getName = function() {
return this.name
}
}
function Dog(name) {
Animal.call(this, name)
}
Dog.prototype = new Animal()
Реализация наследования с помощью конструкторов решает две проблемы наследования цепочки прототипов: совместное использование ссылочного типа и передачу параметров. Однако, поскольку метод должен быть определен в конструкторе, он будет создаваться каждый раз при создании экземпляра подкласса.
наследование композиции
Комбинаторное наследование сочетает в себе цепочку прототипов и кражу конструкторов, объединяя лучшее из обоих миров. Основная идея заключается в использовании цепочки прототипов для наследования свойств и методов прототипа и наследования свойств экземпляра путем кражи конструктора. Это позволяет как определять методы прототипа для повторного использования, так и разрешать каждому экземпляру иметь свои собственные свойства.
function Animal(name) {
this.name = name
this.colors = ['black', 'white']
}
Animal.prototype.getName = function() {
return this.name
}
function Dog(name, age) {
Animal.call(this, name)
this.age = age
}
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog
let dog1 = new Dog('奶昔', 2)
dog1.colors.push('brown')
let dog2 = new Dog('哈赤', 1)
console.log(dog2)
// { name: "哈赤", colors: ["black", "white"], age: 1 }
наследование паразитарного состава
Комбинированное наследование было относительно совершенным, но все еще есть проблемы, проблема в том, что конструктор родительского класса вызывается дважды, первый раз в new Animal(), а второй раз в Animal.call() здесь.
Таким образом, решение состоит не в том, чтобы напрямую вызывать конструктор родительского класса для присвоения значения прототипу дочернего класса, а в том, чтобы получить копию прототипа родительского класса, создав пустую функцию F.
Паразитическое наследование композиции в основном похоже на наследование композиции в письменной форме, но разница заключается в следующем:
- Dog.prototype = new Animal()
- Dog.prototype.constructor = Dog
+ function F() {}
+ F.prototype = Animal.prototype
+ let f = new F()
+ f.constructor = Dog
+ Dog.prototype = f
После небольшой инкапсуляции кода, добавленного выше:
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function inheritPrototype(child, parent) {
let prototype = object(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
inheritPrototype(Dog, Animal)
Если вам слишком не нравится приведенный выше код, вы также можете изменить код, основанный на составном наследовании, на простейшее паразитное составное наследование:
- Dog.prototype = new Animal()
- Dog.prototype.constructor = Dog
+ Dog.prototype = Object.create(Animal.prototype)
+ Dog.prototype.constructor = Dog
класс реализует наследование
class Animal {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
}
Дедупликация массива
Реализация ES5:
function unique(arr) {
var res = arr.filter(function(item, index, array) {
return array.indexOf(item) === index
})
return res
}
Реализация ES6:
var unique = arr => [...new Set(arr)]
Выравнивание массива
Сведение массива — это сведение многослойного массива, такого как [1, [2, [3]]], в слой [1, 2, 3]. Используйте Array.prototype.flat, чтобы объединить многослойный массив непосредственно в один слой:
[1, [2, [3]]].flat(2) // [1, 2, 3]
Теперь стоит добиться эффекта квартиры.
Реализация ES5: рекурсия.
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
} else {
result.push(arr[i])
}
}
return result;
}
Реализация ES6:
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
глубокая копия
Неглубокая копия: учитывается только тип объекта.
function shallowCopy(obj) {
if (typeof obj !== 'object') return
let newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
Простая версия глубокого копирования: учитывайте только обычные свойства объекта, игнорируя встроенные объекты и функции.
function deepClone(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return newObj;
}
Глубокий клон сложной версии: основанный на простой версии, он также учитывает встроенные объекты, такие как Date, RegExp и другие объекты и функции, и решает проблему циклических ссылок.
const isObject = (target) => (typeof target === "object" || typeof target === "function") && target !== null;
function deepClone(target, map = new WeakMap()) {
if (map.get(target)) {
return target;
}
// 获取当前值的构造函数:获取它的类型
let constructor = target.constructor;
// 检测当前对象target是否与正则、日期格式对象匹配
if (/^(RegExp|Date)$/i.test(constructor.name)) {
// 创建一个新的特殊对象(正则类/日期类)的实例
return new constructor(target);
}
if (isObject(target)) {
map.set(target, true); // 为循环引用的对象做标记
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
} else {
return target;
}
}
Шина событий (модель публикации-подписки)
class EventEmitter {
constructor() {
this.cache = {}
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn)
} else {
this.cache[name] = [fn]
}
}
off(name, fn) {
let tasks = this.cache[name]
if (tasks) {
const index = tasks.findIndex(f => f === fn || f.callback === fn)
if (index >= 0) {
tasks.splice(index, 1)
}
}
}
emit(name, once = false, ...args) {
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
let tasks = this.cache[name].slice()
for (let fn of tasks) {
fn(...args)
}
if (once) {
delete this.cache[name]
}
}
}
}
// 测试
let eventBus = new EventEmitter()
let fn1 = function(name, age) {
console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)
eventBus.emit('aaa', false, '布兰', 12)
// '布兰 12'
// 'hello, 布兰 12'
Разбирать параметры URL в объекты
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}
шаблон строки
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}
тестовое задание:
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let person = {
name: '布兰',
age: 12
}
render(template, person); // 我是布兰,年龄12,性别undefined
Ленивая загрузка изображения
В отличие от обычной ленивой загрузки изображения, следующее выполнило 2 более тщательную обработку:
- Удалите прослушиватели событий после загрузки всех изображений;
- Загруженное изображение удаляется из imgList;
let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
// 修正错误,需要加上自执行
- const imgLazyLoad = function() {
+ const imgLazyLoad = (function() {
let count = 0
return function() {
let deleteIndexList = []
imgList.forEach((img, index) => {
let rect = img.getBoundingClientRect()
if (rect.top < window.innerHeight) {
img.src = img.dataset.src
deleteIndexList.push(index)
count++
if (count === length) {
document.removeEventListener('scroll', imgLazyLoad)
}
}
})
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
}
- }
+ })()
// 这里最好加上防抖处理
document.addEventListener('scroll', imgLazyLoad)
Ссылаться на:Ленивая загрузка изображения
Функция защиты от тряски
Высокочастотное событие будет выполнено только один раз через N секунд, и если событие будет запущено снова в течение N секунд, время будет изменено.
Простая версия: внутри функции поддерживается использование объектов this и event;
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
использовать:
var node = document.getElementById('layout')
function getUserAction(e) {
console.log(this, e) // 分别打印:node 这个节点 和 MouseEvent
node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)
Окончательная версия: помимо поддержки this и event поддерживаются следующие функции:
- поддержка немедленного исполнения;
- Функции могут иметь возвращаемые значения;
- Поддержка функции отмены;
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
} else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
использовать:
var setUseAction = debounce(getUserAction, 10000, true);
// 使用防抖
node.onmousemove = setUseAction
// 取消防抖
setUseAction.cancel()
Ссылаться на:Тема JavaScript: изучите защиту от сотрясений с помощью подчеркивания
регулирование функции
Инициировать высокочастотное событие и выполнять его только один раз в N секунд.
Простая версия: реализована с использованием временных меток, выполняется сразу один раз, затем каждые N секунд.
function throttle(func, wait) {
var context, args;
var previous = 0;
return function() {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
Окончательная версия: поддержка отмены дросселирования; кроме того, передайте третий параметр, options.leading, чтобы указать, может ли он быть выполнен один раз немедленно, и optons.trailing, чтобы указать, следует ли выполнять его снова, когда вызов завершается, и значение по умолчанию равно истинный. Обратите внимание, что вы не можете одновременно установить начальное или конечное значение false.
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled;
}
Использование троттлинга не беру код в качестве примера, просто сошлемся на написание анти-шейка.
Ссылаться на:Тема JavaScript: научитесь сокращать количество потоков с помощью подчеркивания
Кориизация
Что такое каррирование функций? По сути, это метод преобразования функции, принимающей несколько параметров, в ряд функций, принимающих один параметр. Все еще не знаю? Возьмем пример.
function add(a, b, c) {
return a + b + c
}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)
Теперь нам нужно реализовать карри-функцию, чтобы функция менялась с одного вызова с несколькими параметрами на несколько вызовов с одним параметром за раз.
function curry(fn) {
let judge = (...args) => {
if (args.length == fn.length) return fn(...args)
return (...arg) => judge(...args, ...arg)
}
return judge
}
частичная функция
Что такое частичная функция? Частичная функция предназначена для преобразования функции с n параметрами в функцию с фиксированными параметрами x, а остальные параметры (n - x) будут переданы при следующем вызове. Например:
function add(a, b, c) {
return a + b + c
}
let partialAdd = partial(add, 1)
partialAdd(2, 3)
Я не нашел, на самом деле частичная функция немного похожа на каррирование функции, поэтому в соответствии с реализацией функции каррирования можно быстро написать реализацию частичной функции:
function partial(fn, ...args) {
return (...arg) => {
return fn(...args, ...arg)
}
}
Вышеупомянутая функция относительно проста, и теперь мы надеемся, что частичная функция может выполнять ту же функцию-заполнитель, что и каррирование, например:
function clg(a, b, c) {
console.log(a, b, c)
}
let partialClg = partial(clg, '_', 2)
partialClg(1, 3) // 依次打印:1, 2, 3
_
Бит фактически приходится на позицию. Соответствует: частичному (clg, 1, 2), а затем частичному Clg (3). Поймите принципы, мы должны написать достижения:
function partial(fn, ...args) {
return (...arg) => {
args[index] =
return fn(...args, ...arg)
}
}
JSONP
Основной принцип JSONP: тег script не ограничен политикой одного и того же источника, поэтому его можно использовать для междоменных запросов.Преимущество заключается в хорошей совместимости, но его можно использовать только для запросов GET;
const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataSrc = ''
for (let key in params) {
if (params.hasOwnProperty(key)) {
dataSrc += `${key}=${params[key]}&`
}
}
dataSrc += `callback=${callbackName}`
return `${url}?${dataSrc}`
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement('script')
scriptEle.src = generateUrl()
document.body.appendChild(scriptEle)
window[callbackName] = data => {
resolve(data)
document.removeChild(scriptEle)
}
})
}
AJAX
const getJSON = function(url) {
return new Promise((resolve, reject) => {
const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');
xhr.open('GET', url, false);
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.responseText);
} else {
reject(new Error(xhr.responseText));
}
}
xhr.send();
})
}
Реализовать методы прототипа массива
forEach
Array.prototype.forEach2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this) // this 就是当前的数组
const len = O.length >>> 0 // 后面有解释
let k = 0
while (k < len) {
if (k in O) {
callback.call(thisArg, O[k], k, O);
}
k++;
}
}
Ссылаться на:forEach#polyfill
Какова операция O.length >>> 0? Это беззнаковый сдвиг вправо на 0 бит, какой в этом смысл? Просто чтобы убедиться, что преобразованное значение является положительным целым числом. Фактически, нижний уровень выполнил два уровня преобразования: первый — преобразовать нечисловой тип в числовой, а второй — преобразовать числовой в тип Uint32. Интересно читатьЧто означает что-то >>> 0?.
map
Реализация на основе forEach может легко написать реализацию карты:
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.map2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
- let k = 0
+ let k = 0, res = []
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ res[k] = callback.call(thisArg, O[k], k, O);
}
k++;
}
+ return res
}
filter
Аналогично, реализации на основе forEach могут легко написать реализацию фильтра:
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.filter2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
- let k = 0
+ let k = 0, res = []
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ if (callback.call(thisArg, O[k], k, O)) {
+ res.push(O[k])
+ }
}
k++;
}
+ return res
}
some
Точно так же реализация, основанная на forEach, может легко написать реализацию некоторых :
- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.some2 = function(callback, thisArg) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
let k = 0
while (k < len) {
if (k in O) {
- callback.call(thisArg, O[k], k, O);
+ if (callback.call(thisArg, O[k], k, O)) {
+ return true
+ }
}
k++;
}
+ return false
}
reduce
Array.prototype.reduce2 = function(callback, initialValue) {
if (this == null) {
throw new TypeError('this is null or not defined')
}
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function')
}
const O = Object(this)
const len = O.length >>> 0
let k = 0, acc
if (arguments.length > 1) {
acc = initialValue
} else {
// 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
while (k < len && !(k in O)) {
k++
}
if (k > len) {
throw new TypeError( 'Reduce of empty array with no initial value' );
}
acc = O[k++]
}
while (k < len) {
if (k in O) {
acc = callback(acc, O[k], k, O)
}
k++
}
return acc
}
Реализовать метод прототипа функции
call
Вызывает функцию с указанным значением this и одним или несколькими параметрами.
Точки реализации:
- это может быть передано в null;
- Передайте переменное количество параметров;
- Функции могут иметь возвращаемые значения;
Function.prototype.call2 = function (context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args +')');
delete context.fn
return result;
}
apply
apply — это то же самое, что и call, с той лишь разницей, что call передает переменное количество параметров, а apply — массив.
Точки реализации:
- это может быть передано в null;
- передать массив;
- Функции могут иметь возвращаемые значения;
Function.prototype.apply2 = function (context, arr) {
var context = context || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
bind
Метод bind создаст новую функцию.При вызове bind() this новой функции указывается как первый параметр bind(), а остальные параметры будут использоваться как параметры новой функции для использовать при звонке.
Точки реализации:
- В дополнение к этому, bind() также может передавать несколько параметров;
- Новые функции, созданные bing, могут передавать несколько параметров;
- Новые функции могут вызываться как конструкторы;
- Функции могут иметь возвращаемые значения;
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
Внедрить новое ключевое слово
Оператор new используется для создания экземпляров пользовательских типов объектов или встроенных объектов с конструкторами.
Точки реализации:
- new создает новый объект;
- Новый объект должен иметь доступ к свойствам конструктора, поэтому ему необходимо повторно указать свой прототип;
- Конструкторы могут явно возвращаться;
function objectFactory() {
var obj = new Object()
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
// ret || obj 这里这么写考虑了构造函数显示返回 null 的情况
return typeof ret === 'object' ? ret || obj : obj;
};
использовать:
function person(name, age) {
this.name = name
this.age = age
}
let p = objectFactory(person, '布兰', 12)
console.log(p) // { name: '布兰', age: 12 }
Реализовать ключевое слово instanceof
instanceof должен определить, появляется ли свойство прототипа конструктора в цепочке прототипов экземпляра.
function instanceOf(left, right) {
let proto = left.__proto__
while (true) {
if (proto === null) return false
if (proto === right.prototype) {
return true
}
proto = proto.__proto__
}
}
вверху слева.protoЭто обозначение можно заменить на Object.getPrototypeOf(left).
Реализовать Object.create
Метод Object.create() создает новый объект, используя существующий объект для предоставления __proto__ вновь созданного объекта.
Object.create2 = function(proto, propertyObject = undefined) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object or null.')
if (propertyObject == null) {
new TypeError('Cannot convert undefined or null to object')
}
function F() {}
F.prototype = proto
const obj = new F()
if (propertyObject != undefined) {
Object.defineProperties(obj, propertyObject)
}
if (proto === null) {
// 创建一个没有原型对象的对象,Object.create(null)
obj.__proto__ = null
}
return obj
}
Реализовать Object.assign
Object.assign2 = function(target, ...source) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object')
}
let ret = Object(target)
source.forEach(function(obj) {
if (obj != null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key]
}
}
}
})
return ret
}
Реализовать JSON.stringify
Метод JSON.stringify([ replacer [ пробел]) преобразует значение JavaScript (объект или массив) в строку JSON. Реализация моделирования здесь не учитывает необязательный второй заменитель параметра и пространство третьего параметра.Если вы не понимаете роль этих двух параметров, рекомендуется прочитатьMDNдокументация.
- Основные типы данных:
- undefined остается неопределенным после преобразования (тип также не определен)
- Логическое значение преобразуется в строку "false"/"true"
- числовые типы (кроме NaN и Infinity) преобразуются в числовые значения типа string
- символ не определен после преобразования
- После нулевого преобразования будет строка "null"
- строка остается строкой после преобразования
- Строка "null" после преобразования NaN и Infinity
- Тип функции: не определено после преобразования
- Если это тип объекта (не функция)
- Если это массив: если он не определен, любая функция и символ появляются в значении свойства, преобразуйте его в строку "null" ;
- Если это объект RegExp: return {} (тип — строка);
- Если это объект Date, верните строковое значение toJSON Date;
- если это обычный объект;
- Сериализуйте возвращаемое значение toJSON(), если есть метод toJSON().
- Если в значениях свойств появляются неопределенные, произвольные функции и значения символов, игнорируйте их.
- Все свойства с символом в качестве ключа полностью игнорируются.
- Выполнение этого метода для объектов, содержащих циклические ссылки (объекты, ссылающиеся друг на друга, образующие бесконечный цикл), вызовет ошибку.
function jsonStringify(data) {
let dataType = typeof data;
if (dataType !== 'object') {
let result = data;
//data 可能是 string/number/null/undefined/boolean
if (Number.isNaN(data) || data === Infinity) {
//NaN 和 Infinity 序列化返回 "null"
result = "null";
} else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
//function 、undefined 、symbol 序列化返回 undefined
return undefined;
} else if (dataType === 'string') {
result = '"' + data + '"';
}
//boolean 返回 String()
return String(result);
} else if (dataType === 'object') {
if (data === null) {
return "null"
} else if (data.toJSON && typeof data.toJSON === 'function') {
return jsonStringify(data.toJSON());
} else if (data instanceof Array) {
let result = [];
//如果是数组
//toJSON 方法可以存在于原型链中
data.forEach((item, index) => {
if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {
result[index] = "null";
} else {
result[index] = jsonStringify(item);
}
});
result = "[" + result + "]";
return result.replace(/'/g, '"');
} else {
//普通对象
/**
* 循环引用抛错(暂未检测,循环引用时,堆栈溢出)
* symbol key 忽略
* undefined、函数、symbol 为属性值,被忽略
*/
let result = [];
Object.keys(data).forEach((item, index) => {
if (typeof item !== 'symbol') {
//key 如果是symbol对象,忽略
if (data[item] !== undefined && typeof data[item] !== 'function'
&& typeof data[item] !== 'symbol') {
//键值如果是 undefined、函数、symbol 为属性值,忽略
result.push('"' + item + '"' + ":" + jsonStringify(data[item]));
}
}
});
return ("{" + result + "}").replace(/'/g, '"');
}
}
}
Ссылаться на:Реализовать JSON.stringify
Реализовать JSON.parse
Предложите 2 метода достижения:
- оценка реализации;
- реализация новой функции;
оценка реализации
Первый метод самый простой и интуитивно понятный, это прямой вызов eval Код выглядит следующим образом:
var json = '{"a":"1", "b":2}';
var obj = eval("(" + json + ")"); // obj 就是 json 反序列化之后得到的对象
Но прямой вызов eval будет иметь проблемы с безопасностью.Если данные могут быть не данными json, а исполняемым кодом JavaScript, это, вероятно, вызовет XSS-атаки. Поэтому перед вызовом eval данные необходимо проверить.
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
rx_one.test(
json.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {
var obj = eval("(" +json + ")");
}
Ссылаться на:Три реализации JSON.parse
реализация новой функции
Функция имеет те же свойства строкового параметра, что и eval.
var json = '{"name":"小姐姐", "age":20}';
var obj = (new Function('return ' + json))();
Реализовать обещания
Реализация промисов требует полного пониманияОбещание A+ Спецификация, но с общей точки зрения реализации необходимо учитывать следующие моменты:
- затем необходимость поддержки связанных вызовов, поэтому мы должны вернуть новый промис;
- Чтобы справиться с асинхронными проблемами, вы должны сначала использовать onResvedCallbacks и OnrejectedCallbacks для хранения успешных и неудачных обратных вызовов соответственно;
- Чтобы цепочка вызовов проходила нормально, необходимо определить типы onFulfilled и onRejected;
- onFulfilled и onRejected нужно вызывать асинхронно, здесь setTimeout используется для имитации асинхронности;
- Обработать решение Promise;
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) = > {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach((fn) = > fn());
}
};
let reject = (reason) = > {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) = > fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 解决 onFufilled,onRejected 没有传值的问题
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
// 因为错误的值要让后面访问到,所以这里也要抛出错误,不然会在之后 then 的 resolve 中捕获
onRejected = typeof onRejected === "function" ? onRejected : (err) = > {
throw err;
};
// 每次调用 then 都返回一个新的 promise
let promise2 = new Promise((resolve, reject) = > {
if (this.status === FULFILLED) {
//Promise/A+ 2.2.4 --- setTimeout
setTimeout(() = > {
try {
let x = onFulfilled(this.value);
// x可能是一个proimise
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === REJECTED) {
//Promise/A+ 2.2.3
setTimeout(() = > {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() = > {
setTimeout(() = > {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
const resolvePromise = (promise2, x, resolve, reject) = > {
// 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise Promise/A+ 2.3.1
if (promise2 === x) {
return reject(
new TypeError("Chaining cycle detected for promise #<Promise>"));
}
// Promise/A+ 2.3.3.3.3 只能调用一次
let called;
// 后续的条件要严格判断 保证代码能和别的库一起使用
if ((typeof x === "object" && x != null) || typeof x === "function") {
try {
// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候) Promise/A+ 2.3.3.1
let then = x.then;
if (typeof then === "function") {
// 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty Promise/A+ 2.3.3.3
then.call(
x, (y) = > {
// 根据 promise 的状态决定是成功还是失败
if (called) return;
called = true;
// 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
resolvePromise(promise2, y, resolve, reject);
}, (r) = > {
// 只要失败就失败 Promise/A+ 2.3.3.3.2
if (called) return;
called = true;
reject(r);
});
} else {
// 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4
resolve(x);
}
} catch (e) {
// Promise/A+ 2.3.3.2
if (called) return;
called = true;
reject(e);
}
} else {
// 如果 x 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.4
resolve(x);
}
};
После того, как Promise написан, мы можем использовать пакет promises-aplus-tests, чтобы протестировать написанный нами код и убедиться, что он соответствует спецификации A+. Но перед тестом мы должны добавить кусок кода:
// promise.js
// 这里是上面写的 Promise 全部代码
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
Установить глобально:
npm i promises-aplus-tests -g
Выполните команду проверки в терминале:
promises-aplus-tests promise.js
Код, написанный выше, может успешно пройти все 872 теста.
Ссылаться на:
- Классические вопросы для интервью с интерфейсом BAT: самое подробное рукописное руководство по Promise в истории
- 100 строк кода для реализации спецификации Promises/A+
Promise.resolve
Promsie.resolve(value) может преобразовать любое значение в обещание, состояние значения которого выполнено, но если переданное значение само является обещанием, оно вернет его как есть.
Promise.resolve = function(value) {
// 如果是 Promsie,则直接输出它
if(value instanceof Promise){
return value
}
return new Promise(resolve => resolve(value))
}
Ссылаться на:Глубокое понимание промисов
Promise.reject
Подобно Promise.resolve(), Promise.reject() создает экземпляр отклоненного промиса. Но в отличие от Promise.resolve(), если Promise.reject() передается объект Promise, этот объект становится значением нового Promise.
Promise.reject = function(reason) {
return new Promise((resolve, reject) => reject(reason))
}
Promise.all
Правила для Promise.all таковы:
- Все переданные промисы выполняются, затем возвращается новый промис с выполненным статусом, состоящий из их значений;
- Пока есть одно отклоненное обещание, возвращается новое обещание в отклоненном состоянии, и его значение равно значению первого отклоненного обещания;
- Пока существует ожидающее обещание, вернуть новое обещание в состоянии ожидания;
Promise.all = function(promiseArr) {
let index = 0, result = []
return new Promise((resolve, reject) => {
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(val => {
index++
result[i] = val
if (index === promiseArr.length) {
resolve(result)
}
}, err => {
reject(err)
})
})
})
}
Promise.race
Promise.race возвращает новый экземпляр, обернутый первым выполненным или отклоненным экземпляром всех итерируемых экземпляров.
Promise.race = function(promiseArr) {
return new Promise((resolve, reject) => {
promiseArr.forEach(p => {
Promise.resolve(p).then(val => {
resolve(val)
}, err => {
rejecte(err)
})
})
})
}
Promise.allSettled
Правило для Promise.allSettled таково:
- Состояние всех промисов изменилось, затем возвращается новый промис, состояние которого выполнено, и его значение представляет собой массив, и каждый элемент массива представляет собой объект, составленный из всех значений и состояний промисов;
- Если есть ожидающее обещание, верните новый экземпляр, состояние которого находится в ожидании;
Promise.allSettled = function(promiseArr) {
let result = []
return new Promise((resolve, reject) => {
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(val => {
result.push({
status: 'fulfilled',
value: val
})
if (result.length === promiseArr.length) {
resolve(result)
}
}, err => {
result.push({
status: 'rejected',
reason: err
})
if (result.length === promiseArr.length) {
resolve(result)
}
})
})
})
}
Promise.any
Правила для Promise.any таковы:
- Если пустой массив или все обещания отклонены, статус возврата — новое обещание отклонено, а значение — AggregateError;
- Пока один выполняется, возвращайте первый новый выполненный экземпляр;
- В противном случае возвращается новый экземпляр ожидания;
Promise.any = function(promiseArr) {
let index = 0
return new Promise((resolve, reject) => {
if (promiseArr.length === 0) return
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(val => {
resolve(val)
}, err => {
index++
if (index === promiseArr.length) {
reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
позже
Я вижу, что мне очень нравится код здесь, ведь код выглядит очень скучно, но если вы его поймете, вы будете так же счастливы, как выиграть игру, и эта вещь будет затягивать, когда вы пройдете уровень Подробнее уровней, которые вы проходите, тем выше будут ваши способности. Говоря словами заголовка: после понимания улучшение действительно большое. Давай 💪, готовь
О нет, кодеры.