предисловие
Недавно я готовился к интервью, и я просто использовал несколько дней, чтобы систематически разобраться в типах вопросов, которые часто тестирует JS.
Вот, в виде карты мозга, я вам разберу все вопросы (с аннотациями) в этой статье.
Хотите привлечь внимание карты мозгапередний дом времениОфициальный аккаунт, ответ "JS hand слеза", можно получить ❤️
Все типы вопросов на карте мозга описаны в этой статье.30道
Часто задаваемые вопросы
Карта мозга👇
1. Вручную внедрить неглубокий клон
Мелкий клон: копировать только первый слой объекта или массива
const shallClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) { // 遍历对象自身可枚举属性(不考虑继承属性和原型对象)
cloneTarget[prop] = target[prop];
}
return cloneTarget;
} else {
return target;
}
}
2. Вручную реализовать глубокое клонирование (простая версия)
Глубокое клонирование: копирование содержимого каждого слоя объекта или массива слой за слоем.
function deepClone(target) {
if (target === null) return null;
if (typeof target !== 'object') return target;
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop]);
}
}
return cloneTarget;
}
3. Вручную реализовать глубокое клонирование (рассмотрите специальные объекты, такие как дата/обычные и разрешите циклические ссылки)
const isObject = (target) =>
(typeof target === "object" || typeof target === "function") &&
target !== null;
function deepClone(target, map = new Map()) {
// 先判断该引用类型是否被 拷贝过
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;
}
}
4. Механизм ручной реализации instanceOf
Идеи:
Шаг 1: Сначала получите прототип текущего класса, цепочку прототипов текущего экземпляра объекта.
Шаг 2: Цикл все время (выполнение механизма поиска цепочки прототипов)
- Получить цепочку прототипов цепочки прототипов текущего объекта экземпляра (
proto = proto.____proto____
, ищем цепочку прототипов) - Если цепочка прототипов текущего экземпляра
____proto__
Прототип текущего класса находится наprototype
, затем вернутьсяtrue
- Если вы продолжаете находить
Object.prototype.____proto____ == null
, если базовый класс (null) Object не найден, возвращается false
function _instanceof (instanceObject, classFunc) {
let classFunc = classFunc.prototype; // 取得当前类的原型
let proto = instanceObject.__proto__; // 取得当前实例对象的原型链
while (true) {
if (proto === null) { // 找到了 Object的基类 Object.prototype.__proto__
return false;
};
if (proto === classFunc) { // 在当前实例对象的原型链上,找到了当前类
return true;
}
proto = proto.__proto__; // 沿着原型链__ptoto__一层一层向上查找
}
}
Оптимизированная версия (для решения проблем совместимости)
Объект.getPrototypeOf():Используется для получения прототипа экземпляра объекта (внутренний[[prototype]]Стоимость имущества, в том числеprotoАтрибуты)
function _instanceof (instanceObject, classFunc) {
let classFunc = classFunc.prototype; // 取得当前类的原型
let proto = Object.getPrototypeOf(instanceObject); // 取得当前实例对象的原型链上的属性
while (true) {
if (proto === null) { // 找到了 Object的基类 Object.prototype.__proto__
return false;
};
if (proto === classFunc) { // 在当前实例对象的原型链上,找到了当前类
return true;
}
proto = Object.getPrototypeOf(proto); // 沿着原型链__ptoto__一层一层向上查找
}
}
5. Вручную внедрите функцию защиты от сотрясений
Реализовать анти-дрожание функции (цель состоит в том, чтобы выполняться только один раз при частых триггерах)
Возьмите последний триггер в качестве эталона
/**
* 实现函数的防抖(目的是频繁触发中只执行一次)
* @param {*} func 需要执行的函数
* @param {*} wait 检测防抖的间隔频率
* @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次
* @return {可被调用执行的函数}
*/
function debounce(func, wati = 500, immediate = false) {
let timer = null
return function anonymous(... params) {
clearTimeout(timer)
timer = setTimeout(_ => {
// 在下一个500ms 执行func之前,将timer = null
//(因为clearInterval只能清除定时器,但timer还有值)
// 为了确保后续每一次执行都和最初结果一样,赋值为null
// 也可以通过 timer 是否 为 null 是否有定时器
timer = null
func.call(this, ...params)
}, wait)
}
}
Возьмите первый триггер за стандарт
/**
* 实现函数的防抖(目的是频繁触发中只执行一次)
* @param {*} func 需要执行的函数
* @param {*} wait 检测防抖的间隔频率
* @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次
* @return {可被调用执行的函数}
*/
function debounce(func, wait = 500, immediate = true) {
let timer = null
return function anonymous(... params) {
// 第一点击 没有设置过任何定时器 timer就要为 null
let now = immediate && !timer
clearTimeout(timer)
timer = setTimeout(_ => {
// 在下一个500ms 执行func之前,将timer = null
//(因为clearInterval只能在系统内清除定时器,但timer还有值)
// 为了确保后续每一次执行都和最初结果一样,赋值为null
// 也可以通过 timer 是否 为 null 是否有定时器
timer = null!immediate ? func.call(this, ...params) : null
}, wait)
now ? func.call(this, ...params) : null
}
}
function func() {
console. log('ok')
}
btn. onclick = debounce(func, 500)
6. Вручную реализовать функцию дросселирования
Реализовать дросселирование функций (цель — уменьшить частоту частых срабатываний)
Аннотированная версия
[Первый триггер:reaminingотрицательное число,previousприсваивается текущее время]
[Второй триггер: при условии, что временной интервал составляет 500 мс, после первого выполнения20ms
После этого сразу запускайте второй раз, затемосталось = 500 - (новое текущее время - время последнего срабатывания) = 500 - 20 = 480】
/**
* 实现函数的节流 (目的是频繁触发中缩减频率)
* @param {*} func 需要执行的函数
* @param {*} wait 检测节流的间隔频率
* @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次
* @return {可被调用执行的函数}
*/
function throttle(func, wait) {
let timer = null
let previous = 0 // 记录上一次操作的时间点
return function anonymous(... params) {
let now = new Date() // 当前操作的时间点
remaining = wait - (now - previous) // 剩下的时间
if (remaining <= 0) {
// 两次间隔时间超过频率,把方法执行
clearTimeout(timer); // clearTimeout是从系统中清除定时器,但timer值不会变为null
timer = null; // 后续可以通过判断 timer是否为null,而判断是否有 定时器
// 此时已经执行func 函数,应该将上次触发函数的时间点 = 现在触发的时间点 new Date()
previous = new Date(); // 把上一次操作时间修改为当前时间
func.call(this, ...params);
} else if(!timer){
// 两次间隔的事件没有超过频率,说明还没有达到触发标准,设置定时器等待即可(还差多久等多久)
// 假设事件间隔为500ms,第一次执行完之后,20ms后再次点击执行,则剩余 480ms,就能等待480ms
timer = setTimeout( _ => {
clearTimeout(timer)
timer = null // 确保每次执行完的时候,timer 都清 0,回到初始状态
//过了remaining时间后,才去执行func,所以previous不能等于初始时的 now
previous = new Date(); // 把上一次操作时间修改为当前时间
func.call(this, ...params);
}, remaining)
}
}
}
function func() {
console. log('ok')
}
btn. onclick = throttle(func, 500)
неаннотированная версия
/**
* 实现函数的节流 (目的是频繁触发中缩减频率)
* @param {*} func 需要执行的函数
* @param {*} wait 检测节流的间隔频率
* @param {*} immediate 是否是立即执行 True:第一次,默认False:最后一次
* @return {可被调用执行的函数}
*/
function throttle(func, wait) {
let timer = null;
let previous = 0;
return function anonymous(... params) {
let now = new Date();
remaining = wait - (now - previous);
if (remaining <= 0) {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this, ...params);
} else if(!timer){
timer = setTimeout( _ => {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this, ...params);
}, remaining)
}
}
}
function func() {
console. log('ok')
}
btn. onclick = throttle(func, 500);
7. Вручную внедрить Object.create
Object.create() = function create(prototype) {
// 排除传入的对象是 null 和 非object的情况
if (prototype === null || typeof prototype !== 'object') {
throw new TypeError(`Object prototype may only be an Object: ${prototype}`);
}
// 让空对象的 __proto__指向 传进来的 对象(prototype)
// 目标 {}.__proto__ = prototype
function Temp() {};
Temp.prototype = prototype;
return new Temp;
}
8. Вручную реализовать принцип встроенного нового
упрощенная версия
- Шаг 1: Создайте экземпляр объекта Func (instance.proto= класс.прототип)
- Шаг 2: ПоместитеFuncВыполняется как обычная функция и изменяетсяthisнаправление
- Шаг 3: Анализ возвращаемого значения функции
/**
* Func: 要操作的类(最后要创建这个类的实例)
* args:存储未来传递给Func类的实参
*/
function _new(Func, ...args) {
// 创建一个Func的实例对象(实例.____proto____ = 类.prototype)
let obj = {};
obj.__proto__ = Func.prototype;
// 把Func当做普通函数执行,并改变this指向
let result = Func.call(obj, ...args);
// 分析函数的返回值
if (result !== null && /^(object|function)$/.test(typeof result)) {
return result;
}
return obj;
}
Оптимизировано
protoНе поддерживается в браузере IE
let x = { name: "lsh" };
Object.create(x);
{}.__proto__ = x;
function _new(Func, ...args) {
// let obj = {};
// obj.__proto__ = Func.prototype;
// 创建一个Func的实例对象(实例.____proto____ = 类.prototype)
let obj = Object.create(Func.prototype);
// 把Func当做普通函数执行,并改变this指向
let result = Func.call(obj, ...args);
// 分析函数的返回值
if (result !== null && /^(object|function)$/.test(typeof result)) {
return result;
}
return obj;
}
9. Вручную реализовать метод вызова
Простая версия (не учитывает контекстную ситуацию без объекта, не рассматривает ситуацию, когда Symbol\BigInt не может new.constructor( context ))
/**
* context: 要改变的函数中的this指向,写谁就是谁
* args:传递给函数的实参信息
* this:要处理的函数 fn
*/
Function.prototype.call = function(context, ...args) {
// null,undefined,和不传时,context为 window
context = context == null ? window : context;
let result;
context['fn'] = this; // 把函数作为对象的某个成员值
result = context['fn'](...args); // 把函数执行,此时函数中的this就是
delete context['fn']; // 设置完成员属性后,删除
return result;
}
Идеальная версия (контекст должен быть типа объекта, совместимым с символом и т. д.)
/**
* context: 要改变的函数中的this指向,写谁就是谁
* args:传递给函数的实参信息
* this:要处理的函数 fn
*/
Function.prototype.call = function(context, ...args) {
// null,undefined,和不传时,context为 window
context = context == null ? window : context;
// 必须保证 context 是一个对象类型
let contextType = typeof context;
if (!/^(object|function)$/i.test(contextType)) {
// context = new context.constructor(context); // 不适用于 Symbol/BigInt
context = Object(context);
}
let result;
context['fn'] = this; // 把函数作为对象的某个成员值
result = context['fn'](...args); // 把函数执行,此时函数中的this就是
delete context['fn']; // 设置完成员属性后,删除
return result;
}
10. Вручную внедрить метод применения
/**
* context: 要改变的函数中的this指向,写谁就是谁
* args:传递给函数的实参信息
* this:要处理的函数 fn
*/
Function.prototype.apply = function(context, args) {
context = context == null ? window : context;
let contextType = typeof context;
if (!/^(object|function)$/i.test(contextType)) {
context = Object(context);
}
let result;
context['fn'] = this;
result = context['fn'](...args);
delete context['fn'];
return result;
}
11. Вручную внедрить метод привязки
/**
* this: 要处理的函数 func
* context: 要改变的函数中的this指向 obj
* params:要处理的函数传递的实参 [10, 20]
*/
Function.prototype._bind = function(context, ...params) {
let _this = this; // this: 要处理的函数
return function anonymous (...args) {
// args: 可能传递的事件对象等信息 [MouseEvent]
// this:匿名函数中的this是由当初绑定的位置 触发决定的 (总之不是要处理的函数func)
// 所以需要_bind函数 刚进来时,保存要处理的函数 _this = this
_this.call(context, ...params.concat(args));
}
}
12.ES5 реализует плоский метод выравнивания массива
Идеи:
- цикл по каждому элементу массива
- Проверьте, является ли элемент массивом
- Если это массив, продолжайте перебирать этот элемент - массив
- Если это не массив, добавьте элемент в новый массив
let arr = [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
];
function myFlat() {
_this = this; // 保存 this:arr
let newArr = [];
// 循环arr中的每一项,把不是数组的元素存储到 newArr中
let cycleArray = (arr) => {
for (let i=0; i< arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) { // 元素是数组的话,继续循环遍历该数组
cycleArray(item);
continue;
} else{
newArr.push(item); // 不是数组的话,直接添加到新数组中
}
}
}
cycleArray(_this); // 循环数组里的每个元素
return newArr; // 返回新的数组对象
}
Array.prototype.myFlat = myFlat;
arr = arr.myFlat(); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
13.ES6 реализует плоский метод выравнивания массива
const myFlat = (arr) => {
let newArr = [];
let cycleArray = (arr) => {
for(let i = 0; i < arr.length; i++) {
let item = arr[i];
if (Array.isArray(item)) {
cycleArray(item);
continue;
} else {
newArr.push(item);
}
}
}
cycleArray(arr);
return newArr;
}
myFlat(arr); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
14. Используйте reduce для ручной реализации плоского метода выравнивания массива.
согласно сArray.isArray
Проверьте, является ли каждый элемент в массиве элементом массива один за другим
const myFlat = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? myFlat(cur) : cur);
}, []);
};
console.log(myFlat(arr));
// [12, 23, 34, 56, 78, 90, 100, 110, 120, 130, 140]
15. Использовать три разных идеи для дедупликации массива?
Идея 1: последний элемент массива заменяет текущий элемент и удаляет последний элемент
let arr = [12, 23, 12, 15, 25, 23, 16, 25, 16];
for(let i = 0; i < arr.length - 1; i++) {
let item = arr[i]; // 取得当前数组中的每一项
let remainArgs = arr.slice(i+1); // 从 i+1项开始截取数组中剩余元素,包括i+1位置的元素
if (remainArgs.indexOf(item) > -1) { // 数组的后面元素 包含当前项
arr[i] = arr[arr.length - 1]; // 用数组最后一项替换当前项
arr.length--; // 删除数组最后一项
i--; // 仍从当前项开始比较
}
}
console.log(arr); // [ 16, 23, 12, 15, 25 ]
Идея 2: Новая идея контейнерного хранилища — пара ключ-значение объекта
Мысль:
рассматривать элементы массива как
对象属性
Обходя массив,
判断数组元素是否已经是对象的属性
Если свойство объекта определено, оказывается повторяющимся элементом, а затем удалить重复元素
let obj = {};
for (let i=0; i < arr.length; i++) {
let item = arr[i]; // 取得当前项
if (typeof obj[item] !== 'undefined') {
// obj 中存在当前属性,则证明当前项 之前已经是 obj属性了
// 删除当前项
arr[i] = arr[arr.length-1];
arr.length--;
i--;
}
obj[item] = item; // obj {10: 10, 16: 16, 25: 25 ...}
}
obj = null; // 垃圾回收
console.log(arr); // [ 16, 23, 12, 15, 25 ]
Идея 3: идея плана обработки для соседних предметов - на основе регулярности
let arr = [12, 23, 12, 15, 25, 23, 16, 25, 16];
arr.sort((a,b) => a-b);
arrStr = arr.join('@') + '@';
let reg = /(\d+@)\1*/g,
newArr = [];
arrStr.replace(reg, (val, group1) => {
// newArr.push(Number(group1.slice(0, group1.length-1)));
newArr.push(parseFloat(group1));
})
console.log(newArr); // [ 12, 15, 16, 23, 25 ]
16. Реализовать принцип async/await на основе функции Generator
**ядро:** передай мне
Generator
функция, которая основывает содержимое функции наIterator
Итератор имеет пошаговое выполнение
function readFile(file) {
return new Promise(resolve => {
setTimeout(() => {
resolve(file);
}, 1000);
})
};
function asyncFunc(generator) {
const iterator = generator(); // 接下来要执行next
// data为第一次执行之后的返回结果,用于传给第二次执行
const next = (data) => {
let { value, done } = iterator.next(data); // 第二次执行,并接收第一次的请求结果 data
if (done) return; // 执行完毕(到第三次)直接返回
// 第一次执行next时,yield返回的 promise实例 赋值给了 value
value.then(data => {
next(data); // 当第一次value 执行完毕且成功时,执行下一步(并把第一次的结果传递下一步)
});
}
next();
};
asyncFunc(function* () {
// 生成器函数:控制代码一步步执行
let data = yield readFile('a.js'); // 等这一步骤执行执行成功之后,再往下走,没执行完的时候,直接返回
data = yield readFile(data + 'b.js');
return data;
})
17. Инкапсулируйте Ajax на основе Promise
Идеи:
-
вернуть новый
Promise
Пример -
Создайте
HMLHttpRequest
асинхронный объект -
передача
open
метод, открытыйurl
, установить связь с сервером (некоторая обработка перед отправкой) -
монитор
Ajax
информация о состоянии -
если
xhr.readyState == 4
(Указывает, что ответ сервера завершен, и ответ от сервера можно получить)-
xhr.status == 200
,вернутьresolve
условие -
xhr.status == 404
,вернутьreject
условие
-
-
xhr.readyState !== 4
, который основывает информацию тела запроса наsend
отправить на сервер
function ajax(url, method) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(url, method, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText)
} else if (xhr.status === 404) {
reject(new Error('404'))
}
} else {
reject('请求数据失败')
}
}
xhr.send(null)
})
}
18. Вручную внедрить кросс-доменный JSONP
Идеи:
- Создайте
script
Этикетка - настраивать
script
помеченsrc
атрибут, переданный как вопросительный знак参数
, установите функцию обратного вызоваcallback
имя - вставить в
html
в тексте - вызвать функцию обратного вызова,
res
Параметр - полученные данные
let script = document.createElement('script');
script.src = 'http://www.baidu.cn/login?username=JasonShu&callback=callback';
document.body.appendChild(script);
function callback (res) {
console.log(res);
}
19. Реализовать сон вручную
Через определенное время выполнить функцию, основанную наPromise
Инкапсулировать асинхронные задачи
/**
*
* @param {*} fn 要执行的函数
* @param {*} wait 等待的时间
*/
function sleep(wait) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, wait);
});
}
let sayHello = (name) => console.log(`hello ${name}`);
async function autoRun() {
await sleep(3000);
let demo1 = sayHello("时光屋小豪");
let demo2 = sayHello("掘友们");
let demo3 = sayHello("aaa");
}
autoRun();
20.ES5 вручную реализовать уменьшение массива
Функции:
-
Специальная обработка, когда начальное значение не передано: по умолчанию будет использоваться первый элемент в массиве
- Возвращаемый результат функции будет использоваться в качестве следующего цикла
prev
- Функция обратного вызова принимает всего четыре параметра (
arr.reduce(prev, next, currentIndex, array))
)-
prev
: значение, возвращаемое при последнем вызове обратного вызова - обрабатываемый элемент
- индекс обрабатываемого элемента
- проходимый объект коллекции
-
- Возвращаемый результат функции будет использоваться в качестве следующего цикла
Array.prototype.myReduce = function(fn, prev) {
for (let i = 0; i < this.length; i++) {
if (typeof prev === 'undefined') {
prev = fn(this[i], this[i+1], i+1, this);
++i;
} else {
prev = fn(prev, this[i], i, this);
}
}
return prev
}
прецедент
let sum = [1, 2, 3].myReduce((prev, next) => {
return prev + next
});
console.log(sum); // 6
21. Ручная реализация обычной физико-химической функции KE
**Смысл функции курицизации: **Это пошаговая передача параметров в функцию, каждый раз передавать часть параметров и возвращать более конкретную функцию для получения оставшихся параметров, которые могут быть вложены посередине нескольких слоев таких функций, которые получают частичные параметры, пока не будет возвращен окончательный результат.
// add的参数不固定,看有几个数字累计相加
function add (a,b,c,d) {
return a+b+c+d
}
function currying (fn, ...args) {
// fn.length 回调函数的参数的总和
// args.length currying函数 后面的参数总和
// 如:add (a,b,c,d) currying(add,1,2,3,4)
if (fn.length === args.length) {
return fn(...args)
} else {
// 继续分步传递参数 newArgs 新一次传递的参数
return function anonymous(...newArgs) {
// 将先传递的参数和后传递的参数 结合在一起
let allArgs = [...args, ...newArgs]
return currying(fn, ...allArgs)
}
}
}
let fn1 = currying(add, 1, 2) // 3
let fn2 = fn1(3) // 6
let fn3 = fn2(4) // 10
23. ES5 реализует наследование
Паразитическое наследование композиции (лучший способ наследования в ES5)
Так называемое паразитарное наследование состава, то есть через заимствование构造函数来继承属性
,пройти через原型链的形式来继承方法
.
позвонил только один раз父类
Конструктор, более эффективный. избегать в子类.prototype
Вышеупомянутое создает ненужные, избыточные свойства, сохраняя цепочку прототипов нетронутой.
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
return this.name;
}
function Child(name, age) {
Parent.call(this, name); // 调用父类的构造函数,将父类构造函数内的this指向子类的实例
this.age = age;
}
//寄生组合式继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.getAge = function () {
return this.age;
}
let girl = new Child('Lisa', 18);
girl.getName();
24. Вручную реализовать публикацию и подписку
Ядро публикации-подписки::Каждый раз, когда event.emit (релиз), он запускает event.on (регистрация).
class EventEmitter {
constructor() {
// 事件对象,存放订阅的名字和事件
this.events = {};
}
// 订阅事件的方法
on(eventName,callback) {
if (!this.events[eventName]) {
// 注意数据,一个名字可以订阅多个事件函数
this.events[eventName] = [callback];
} else {
// 存在则push到指定数组的尾部保存
this.events[eventName].push(callback)
}
}
// 触发事件的方法
emit(eventName) {
// 遍历执行所有订阅的事件
this.events[eventName] && this.events[eventName].forEach(cb => cb());
}
}
прецедент
let em = new EventEmitter();
function workDay() {
console.log("每天工作");
}
function makeMoney() {
console.log("赚100万");
}
function sayLove() {
console.log("向喜欢的人示爱");
}
em.on("money",makeMoney);
em.on("love",sayLove);
em.on("work", workDay);
em.emit("money");
em.emit("love");
em.emit("work");
26. Вручную реализовать шаблон наблюдателя
Режим наблюдателя (на основе режима публикации-подписки) Есть наблюдатели и наблюдатели
观察者需要放到被观察者中
,被观察者的状态变化需要通知观察者
Я изменился.Внутренне он также основан на модели публикации-подписки, собирая наблюдателей и активно уведомляя наблюдателей после изменения статуса.
class Subject { // 被观察者 学生
constructor(name) {
this.state = '开心的'
this.observers = []; // 存储所有的观察者
}
// 收集所有的观察者
attach(o){ // Subject. prototype. attch
this.observers.push(o)
}
// 更新被观察者 状态的方法
setState(newState) {
this.state = newState; // 更新状态
// this 指被观察者 学生
this.observers.forEach(o => o.update(this)) // 通知观察者 更新它们的状态
}
}
class Observer{ // 观察者 父母和老师
constructor(name) {
this.name = name
}
update(student) {
console.log('当前' + this.name + '被通知了', '当前学生的状态是' + student.state)
}
}
let student = new Subject('学生');
let parent = new Observer('父母');
let teacher = new Observer('老师');
// 被观察者存储观察者的前提,需要先接纳观察者
student. attach(parent);
student. attach(teacher);
student. setState('被欺负了');
27. Вручную реализовать Object.freeze
Object.freeze
Заморозить объект, чтобы он больше не мог добавлять/удалять свойства или изменять свойства существующих свойств объекта.可枚举性、可配置可写性
, а также не может изменить значение существующего свойства и его свойства-прототипа и, наконец, вернуть объект, который совпадает с входящим параметром.
function myFreeze(obj){
// 判断参数是否为Object类型,如果是就封闭对象,循环遍历对象。去掉原型属性,将其writable特性设置为false
if(obj instanceof Object){
Object.seal(obj); // 封闭对象
for(let key in obj){
if(obj.hasOwnProperty(key)){
Object.defineProperty(obj,key,{
writable:false // 设置只读
})
// 如果属性值依然为对象,要通过递归来进行进一步的冻结
myFreeze(obj[key]);
}
}
}
}
28. Вручную внедрить Promise.all
Promise.all
: если задача обещания не выполняется, все
Promise.all
метод возвращаетpromise
function isPromise (val) {
return typeof val.then === 'function'; // (123).then => undefined
}
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let arr = []; // 存放 promise执行后的结果
let index = 0; // 计数器,用来累计promise的已执行次数
const processData = (key, data) => {
arr[key] = data; // 不能使用数组的长度来计算
/*
if (arr.length == promises.length) {
resolve(arr); // [null, null , 1, 2] 由于Promise异步比较慢,所以还未返回
}
*/
if (++index === promises.length) {
// 必须保证数组里的每一个
resolve(arr);
}
}
// 遍历数组依次拿到执行结果
for (let i = 0; i < promises.length; i++) {
let result = promises[i];
if(isPromise(result)) {
// 让里面的promise执行,取得成功后的结果
// data promise执行后的返回结果
result.then((data) => {
// 处理数据,按照原数组的顺序依次输出
processData(i ,data)
}, reject) // reject本事就是个函数 所以简写了
} else {
// 1 , 2
processData(i ,result)
}
}
})
}
прецедент
let fs = require('fs').promises;
let getName = fs.readFile('./name.txt', 'utf8');
let getAge = fs.readFile('./age.txt', 'utf8');
Promise.all([1, getName, getAge, 2]).then(data => {
console.log(data); // [ 1, 'name', '11', 2 ]
})
29. Вручную реализовать Promise.allSettled
MDN:
Promise.allSettled()
Метод возвращает обещание, которое былоfulfilled
илиrejected
После обещания с массивом объектов каждый объект представляет соответствующий результат обещания.
Если у вас есть несколько задач Async, которые не зависят друг от друга завершаются успешно, или вы всегда хотите знать о каждом
promise
результат, он обычно используется.
【Перевод】Promise.allSettled
а такжеPromise.all
Точно так же его параметр принимаетPromise
массив , возвращает новыйPromise
, разница только в том, что он не закорачивает, т.е.Promise
После завершения всей обработки мы можем получить каждыйPromise
статус, независимо от того, успешно он был обработан или нет.
Использование | Тестовые примеры
let fs = require('fs').promises;
let getName = fs.readFile('./name.txt', 'utf8'); // 读取文件成功
let getAge = fs.readFile('./age.txt', 'utf8');
Promise.allSettled([1, getName, getAge, 2]).then(data => {
console.log(data);
});
// 输出结果
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'fulfilled', value: 'zf' },
{ status: 'fulfilled', value: '11' },
{ status: 'fulfilled', value: 2 }
]
*/
let getName = fs.readFile('./name123.txt', 'utf8'); // 读取文件失败
let getAge = fs.readFile('./age.txt', 'utf8');
// 输出结果
/*
[
{ status: 'fulfilled', value: 1 },
{
status: 'rejected',
value: [Error: ENOENT: no such file or directory, open './name123.txt'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: './name123.txt'
}
},
{ status: 'fulfilled', value: '11' },
{ status: 'fulfilled', value: 2 }
]
*/
выполнить
function isPromise (val) {
return typeof val.then === 'function'; // (123).then => undefined
}
Promise.allSettled = function(promises) {
return new Promise((resolve, reject) => {
let arr = [];
let times = 0;
const setData = (index, data) => {
arr[index] = data;
if (++times === promises.length) {
resolve(arr);
}
console.log('times', times)
}
for (let i = 0; i < promises.length; i++) {
let current = promises[i];
if (isPromise(current)) {
current.then((data) => {
setData(i, { status: 'fulfilled', value: data });
}, err => {
setData(i, { status: 'rejected', value: err })
})
} else {
setData(i, { status: 'fulfilled', value: current })
}
}
})
}
30. Вручную реализовать Promise.prototype.finally
переднийpromise
Удастся ли это или потерпит неудачу, оно придетfinally
в иfinally
После этого можно продолжитьthen
(указывая, что это все ещеthen
путь关键
), и преобразует начальныйpromise
Затем значение передается без изменений.
Самая большая роль Promise.prototype.finally
-
finally
Функция внутри будет выполняться несмотря ни на что и будет передавать предыдущее значение следующему.then
метод(эквивалент действия в качестве промежуточного перехода) - соответствует случаям 1, 2, 3
-
если
finally
функция имеетpromise
При ожидании асинхронных задач он будет ждать их выполнения, а затем комбинирует предыдущий статус успеха или сбоя, чтобы вернуть значение
Использование Promise.prototype.finally в шести ситуациях
// 情况1
Promise.resolve(123).finally((data) => { // 这里传入的函数,无论如何都会执行
console.log(data); // undefined
})
// 情况2 (这里,finally方法相当于做了中间处理,起一个过渡的作用)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
}).then(data => {
console.log(data); // 123
})
// 情况3 (这里只要reject,都会走到下一个then的err中)
Promise.reject(123).finally((data) => {
console.log(data); // undefined
}).then(data => {
console.log(data);
}, err => {
console.log(err, 'err'); // 123 err
})
// 情况4 (一开始就成功之后,会等待finally里的promise执行完毕后,再把前面的data传递到下一个then中)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 3000)
})
}).then(data => {
console.log(data, 'success'); // 123 success
}, err => {
console.log(err, 'err');
})
// 情况5 (虽然一开始成功,但是只要finally函数中的promise失败了,就会把其失败的值传递到下一个then的err中)
Promise.resolve(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('rejected');
}, 3000)
})
}).then(data => {
console.log(data, 'success');
}, err => {
console.log(err, 'err'); // rejected err
})
// 情况6 (虽然一开始失败,但是也要等finally中的promise执行完,才能把一开始的err传递到err的回调中)
Promise.reject(123).finally((data) => {
console.log(data); // undefined
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('resolve');
}, 3000)
})
}).then(data => {
console.log(data, 'success');
}, err => {
console.log(err, 'err'); // 123 err
})
реализация исходного кода
Promise.prototype.finally = function (callback) {
return this.then((data) => {
// 让函数执行 内部会调用方法,如果方法是promise,需要等待它完成
// 如果当前promise执行时失败了,会把err传递到,err的回调函数中
return Promise.resolve(callback()).then(() => data); // data 上一个promise的成功态
}, err => {
return Promise.resolve(callback()).then(() => {
throw err; // 把之前的失败的err,抛出去
});
})
}
Резюме этой статьи
Подробности смотрите на карте мозга~
Прочитав три вещи ❤️
Если вы считаете, что этот контент очень полезен для вас, я хотел бы пригласить вас сделать мне три небольших одолжения:
-
Лайки, репосты и ваши "лайки и комментарии" - движущая сила моего творчества.
-
Обратите внимание на паблик «Front-end Time House» и время от времени делитесь оригинальными знаниями.
-
В то же время вы можете рассчитывать на последующие статьи🚀