function debounce(func, ms = 1000) { let timer; return function (...args) { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { func.apply(this, args) }, ms) } } // 測試 const task = () => { console.log('run task') } const debounceTask = debounce(task, 1000) window.addEventListener('scroll', debounceTask)
function throttle(func, ms = 1000) { let canRun = true return function (...args) { if (!canRun) return canRun = false setTimeout(() => { func.apply(this, args) canRun = true }, ms) } } // 測試 const task = () => { console.log('run task') } const throttleTask = throttle(task, 1000) window.addEventListener('scroll', throttleTask)
function myNew(Func, ...args) { const instance = {}; if (Func.prototype) { Object.setPrototypeOf(instance, Func.prototype) } const res = Func.apply(instance, args) if (typeof res === "function" || (typeof res === "object" && res !== null)) { return res } return instance } // 測試 function Person(name) { this.name = name } Person.prototype.sayName = function() { console.log(`My name is ${this.name}`) } const me = myNew(Person, 'Jack') me.sayName() console.log(me)
Function.prototype.myBind = function (context = globalThis) { const fn = this const args = Array.from(arguments).slice(1) const newFunc = function () { const newArgs = args.concat(...arguments) if (this instanceof newFunc) { // 經過 new 調用,綁定 this 爲實例對象 fn.apply(this, newArgs) } else { // 經過普通函數形式調用,綁定 context fn.apply(context, newArgs) } } // 支持 new 調用方式 newFunc.prototype = Object.create(fn.prototype) return newFunc } // 測試 const me = { name: 'Jack' } const other = { name: 'Jackson' } function say() { console.log(`My name is ${this.name || 'default'}`); } const meSay = say.bind(me) meSay() const otherSay = say.bind(other) otherSay()
Function.prototype.myCall = function (context = globalThis) { // 關鍵步驟,在 context 上調用方法,觸發 this 綁定爲 context,使用 Symbol 防止原有屬性的覆蓋 const key = Symbol('key') context[key] = this let args = [].slice.call(arguments, 1) let res = context[key](...args) delete context[key] return res }; // 測試 const me = { name: 'Jack' } function say() { console.log(`My name is ${this.name || 'default'}`); } say.myCall(me)
Function.prototype.myApply = function (context = globalThis) { // 關鍵步驟,在 context 上調用方法,觸發 this 綁定爲 context,使用 Symbol 防止原有屬性的覆蓋 const key = Symbol('key') context[key] = this let res if (arguments[1]) { res = context[key](...arguments[1]) } else { res = context[key]() } delete context[key] return res } // 測試 const me = { name: 'Jack' } function say() { console.log(`My name is ${this.name || 'default'}`); } say.myApply(me)
function deepCopy(obj, cache = new WeakMap()) { if (!obj instanceof Object) return obj // 防止循環引用 if (cache.get(obj)) return cache.get(obj) // 支持函數 if (obj instanceof Function) { return function () { obj.apply(this, arguments) } } // 支持日期 if (obj instanceof Date) return new Date(obj) // 支持正則對象 if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags) // 還能夠增長其餘對象,好比:Map, Set等,根據狀況判斷增長便可,面試點到爲止就能夠了 // 數組是 key 爲數字素銀的特殊對象 const res = Array.isArray(obj) ? [] : {} // 緩存 copy 的對象,用於處理循環引用的狀況 cache.set(obj, res) Object.keys(obj).forEach((key) => { if (obj[key] instanceof Object) { res[key] = deepCopy(obj[key], cache) } else { res[key] = obj[key] } }); return res } // 測試 const source = { name: 'Jack', meta: { age: 12, birth: new Date('1997-10-10'), ary: [1, 2, { a: 1 }], say() { console.log('Hello'); } } } source.source = source const newObj = deepCopy(source) console.log(newObj.meta.ary[2] === source.meta.ary[2]);
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) { const 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) { if (this.cache[name]) { // 建立副本,若是回調函數內繼續註冊相同事件,會形成死循環 const tasks = this.cache[name].slice() for (let fn of tasks) { fn(); } } } emit(name, once = false) { if (this.cache[name]) { // 建立副本,若是回調函數內繼續註冊相同事件,會形成死循環 const tasks = this.cache[name].slice() for (let fn of tasks) { fn(); } if (once) { delete this.cache[name] } } } } // 測試 const eventBus = new EventEmitter() const task1 = () => { console.log('task1'); } const task2 = () => { console.log('task2'); } eventBus.on('task', task1) eventBus.on('task', task2) setTimeout(() => { eventBus.emit('task') }, 1000)
function curry(func) { return function curried(...args) { // 關鍵知識點:function.length 用來獲取函數的形參個數 // 補充:arguments.length 獲取的是實參個數 if (args.length >= func.length) { return func.apply(this, args) } return function (...args2) { return curried.apply(this, args.concat(args2)) } } } // 測試 function sum (a, b, c) { return a + b + c } const curriedSum = curry(sum) console.log(curriedSum(1, 2, 3)) console.log(curriedSum(1)(2,3)) console.log(curriedSum(1)(2)(3))
function create(proto) { function F() {} F.prototype = proto; return new F(); } // Parent function Parent(name) { this.name = name } Parent.prototype.sayName = function () { console.log(this.name) }; // Child function Child(age, name) { Parent.call(this, name) this.age = age } Child.prototype = create(Parent.prototype) Child.prototype.constructor = Child Child.prototype.sayAge = function () { console.log(this.age) } // 測試 const child = new Child(18, 'Jack') child.sayName() child.sayAge()
function isInstanceOf(instance, klass) { let proto = instance.__proto__ let prototype = klass.prototype while (true) { if (proto === null) return false if (proto === prototype) return true proto = proto.__proto__ } } // 測試 class Parent {} class Child extends Parent {} const child = new Child() console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array));
/** * 關鍵點 * 1. new promise 一經建立,當即執行 * 2. 使用 Promise.resolve().then 能夠把任務加到微任務隊列,防止當即執行迭代方法 * 3. 微任務處理過程當中,產生的新的微任務,會在同一事件循環內,追加到微任務隊列裏 * 4. 使用 race 在某個任務完成時,繼續添加任務,保持任務按照最大併發數進行執行 * 5. 任務完成後,須要從 doingTasks 中移出 */ function limit(count, array, iterateFunc) { const tasks = [] const doingTasks = [] let i = 0 const enqueue = () => { if (i === array.length) { return Promise.resolve() } const task = Promise.resolve().then(() => iterateFunc(array[i++])) tasks.push(task) const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1)) doingTasks.push(doing) const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve() return res.then(enqueue) }; return enqueue().then(() => Promise.all(tasks)) } // test const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i)) limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => { console.log(res) })
// 字節面試題,實現一個異步加法 function asyncAdd(a, b, callback) { setTimeout(function () { callback(null, a + b); }, 500); } // 解決方案 // 1. promisify const promiseAdd = (a, b) => new Promise((resolve, reject) => { asyncAdd(a, b, (err, res) => { if (err) { reject(err) } else { resolve(res) } }) }) // 2. 串行處理 async function serialSum(...args) { return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0)) } // 3. 並行處理 async function parallelSum(...args) { if (args.length === 1) return args[0] const tasks = [] for (let i = 0; i < args.length; i += 2) { tasks.push(promiseAdd(args[i], args[i + 1] || 0)) } const results = await Promise.all(tasks) return parallelSum(...results) } // 測試 (async () => { console.log('Running...'); const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12) console.log(res1) const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12) console.log(res2) console.log('Done'); })()
// Dep module class Dep { static stack = [] static target = null deps = null constructor() { this.deps = new Set() } depend() { if (Dep.target) { this.deps.add(Dep.target) } } notify() { this.deps.forEach(w => w.update()) } static pushTarget(t) { if (this.target) { this.stack.push(this.target) } this.target = t } static popTarget() { this.target = this.stack.pop() } } // reactive function reactive(o) { if (o && typeof o === 'object') { Object.keys(o).forEach(k => { defineReactive(o, k, o[k]) }) } return o } function defineReactive(obj, k, val) { let dep = new Dep() Object.defineProperty(obj, k, { get() { dep.depend() return val }, set(newVal) { val = newVal dep.notify() } }) if (val && typeof val === 'object') { reactive(val) } } // watcher class Watcher { constructor(effect) { this.effect = effect this.update() } update() { Dep.pushTarget(this) this.value = this.effect() Dep.popTarget() return this.value } } // 測試代碼 const data = reactive({ msg: 'aaa' }) new Watcher(() => { console.log('===> effect', data.msg); }) setTimeout(() => { data.msg = 'hello' }, 1000)
// 建議閱讀 [Promises/A+ 標準](https://promisesaplus.com/) class MyPromise { constructor(func) { this.status = 'pending' this.value = null this.resolvedTasks = [] this.rejectedTasks = [] this._resolve = this._resolve.bind(this) this._reject = this._reject.bind(this) try { func(this._resolve, this._reject) } catch (error) { this._reject(error) } } _resolve(value) { setTimeout(() => { this.status = 'fulfilled' this.value = value this.resolvedTasks.forEach(t => t(value)) }) } _reject(reason) { setTimeout(() => { this.status = 'reject' this.value = reason this.rejectedTasks.forEach(t => t(reason)) }) } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { this.resolvedTasks.push((value) => { try { const res = onFulfilled(value) if (res instanceof MyPromise) { res.then(resolve, reject) } else { resolve(res) } } catch (error) { reject(error) } }) this.rejectedTasks.push((value) => { try { const res = onRejected(value) if (res instanceof MyPromise) { res.then(resolve, reject) } else { reject(res) } } catch (error) { reject(error) } }) }) } catch(onRejected) { return this.then(null, onRejected); } } // 測試 new MyPromise((resolve) => { setTimeout(() => { resolve(1); }, 500); }).then((res) => { console.log(res); return new MyPromise((resolve) => { setTimeout(() => { resolve(2); }, 500); }); }).then((res) => { console.log(res); throw new Error('a error') }).catch((err) => { console.log('==>', err); })
// 方案 1 function recursionFlat(ary = []) { const res = [] ary.forEach(item => { if (Array.isArray(item)) { res.push(...recursionFlat(item)) } else { res.push(item) } }) return res } // 方案 2 function reduceFlat(ary = []) { return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), []) } // 測試 const source = [1, 2, [3, 4, [5, 6]], '7'] console.log(recursionFlat(source)) console.log(reduceFlat(source))
function objectFlat(obj = {}) { const res = {} function flat(item, preKey = '') { Object.entries(item).forEach(([key, val]) => { const newKey = preKey ? `${preKey}.${key}` : key if (val && typeof val === 'object') { flat(val, newKey) } else { res[newKey] = val } }) } flat(obj) return res } // 測試 const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } } console.log(objectFlat(source));
// <img src="default.png" data-src="https://xxxx/real.png"> function isVisible(el) { const position = el.getBoundingClientRect() const windowHeight = document.documentElement.clientHeight // 頂部邊緣可見 const topVisible = position.top > 0 && position.top < windowHeight; // 底部邊緣可見 const bottomVisible = position.bottom < windowHeight && position.bottom > 0; return topVisible || bottomVisible; } function imageLazyLoad() { const images = document.querySelectorAll('img') for (let img of images) { const realSrc = img.dataset.src if (!realSrc) continue if (isVisible(img)) { img.src = realSrc img.dataset.src = '' } } } // 測試 window.addEventListener('load', imageLazyLoad) window.addEventListener('scroll', imageLazyLoad) // or window.addEventListener('scroll', throttle(imageLazyLoad, 1000))
歡迎一塊兒補充 ~ Github 地址javascript