Этот сборник призван закрепить основные базовые навыки проектировщиков.Начиная с часто задаваемых от руки вопросов различных крупных и малых предприятий, он обобщает распространенные реализации моделирования.彩蛋Эй.
Более подробныймногоИсходный код размещен вgithubВ проекте один вопрос содержит несколько вариантов решения, и кому интересно, можете скачать и изучить.Долгосрочные обновления и обслуживание.
PS:文章如有误,请不吝指正。Твой「赞」Это движущая сила авторского творчества.
Следующий:Сборник рукописного кода "Intermediate and Advanced Front-end Interview" (2)
Чтобы начать с темы, сначала поставьте интеллект-карту:
1.instanceof
instanceofОпределяет, существует ли прототип слева в цепочке прототипов справа.
Идея реализации: Найти прототип слой за слоем, если окончательный прототипnull, что доказывает, что он не существует в цепочке прототипов, иначе он существует.
function myInstanceof(left, right) {
if (typeof left !== 'object' || left === null) return false // 基础类型一律为 false
let proto = Object.getPrototypeOf(left) // 获取对象的原型
while(true) {
if (proto === null) return false
if (proto === right.prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
2.Object.create
const myCreate = function(obj) {
function F() {}
F.prototype = obj
return new F() // 创建一个继承 obj 原型属性的纯净对象
}
3.new
newПосле вызова он делает три вещи:
- Разрешить объектам экземпляра доступ к закрытым свойствам
- Сделайте объект экземпляра доступным для свойств в цепочке прототипов, где находится прототип конструктора (constructor.prototype).
- Рассмотрим случай, когда конструктор имеет возвращаемое значение
function myNew(ctor, ...args) {
let fn = Array.prototype.shift.call(arguments) // 取出第一个参数 ctor
if (typeof fn !== 'function') throw `${fn} is not a constructor`
let obj = Object.create(fn.prototype)
let res = fn.apply(obj, args) // 考虑构造函数有返回值的情况,直接执行
let isObject = typeof res === 'object' && res !== null
let isFunction = typeof res === 'function'
return isObject || isFunction ? res : obj
}
4.call & apply
Идея реализации: используйте, кто вызывает функцию,thisЧтобы понять, на кого указывает эта функция.
Function.prototype.myCall = function() { // myApply 通用
if (typeof this !== 'function') throw 'caller must be a function'
let self = arguments[0] || window
self._fn = this
let args = [...arguments].flat().slice(1) // 展开后取参数列表
let res = self._fn(...args) // 谁调用函数,函数的 this 就指向谁
Reflect.deleteProperty(self, '_fn') // 删除 _fn 属性
return res
}
5.bind
bindдля изменения функцииthisУказывает на функцию и возвращает ее.
Function.prototype.myBind = function() {
if (typeof this !== 'function') throw 'caller must be a function'
let self = this
let context = arguments[0]
let args = Array.prototype.slice.call(arguments, 1)
let fn = function() {
let fnArgs = Array.prototype.slice.call(arguments)
// bind 函数的参数 + 延迟函数的参数
self.apply(this instanceof self ? this : context, args.concat(fnArgs)
)
}
fn.prototype = Object.create(self.prototype) // 维护原型
return fn
}
6. Карри
柯里化:
- Определение: связывает функцию с подмножеством ее аргументов и возвращает новую функцию.
- Преимущества: уменьшение избыточности кода, повышение удобочитаемости и упрощенный способ реализации делегирования функций.
Возьмите простой 🌰:
function multiFn(x, y, z) {
return x * y * z
}
function curry() { ... } // 假设有一个 curry 函数可以做到柯里化
let multi = curry(multiFn)
multi(2, 3, 4)
multi(2)(3)(4)
multi(2, 3)(4)
multi(2)(3, 4) // 以上结果都是 3,柯里化将参数拆开自由绑定,结果不变。
let seniorMulti = multi(2) // seniorMulti 可以多次使用
seniorMulti(3)(4) // 当我们觉得重复传递参数 2 总是冗余时,可以这样。
Код:
function curry(fn, args=[]) {
return function() {
let newArgs = args.concat(Array.prototype.slice.call(arguments))
if (newArgs.length < fn.length) { // 假如:实参个数 < 形参个数
return curry.call(this, fn, newArgs)
} else {
return fn.apply(this, newArgs)
}
}
}
// ES6 高颜值写法
const curry = fn =>
judge = (...args) =>
args.length === fn.length
? fn(...args)
: (arg2) => judge(...args, arg2)
7. Паразитарная композиция наследство
function Parent() {
this.favorite = 'money'
}
function Child() {
Parent.call(this) // 继承父类的属性
this.age = 18
}
Child.prototype = Object.create(Parent.prototype) // 继承父类的原型属性
Object.setPrototypeOf(Child, Parent) // 继承父类的静态方法
Child.prototype.constructor = Child // constructor 重新指向 Child
8. Принцип частного свойства класса в TypeScript
私有属性Обычно встречаются:
- Можно получить доступ различными методами внутри класса, но не вне класса
- Подклассы не могут наследовать частные свойства родительского класса
ES6 уже предоставляет#для нас, чтобы использовать, но нужноbabelПереведи, так что давайте реализуем сами.
const MyClass = (function() { // 利用闭包和 WeakMap
const _x = new WeakMap()
class InnerClass {
constructor(x) {
_x.set(this, x)
}
getX() {
return x.get(this)
}
}
return InnerClass
})()
let myClass = new MyClass(5)
console.log(myClass.getX()) // 5
console.log(myClass.x) // undefined
9. Сортировка массива
пузырь
function bubbleSort(arr) {
let len = arr.length
for (let i=len; i>=2; i--) { // 排完第 2 个,第一个自动为最小
for (let j=0; j<i-1; j++) { // 逐渐缩小范围
if (arr[j] > arr[j+1])
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
}
}
return arr
}
сортировка выбором
Идея реализации: пройтись по элементам после себя и поменять местами наименьший элемент с самим собой.
function selectSort(arr) {
let len = arr.length
for (let i=0; i<len-1; i++) {
for (let j=i; j<len; j++) {
if (arr[j] < arr[i])
[arr[i], arr[j]] = [arr[j], arr[i]]
}
}
return arr
}
Сортировка вставками
Идея реализации: Вставить элементы в отсортированный массив.
function insertSort(arr) {
for (let i=1; i<arr.length; i++) { // arr[0] 默认为已排序的数组
for (let j=i; j>0; j--) {
if (arr[j] < arr[j-1]) {
[arr[j],arr[j-1]] = [arr[j-1],arr[j]]
} else { break }
}
}
return arr
}
быстрая сортировка
Идея реализации: выберите среднее опорное значение, зациклите исходный массив, поместите массив слева, если он меньше опорного значения, поместите массив справа, если он больше опорного значения, затем объедините комбинацию и наконец, завершить сортировку рекурсией.
function quickSort(arr) {
if (arr.length <= 1) return arr
let left = [], right = [], mid = arr.splice(0, 1)
for (let i=0; i<arr.length; i++) {
if (arr[i] < mid) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat(mid, quickSort(right)) // 别忘了 mid
}
sort
Просто подумайте об этом здесьsortПо сравнению с другими методами метод сортировки является относительно продвинутым алгоритмом внутри движка V8.
Сначала бросьте форму:
| Алгоритм сортировки | Средняя временная сложность | космическая сложность | Лучший случай! | стабильность |
|---|---|---|---|---|
| Пузырьковая сортировка | O(n^2) | O(1) | O(n) | стабилизировать |
| сортировка выбором | O(n^2) | O(1) | O(n^2) | нестабильный |
| Сортировка вставками | O(n^2) | O(1) | O(n) | стабилизировать |
| быстрая сортировка | O(nlogn) | O(logn) | O(nlogn) | нестабильный |
Определение стабильности: Относительное положение двух одинаковых чисел до и после сортировки остается неизменным, а алгоритм стабилен. Преимущество состоит в том, что результат первой сортировки по ключу можно использовать для второй сортировки по ключу.
Пусть количество элементов, которые нужно отсортировать, равно n:
- когда
n <= 10, использовать сортировку вставками(Сортировка вставками выполняется быстрее, чем быстрая сортировка, когда n достаточно мало, см. таблицу). - когда
n > 10, использовать трехфакторную быструю сортировку.
в10 < n <= 1000, используя медиану в качестве контрольного элемента;n > 1000, выберите элемент каждые 200 ~ 215 элементов, поместите его в новый массив, затем отсортируйте его, найдите число в середине и используйте его как медиану.
10. Дедупликация массива
двойной цикл
function unique(arr) {
for (let i=0; i<arr.length; i++) { // 注意这里的 arr 长度是变化的
for (let j=i+1; j<arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1)
j--
}
}
}
return arr
}
indexOf
function unique(arr) {
let res = []
for (let i=0; i<arr.length; i++) {
let current = arr[i]
if (res.indexOf(current) === -1) res.push(current)
}
return res
}
// 或者可以这样,利用 filter + indexOf
function unique(arr) {
let res = arr.filter(function(item, index, array){
return arr.indexOf(item) === index;
})
return res;
}
Дедупликация после сортировки
function unique(arr) {
let res = []
let sortedArray = arr.concat().sort()
let lastVal
for (let i=0; i<sortedArray.length; i++) {
// 如果是第一个元素或者相邻的元素不相同
if (!i || lastVal !== sortedArray[i])
res.push(sortedArray[i])
lastVal = sortedArray[i]
}
return res
}
// 或者可以这样,利用排序 + filter
function unique(arr) {
return arr.concat().sort().filter(function(item, index, array){
return !index || item !== arr[index - 1]
})
}
ES6 Set or Map
function unique(arr) {
return [...new Set(arr)];
}
// 或者可以这样,利用 Map
function unique (arr) {
const last = new Map()
return arr.filter((item) => !last.has(item) && last.set(item, 1))
}
11.map
в соответствии сспецификация картыдля имитации реализации:
Array.prototype.map = function(callbackFn, thisArg) {
if (this === null || this === undefined)
throw new TypeError(`Cannot read property 'map' of ${this}`)
// 处理回调类型异常
if (Object.prototype.toString.call(callbackFn) !== '[object Function]')
throw new TypeError(`${callbackFn} is not a function`)
let O = Object(this), // 规定 this 要先转化为对象
T = thisArg,
len = O.length >>> 0, // 保证 len 为数字且为整数
A = new Array(len)
for (let k=0; k<len; k++) {
if (k in O) { // 原型链查找属性
let mappedValue = callbackFn.call(T, O[k], k, O)
A[k] = mappedValue
}
}
return A
}
12.reduce
в соответствии суменьшить спецификациюдля имитации реализации:
Array.prototype.reduce = function(callbackFn, initialValue) {
if (this === null || this === undefined)
throw new TypeError(`Cannot read property 'reduce' of ${this}`)
// 处理回调类型异常
if (Object.prototype.toString.call(callbackFn) !== '[object Function]')
throw new TypeError(`${callbackFn} is not a function`)
let O = Object(this), // 规定 this 要先转化为对象
k = 0,
len = O.length >>> 0, // 保证 len 为数字且为整数
accumulator = initialValue
if (accumulator === undefined) {
for (; k<len; k++) {
if (k in O) {
accumulator = O[k]
k++
break
}
}
}
if (k === len && accumulator === undefined) // 表示数组全为空
throw new Error('Each element of the array is empty')
for(; k<len; k++) {
if (k in O) {
accumulator = callbackfn.call(undefined, accumulator, O[k], k, O);
}
}
return accumulator
}
13.filter
в соответствии сспецификация фильтрадля имитации реализации:
Array.prototype.filter = function(callbackFn, thisArg) {
if (this === null || this === undefined)
throw new TypeError(`Cannot read property 'filter' of ${this}`)
// 处理回调类型异常
if (Object.prototype.toString.call(callbackFn) !== '[object Function]')
throw new TypeError(`${callbackFn} is not a function`)
let O = Object(this), // 规定 this 要先转化为对象
resLen = 0,
len = O.length >>> 0, // 保证 len 为数字且为整数
res = []
for (let i=0; i<len; i++) {
if (i in O) { // 原型链查找属性
let element = O[i];
if (callbackfn.call(thisArg, O[i], i, O)) res[resLen++] = element
}
}
return res
}
14. Случайные строки
function generateRandomString(len) {
let randomStr = ''
for (; randomStr.length<len; randomStr+=Math.random().toString(36).substr(2)) {}
return randomStr.substr(0, len)
}
15. Последовательность Фибоначчи
Вы голодны после столь долгого обучения? Поздравляю, вы нашли...
посмотриКухня из основных объектов фибоначчи, молодец, слезы текли из уголков рта невольно...
// 递归,时间复杂度为 O(2^n)
function fibSequence(n) {
if (n === 1 || n === 2) return n - 1;
return fib(n - 1) + fib(n - 2)
}
// 或者使用迭代,时间复杂度为 O(n),推荐!
function fibSequence(n) {
let a = 0, b = 1, c = a + b
for (let i=3; i<n; i++) {
a = b
b = c
c = a + b
}
return c
}
16. Поверхностное копирование
浅拷贝:
- Копировать можно только один слой объектов, при наличии вложенных объектов поверхностное копирование бессильно.
- Возможная проблема: если скопированный атрибут является ссылочным типом, скопированное содержимое является адресом памяти, а измененное содержимое будет влиять друг на друга.
const shallowClone = (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 // 基础类型直接返回
}
}
// 或者你可以
console.log(Object.assign(array, ...sources))
console.log(array.concat())
console.log(array.slice())
console.log([...array])
17. Глубокое копирование
深拷贝Создайте копию объекта, ссылочный адрес у обоих разный. Если вы хотите использовать объект, но не хотите изменять исходный объект, хорошим выбором будет глубокая копия.
JSON.parse(JSON.stringify());
Приведенная выше версия для нищих может охватывать большинство сценариев применения, но, пожалуйста, забудьте об этом во время собеседования! У него есть несколько проблем:
- Не могу решить
循环引用. - Невозможно скопировать специальные объекты, такие как
函数、RegExp、Date、Set、MapЖдать.
На работе, если мы сталкиваемся со сложными объектами, мы можем использовать библиотеки инструментов, такие как lodashcloneDeepметод, не злоупотребляйте!
Начните простое расчесывание:
Теперь давайте реализуем глубокую копию, которая охватывает большинство сцен (вы можете пропустить это и посмотреть следующую версию для лучшего понимания):
// Map 强引用,需要手动清除属性才能释放内存。
// WeakMap 弱引用,随时可能被垃圾回收,使内存及时释放,是解决循环引用的不二之选。
function cloneDeep(obj, map = new WeakMap()) {
if (obj === null || obj === undefined) return obj // 不进行拷贝
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
// 基础类型不需要深拷贝
if (typeof obj !== 'object' && typeof obj !== 'function') return obj
// 处理普通函数和箭头函数
if (typeof obj === 'function') return handleFunc(obj)
// 是对象的话就要进行深拷贝
if (map.get(obj)) return map.get(obj)
let cloneObj = new obj.constructor()
// 找到的是所属类原型上的 constructor,而原型上的 constructor 指向的是当前类本身。
map.set(obj, cloneObj)
if (getType(obj) === '[object Map]') {
obj.forEach((item, key) => {
cloneObj.set(cloneDeep(key, map), cloneDeep(item, map));
})
}
if (getType(obj) === '[object Set]') {
obj.forEach(item => {
cloneObj.add(cloneDeep(item, map));
})
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], map)
}
}
return cloneObj
}
// 获取更详细的数据类型
function getType(obj) {
return Object.prototype.toString.call(obj)
}
// 处理普通函数和箭头函数
function handleFunc(func) {
if(!func.prototype) return func // 箭头函数直接返回自身
const bodyReg = /(?<={)(.|\n)+(?=})/m
const paramReg = /(?<=\().+(?=\)\s+{)/
const funcString = func.toString()
// 分别匹配 函数参数 和 函数体
const param = paramReg.exec(funcString)
const body = bodyReg.exec(funcString)
if(!body) return null
if (param) {
const paramArr = param[0].split(',')
return new Function(...paramArr, body[0])
} else {
return new Function(body[0])
}
}
Вот что я изменил:
function cloneDeep(obj, map = new WeakMap()) {
if (!(obj instanceof Object)) return obj; // 基本数据
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
if (map.get(obj)) return map.get(obj); // 解决循环引用
if (obj instanceof Function) { // 解决函数
return function () {
return obj.apply(this, [...arguments]);
};
}
const res = new obj.constructor(); // 下面是数组/普通对象/Set/Map 的处理
obj instanceof Object && map.set(obj, res);
if (obj instanceof Map) {
obj.forEach((item, index) => {
res.set(cloneDeep(index, map), cloneDeep(item, map));
});
}
if (obj instanceof Set) {
obj.forEach((item) => {
res.add(cloneDeep(item, map));
});
}
Object.keys(obj).forEach((key) => {
if (obj[key] instanceof Object) {
res[key] = cloneDeep(obj[key], map);
} else {
res[key] = obj[key];
}
});
return res;
}
const map = new Map();
map.set({ a: 1 }, "1");
const source = {
name: "Jack",
meta: {
age: 12,
birth: new Date("1997-10-10"),
ary: [1, 2, { a: 1 }],
say() {
console.log("Hello");
},
map
},
};
source.source = source;
const newObj = cloneDeep(source);
console.log(newObj.meta.ary[2] === source.meta.ary[2]); // false
console.log(newObj.meta.birth === source.meta.birth); // false
console.log(newObj);
18. Анализ URL-адресов
Давайте сначала посмотрим, как выглядит полный URL?
Например 🌰:https://keith:miao@www.foo.com:80/file?test=3&miao=4#heading-0
Простая реализация с использованием регулярных выражений:
function parseUrl(url) {
// scheme://user:passwd@ 部分
let schemeStr = '(?:([^/?#]+))?//(?:([^:]*)(?::?(.*))@)?',
// host:port path?query 部分
urlStr = '(?:([^/?#:]*):?([0-9]+)?)?([^?#]*)(\\?(?:[^#]*))?',
// #fragment 部分
fragmentStr = '(#(?:.*))'
let pattern = RegExp(`^${schemeStr}${urlStr}${fragmentStr}?`)
let matched = url.match(pattern) || []
return {
protocol: matched[1], // 协议
username: matched[2], // 用户名
password: matched[3], // 密码
hostname: matched[4], // 主机
port: matched[5], // 端口
pathname: matched[6], // 路径
search: matched[7], // 查询字符串 queryString
hash: matched[8], // 锚点
}
}
// 或者你可以这样
function parseUrl(url) {
const urlObj = new URL(url)
return {
protocol: urlObj.protocol,
username: urlObj.username,
password: urlObj.password,
hostname: urlObj.hostname,
port: urlObj.port,
pathname: urlObj.pathname,
search: urlObj.search,
hash: urlObj.hash
}
}
Анализировать строки запроса по отдельностиqueryString:
function parseQueryString(query) {
if (!query) return {}
query = query.replace(/^\?/, '')
const queryArr = query.split('&')
const result = {}
queryArr.forEach(query => {
let [key, value] = query.split('=')
try {
key = decodeURLComponent(key || '').replace(/\+/g, ' ')
value = decodeURLComponent(value || '').replace(/\+/g, ' ')
} catch(e) {
return console.log(e) // 非法字符不处理
}
const type = getQueryType(key)
switch(type) {
case 'ARRAY':
key = key.replace(/\[\]$/, '') // 对于形如 `list[]` 的解析成数组
if (!result[key]) {
result[key] = [value]
} else {
result[key].push(value)
}
break;
case 'JSON':
key = key.replace(/\{\}$/, '') // 对于形如 obj{} 的解析为对象
value = JSON.parse(value)
result.json = value
break;
default:
result[key] = value
}
})
return result
}
function getQueryType (key) {
if (key.endsWith('[]')) return 'ARRAY'
if (key.endsWith('{}')) return 'JSON'
return 'DEFAULT'
}
// 或者你可以这样,如果你做好了被面试官打si的准备...
// 简易版
function getUrlQuery(search) {
let searchObj = {};
for (let [key, value] of new URLSearchParams(search)) {
searchObj[key] = value
}
return searchObj
}
Конечно, это не строго, и не были рассмотрены следующие вопросы:
- Как работать с одним и тем же полем
- нет замены
+для%20 - Только
key/ Толькоvalue - ...
В работе рекомендуются две библиотеки с открытым исходным кодом:js-urlа такжеquery-string, учитываются границы, и, конечно, это сложнее, чем то, что мы поняли.
Кстати, не забывайте об этих двух модульных методах nodejs:url.parse,querystring.parseЭто уже очень полезно.
19.JSONP
JSONP: общие междоменные средства, использующие<script>Тег не ограничивает междоменные уязвимости для достижения цели общения с третьими лицами.
const jsonp = ({url, params, callbackName}) => {
const generateURL = () => { // 根据 URL 格式生成地址
let dataStr = ''
for (let key in params) {
dataStr += `${key}=${params[key]}&`
}
dataStr += `callback=${callbackName}`
return `${url}?${dataStr}`
}
return new Promise((resolve, reject) => {
callbackName = callbackName || Math.random().toString()
let scriptEle = document.createElement('script')
scriptEle.src = generateURL()
document.body.appendChild(scriptEle)
// 服务器返回字符串 `${callbackName}(${服务器的数据})`,浏览器解析即可执行。
window[callbackName] = (data) => {
resolve(data)
document.body.removeChild(scriptEle) // 别忘了清除 dom
}
})
}
20. Защита от тряски/троттлинга
Стабилизатор
- QA: Почему я нажимаю эту кнопку и отправляю два запроса подряд?
- ФЭ: Ты нажал ее дважды подряд, потому что был слишком быстр.
- QA: Тогда вы делаете предел.
Итак防抖, используется, чтобы избежать ненужных операций, таких как эта кнопка, QA хочет выполнить только операцию последнего щелчка за короткое время.
Есть еще один способ, который я обычно запоминаю: маг читает статью, а второй будет планировать кастинг первого...
const debounce = (fn, delay) => {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => { // 这里是箭头函数,this 指向外层的非箭头函数的 this
fn.apply(this, args)
}, delay)
}
}
дросселирование
节流То есть используется триггер с фиксированной частотой.scrollСобытие как нельзя более актуально.
function throttle(fn, interval) {
let flag = true
return function(...args) {
if (!flag) return;
flag = false
setTimeout(() => {
fn.apply(this, args)
flag = true
}, interval)
}
}
// 或者可以这样,挑你喜欢的。
function throttle(fn, interval) {
let last = 0 // 首次直接执行
return function (...args) {
let now = +new Date()
if(now - last < interval) return;
last = now // 时间一到就更新 last
fn.apply(this, args)
}
}
Улучшенное регулирование
Теперь кнопка использовала версию стабилизации изображения выше...
- QA нажал два раза подряд, только последний запрос был сделан, тест пройден, и сравнивался жест ОК.
- ФЕ вздохнул с облегчением.
- QA вернулся с большей энергией и начал все время нажимать кнопку, не слишком уставший.
- QA: Почему он не срабатывает все время?
- ФЭ:...
加强版节流Неограниченный антивибрационный триггер с фиксированной частотой.
const throttle = (fn, delay) => {
let timer = null, last = 0
return function(...args) {
let now = +new Date()
if (now - last < delay && timer) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
} else { // 到了 delay 时间直接触发,不管了...
last = now
fn.apply(this, args)
}
}
}
21. Ленивая загрузка изображений
Иногда вы усердно работали, чтобы выполнить несколько оптимизаций производительности, и в результате вы не сжали изображение, а отображение отложенной загрузки стало более эффективным и экономичным. Так что оптимизация производительности, а не безмозглая оптимизация.
图片懒加载Обычно есть три способа добиться этого:
- clientHeight, scrollTop и offsetTop
- getBoundingClientRect
- IntersectionObserver
Во-первых, дайте всем изображениям ресурс-заполнитель:
<img src="default.jpg" data-src="https://www.xxx.com/target-1.jpg" />
<img src="default.jpg" data-src="https://www.xxx.com/target-2.jpg" />
......
<img src="default.jpg" data-src="https://www.xxx.com/target-39.jpg" />
① clientHeight, scrollTop и offsetTop
Первая картинка:
фотографии можно увидетьoffsetTopменьше, чемПурпурныйлиния (scrollHeight + clientHeight) отображается в окне.
let imgs = document.getElementsByTagName("img"), count = 0
// 首次加载
lazyLoad()
// 通过监听 scroll 事件来判断图片是否到达视口,别忘了防抖节流
window.addEventListener('scroll', throttle(lazyLoad, 160))
function lazyLoad() {
let viewHeight = document.documentElement.clientHeight //视口高度
//滚动条卷去的高度
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
for(let i=count; i<imgs.length; i++) {
// 元素现在已经出现在视口中
if(imgs[i].offsetTop < scrollTop + viewHeight) {
if(imgs[i].getAttribute("src") !== "default.jpg") continue;
imgs[i].src = imgs[i].getAttribute("data-src")
count ++
}
}
}
② getBoundingClientRect
элемента домgetBoundingClientRect().topСвойство может напрямую определять, появляется ли изображение в текущем окне просмотра.
// 只修改一下 lazyLoad 函数
function lazyLoad() {
for(let i=count; i<imgs.length; i++) {
if(imgs[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
if(imgs[i].getAttribute("src") !== "default.jpg") continue;
imgs[i].src = imgs[i].getAttribute("data-src")
count ++
}
}
}
③ Обозреватель пересечения
IntersectionObserverВстроенный API браузера реализует мониторинг окнаscrollмероприятие,判断是否在视口中так же как节流Три функции. Для этого API требуется полифилл.
let imgs = document.getElementsByTagName("img")
const observer = new IntersectionObserver(changes => {
for(let i=0, len=imgs.length; i<len; i++) {
let img = imgs[i]
// 通过这个属性判断是否在视口中,返回 boolean 值
if(img.isIntersecting) {
const imgElement = img.target
imgElement.src = imgElement.getAttribute("data-src")
observer.unobserve(imgElement) // 解除观察
}
}
})
Array.from(imgs).forEach(item => observer.observe(item)) // 调用
22. Серия обещаний асинхронного обратного вызова
Promise
PromiseДля достижения расчесывания:
- цепной вызов
- Перехват ошибок (бублинг)
const PENDING = 'PENDING'; // 进行中
const FULFILLED = 'FULFILLED'; // 已成功
const REJECTED = 'REJECTED'; // 已失败
class Promise {
constructor(exector) {
// 初始化状态
this.status = PENDING;
// 将成功、失败结果放在this上,便于then、catch访问
this.value = undefined;
this.reason = undefined;
// 成功态回调函数队列
this.onFulfilledCallbacks = [];
// 失败态回调函数队列
this.onRejectedCallbacks = [];
const resolve = value => {
// 只有进行中状态才能更改状态
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 成功态函数依次执行
this.onFulfilledCallbacks.forEach(fn => fn(this.value));
}
}
const reject = reason => {
// 只有进行中状态才能更改状态
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 失败态函数依次执行
this.onRejectedCallbacks.forEach(fn => fn(this.reason))
}
}
try {
// 立即执行executor
// 把内部的resolve和reject传入executor,用户可调用resolve和reject
exector(resolve, reject);
} catch(e) {
// executor执行出错,将错误内容reject抛出去
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected:
reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
// 保存this
const self = this;
return new Promise((resolve, reject) => {
if (self.status === PENDING) {
self.onFulfilledCallbacks.push(() => {
// try捕获错误
try {
// 模拟微任务
setTimeout(() => {
const result = onFulfilled(self.value);
// 分两种情况:
// 1. 回调函数返回值是Promise,执行then操作
// 2. 如果不是Promise,调用新Promise的resolve函数
result instanceof Promise ? result.then(resolve, reject) :
resolve(result)
})
} catch(e) {
reject(e);
}
});
self.onRejectedCallbacks.push(() => {
// 以下同理
try {
setTimeout(() => {
const result = onRejected(self.reason);
// 不同点:此时是reject
result instanceof Promise ? result.then(resolve, reject) :
reject(result)
})
} catch(e) {
reject(e);
}
})
} else if (self.status === FULFILLED) {
try {
setTimeout(() => {
const result = onFulfilled(self.value)
result instanceof Promise ? result.then(resolve, reject) : resolve(result)
});
} catch(e) {
reject(e);
}
} else if (self.status === REJECTED){
try {
setTimeout(() => {
const result = onRejected(self.reason);
result instanceof Promise ? result.then(resolve, reject) : reject(result)
})
} catch(e) {
reject(e)
}
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
if (value instanceof Promise) {
// 如果是Promise实例,直接返回
return value;
} else {
// 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
return new Promise((resolve, reject) => resolve(value));
}
}
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
}
}
Promise.prototype.finally = function(callback) {
this.then(value => {
return Promise.resolve(callback()).then(() => {
return value
})
}, error => {
return Promise.resolve(callback()).then(() => {
throw error
})
})
}
Promise.resolve
Promise.resolveРасчесывание статическим методом:
- Если параметр является обещанием, он будет возвращен напрямую.
- Параметр передается как объект, который можно использовать, и возвращаемый Promise будет следовать за этим объектом и использовать его конечное состояние как собственное состояние.
- В других случаях объект обещания со статусом успеха возвращается напрямую.
Promise.resolve = (param) => {
if(param instanceof Promise) return param // 符合 1
return new Promise((resolve, reject) => {
if (param && param.then && typeof param.then === 'function') { // 符合 2
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject)
} else { // 符合 3
resolve(param)
}
})
}
Promise.reject
// 冒泡捕获
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
Promise.all
Promise.allДля достижения расчесывания:
- Его аргумент является пустым, что затем напрямую разрешается.
- Если один из аргументов не работает, объект обещания, возвращаемый Promise.all, не работает.
- В любом случае результат выполнения обещания, возвращаемый Promise.all, представляет собой массив.
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let result = [],
index = 0,
len = promises.length
if(len === 0) {
resolve(result)
return;
}
for(let i=0; i<len; i++) {
// 为什么不直接 promise[i].then, 考虑 promise[i] 可能不是一个 promise 对象
Promise.resolve(promise[i]).then(data => {
result[i] = data
index++
if(index === len) resolve(result)
}).catch(err => {
reject(err)
})
}
})
}
Promise.race
Promise.raceКак только одно обещание выполнено, оно разрешается напрямую и прекращает выполнение.
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
let len = promises.length
if(len === 0) return;
for(let i=0; i<len; i++) {
// promises[i] 可能不是一个 promise 对象
Promise.resolve(promises[i]).then(data => {
resolve(data)
return;
}).catch(err => {
reject(err)
return;
})
}
})
}
❤️ смотри здесь (* ̄︶ ̄)
Если вы считаете, что этот контент вас очень вдохновляет, не забудьте поставить лайк, чтобы этот контент увидело больше людей, пожалуйста, это действительно важно для меня.
Прошлые выборы:
Сборник рукописного кода "Intermediate and Advanced Front-end Interview" (2)
Подробное описание процесса рукопожатия Byte Interview-TLS
использованная литература
JavaScript рукописный код непобедимые читы
Стандартные встроенные объекты JavaScript
Серия вопросов о родной JS Soul