手写前端常用功能
目录
1. new
思路
js 的new
运算符用于创建对象的实例,使用new
的时候主要有以下操作:- 创建空对象
{}
- 链接该对象(设置该对象的
constructor
)到另一个对象 - 将步骤1新创建的对象作为
this
的上下文 - 如果该函数没有返回对象,则返回
this
- 创建空对象
show me the code
1 2 3 4 5 6 7 8
function customizeNew(Fn, ...args) { // 创建空对象并链接到原型 let obj = Object.create(Fn.prototype); // 绑定 this let res = Fn.apply(obj, args); return res instanceof Object ? res : obj; }
2. 手写Promise
思路
Promise
主要解决传统异步编程中“回调地狱”导致代码阅读性和维护性差的问题,使用链式风格替代传统的嵌套回调风格,是更优雅的异步编程解决方案。 根据「Promise/A+ 规范」,promise
有三个状态:pending
(待定)、fulfilled
(已兑现)、rejected
(已拒绝),需要实现的基础功能包括:new Promise()
参数需要一个处理器函数executor()
- 处理器函数的参数为
resolve
和reject
,成功调用resolve
函数,失败调用reject
函数 - 默认状态为
pending
,只能从peding
到fulfilled
或rejected
,且状态确认后不可再改变 - 包含
then
方法,接收onFulfilled
和onRejected
参数,成功执行onFulfilled
,失败执行onRejected
show me the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
const STATUS = { PENDING: 'PENDING', FULFILLED: 'FULFILLED', REJECTED: 'REJECTED', }; class MyPromise { constructor(executor) { this.status = STATUS.PENDING; //默认状态 pending this.value = undefined; //fulfilled 状态时的值 this.error = undefined; //rejected 状态时的错误原因 this.onResolvedCallbacks = []; //存放成功的回调 this.onRejectedCallbacks = []; //存放失败的回调 this._resolve = this._resolve.bind(this); this._reject = this._reject.bind(this); try { executor(this._resolve, this._reject); } catch (e) { this._reject(e); } } _resolve(value) { setTimeout(() => { this.value = value; this.status = STATUS.FULFILLED; this.onResolvedCallbacks.forEach(fn => fn(value)); }) } _reject(error) { setTimeout(() => { this.error = error; this.status = STATUS.REJECTED; this.onRejectedCallbacks.forEach(fn => fn(error)); }) } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { this.onResolvedCallbacks.push((value) => { try { const res = onFulfilled(value); if (res instanceof MyPromise) { res.then(resolve, reject); } else { resolve(res); } } catch (e) { reject(e); } }); this.onRejectedCallbacks.push((value) => { try { const res = onRejected(value); if (res instanceof MyPromise) { res.then(resolve, reject); } else { reject(res); } } catch (e) { reject(e); } }) }) } }
3. 节流throttle
思路
限制目标函数使用频率,规定的单位时间内只能触发一次,如果此时间内多次触发,只有一次生效show me the code
1 2 3 4 5 6 7 8 9 10 11 12
function throttle(func, ms=1000) { let canRun = true; return function(...args) { if (!canRun) return; canRun = false; setTimeout(()=> { func.apply(this, args); canRun = true; }, ms); } }
4. 防抖debounce
思路
事件触发 n 秒后再执行回调,如果这 n 秒内又被触发,则重新计时show me the code
1 2 3 4 5 6 7 8 9 10 11
function debounce(func, ms=1000) { let timer; return function(...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { func.apply(this, args); }, ms); } }
5. bind
思路
改变函数this
指向,并返回一个待执行方法,执行时可继续传入参数,即fn.bind(ctx, arg1)(arg2) == fn.cal(ctx, arg1, arg2)
show me the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Function.prototype.myBind = function() { if (typeof this !== 'function') { throw 'caller must be a function' } let self = this; let [ctx, ...args] = [...arguments]; let fn = function() { const newArgs = [...args, ...arguments] if (this instanceof fn) { // new 调用,绑定 this 为实例对象 self.apply(this, newArgs) } else { // 普通调用 self.apply(ctx, newArgs) } } fn.prototype = Object.create(self.prototype); return fn; }
6. call
思路
将this
指向调用者,在调用者(context)上直接调用方法,触发 this 的绑定show me the code
1 2 3 4 5 6 7 8 9 10 11 12
Function.prototype.myCall = function() { if (typeof this !== 'function') { throw 'caller must be function' } const [ctx, ...args] = [...arguments] // 可以通过Symbol设置key,防止覆盖原有属性 ctx._fn = this const res = ctx._fn(args) delete ctx['_fn'] return res }
7. apply
思路
同call
show me the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Function.prototype.myApply = function() { if (typeof this !== 'function') { throw 'caller must be function' } let res const [ctx, args, ...others] = [...arguments] ctx._fn = this if (args) { res = ctx._fn(...args) } else { res = ctx._fn() } delete ctx._fn return res }
8. 深拷贝
思路
核心思路是在浅拷贝的基础上,对子项为对象类型的进行递归;对于循环引用的问题可以使用WeakMap
缓存比较来解决show me the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
function cloneDeep(obj, cache = 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 (cache.get(obj)) return cache.get(obj) // 支持函数 if (obj instanceof Function) { return function() { obj.apply(this, arguments) } } const res = Array.isArray(obj) ? [] : {} cache.set(obj, res) Object.keys(obj).forEach(key => { if (obj[key] instanceof Object) { res[key] = cloneDeep(obj[key], cache) } else { res[key] = obj[key] } }) return res }
9. 柯里化
- show me the code
1 2 3 4 5 6 7 8 9 10
function curry(fn, args = []) { return function() { let newArgs = [...args, ...arguments]; if (newArgs.length < fn.length) { return curry.call(this, fn, newArgs) } else { return fn.apply(this, newArgs) } } }