所有遍历数组的方法 forEach Array .prototype .myForEach = function (callback, thisArg ) { if (typeof callback !== 'function' ) { throw new TypeError ('第一个参数必须是一个函数' ); } for (let i = 0 ; i < this .length ; i++) { if (i in this ) { callback.call (thisArg, this [i], i, this ); } } };
map Array .prototype .myMap = function (callback ) { if (typeof callback !== 'function' ) { throw new TypeError ('第一个参数必须是一个函数' ); } let res = []; for (let i = 0 ; i < this .length ; i++) { if (i in this ) { res.push (callback (this [i], i, this )); } } return res };
Filter Array .prototype .my_filter = function (cb ) { let newArr = [] for (var i = 0 ; i < this .length ; i++) { if (cb (this [i], i, this )) { newArr.push (this [i]) } } return newArr }
every(判断数组中的每一个元素是否都满足某个条件) Array .prototype .my_every = function (cb ) { for (let i = 0 ; i < this .length ; i++) { if (!cb (this [i], i, this )) { return false ; } } return true ; }
some(判断数组中是否存在一个元素满足某个条件) Array .prototype .my_some = function (cb ) { for (let i = 0 ; i < this .length ; i++) { if (cb (this [i], i, this )) { return true ; } } return false ; }
reduce Array .prototype .my_reduce = function (cb, ...args ) { let start, index = 0 if (args.length ) { start = args[0 ] } else { start = this [0 ]; index = 1 } for (let i = index; i < this .length ; i++) { start = cb (start, this [i], i, this ); } return start; }
includes Array .prototype .my_includes = function (item, ...args ) { let index if (args.length && !Number .isNaN (Number (args[0 ])) && Number (args[0 ])>0 ) { index = Number (args[0 ]) } else { index = 0 ; } for (let i = index; i < this .length ; i++) { if (this [i] === item) return true ; } return false }
splice Array .prototype .mySplice = function (start, len, ...args ) { let newArr = [], resArr = []; if (arguments .length === 0 ) return resArr; if (typeof len === 'undefined' ) { len = 0 ; } if (typeof start === 'undefined' ) { start = 0 ; } else if (start < 0 ) { start = start + this .length ; start = start < 0 ? 0 : start; } else if (start >= this .length ) { start = this .length ; } for (let i = 0 ; i < this .length ; ++i) { if (i < start || i >= start + len) { newArr.push (this [i]); } else { resArr.push (this [i]); } if (i === start || start + i === this .length * 2 - 1 ) { newArr.push (...args); } } while (this .length ) { this .pop (); } this .push (...newArr); return resArr; }
JS内置方法、对象以及运算符 new运算符 function myNew (Func, ...arg ){ if (fn.prototype === undefined ) throw new TypeError ('function is not a constructor' ); let obj = {} obj.__proto__ = Func .prototype let res = Func .call (obj, ...arg) return res instanceof Object ? res : obj }
手撕curry const my_curry = (fn, ...args ) => args.length >= fn.length ? fn (...args) : (...args1 ) => my_curry (fn, ...args, ...args1); function adder (x, y, z ) { return x + y + z; } const add = my_curry (adder);console .log (add (1 , 2 , 3 )); console .log (add (1 )(2 )(3 )); console .log (add (1 , 2 )(3 )); console .log (add (1 )(2 , 3 ));
手写compose函数 const compose = (...fns )=>(x )=> fns.reduceRight ((v, fn )=> fn (v),x);const doubleAndSuare = compose (double, square);console .log (doubleAndSuare (3 ));
手撕vue3响应式代理 class Depend { constructor ( ) { this .reactiveFns = new Set () } depend ( ) { if (activeReactiveFn) this .reactiveFns .add (activeReactiveFn) } notify ( ) { console .log (this .reactiveFns ) this .reactiveFns .forEach (fn => { if (fn) fn () }) } } let activeReactiveFn = null function watchFn (fn ) { activeReactiveFn = fn fn () activeReactiveFn = null } const targetMap = new WeakMap ()function getDepend (target, key ) { let map = targetMap.get (target) if (!map) { map = new Map () targetMap.set (target, map) } let depend = map.get (key) if (!depend) { depend = new Depend () map.set (key, depend) } return depend } function reactive (obj ) { return new Proxy (obj, { get : function (target, key, receiver ) { const depend = getDepend (target, key) depend.depend () return Reflect .get (target, key, receiver) }, set : function (target, key, newValue, receiver ) { Reflect .set (target, key, newValue, receiver) const depend = getDepend (target, key) depend.notify () } }) } const obj = { name : "coderwhy" , age : 18 } const info = { name :"小余" , age :18 } const objRef = reactive ({ name : "coderwhy" , age : 18 }) const infoRef = reactive ({ name :"小余" , age :18 }) watchFn (() => { console .log (infoRef.name ); }) watchFn (() => { console .log (objRef.name ); }) infoRef.name = '响应式-小余' objRef.name = '响应式-coderwhy'
手撕instanceof const myInstanceOf= (Left ,Right )= > { if(! Left ){ return false } while(Left ){ if(Left.__proto__= = = Right.prototype){ return true }else { Left = Left.__proto__ } } return false } / / 验证console.log (myInstanceOf({},Array )); / / false
call,bind,apply function foo (x,y ){ console .log (this .a ,x+y); } const obj={ a :1 } Function .prototype .myCall =function (context,...args ){ if (typeof this !== 'function' ) return new TypeError ('is not a function' ) const fn = Symbol ('fn' ) context[fn] = this const res = context[fn](...args) delete context[fn] return res } foo.myCall (obj,1 ,2 ) function foo (x,y ){ console .log (this .a ,x+y); } const obj={ a :1 } Function .prototype .myApply =function (context,args ){ if (typeof this !== 'function' ) return new TypeError ('is not a function' ) const fn = Symbol ('fn' ) context[fn] = this const res = context[fn](...args) delete context[fn] return res } foo.myApply (obj,1 ,2 ) function foo (x,y,z ){ this .name ='zt' console .log (this .a ,x+y+z); } const obj={ a :1 } Function .prototype .myBind =function (context,...args ){ if (typeof this !== 'function' ) return new TypeError ('It is not a function' ); context = context || window ; const _this = this ; return function F (...arg ) { if (this instanceof F) { return new _this (...args, ...arg); } else { _this.call (this , ...args, ...arg); } } } const bar=foo.myBind (obj,1 ,2 )console .log (new bar (3 ));
值相等 function compare (data1, data2 ) { if (typeof data1 != typeof data2) { return false ; } if (typeof data1 != "object" && typeof data1 == typeof data2) { return data1 === data2; } if ((!data1 instanceof Array && data2 instanceof Array ) || (data1 instanceof Array && !data2 instanceof Array )) { return false ; } else if (data1 instanceof Array && data2 instanceof Array ) { if (data1.length == data2.length ) { for (let i = 0 ; i < data1.length ; i++) { if (!compare (data1[i], data2[i])) { return false ; } } } else { return false ; } } else { if (Object .keys (data1).length == Object .keys (data2).length ) { for (let key in data1) { if (!data2[key] || !compare (data1[key], data2[key])) { return false ; } } } else { return false ; } } return true ; }
深拷贝 function isObject (value ) { const valueType = typeof value return (value !== null ) && (valueType === "object" || valueType === "function" ) } function deepClone (originValue ) { if (!isObject (originValue)) { return originValue } const newObject = {} for (const key in originValue) { newObject[key] = deepClone (originValue[key]) } return newObject }
实现Object.create() 创建一个空对象,定义其原型对象并设置其枚举属性
Object .myCreate = function (proto, defineProperties ){ if ((typeof proto === 'object' && proto !== null ) || typeof proto === 'function' ){ let obj = {}; Object .setPrototypeOf (obj, proto); Object .defineProperty (obj, defineProperties); return obj; }else { throw new TypeError ('类型错误' ); } }
实现Object.assign(浅拷贝) function myAssign (target,...objs ){ if (target === null || target === undefined ){ throw new TypeError ("can not convert null or undefined to object" ) } let res = Object (target) objs.forEach (obj => { 'use strict' if (obj != null && obj != undefined ){ for (let key in obj){ if (Object .prototype .hasOwnProperty .call (obj,key)){ res[key] = obj[key] } } } }) return res } Object .defineProperty (Object ,'myAssign' ,{ value : myAssign, writable : true , configurable : true , enumerable : false })
trim方法 function myTrim (str ) { const reg = /^\s+|\s+$/g ; return str.replace (reg, '' ); }
模板字符串 const render = (template, data ) => { const reg = /\$\{(.*?)\}/g ; template = template.replace (reg, (match, key ) => { let keys = key.trim ().split ('.' ); return keys.reduce ((obj, k ) => obj?.[k], data) || '' ; }) return template; }
sleep函数 function sleep (ms ) { return new Promise ((resolve ) => { setTimeout (() => { console .log ('sleep...' ) resolve () }, ms); }) } async function test ( ){ console .log ('1' ); await sleep (400 ) console .log ('2' ) } test ();
利用setTimeout实现setInterval function coustomSetInterval (callback, time ) { let intervalId = null ; function loop ( ) { intervalId = setTimeout (() => { callback (); loop (); }, time) } loop (); return () => clearTimeout (intervalId) } const interval = coustomSetInterval (() => { console .log ('想你了' ) }, 1000 ) setTimeout (() => { interval () }, 5000 )
🚀 Promise 源码实现完全指南 - 从零手写 Promise
🎯 阅读本节你将收获 :
深入理解 Promise 内部运行机制
手写一个符合 Promise/A+ 规范的 Promise
掌握面试中 Promise 相关的核心考点
理解 async/await 的实现原理
📌 开篇:为什么要手写 Promise? 在面试中,Promise 相关的题目可以说是必考题。但很多同学只会用,不知道其内部原理。今天我们就一起来揭开 Promise 的神秘面纱,手写一个完整的 Promise!
一、Promise 基础架构 - 搭建地基 🏗️ 1.1 最简单的 Promise 结构 我们先从最简单的结构开始,就像盖房子要先打地基:
class MyPromise { constructor (executor ) { const resolve = (value ) => { console .log ('调用了 resolve,值为:' , value); } const reject = (reason ) => { console .log ('调用了 reject,原因为:' , reason); } executor (resolve, reject); } } new MyPromise ((resolve, reject ) => { resolve ('成功啦!' ); });
1.2 Promise 的三种状态 - 状态机 🚦 Promise 就像一个有三种状态的交通灯:
🟡 pending (等待态):初始状态,既不是成功,也不是失败
🟢 fulfilled (成功态):操作成功完成
🔴 rejected (失败态):操作失败
重要特性 :
状态只能从 pending
→ fulfilled
或 pending
→ rejected
状态一旦改变,就永远不会再变(这就是为什么叫 “Promise” - 承诺)
让我们加上状态管理:
class MyPromise { constructor (executor ) { this .status = 'pending' ; this .value = undefined ; this .reason = undefined ; const resolve = (value ) => { if (this .status === 'pending' ) { this .status = 'fulfilled' ; this .value = value; } } const reject = (reason ) => { if (this .status === 'pending' ) { this .status = 'rejected' ; this .reason = reason; } } try { executor (resolve, reject); } catch (error) { reject (error); } } }
二、实现 then 方法 - Promise 的灵魂 ✨ 2.1 then 方法的基本实现 then
方法是 Promise 的核心,它用来注册当 Promise 状态改变时的回调函数。
class MyPromise { then (onFulfilled, onRejected ) { if (this .status === 'fulfilled' ) { onFulfilled (this .value ); } if (this .status === 'rejected' ) { onRejected (this .reason ); } } } const promise = new MyPromise ((resolve, reject ) => { resolve ('成功!' ); }); promise.then ( value => console .log ('成功:' , value), reason => console .log ('失败:' , reason) );
2.2 处理异步情况 - 发布订阅模式 📢 上面的代码有个问题:如果 executor 中有异步操作怎么办?
const promise = new MyPromise ((resolve, reject ) => { setTimeout (() => { resolve ('异步成功!' ); }, 1000 ); }); promise.then (value => console .log (value));
解决方案:使用发布订阅模式 ,先把回调存起来,等状态改变时再执行:
class MyPromise { constructor (executor ) { this .status = 'pending' ; 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 ()); } } const 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 ) { if (this .status === 'fulfilled' ) { onFulfilled (this .value ); } if (this .status === 'rejected' ) { onRejected (this .reason ); } if (this .status === 'pending' ) { this .onFulfilledCallbacks .push (() => { onFulfilled (this .value ); }); this .onRejectedCallbacks .push (() => { onRejected (this .reason ); }); } } }
2.3 链式调用的实现 - then 返回 Promise 🔗 Promise 最强大的特性之一就是链式调用。要实现链式调用,then
方法必须返回一个新的 Promise:
then (onFulfilled, onRejected ) { return new MyPromise ((resolve, reject ) => { const fulfilledMicrotask = ( ) => { queueMicrotask (() => { try { const x = onFulfilled (this .value ); resolvePromise (promise2, x, resolve, reject); } catch (error) { reject (error); } }); }; const rejectedMicrotask = ( ) => { queueMicrotask (() => { try { const x = onRejected (this .reason ); resolvePromise (promise2, x, resolve, reject); } catch (error) { reject (error); } }); }; if (this .status === 'fulfilled' ) { fulfilledMicrotask (); } else if (this .status === 'rejected' ) { rejectedMicrotask (); } else if (this .status === 'pending' ) { this .onFulfilledCallbacks .push (fulfilledMicrotask); this .onRejectedCallbacks .push (rejectedMicrotask); } }); }
2.4 处理 then 的返回值 - resolvePromise 🎯 这是实现 Promise 最复杂的部分,需要处理各种情况:
function resolvePromise (promise2, x, resolve, reject ) { if (x === promise2) { return reject (new TypeError ('Chaining cycle detected' )); } if (x instanceof MyPromise ) { x.then ( value => resolvePromise (promise2, value, resolve, reject), reason => reject (reason) ); return ; } if (x !== null && (typeof x === 'object' || typeof x === 'function' )) { let then; try { then = x.then ; } catch (error) { return reject (error); } if (typeof then === 'function' ) { let called = false ; try { then.call ( x, value => { if (called) return ; called = true ; resolvePromise (promise2, value, resolve, reject); }, reason => { if (called) return ; called = true ; reject (reason); } ); } catch (error) { if (called) return ; reject (error); } } else { resolve (x); } } else { resolve (x); } }
三、实现 Promise 的静态方法 🛠️ 3.1 Promise.resolve 和 Promise.reject class MyPromise { static resolve (value ) { if (value instanceof MyPromise ) { return value; } return new MyPromise (resolve => resolve (value)); } static reject (reason ) { return new MyPromise ((resolve, reject ) => reject (reason)); } }
3.2 Promise.all - 一个都不能少 🎯 Promise.all
接收一个 Promise 数组,只有全部成功才成功,有一个失败就失败:
static all (promises ) { return new MyPromise ((resolve, reject ) => { const results = []; let completedCount = 0 ; if (promises.length === 0 ) { resolve (results); return ; } promises.forEach ((promise, index ) => { MyPromise .resolve (promise).then ( value => { results[index] = value; completedCount++; if (completedCount === promises.length ) { resolve (results); } }, reason => { reject (reason); } ); }); }); }
3.3 Promise.race - 谁快用谁 🏃 static race (promises ) { return new MyPromise ((resolve, reject ) => { if (promises.length === 0 ) return ; promises.forEach (promise => { MyPromise .resolve (promise).then (resolve, reject); }); }); }
3.4 Promise.allSettled - 等待所有结果 📊 不管成功还是失败,等所有 Promise 都有结果:
static allSettled (promises ) { return new MyPromise ((resolve ) => { const results = []; let settledCount = 0 ; if (promises.length === 0 ) { resolve (results); return ; } promises.forEach ((promise, index ) => { MyPromise .resolve (promise).then ( value => { results[index] = { status : 'fulfilled' , value }; settledCount++; if (settledCount === promises.length ) { resolve (results); } }, reason => { results[index] = { status : 'rejected' , reason }; settledCount++; if (settledCount === promises.length ) { resolve (results); } } ); }); }); }
3.5 Promise.any - 一个成功就够了 ✅ static any (promises ) { return new MyPromise ((resolve, reject ) => { const errors = []; let rejectedCount = 0 ; if (promises.length === 0 ) { reject (new AggregateError ([], 'All promises were rejected' )); return ; } promises.forEach ((promise, index ) => { MyPromise .resolve (promise).then ( value => { resolve (value); }, reason => { errors[index] = reason; rejectedCount++; if (rejectedCount === promises.length ) { reject (new AggregateError (errors, 'All promises were rejected' )); } } ); }); }); }
四、实现 catch 和 finally 🎣 4.1 catch - 错误处理 catch (onRejected) { return this .then (null , onRejected); }
4.2 finally - 无论如何都要执行 finally (callback ) { return this .then ( value => MyPromise .resolve (callback ()).then (() => value), reason => MyPromise .resolve (callback ()).then (() => { throw reason }) ); }
五、async/await 的实现原理 🔮 5.1 理解 async/await async/await
本质上是 Generator + Promise 的语法糖。让我们看看它是如何工作的:
async function fetchData ( ) { const user = await getUser (); const posts = await getPosts (user.id ); return posts; } function fetchData ( ) { return spawn (function * () { const user = yield getUser (); const posts = yield getPosts (user.id ); return posts; }); }
5.2 实现自动执行器 function spawn (genFunc ) { return new Promise ((resolve, reject ) => { const gen = genFunc (); function step (nextFunc ) { let next; try { next = nextFunc (); } catch (error) { return reject (error); } if (next.done ) { return resolve (next.value ); } Promise .resolve (next.value ).then ( value => step (() => gen.next (value)), error => step (() => gen.throw (error)) ); } step (() => gen.next ()); }); }
5.3 一个更简单的实现 function asyncToGenerator (generatorFunc ) { return function (...args ) { const gen = generatorFunc.apply (this , args); return new Promise ((resolve, reject ) => { function step (key, arg ) { let generatorResult; try { generatorResult = gen[key](arg); } catch (error) { return reject (error); } const { value, done } = generatorResult; if (done) { return resolve (value); } else { return Promise .resolve (value).then ( val => step ('next' , val), err => step ('throw' , err) ); } } step ('next' ); }); }; } const getData = asyncToGenerator (function * () { const data1 = yield fetch ('/api/1' ); const data2 = yield fetch ('/api/2' ); return [data1, data2]; }); getData ().then (result => console .log (result));
🎯 面试高频考点总结
Promise 的状态机制 :三种状态,只能单向改变,一旦改变不可逆
then 的链式调用 :then 返回新的 Promise,根据回调函数的返回值决定新 Promise 的状态
微任务队列 :Promise 的回调在微任务队列中执行
错误处理 :catch 能捕获前面所有 then 中的错误
Promise.all vs Promise.race :一个要全部成功,一个要最快完成
async/await 原理 :Generator + 自动执行器 + Promise
模板渲染引擎 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 模板渲染示例</title > </head > <body > <div id ="app" > </div > <script src ="script.js" > </script > </body > </html > // 渲染引擎 function renderTemplate(template, data) { // 处理条件判断 template = template.replace(/{{\s*#if\s*(\w+)\s*}}([\s\S]*?){{\s*\/if\s*}}/g, function (_, condition, content) { return data[condition] ? content : ''; }); // 处理循环 template = template.replace(/{{\s*#each\s*(\w+)\s*}}([\s\S]*?){{\s*\/each\s*}}/g, function (_, arrayKey, content) { let result = ''; let array = data[arrayKey]; if (Array.isArray(array)) { array.forEach((item, index) => { // 创建一个独立作用域,避免冲突 let temp = content; // 替换模板内的 {{ this }} 为循环项 temp = temp.replace(/{{\s*this\s*}}/g, function () { return item; }); // 如果是对象,处理它的属性 temp = temp.replace(/{{\s*this\.(\w+)\s*}}/g, function (_, prop) { return item[prop] || ''; }); // 替换循环索引(如果需要) temp = temp.replace(/{{\s*index\s*}}/g, index); // 将处理后的结果添加到最终结果中 result += temp; }); } return result; }); // 替换变量,一定要放到最后!! template = template.replace(/\{\{(.*?)\}\}/g, (match, key) => { let keys = key.trim().split('.'); return keys.reduce((obj, k) => obj?.[k], data) || ''; }) return template; } // 定义数据 const data = { name: "张三", age: 25, isEmployed: true, occupation: "工程师", hobbies: ["阅读", "编程", "旅游"], friends: [ { name: "李四", age: 26 }, { name: "王五", age: 24 } ] }; // 定义模板 const template = ` <h1 > 个人信息</h1 > <p > 姓名: {{ name }}</p > <p > 年龄: {{ age }}</p > <p > 职业: {{ occupation }}</p > {{#if isEmployed}} <p > 当前状态: 在职</p > {{/if}} <h2 > 兴趣爱好</h2 > <ul > {{#each hobbies}} <li > {{ this }}</li > {{/each}} </ul > <h2 > 朋友列表</h2 > <ul > {{#each friends}} <li > {{ this.name }} - {{ this.age }} 岁</li > {{/each}} </ul > `; // 渲染模板并插入 HTML document.getElementById('app').innerHTML = renderTemplate(template, data);
防抖截流 const debounce = (fn, ms, Immediate = false ) => { let timer = null ; return function (...thisArgs ) { if (timer) { clearTimeout (timer) } if (Immediate ) { let flag = !timer flag && fn.apply (this , thisArgs) timer = setTimeout (() => { timer = null }, ms) } else { timer = setTimeout (() => { fn.apply (this , thisArgs) timer = null }, ms) } } } const throttle = (fn, ms ) => { let timer = null ; return function (...thisArgs ) { if (!timer) { fn.apply (this , thisArgs); timer = setTimeout (() => { timer = null ; }, ms); } } }
技巧 数组去重 let arr = [1 , 0 , 2 , 3 , 4 , 5 , 2 , 3 , 4 ];function removeRepeat (arr ) { let res = []; for (let i of arr) { if (res.indexOf (i) == -1 ) { res.push (i); } } return res; } function removeRepeat (arr ) { let res = new Set (arr); return Array .from (res); } function removeRepeat (arr ) { for (let i = 0 ; i < arr.length ; i++) { for (let j = i + 1 ; j < arr.length ; j++) { if (arr[i] == arr[j]) { arr.splice (j, 1 ); j--; } } } return arr; } function removeRepeat (arr ) { return arr.filter ((item, index ) => { return arr.indexOf (item) == index; }); } function removeRepeat (arr ) { let res = []; for (let i of arr) { if (!res.includes (i)) { res.push (i); } } return res; } let res = removeRepeat (arr);console .log (res);
快排和归并 Array .prototype .quicksort = function (l, r ) { if (l >= r) return this ; let key = this [l]; let i = l - 1 , j = r + 1 ; while (i < j) { do i++; while (this [i] < key); do j--; while (this [j] > key); if (i < j) [this [i], this [j]] = [this [j], this [i]]; } this .quicksort (l, j); this .quicksort (j + 1 , r); return this ; } let arr = [5 ,8 ,3 ,6 ,4 ,2 ];console .log (arr.quicksort (0 , arr.length -1 ));Array .prototype .mergesort = function (temp, l, r ) { if (l >= r) return this ; let mid = l + r >> 1 ; this .mergesort (temp, l, mid); this .mergesort (temp, mid + 1 , r); let i = l, j = mid + 1 , k = 0 ; while (i <= mid && j <= r) { if (this [i] <= this [j]) temp[k++] = this [i++]; else temp[k++] = this [j++]; } while (i <= mid) temp[k++] = this [i++]; while (j <= r) temp[k++] = this [j++]; for (let m = 0 , n = l; n <= r; m++, n++) { this [n] = temp[m]; } return this ; } let arr = [7 , 1 , 0 , 3 , 0 , 5 , 6 , 4 ];console .log (arr.mergesort (Array (arr.length ).fill (0 ), 0 , arr.length - 1 ));
下划线和驼峰相互转换 function transformStr2Hump1 (str ) { if (str == null ) { return "" ; } var strArr = str.split ('-' ); for (var i = 1 ; i < strArr.length ; i++) { strArr[i] = strArr[i].charAt (0 ).toUpperCase () + strArr[i].substring (1 ); } return strArr.join ('' ); } function transformStr2Hump2 (str ) { if (str == null ) { return "" ; } var strArr =str.split ('' ); for (var i = 0 ; i < strArr.length ; i++) { if (strArr[i] == "-" ){ strArr.splice (i, 1 ); if (i < strArr.length ) { strArr[i] = strArr[i].toUpperCase (); } } } return strArr.join ("" ); } function transformStr2Hump3 (str ) { if (str == null ) { return "" ; } var reg = /-(\w)/g ; return str.replace (reg, function ($0, $1 ) { return $1.toUpperCase (); }) }
懒加载
首先,不要将图片地址放到src属性中,而是放到其它属性(data-original)中。
页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中。
在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出存放到src属性中。
elementNode.getAttribute(name):方法通过名称获取属性的值。
elementNode.setAttribute(name, value):方法创建或改变某个新属性。
elementNode.removeAttribute(name):方法通过名称删除属性的值。
var viewHeight = document .documentElement .clientHeight ;function lazyload ( ) { let eles = document .querySelectorAll ('img[data-original][lazyload]' ); Array .prototype .forEach .call (eles, function (item, index ) { let rect; if (item.dataset .original === '' ) { return ; } rect = item.getBoundingClientRect (); if (rect.bottom >= 0 && rect.top < viewHeight) { !function ( ) { let img = new Image (); img.src = item.dataset .original ; img.onload = function ( ) { item.src = img.src ; } item.removeAttribute ('data-original' ); item.removeAttribute ('lazyload' ); }(); } }) } lazyload ();document .addEventListener ('scroll' , lazyload);
数组扁平化 Array .prototype ._flat = function (n ) { let result = []; let num = n; for (let item of this ) { if (Array .isArray (item)) { n--; if (n < 0 ) { result.push (item); } else { result.push (...item._flat (n)); } } else { result.push (item); } n = num; } return result; }; let arr = [1 , 2 , [3 , 4 ], [5 , 6 , [7 , 8 ]]];let res = arr._flat (1 );console .log (res);
计算属性 请不要为所有函数添加缓存!
const computed = (func, content ) => { let cache = Object .create (null ); content = content || this ; return (...key ) => { console .log (cache) if (!cache[key]) { cache[key] = func.apply (content, key); } return cache[key]; } }
有并发限制的Promise调度器 class Scheduler { constructor (limit ){ this .limit = limit; this .running = 0 ; this .queue = []; } createTask (callback, duration ){ return ()=> { return new Promise ((resolve, reject )=> { setTimeout (()=> { callback (); resolve (); },duration); }); }; } add (callback, duration ){ const task = this .createTask (callback, duration); this .queue .push (task); } start ( ){ for (let i=0 ;i<this .limit ;++i){ this .schedule (); } } schedule ( ){ if (this .queue .length === 0 || this .running >= this .limit )return ; this .running ++; const task = this .queue .shift (); task ().then (()=> { this .running --; schedule (); }); } }
网络请求和跨域解决方案 原生ajax function sendajax ( ) { const xhr = new XMLHttpRequest (); xhr.open ("get" , "./data.json" ); xhr.send (); xhr.onreadystatechange = function ( ) { if (xhr.readyState == 4 ) { if (xhr.status >= 200 && xhr.status < 300 ) { console .log (xhr.responseText ); } } }; } sendajax ();
JSONP跨域 首先在客户端注册一个callback,然后把callback的名字传给服务器。此时,服务器先生成json数据,然后以JavaScript的语法方式,生成function,function的名字就是传递上来带参数的jsonp,最后将json数据直接以入参的方式,放置在function中,这样子就生成JavaScript语法文档,返回给客户端。客户端浏览器,通过解析,并执行返回JavaScript文档,此时数据作为参数,传入到客户端预先定义好的callback函数中,简单地说,就是利用script标签没有跨域限制地漏洞来达到第三方通讯的目的(href、src 都不受同源策略的限制。)
优点:
它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制,JSONP可以跨越同源策略;
它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;
缺点:
它只支持GET请求 而不支持POST等其它类型的HTTP请求
它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
jsonp在调用失败的时候不会返回各种HTTP状态码。
需要后端配合
function jsonp ({url,params,cb} ){ return new Promise ((resolve, reject )=> { window [cb] = function (data ){ console .log (data) resolve (data); document .body .removeChild (script); } params= {...params,cb} let arrs = []; for (let key in params){ arrs.push (`${key} =${params[key]} ` ) } let script = document .createElement ('script' ); script.src = `${url} ?${arrs.join('&' )} ` ; script.onerror = () => reject ('加载失败' ) document .body .appendChild (script); }) } jsonp ({ url :"http://localhost:3000/users" , params :{name :"jin" ,age :12 }, cb :'show' }).then (data => { console .log (data) })
后端:
let express = require ('express' );let app = express ();app.get ('/users' , function (req, res, next ) { let {name,age,cb} = req.query let data = `"${name} 现在${age} 岁"` res.send (`${cb} (${data} )` ); }); app.listen (3000 )
cors跨域 同源策略是不允许接收响应而不是不允许发送请求,所以可以通过在响应头中设置某些字段来允许满足条件的请求跨域,比如设置 Access-Control-Allow-Origin
字段允许来自某个源的请求跨域,比如设置 Access-Control-Allow-Methods
字段允许’GET’或者’POST’方式的请求跨域
后端:
let express = require ('express' );let app = express ();app.all ('*' , function (req, res, next ) { let origin = req.headers .origin res.header ("Access-Control-Allow-Origin" ,origin); res.header ("Access-Control-Allow-Headers" , "name" ); res.header ("Access-Control-Allow-Methods" , "POST" ); res.set ("Access-Control-Allow-Credentials" , true ); res.header ("Access-Control-Max-Age" , 6 ); res.header ("Access-Control-Expose-Headers" , "name" ); res.header ("Content-Type" , "application/json;charset=utf-8" ); next (); }); app.post ('/getData' , function (req, res, next ) { console .log (req.headers ) res.send ("你拿不到数据了!" ); }); app.listen (4000 )
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。 代码修改一下:
if (req.method ==='OPTIONS' ){ res.end (); }
postMessage跨域 「window.postMessage()」 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 [Document.domain
]设置为相同的值) 时,这两个脚本才能相互通信。「window.postMessage()」 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
用途
1.页面和其打开的新窗口的数据传递
2.多窗口之间消息传递
3.页面与嵌套的 iframe 消息传递
实现
a.html
<iframe src ="http://localhost:4000/b.html" frameborder ="0" id ="frame" onload ="load()" > </iframe > <script > function load ( ){ let frame = document .getElementById ('frame' ); frame.contentWindow .postMessage ('你好' ,'http://localhost:4000/' ); window .onmessage = function (e ){ console .log (e.data ) } } </script >
b.html
window.onmessage = function(e){ console.log(e.data); //发送 e.source.postMessage('hello',e.origin) }
本地代理跨域 proxy其实就是因为浏览器同源协议无法请求非同源的地址,但是服务器直接没有同源协议,利用将本地请求转到本地服务器进行代理转发,从而绕过了同源协议的限制,通过代理的实现可以解决跨域的问题
通过设置一个 node 后端作为中间层,前端发送的请求首先到达这个中间层,然后再由中间层将请求转发到目标服务器。响应过程也是如此,服务器先响应给中间层,中间层再将响应数据发送回前端。
这个中间层就起到了一个代理的作用。这样,浏览器看到的是同源请求,从而绕过了CORS限制。
假设前端现在要将请求发送给 http://192.168.1.63:3000 这个后端,就可以先由本机的 3001 端口作一个代理
<body > <script > const xhr = new XMLHttpRequest () xhr.open ('GET' , 'https://localhost:3001' ) xhr.send () xhr.onreadystatechange = function ( ) { if (xhr.readyState === 4 && xhr.status === 200 ) { console .log (xhr.responseText ) } } </script > </body >
本机后端
const http = require ('http' )http.createServer ((req, res ) => { res.writeHead (200 , { 'Access-Control-Allow-Origin' : '*' }) http.request ({ host : '192.168.1.63' , port : 3000 , path : '/' , method : 'GET' , headers : {} }, proxyRes => { proxyRes.on ('data' , chunk => { res.end (chunk.toString ()) }) }).end () }).listen (3001 )
各脚手架的proxy配置: Webpack (4.x) 在webpack
中可以配置proxy
来快速获得接口代理的能力。
const path = require ("path" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : { index : "./index.js" }, output : { filename : "bundle.js" , path : path.resolve (__dirname, "dist" ) }, devServer : { port : 8000 , proxy : { "/api" : { target : "http://localhost:8080" } } }, plugins : [ new HtmlWebpackPlugin ({ filename : "index.html" , template : "webpack.html" }) ] };
Vue-cli 2.x ... proxyTable : { '/api' : { target : 'http://localhost:8080' , } }, ...
Vue-cli 3.x module .exports = { devServer : { port : 8000 , proxy : { "/api" : { target : "http://localhost:8080" } } } };
vite import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import styleImport, { VantResolve } from 'vite-plugin-style-import' ;export default defineConfig ({ plugins : [ vue (), styleImport ({ resolves : [VantResolve ()], }),], server : { host : '127.0.0.1' , port : 3000 , proxy : { '/api' : { target : 'http://127.0.0.1:8888' , changeOrigin : true , rewrite : (path ) => path.replace (/^\/api/ , '' ) }, } } })
nginx反向代理跨域 反向代理与本地代理相反。本地代理是客户端搞定的,反向代理就是服务端搞定的
反向代理的是在服务端内部完成。
是服务端(数据应用)向服务端(网页应用)发送数据, 服务端向客户端发送数据 其本质是在服务端(网页应用)通过配置Access-Control-Allow-Origin * 来解决跨域问题。相当于对后端接口进行了统一的cors配置
Access-Control-Allow-Origin: *
值表明,该资源可以被任意 外源访问。
events{} http{ include mime.types; default_type application/octet-stream; server{ listen 80; server_name 127.0.0.1; root D:/nginx-1.26.1/dist; index index.html; location / { location /api { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $scheme ; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD, PUT, DELETE" ; add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Authorization, User-Agent, DNT" ; add_header Access-Control-Max-Age 86400; } } } }
设计模式 单例模式 class Singleton { constructor ( ) { if (!Singleton .instance ) { Singleton .instance = this ; } return Singleton .instance ; } createInstance ( ) { const object = { name : "example" }; return object ; } getInstance ( ) { if (!Singleton .instance ) { Singleton .instance = this .createInstance (); } return Singleton .instance ; } } const instance1 = new Singleton ();const instance2 = new Singleton ();console .log (instance1 === instance2);
工厂模式 class Product { constructor (name ) { this .name = name; } getName ( ) { return this .name ; } } class ProductFactory { static createProduct (name ) { return new Product (name); } } const product = ProductFactory .createProduct ("Example Product" );console .log (product.getName ());
观察者模式 class Subject { constructor ( ) { this .observers = []; } addObserver (observer ) { this .observers .push (observer); } removeObserver (observer ) { const index = this .observers .indexOf (observer); if (index !== -1 ) { this .observers .splice (index, 1 ); } } notify (data ) { this .observers .forEach (observer => observer.update (data)); } } class Observer { update (data ) { console .log (`Received data: ${data} ` ); } } const subject = new Subject ();const observer1 = new Observer ();const observer2 = new Observer ();subject.addObserver (observer1); subject.addObserver (observer2); subject.notify ("Hello World!" );
装饰器模式 interface Component { operation(): void ; } class ConcreteComponent implements Component { public operation () : void { console.log("ConcreteComponent: operation." ); } } class Decorator implements Component { protected component: Component; constructor(component: Component) { this .component = component; } public operation () : void { console.log("Decorator: operation." ); this .component.operation(); } } class ConcreteDecoratorA extends Decorator { public operation () : void { super .operation(); console.log("ConcreteDecoratorA: operation." ); } } class ConcreteDecoratorB extends Decorator { public operation () : void { super .operation(); console.log("ConcreteDecoratorB: operation." ); } } const concreteComponent = new ConcreteComponent ();const concreteDecoratorA = new ConcreteDecoratorA (concreteComponent);const concreteDecoratorB = new ConcreteDecoratorB (concreteDecoratorA);concreteDecoratorB.operation();
代理模式 const target = { method ( ) { console .log ("Target method." ); } }; const proxy = new Proxy (target, { get (target, prop ) { console .log (`Called ${prop} method.` ); return target[prop]; } }); proxy.method ();
适配器模式 class Adaptee { specificRequest ( ) { return "适配者中的业务代码被调用" ; } } class Target { constructor ( ) { this .adaptee = new Adaptee (); } request ( ) { let info = this .adaptee .specificRequest (); return `${info} - 转换器 - 适配器代码被调用` ; } } let target = new Target ();target.request ();
MVC模式 class Model { constructor ( ) { this .data = { name : "example" , age : 18 , gender : "male" }; } setData (key, value ) { this .data [key] = value; } getData ( ) { return this .data ; } } class View { constructor ( ) { this .container = document .createElement ("div" ); } render (data ) { const { name, age, gender } = data; this .container .innerHTML = ` <p>Name: ${name} </p> <p>Age: ${age} </p> <p>Gender: ${gender} </p> ` ; document .body .appendChild (this .container ); } } class Controller { constructor (model, view ) { this .model = model; this .view = view; this .view .render (this .model .getData ()); } setData (key, value ) { this .model .setData (key, value); this .view .render (this .model .getData ()); } } const model = new Model ();const view = new View ();const controller = new Controller (model, view);controller.setData ("age" , 20 );
策略模式 表单验证情景:
const validationStrategies = { required : { validate : (value ) => value !== '' , message : '该字段不能为空' }, minLength : { validate : (value, length ) => value.length >= length, message : (length ) => `最少需要${length} 个字符` }, email : { validate : (value ) => /^\S+@\S+\.\S+$/ .test (value), message : '请输入有效的邮箱地址' }, phone : { validate : (value ) => /^1[3-9]\d{9}$/ .test (value), message : '请输入有效的手机号' }, custom : { validate : (value, validator ) => validator (value), message : '自定义验证失败' } }; class FormValidator { constructor ( ) { this .rules = []; } addRule (field, strategy, ...params ) { this .rules .push ({ field, strategy, params }); } validate (formData ) { const errors = []; for (const rule of this .rules ) { const { field, strategy, params } = rule; const value = formData[field]; const validationStrategy = validationStrategies[strategy]; if (!validationStrategy.validate (value, ...params)) { errors.push ({ field, message : typeof validationStrategy.message === 'function' ? validationStrategy.message (...params) : validationStrategy.message }); } } return { isValid : errors.length === 0 , errors }; } } const validator = new FormValidator ();validator.addRule ('username' , 'required' ); validator.addRule ('username' , 'minLength' , 3 ); validator.addRule ('email' , 'required' ); validator.addRule ('email' , 'email' ); validator.addRule ('phone' , 'phone' ); const formData = { username : 'ab' , email : 'invalid-email' , phone : '12345678901' }; const result = validator.validate (formData);console .log (result);