1.Object.create 1 2 3 4 5 6 7 8 9 10 11 function create (proto ) { function Fn ( ) {}; Fn .prototype = proto; Fn .prototype .constructor = Fn ; return new Fn (); }let demo = { c : '123' }let cc = Object .create (demo)
2.instanceof 1 2 3 4 5 6 7 8 9 10 11 function myInstanceof (left , right ) { while (true ) { if (left === null) { return false } if (left .__proto__ === right .prototype) { return true } left = left .__proto__ } }
3.new 1 2 3 4 5 6 7 8 9 10 function myNew (fn, ...args ) { let obj = Object.create(fn.prototype); let res = fn.call(obj, ...args); if (res instanceof Object) { return res; } return obj; }
4.实现sleep 某个时间后就去执行某个函数,使用Promise封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function sleep (fn, time ) { return new Promise ((resolve, reject ) => { setTimeout (() => { resolve (fn); }, time); }); }async function autoPlay ( ) { console .log (" 我在sleep 之前" ); await sleep (time); console .log (" 我在sleep 之后" ); }autoPlay ()
5.Promise 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 const PENDING = "pending" ;const FULFILLED = "fulfilled" ;const REJECTED = "rejected" ;function Promise (excutor ) { let that = this ; that.status = PENDING ; that.value = undefined ; that.reason = undefined ; that.onFulfilledCallbacks = []; that.onRejectedCallbacks = []; function resolve (value ) { if (value instanceof Promise ) { return value.then (resolve, reject); } setTimeout (() => { if (that.status === PENDING ) { that.status = FULFILLED ; that.value = value; that.onFulfilledCallbacks .forEach (cb => cb (that.value )); } }); } function reject (reason ) { setTimeout (() => { if (that.status === PENDING ) { that.status = REJECTED ; that.reason = reason; that.onRejectedCallbacks .forEach (cb => cb (that.reason )); } }); } try { excutor (resolve, reject); } catch (e) { reject (e); } }Promise .prototype .then = function (onFulfilled, onRejected ) { const that = this ; let newPromise; onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason; }; if (that.status === FULFILLED ) { return newPromise = new Promise ((resolve, reject ) => { try { let x = onFulfilled (that.value ); x instanceof Promise ? x.then (resolve, reject) : resolve (x); } catch (e) { reject (e); } }) } if (that.status === REJECTED ) { return newPromise = new Promise ((resolve, reject ) => { try { let x = onRejected (that.reason ); x instanceof Promise ? x.then (resolve, reject) : resolve (x); } catch (e) { reject (e); } }); } if (that.status === PENDING ) { return newPromise = new Promise ((resolve, reject ) => { that.onFulfilledCallbacks .push ((value ) => { try { let x = onFulfilled (value); x instanceof Promise ? x.then (resolve, reject) : resolve (x); } catch (e) { reject (e); } }); that.onRejectedCallbacks .push ((reason ) => { try { let x = onRejected (reason); x instanceof Promise ? x.then (resolve, reject) : resolve (x); } catch (e) { reject (e); } }); }); } };
6.Promise.all 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 Promise .all =function (promises ) { if (typeof promises[Symbol .iterator ] !== 'function' ) { throw (`传入的参数不是一个可迭代对象` ) } return new Promise (function (resolve, reject ) { if (!Array .isArray (promises)){ throw new TypeError (`argument must be a array` ) } var resolvedCounter = 0 ; var promiseNum = promises.length ; var resolvedResult = []; for (let i = 0 ; i < promiseNum; i++) { Promise .resolve (promises[i]).then (value => { resolvedCounter++; resolvedResult[i] = value; if (resolvedCounter == promiseNum) { resolve (resolvedResult); } },error => { reject (error); }) } }) }
7.Promise.race 1 2 3 4 5 6 7 8 9 10 11 Promise .race =function (proms ) {if (typeof promises[Symbol .iterator ] !== 'function' ) { throw (`传入的参数不是一个可迭代对象` ) } return new Promise ((resolve, reject ) => { for (const p of proms) { Promise .resolve (p).then (resolve, reject); } }); }
8.Promise.prototype.finally 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 Promise .prototype .finally = function (onSettled ) { return this .then ( (data ) => { onSettled (); return data; }, (reason ) => { onSettled (); throw reason; } ); } const pro = new Promise ((resolve, reject ) => { resolve (1 ); });const pro2 = pro.finally ((d ) => { console .log ("finally" , d); return 123 ; });setTimeout (() => { console .log (pro2); });
9.Promise.allSettled 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 Promise .allSettled =function (proms ) { const ps = []; for (const p of proms) { ps.push ( Promise .resolve (p).then ( (value ) => ({ status : FULFILLED , value, }), (reason ) => ({ status : REJECTED , reason, }) ) ); } return Promise .all (ps); }
10.Promise.prototype.catch 1 2 3 4 5 6 7 8 Promise .prototype .catch = function (onRejected ) { return this .then (null , onRejected); }
11.Promise.resolve 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Promise .resolve =function (value ) { if (value instanceof Promise ) { return value; } if (value && typeof value.then === 'function' ) { return new Promise (function (resolve, reject ) { value.then (resolve, reject); }); } return new Promise (function (resolve ) { resolve (value); }); }
12.Promise.reject 1 2 3 4 5 6 7 8 9 Promise .reject =function (reason ) { return new MyPromise ((resolve, reject ) => { reject (reason); }); }
13.防抖和节流 setTimeout函数中的第一个参数使用箭头函数时,apply中可直接传入this,因为箭头函数的this指向定义时外部代码块的this,如果不是箭头函数,则需要在外层用一个变量保留this再传入。
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 function debounce (callback, time ) { let timer = null ; return (...args ) => { if (timer) { clearTimeout (timer) timer = null } timer = setTimeout (() => { callback.apply (this , args); }, time); } } function throttle (fn, delay ) { let timer = null ; let lastTime = 0 ; return function ( ) { const args = arguments ; const nowTime = Date .now (); if (nowTime - lastTime >= delay) { fn.apply (this , args); lastTime = nowTime; } else { clearTimeout (timer); timer = setTimeout (()=> { fn.apply (this , args); lastTime = nowTime; }, delay - (nowTime - lastTime)); } }; }
14.类型判断函数 1 2 3 function getType(value ) { return Object . prototype.toString.call(value).slice(8 , -1 ).to LowerCase() ; }
15.call apply bind 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 Function.prototype.call = function (context, ...args ) { context = (context === undefined || context === null ) ? window : context context.__fn = this let result = context.__fn(...args) delete context.__fn return result } Function.prototype.apply = function (context, args) { context = (context === undefined || context === null ) ? window : context context.__fn = this let result = context.__fn(...args) delete context.__fn return result } Function.prototype.bind = function (context, ...args1 ) { context = (context === undefined || context === null ) ? window : context let _this = this return function (...args2 ) { context.__fn = _this let result = context.__fn(...[...args1, ...args2]) delete context.__fn return result } }
16.curry柯里化 1 2 3 4 5 6 7 8 9 10 11 function myCurrying (fn) { return function curried (...args ) { if (args.length >= fn.length) { return fn.apply(this , args); } else { return function (...args2 ) { return curried.apply(this , args.concat(args2)); }; } } }
17.AJAX请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const SERVER_URL = "/server" ; let xhr = new XMLHttpRequest(); xhr.open ("GET" , SERVER_URL, true ); xhr.onreadystatechange = function() { if (this .readyState !== 4 ) return ; if ((this .status >= 200 &&this .status<300 )|| this .status === 304 ) { handle(this .response); } else { console.error(this .statusText); } }; xhr.onerror = function() { console.error(this .statusText); }; xhr.responseType = "json" ; xhr.setRequestHeader("Accept" , "application/json" ); xhr.send(null );
18.Promise封装AJAX请求 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 getJSON (url ) { let promise = new Promise (function (resolve, reject ) { let xhr = new XMLHttpRequest (); xhr.open ("GET" , url, true ); xhr.onreadystatechange = function ( ) { if (this .readyState !== 4 ) return ; if ((this .status >= 200 &&this .status <300 )|| this .status === 304 ) { resolve (this .response ); } else { reject (new Error (this .statusText )); } }; xhr.onerror = function ( ) { reject (new Error (this .statusText )); }; xhr.responseType = "json" ; xhr.setRequestHeader ("Accept" , "application/json" ); xhr.send (null ); }); return promise; }
19.实现浅拷贝 浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。
(1)Object.assign() Object.assign()
是ES6中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法:Object.assign(target, source_1, ···)
,该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。
注意:
如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
因为null
和 undefined
不能转化为对象,所以第一个参数不能为null
或 undefined
,会报错。
1 2 3 4 5 6 let target = {a : 1 };let object2 = {b : 2 };let object3 = {c : 3 };Object .assign (target,object2,object3); console .log (target); 复制代码
(2)扩展运算符 使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法:let cloneObj = { ...obj };
1 2 3 4 5 6 7 8 9 let obj1 = {a :1 ,b :{c :1 }}let obj2 = {...obj1}; obj1.a = 2 ;console .log (obj1); console .log (obj2); obj1.b .c = 2 ;console .log (obj1); console .log (obj2); 复制代码
(3)数组方法实现数组浅拷贝 1)Array.prototype.slice
slice()
方法是JavaScript数组的一个方法,这个方法可以从已有数组中返回选定的元素:用法:array.slice(start, end)
,该方法不会改变原始数组。
该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。
1 2 3 4 let arr = [1 ,2 ,3 ,4 ];console .log (arr.slice ()); console .log (arr.slice () === arr); 复制代码
2)Array.prototype.concat
concat()
方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。
1 2 3 4 let arr = [1 ,2 ,3 ,4 ];console .log (arr.concat ()); console .log (arr.concat () === arr); 复制代码
(4)手写实现浅拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function shallowCopy (object ) { if (!object || typeof object !== "object" ) return ; let newObject = Array .isArray (object) ? [] : {}; for (let key in object) { if (object.hasOwnProperty (key)) { newObject[key] = object[key]; } } return newObject; }
20. 实现深拷贝
浅拷贝: 浅拷贝指的是将一个对象的属性值复制到另一个对象,如果有的属性的值为引用类型的话,那么会将这个引用的地址复制给对象,因此两个对象会有同一个引用类型的引用。浅拷贝可以使用 Object.assign 和展开运算符来实现。
深拷贝: 深拷贝相对浅拷贝而言,如果遇到属性值为引用类型的时候,它新建一个引用类型并将对应的值复制给它,因此对象获得的一个新的引用类型而不是一个原有类型的引用。深拷贝对于一些对象可以使用 JSON 的两个函数来实现,但是由于 JSON 的对象格式比 js 的对象格式更加严格,所以如果属性值里边出现函数或者 Symbol 类型的值时,会转换失败
(1)JSON.stringify()
JSON.parse(JSON.stringify(obj))
是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringify
将js
对象序列化(JSON字符串),再使用JSON.parse
来反序列化(还原)js
对象。
这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()
进行处理之后,都会消失。
1 2 3 4 5 6 7 8 9 10 11 let obj1 = { a : 0 , b : { c : 0 } };let obj2 = JSON .parse (JSON .stringify (obj1)); obj1.a = 1 ; obj1.b .c = 1 ;console .log (obj1); console .log (obj2); 复制代码
(2)函数库lodash的_.cloneDeep方法 该函数库也有提供_.cloneDeep用来做 Deep Copy
1 2 3 4 5 6 7 8 9 var _ = require ('lodash' );var obj1 = { a : 1 , b : { f : { g : 1 } }, c : [1 , 2 , 3 ] };var obj2 = _.cloneDeep (obj1);console .log (obj1.b .f === obj2.b .f ); 复制代码
(3)手写实现深拷贝函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function deepCopy (object ) { if (!object || typeof object !== "object" ) return ; let newObject = Array .isArray (object) ? [] : {}; for (let key in object) { if (object.hasOwnProperty (key)) { newObject[key] = typeof object[key] === "object" ? deepCopy (object[key]) : object[key]; } } return newObject; }
21.日期格式化函数 最简易版本的,注意 没有加0的版本 格式:年-月-日 时:分:秒
1 2 3 4 5 6 7 8 9 10 11 function formatTime ( ) { const time = new Date (); const year = time.getFullYear(); const month = time.getMonth() + 1 ; const day = time.getDate(); const hour = time.getHours(); const minute = time.getMinutes(); const seconds = time.getSeconds(); return `${year } -${month } -${day } ${hour } :${minute } :${seconds} ` ; }console .log (formatTime());
22.数组的乱序输出 主要的实现思路就是:
取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换。
第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行交换
按照上面的规律执行,直到遍历完成
1 2 3 4 5 6 var arr = [1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ];for (var i = 0 ; i < arr.length ; i++) { const randomIndex = Math .round (Math .random () * (arr.length - 1 - i)) + i; [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]; }console .log (arr)
23.数组的扁平化 (1)递归实现 普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let arr = [1 , [2 , [3 , 4 , 5 ]]];function flatten (arr ) { let result = []; for (let i = 0 ; i < arr.length ; i++) { if (Array .isArray (arr[i])) { result = result.concat (flatten (arr[i])); } else { result.push (arr[i]); } } return result; }flatten (arr); 复制代码
(2)reduce 函数迭代 从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:
1 2 3 4 5 6 7 8 let arr = [1 , [2 , [3 , 4 ]]];function flatten (arr ) { return arr.reduce (function (prev, next ){ return prev.concat (Array .isArray (next) ? flatten (next) : next) }, []) }console .log (flatten (arr)); 复制代码
(3)扩展运算符实现 这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:
1 2 3 4 5 6 7 8 9 let arr = [1 , [2 , [3 , 4 ]]];function flatten (arr ) { while (arr.some (item => Array .isArray (item))) { arr = [].concat (...arr); } return arr; }console .log (flatten (arr)); 复制代码
(4)split 和 toString 可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:
1 2 3 4 5 6 let arr = [1 , [2 , [3 , 4 ]]];function flatten (arr ) { return arr.toString ().split (',' ); }console .log (flatten (arr)); 复制代码
通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。
(5)ES6 中的 flat 我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])
其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:
1 2 3 4 5 6 let arr = [1 , [2 , [3 , 4 ]]];function flatten (arr ) { return arr.flat (Infinity ); }console .log (flatten (arr)); 复制代码
可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。 (6)正则和 JSON 方法 在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:
1 2 3 4 5 6 7 8 let arr = [1 , [2 , [3 , [4 , 5 ]]], 6 ];function flatten (arr ) { let str = JSON .stringify (arr); str = str.replace (/(\[|\])/g , '' ); str = '[' + str + ']' ; return JSON .parse (str); }console .log (flatten (arr));
24.数组去重 给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。
ES6方法(使用数据结构集合):
1 2 3 4 const array = [1 , 2 , 3 , 5 , 1 , 5 , 9 , 1 , 2 , 8 ];Array .from (new Set (array)); 复制代码
ES5方法:使用map存储不重复的数字(可以使用reduce代替for循环)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const array = [1 , 2 , 3 , 5 , 1 , 5 , 9 , 1 , 2 , 8 ];uniqueArray (array); function uniqueArray (array ) { let map = {}; let res = []; for (var i = 0 ; i < array.length ; i++) { if (!map.hasOwnProperty ([array[i]])) { map[array[i]] = 1 ; res.push (array[i]); } } return res; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const new Arr2 = []; arr.filter((item, index) =>{if (arr.indexOf(item) === index) {new Arr2 .push(item) } }) console.log(new Arr2 ); let new Arr3 = [];for (let i = 0 ; i<arr.length; i++) {if ( !new Arr3 .includes(arr[i]) ) {new Arr3 .push(arr[i]); } } console.log(new Arr3 );
25.Array.prototype.flat 1 2 3 4 5 6 7 8 9 10 11 12 Array .prototype .flat = function (deep = 1 ) { let res = [] deep-- for (const p of this ) { if (Array .isArray (p) && deep >= 0 ) { res = res.concat (p.flat (deep)) } else { res.push (p) } } return res }
26.Array.prototype.push 1 2 3 4 5 6 Array.prototype.myPush = function (...arg ) { for (let i = 0 ; i < arg.length; i++) { this [this .length] = arg[i]; } return this .length; };
27.Array.prototype.filter 1 2 3 4 5 6 7 Array .prototype .myFilter = function (callback ) { const res = []; for (let i = 0 ; i < this .length ; i++) { callback (this [i], i, this ) && res.push (this [i]); } return res; };
28.Array.prototype.map 1 2 3 4 5 6 7 Array .prototype .map = function (callback ) { const res = []; for (let i = 0 ; i < this .length ; i++) { res.push (callback (this [i], i, this )) } return res; }
29.String.prototype.repeat 1 2 3 4 5 6 7 8 9 10 11 12 13 String .prototype .repeat = function (n ) { let str = this ; let res = '' while (n) { res += str; n-- } return res }function repeat (s, n ) { return (n > 0 ) ? s.concat (repeat (s, --n)) : "" ; }
30.类数组转化为数组
通过 call 调用数组的 slice 方法来实现转换
1 Array .prototype .slice .call (arrayLike);
通过 call 调用数组的 splice 方法来实现转换
1 Array .prototype .splice .call (arrayLike, 0 );
通过 apply 调用数组的 concat 方法来实现转换
1 Array .prototype .concat .apply ([], arrayLike);
31.解析 URL Params 为对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function parseParam(url) { const paramsStr = /.+\?(.+)$/ .exec(url)[1 ]; // 将 ? 后面的字符串取出来 const paramsArr = paramsStr.split('&' ); // 将字符串以 & 分割后存到数组中 let paramsObj = {}; // 将 params 存到对象中 paramsArr.forEach(param => { if (/=/ .test(param)) { // 处理有 value 的参数 let [key, val] = param.split('=' ); // 分割 key 和 value val = decodeURIComponent(val); // 解码 if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果对象没有这个 key,创建 key 并设置值 paramsObj[key] = val; } } else { // 处理没有 value 的参数 paramsObj[param] = true; } }) return paramsObj; }
32.循环打印红黄绿 下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
三个亮灯函数:
1 2 3 4 5 6 7 8 9 10 function red ( ) { console .log ('red' ); }function green ( ) { console .log ('green' ); }function yellow ( ) { console .log ('yellow' ); } 复制代码
这道题复杂的地方在于需要“交替重复”亮灯 ,而不是“亮完一次”就结束了。
(1)用 callback 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const task = (timer, light, callback ) => { setTimeout (() => { if (light === 'red' ) { red () } else if (light === 'green' ) { green () } else if (light === 'yellow' ) { yellow () } callback () }, timer) }task (3000 , 'red' , () => { task (2000 , 'green' , () => { task (1000 , 'yellow' , Function .prototype ) }) }) 复制代码
这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢?
上面提到过递归,可以递归亮灯的一个周期:
1 2 3 4 5 6 7 8 9 const step = ( ) => { task (3000 , 'red' , () => { task (2000 , 'green' , () => { task (1000 , 'yellow' , step) }) }) }step () 复制代码
注意看黄灯亮的回调里又再次调用了 step 方法 以完成循环亮灯。
(2)用 promise 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const task = (timer, light ) => new Promise ((resolve, reject ) => { setTimeout (() => { if (light === 'red' ) { red () } else if (light === 'green' ) { green () } else if (light === 'yellow' ) { yellow () } resolve () }, timer) })const step = ( ) => { task (3000 , 'red' ) .then (() => task (2000 , 'green' )) .then (() => task (2100 , 'yellow' )) .then (step) }step () 复制代码
这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。
(3)用 async/await 实现 1 2 3 4 5 6 7 const taskRunner = async ( ) => { await task (3000 , 'red' ) await task (2000 , 'green' ) await task (2100 , 'yellow' ) taskRunner () }taskRunner ()
33.每隔一秒打印 1,2,3,4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (var i = 0 ; i < 5 ; i++) { (function (i ) { setTimeout (function ( ) { console .log (i); }, i * 1000 ); })(i); }for (let i = 0 ; i < 5 ; i++) { setTimeout (function ( ) { console .log (i); }, i * 1000 ); }
34.Promise实现图片的异步加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let imageAsync =(url )=>{ return new Promise ((resolve,reject )=> { let img = new Image (); img.src = url; img.οnlοad=()=> { console .log (`图片请求成功,此处进行通用操作` ); resolve (image); } img.οnerrοr=(err )=> { console .log (`失败,此处进行失败的通用操作` ); reject (err); } }) } imageAsync ("url" ).then (()=> { console .log ("加载成功" ); }).catch ((error )=> { console .log ("加载失败" ); })
35.发布-订阅模式 题目描述:实现一个发布订阅模式拥有 on emit once off 方法
实现代码如下:
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 class EventEmitter { constructor ( ) { this .events = {}; } on (type, callBack ) { if (!this .events [type]) { this .events [type] = [callBack]; } else { this .events [type].push (callBack); } } off (type, callBack ) { if (!this .events [type]) return ; this .events [type] = this .events [type].filter ((item ) => { return item !== callBack; }); } once (type, callBack ) { function fn ( ) { callBack (); this .off (type, fn); } this .on (type, fn); } emit (type, ...rest ) { this .events [type] && this .events [type].forEach ((fn ) => fn.apply (this , rest)); } }
36.封装异步的fetch,使用async await方式来使用 1 2 3 4 5 6 7 8 9 10 11 12 13 async function myFetchAsync (url, options ) { try { const response = await fetch (url, options); if (!response.ok ) { throw new Error (`${response.status} ${response.statusText} ` ); } return response; } catch (error) { console .error (error); throw error; } }
37.实现简单路由 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 Router(){ this .routes={} this .curUrl='' this .route=function(path,callback){ this .routes[path]=callback||function(){} } this .refresh=function(){ this .curUrl=location.hash.slice(1 )||'/' this .routes[this .curUrl]() } this .init =function(){ window.addEventListener('load' ,this .refresh.bind(this ),false ) window.addEventListener('hashchange' ,this .refresh.bind(this ),false ) } } let res=document.getElementById('div' ) let R=new Router() R.init () R.route('/' ,function(){ res.style.backgroundColor='pink' res.innerHTML='11111' })
38.斐波那契数列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function Fibonacci(n ,num =1,sum =1) { if (n<=2 ){ return sum; } return Fibonacci(n -1,sum ,num +sum ) ; }function feibo3(n) { let pre = 1 ; let cur = 1 ; for (let i = 2 ; i <= n; i++) { [pre , cur ] = [cur , cur + pre ] ; } return cur; }
39.使用 setTimeout 实现 setInterval 使用 setTimeout 递归调用来模拟 setInterval,确保了只有一个事件结束了,才会触发下一个定时器事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function mySetInterval(fn , timeout ) { var timer = { flag: true }; function interval() { if (timer.flag) { fn() ; setTimeout(interval , timeout ) ; } } setTimeout(interval , timeout ) ; return timer; }
40.jsonp JSONP 原理
定义好回调函数,比方说命名为 callback ,并将函数名作为 url 的参数;
添加 script 标签,指定的资源为目标域的方法,也就是上面的 url ;
后端接收 GET 请求,返回 callback(responseData)
格式数据,把要返回的数据 responseData 传到 callback()
中;
前端接收 javaScript 内容,执行了后端返回的 callback(responseData)
,这样就完成了一次前后端交互了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function addScript(src ) { const script = document.createElement('script ') ; script.src = src; script.type = "text/javascript" ; document.body.appendChild(script ) ; } addScript("http://xxx.xxx.com/xxx.js?callback=handleRes" ) ;function handleRes(res ) { console.log(res); } handleRes({a : 1, b : 2}) ;
41.判断对象是否存在循环引用 对象循环引用会导致垃圾回收机制无法正确回收这些对象,从而导致内存泄漏,使得程序消耗更多的内存,直到最终内存耗尽,导致程序崩溃。
具体来说,当出现循环引用的情况时,垃圾回收机制会出现一些问题。因为垃圾回收机制是通过遍历对象的引用关系来确定哪些对象不再被引用,从而将它们标记为垃圾并回收。但是,当对象之间存在循环引用时,垃圾回收机制无法确定哪些对象是垃圾,哪些对象还在使用中,从而导致无法正确回收垃圾对象,造成内存泄漏。
此外,对象循环引用也会影响程序的性能和稳定性。当内存不足时,程序会变得非常缓慢,甚至无法响应用户的操作,最终导致程序崩溃。
JSON.stringify
将对象转换为字符串时,会抛出异常。
这是因为在进行对象字符串化时,字符串化工具会尝试遍历整个对象,并将对象中的所有属性都转换为字符串。如果对象中存在循环引用,字符串化工具会陷入死循环,无法正常完成操作。为了防止这种情况发生,字符串化工具会检测对象中是否存在循环引用,如果存在,就会抛出异常,防止程序陷入死循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function hasCircularReference(obj ) { let hasCircular = false ; const seenObjects = new WeakSet() ; function detectCircularReference(obj ) { if (typeof obj === 'object ' && obj !== null) { if (seenObjects.has(obj)) { hasCircular = true ; return; } seenObjects.add(obj); for (const key in obj) { detectCircularReference(obj [key ]) ; } } } detectCircularReference(obj ) ; return hasCircular; }
42.继承 寄生组合式继承 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 function clone (parent, child) { child.prototype = Object .create (parent.prototype ); child.prototype .constructor = child; }function Parent6 ( ) { this .name = 'parent6' ; this .play = [1 , 2 , 3 ]; }Parent6 .prototype .getName = function ( ) { return this .name ; }function Child6 ( ) { Parent6 .call (this ); this .friends = 'child5' ; }clone (Parent6 , Child6 );Child6 .prototype .getFriends = function ( ) { return this .friends ; }let person6 = new Child6 ();console .log (person6); console .log (person6.getName ()); console .log (person6.getFriends ());
原型链继承 原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针
举个例子
1 2 3 4 5 6 7 8 9 function Parent ( ) { this .name = 'parent1' ; this .play = [1 , 2 , 3 ] } function Child ( ) { this .type = 'child2' ; } Child .prototype = new Parent (); console .log (new Child ())
上面代码看似没问题,实际存在潜在问题
1 2 3 4 var s1 = new Child ();var s2 = new Child (); s1.play .push (4 );console .log (s1.play , s2.play );
改变s1
的play
属性,会发现s2
也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的
构造函数继承 借助 call
调用Parent
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Parent ( ){ this .name = 'parent1' ; }Parent .prototype .getName = function ( ) { return this .name ; }function Child ( ){ Parent1 .call (this ); this .type = 'child' }let child = new Child ();console .log (child); console .log (child.getName ());
可以看到,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法
相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法
组合继承 前面我们讲到两种继承方式,各有优缺点。组合继承则将前两种方式继承起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function Parent3 () { this .name = 'parent3' ; this .play = [1 , 2 , 3 ]; }Parent3 .prototype .getName = function ( ) { return this .name ; }function Child3 ( ) { Parent3 .call (this ); this .type = 'child3' ; }Child3 .prototype = new Parent3 ();Child3 .prototype .constructor = Child3 ;var s3 = new Child3 ();var s4 = new Child3 (); s3.play .push (4 );console .log (s3.play , s4.play ); console .log (s3.getName ()); console .log (s4.getName ());
这种方式看起来就没什么问题,方式一和方式二的问题都解决了,但是从上面代码我们也可以看到Parent3
执行了两次,造成了多构造一次的性能开销
原型式继承 这里主要借助Object.create
方法实现普通对象的继承
同样举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let parent4 = { name : "parent4" , friends : ["p1" , "p2" , "p3" ], getName : function ( ) { return this .name ; } }; let person4 = Object .create (parent4); person4.name = "tom" ; person4.friends .push ("jerry" ); let person5 = Object .create (parent4); person5.friends .push ("lucy" ); console .log (person4.name ); console .log (person4.name === person4.getName ()); console .log (person5.name ); console .log (person4.friends ); console .log (person5.friends );
这种继承方式的缺点也很明显,因为Object.create
方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
寄生式继承 寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let parent5 = { name : "parent5" , friends : ["p1" , "p2" , "p3" ], getName : function ( ) { return this .name ; } };function clone (original ) { let clone = Object .create (original); clone.getFriends = function ( ) { return this .friends ; }; return clone; }let person5 = clone (parent5);console .log (person5.getName ()); console .log (person5.getFriends ());
其优缺点也很明显,跟上面讲的原型式继承一样
43.异步操作串并行执行 串行执行 reduce 方法的第一个参数是一个异步函数,它将前一个异步操作的 Promise 对象作为参数传递给下一个异步操作。reduce 方法的第二个参数是一个 Promise 对象,它的值为 undefined。在每次调用 reduce 方法时,都会等待前一个异步操作执行完毕之后再执行下一个异步操作。
1 2 3 4 5 6 7 8 async function sequentialAsync ( ) { const asyncFunctions = [asyncFunction1, asyncFunction2, asyncFunction3]; return asyncFunctions.reduce (async (previousPromise, asyncFunction) => { await previousPromise; return asyncFunction (); }, Promise .resolve ()); }
并行执行 1 2 3 4 5 6 7 8 9 async function parallelAsync ( ) { const [result1, result2, result3] = await Promise .all ([ asyncFunction1 (), asyncFunction2 (), asyncFunction3 (), ]); }
并行与串行的结合 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 class Scheduler { constructor (max) { this .max = max; this .count = 0 ; this .queue = []; } add(fn) { this .queue.push(fn) this .run() } run() { if (this .count >= this .max || this .queue.length === 0 ) return this .count++ Promise.resolve(this .queue.shift()()).finally (() => { this .count-- this .run() }) } }const sleep = time => new Promise(resolve => setTimeout(resolve, time));const scheduler = new Scheduler(2 );const addTask = (time, val ) => { scheduler.add(() => { return sleep(time).then(() => console.log(val )); }); }; addTask(1000 , '1' ); addTask(500 , '2' ); addTask(300 , '3' ); addTask(400 , '4' );
44.对象的扁平化 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 function flattenObject (obj, prefix = '' ) { let result = {}; for (let key in obj) { if (Object .prototype .hasOwnProperty .call (obj, key)) { const propName = prefix ? `${prefix} .${key} ` : key; if (typeof obj[key] === 'object' && obj[key] !== null ) { Object .assign (result, flattenObject (obj[key], propName)); } else { result[propName] = obj[key]; } } } return result; }const obj = { a : 1 , b : { c : 2 , d : { e : 3 , f : 4 } } }; { "a" : 1 , "b.c" : 2 , "b.d.e" : 3 , "b.d.f" : 4 }
45.版本号排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 arr.sort ((a, b ) => { let i = 0 ; const arr1 = a.split ("." ); const arr2 = b.split ("." ); while (true ) { const s1 = arr1[i]; const s2 = arr2[i]; i++; if (s1 === undefined || s2 === undefined ) { return arr1.length - arr2.length ; } if (s1 === s2) continue ; return s1 - s2; } });
46.DOM 节点输出为 JSON 格式 要将一个 DOM 节点输出为 JSON 格式,需要考虑节点的以下属性:
tagName - 节点的标签名。
attributes - 节点的属性,需要遍历所有属性并以键值对的形式存储。
childNodes - 节点的子节点,需要递归地对每个子节点进行相同的操作。
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 function nodeToJson (node ) { var obj = {}; obj['tagName' ] = node.tagName.toLowerCase(); if (node.attributes) { var attrs = {}; for (var i = 0 ; i < node.attributes.length ; i++) { var attr = node.attributes[i]; attrs[attr.nodeName] = attr.nodeValue; } obj['attributes' ] = attrs; } if (node.childNodes && node.childNodes.length > 0 ) { var children = []; for (var j = 0 ; j < node.childNodes.length ; j++) { var childNode = node.childNodes[j]; if (childNode.nodeType === Node.ELEMENT_NODE) { children.push (nodeToJson(childNode)); } } obj['childNodes' ] = children; } return obj; }var node = document.getElementById('my-element' );var json = nodeToJson(node);
47.JSON 格式输出为DOM 节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function _render(vnode ) { if (typeof vnode === "number" ) { vnode = String(vnode ) ; } if (typeof vnode === "string" ) { return document.createTextNode(vnode ) ; } const dom = document.createElement(vnode .tag ) ; if (vnode.attributes) { // 遍历属性 Object.keys(vnode.attributes) .forEach((key) => { const value = vnode.attributes[key ] ; dom.setAttribute(key , value ) ; }); } vnode.children.for Each((child ) => dom.appendChild(_render (child ) )); return dom; }
48.Array.prototype.reduce 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 使用方法 array.reduce (function (accumulator, currentValue, index, array ) { }, initialValue) 其中,参数 function (accumulator, currentValue, index, array ) 是一个回调函数,用于处理每个数组元素并更新累加器的值。回调函数接受四个参数:accumulator : 累加器的值,初始值为 initialValuecurrentValue : 当前数组元素的值index : 当前数组元素的索引array : 原始数组 实现方法Array .prototype .reduce = function (callback, initialValue ) { var accumulator = (initialValue !== undefined ) ? initialValue : this [0 ]; for (var i = (initialValue !== undefined ) ? 0 : 1 ; i < this .length ; i++) { accumulator = callback.call (undefined , accumulator, this [i], i, this ); } return accumulator; }
49.实现一个事件委托(易错) 事件委托这里就不阐述了,比如给li绑定点击事件
看错误版,(容易过的,看「面试官水平了」 )👇
1 2 3 4 5 6 7 ul.addEventListener ('click' , function (e ) { console .log (e,e.target ) if (e.target .tagName .toLowerCase () === 'li' ) { console .log ('打印' ) } }) 复制代码
「有个小bug,如果用户点击的是 li 里面的 span,就没法触发 fn,这显然不对」 👇
1 2 3 4 5 6 7 8 <ul id="xxx">下面的内容是子元素1 <li >li 内容>>> <span > 这是span 内容123 </span ></li > 下面的内容是子元素2 <li >li 内容>>> <span > 这是span 内容123 </span ></li > 下面的内容是子元素3 <li >li 内容>>> <span > 这是span 内容123 </span ></li > </ul > 复制代码
这样子的场景就是不对的,那我们看看高级版本👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function delegate(element, eventType, selector, fn) { element.addEventListener(eventType, e => { let el = e.target while (!el.matches(selector)) { if (element === el) { el = null break } el = el.parentNode } el && fn.call(el, e, el) },true) return element }
50.事件的代理 由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理。
事件委托的优点
只需要将同类元素的事件委托给父级或者更外级的元素,不需要给所有的元素都绑定事件,减少内存占用空间,提升性能。
动态新增的元素无需重新绑定事件
错误版:
1 2 3 4 5 ul.addEventListener('click ', function (e ) { if (e.target.tagName.to LowerCase() === 'li'){ fn() } })
正确版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function delegate (element, eventType, selector, fn) { element.addEventListener(eventType, e => { let el = e .target while (!el.matches(selector)) { if (element === el ) { el = null break } el = el .parentNode } el && fn.call (el , e , el ) }) return element }
51.大数相加 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function add(a ,b){ let maxLength = Math . max(a.length, b.length); a = a.padStart(maxLength , 0) ; b = b.padStart(maxLength , 0) ; let t = 0 ; let f = 0 ; let sum = "" ; for (let i=maxLength-1 ; i>=0 ; i--){ t = Number(a [i ]) + Number(b [i ]) + f; f = Math . floor(t/10 ); sum = t%10 + sum; } if (f!==0 ){ sum = '' + f + sum; } return sum; }
52.模板字符串解析功能 1 2 3 4 5 6 7 8 9 10 11 12 13 let template = '我是{{name}},年龄{{age}},性别{{sex}}' ;let data = { name : '姓名' , age : 18 }render (template, data); function render (template, data ) { let computed = template.replace (/\{\{(\w+)\}\}/g , function (match, key ) { return data[key]; }); return computed; }
53.列表转成树形结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function listToTree(list) { const map = {}; const roots = []; for (const item of list) { const id = item .id ; const parentId = item .parentId; map [id ] = { ...item , children: map [id ]?.children||[] }; if (parentId === 0 ) { roots.push(map [id ]); } else { if (!map [parentId]){ map [parentId]={children:[]}; } map [parentId].children.push(map [id ]); } } return roots; }
54.树形结构转成列表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function treeToList(data) { let res = [] const dfs = (tree) = > { tree.forEach((item) = > { if (item.children) { dfs(item.children) delete item.children } res.push(item) }) } dfs(data) return res }
55.lodash.get 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let obj = { foo : { bar : { baz : 'Hello World' } } };function get (source, path, defaultValue = undefined ) { const paths = path.split ("." ); let result = source; for (const p of paths) { if (result[p]===undefined ){ return undefined ; } result = result[p]; } return result === undefined ? defaultValue : result; }console .log (get (obj, 'foo.bar.baz' ));
56.将数字每千分位用逗号隔开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let format = n => { let num = n.toString() let decimals = '' num.indexOf ('.' ) > -1 ? decimals = '.' +num.split ('.' )[1 ] : decimals let len = num.length if (len <= 3 ) { return num } else { let remainder = len % 3 if (remainder > 0 ) { return num.slice (0 , remainder) + ',' + num.slice (remainder, len).match(/\d{3}/g ).join(',' ) + decimals } else { return num.slice (0 , len).match(/\d{3}/g ).join(',' ) + temp } } } console .log (format(12323.33 ));
57.双向数据绑定 响应式也可以这这个模式写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 双向数据绑定 let obj = {} let input = document.getElementById ('input' ) let span = document.getElementById ('span' ) Object.defineProperty (obj, 'text' , { configurable: true, enumerable: true, get () { console.log ('获取数据了' ) }, set (newVal) { console.log ('数据更新了' ) input .value = newVal span .innerHTML = newVal } })input .addEventListener ('keyup' , function (e) { obj.text = e.target .value })
58.设计模式 发布订阅模式 单例模式 保证一个类只能被实例一次,每次获取的时候,如果该类已经创建过实例则直接返回该实例,否则创建一个实例保存并返回。
1 2 3 4 5 6 7 8 9 10 11 12 class Singleton { constructor(name, age) { if (!Singleton.instance) { this .name = name this .age = age Singleton.instance = this } return Singleton.instance } } console.log(new Singleton ("Taobao" , 18 ) === new Singleton ("Baidu" , 15 ))
命令模式
命令模式中的命令指的是一个执行某些特定的事情的指令。
命令模式最常见的应用场景如:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时可以通过一种松耦合的方式来设计程序,使得请求发送者和请求接收者消除彼此之间的耦合关系。
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 class Receiver { execute ( ) { console .log ('接收者执行请求' ) } } class Command { constructor (receiver ) { this .receiver = receiver } execute () { console .log ('命令' ); this .receiver .execute () } }class Invoker { constructor (command ) { this .command = command } invoke ( ) { console .log ('开始' ) this .command .execute () } } const warehouse = new Receiver (); const order = new Command (warehouse); const client = new Invoker (order); client.invoke ()
策略模式 策略模式指的是定义一系列算法,把他们一个个封装起来,目的就是将算法的使用和算法的实现分离开来。同时它还可以用来封装一系列的规则,可以被替换使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 let strategies = { add: function (num) { return num + num; }, multiply: function (num) { return num * num; }, }; let calculateBonus = function (strategy, num) { return strategies[strategy](num) ; }; console.log(calculateBonus("add" , 3 )); console.log(calculateBonus("multiply" , 3 ));
观察者模式 定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使它们能够自动更新自己
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 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!' ); subject.removeObserver (observer1); subject.notify ('Goodbye world!' );
工厂模式 工厂模式是用来创建对象的常见设计模式,在不暴露创建对象的具体逻辑,而是将逻辑进行封装,那么它就可以被称为工厂。工厂模式又叫做静态工厂模式,由一个工厂对象决定创建某一个类的实例。
优点
调用者创建对象时只要知道其名称即可
扩展性高,如果要新增一个产品,直接扩展一个工厂类即可。
隐藏产品的具体实现,只关心产品的接口。
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 class DownJacket { production ( ){ console .log ('生产羽绒服' ) } }class Underwear { production ( ){ console .log ('生产内衣' ) } }class TShirt { production ( ){ console .log ('生产t恤' ) } }class clothingFactory { constructor ( ){ this .downJacket = DownJacket this .underwear = Underwear this .t_shirt = TShirt } getFactory (clothingType ){ const _production = new this [clothingType] return _production.production () } }const clothing = new clothingFactory () clothing.getFactory ('t_shirt' )
代理模式 例如:Proxy
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
装饰者模式 动态地给某个对象添加一些额外的职责。
在不改变原对象的基础上,通过对其进行包装扩展,使原有对象可以满足用户的更复杂需求,而不会影响从这个类中派生的其他对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Cellphone { create ( ) { console .log ('生成一个手机' ) } }class Decorator { constructor (cellphone ) { this .cellphone = cellphone } create ( ) { this .cellphone .create () this .createShell (cellphone) } createShell ( ) { console .log ('生成手机壳' ) } }let cellphone = new Cellphone () cellphone.create ()console .log ('------------' )let dec = new Decorator (cellphone) dec.create ()