面试第四篇
前端 js手撕代码 实现解析url 1 2 3 4 5 6 7 8 const url = "https://shanyue.tech?a=3&b=4&c=5" const qs = { a:3 ; b:4 ; c:5 ; }
实例.用正则表达式获取query部分,然后用split分割&、=,获取key、value,同一个key出现多次则设为数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function parse (url ) { const queryString = url.match(/\?([^/?#:]+)?/ )?.[1 ] if (!queryString) { return {} } queryObj = queryString.split('&' ).reduce((params,block )=> { const [_k,_v='' ]=block.split('=' ) const k = decodeURIComponent (_k); const v = decodeURIComponent (_v); if (params[k] !== undefined ){ params[k] = [].concat(params[k],v); }else { params[k] = v } return params },{}) return queryObj }
手写evenbus 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 class EventEmeitter { constructor () { this ._events = this ._events || new Map (); this ._maxListeners = this ._maxListeners || 10 ; } } EventEmeitter.prototype.emit = function (type, ...args ) { let handler; handler = this ._events.get(type); if (args.length > 0 ) { handler.apply(this , args); } else { handler.call(this ); } return true ; }; EventEmeitter.prototype.addListener = function (type, fn ) { if (!this ._events.get(type)) { this ._events.set(type, fn); } }; const emitter = new EventEmeitter();emitter.addListener('arson' , man => { console .log(`expel ${man} ` ); }); emitter.emit('arson' , 'low-end' );
https://juejin.cn/post/6844903587043082247
简易版
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 type Event = (data?: any ) => any class EventHub { cache: { [key: string]: Event[] } = {} on = (name: string, fn: Event ) => { this .cache[name] = this .cache[name] || [] this .cache[name].push(fn) } emit = (name: string, data?: any ) => { if (this .cache[name] === undefined ) return this .cache[name].forEach(fn => { fn(data) }) } off = (name: string, fn: Event ) => { if (this .cache[name] === undefined ) return this .cache[name] = this .cache[name].filter(f => f !== fn) } }
测试
1 2 3 4 5 6 7 8 9 10 11 import EventHub from './index' const eventHub = new EventHub()const f1=(s )=> { console .log("被调用" ,s) } eventHub.on('xxx' ,f1) eventHub.emit('xxx' ,) eventHub.off('xxx' ,f1)
js继承 原型式继承
实例
1 2 3 4 5 6 7 8 9 10 11 function Person (name ) { this .name = name; this .className = "person" } Person.prototype.getClassName = function ( ) { console .log(this .className) } function Man ( ) {} Man.prototype = new Person(); var man = new Man;
组合继承
实例
1 2 3 4 5 6 7 8 9 10 11 12 function Person (name ) { this .name = name || "default name" ; this .className = "person" } Person.prototype.getName = function ( ) { console .log(this .name) } function Man (name ) { Person.apply(this ,arguments ) } Man.prototype = new Person(); var man1 = new Man("Davin" )
寄生式继承
寄生式继承是指创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来增强对象,最后再像真的做了所有事情一样返回对象
寄生组合(分离组合)式继承
通过借用构造函数来继承属性,通过原型链的混成形式继承方法
1 2 3 4 5 6 7 8 9 10 11 12 function Person (name ) { this .name = name; this .className = 'person' } Person.prototype.getName = function ( ) { console .log(this .name) } function Man (name ) { Person.apply(this .arguments) } Man.prototype = Object .create(Person.prototype) var man1 = new Man("Davin" )
将二维数组转换为树结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function arrToTreeArr (arr = [] ) { let map = new Map (); for (let i = 0 ;i<arr.length;i++){ for (let j = 0 ;j<arr[i];j++){ let name = arr[i][j] let isRoot = j == 0 ; let hasItem = map.has(name); if (!hasItem){ map.set(name,{name,child :[],isRoot }) } if (!isRoot && !hasItem){ let item = map.get(name) let pName = arr[i][j-1 ] map.get(pName).child.push(item) } } } return [...map.values()].filter(item => item.isRoot) } arrToTreeArr(arr)
另一个方法
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 toTree (arr ) { const obj = {}; const res = []; for (let i = 0 ;i<arr.length;i++){ for (let j = 0 ;j<arr[i].length;j++){ const item = arr[i][j]; if (!obj[item]){ obj[item] = { name:item, child:[] } } if (j>0 ){ const parent = obj[arr[i][j-1 ]]; if (parent){ if (parent.child.indexOf(obj[item])<0 ){ parent.child.push(obj[item]); } } }else { if (res.indexOf(obj[item])<0 ){ res.push(obj[item]); } } } } return res; }
修改树结构中的值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function treeSelectDataTransformer <T extends Record <string , unknown >>( list: null | undefined | T[], ): treeItemType <T >[] { return (list || []).map(function mapValueAndTitle (item ) { const result = { title: item?.name || item?.groupName, label: item.name, id: item.id || item?.groupId, children: item.children, parentId: item?.parentId, } as any ; if (Array .isArray(result.children)) { result.children = result.children.map(mapValueAndTitle); } return result; }) as treeItemType<T>[]; }
根据id返回树结构中的树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function findTree <T extends Record <string , unknown >>( treeData: T[] | null | undefined , id: ID, ): treeItemType <T > { const temp: any [] = []; function mapTree (data: any , id: ID ) { for (let i = 0 ; i < data.length; i++) { const item = data[i]; if (item.id === id) { temp.push(item); mapTree(treeData, item.parentId); break ; } else { if (Array .isArray(item.children)) { mapTree(item.children, id); } } } } mapTree(treeData, id); return _.last(temp) as treeItemType<T>; }
Promise、Promise.all实现原理、手写promise、promise.all
适合多个异步调用函数,并且多个异步函数的调用的入参和结果都无必然联系,比如多个文件的上传或下载。
多个异步函数的执行只关注成功或失败结果。
Promise.all是挂载到Promise类实例上
返回的是一个Promise
需要遍历入参数组中的每一项,判断传入的是不是promise,如果是promise则执行then方法,然后将then方法中的成功回调的data返回,失败则reject
如果入参数组中有基本数值,则直接返回
通过计数器,来判断函数的执行结果
手写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 function Promise (fn ) { var state = PENDING; function fullfill (result ) { state = FuLFILLED; value = result; handlers.forEach(handle); handlers = null ; } function reject (error ) { state = REJECTED; value = error; handlers.forEach(handle); handlers = null ; } function resolve (result ) { try { var then = getThen(result); if (then){ doResolve(then.bind(result),resolve,reject) return ; } fulfill(result); } catch (e){ reject(e); } } this .then = function (onFulfilled,onRejected )z { var self = this ; return new Promise (function (resolve,reject ) { self.done(function (result ) { if (typeof onFullfilled === 'function' ){ try { return resolve(onFulfilled(result)); }catch (ex){ return reject(ex); } }else { return resolve(result); } },function (error ) { if (typeof onRejected === 'function' ){ try { return resolve(onRejected(error)); }catch (ex){ return reject(ex); } } else { return reject(es); } }); }); } this .catch = function (errorHandle ) { return this .then(null ,errorHandle); } }
手写promise.all
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Promise .all = function (iterator ) { let count = 0 let len = iterator.length let res = [] return new Promise ((resolve,reject )=> { for (let i in iterator){ Promise .resolve(iterator[i]) .then((data )=> { res[i] = data; if (++count === len){ resolve(res) } }) .catch(e => { reject(e) }) } }) }
手写promise.race
1 2 3 4 5 6 7 8 9 10 11 12 13 Promise .race = function (iterators ) { return new Promise ((resolve,reject )=> { for (const p of iterators){ Promise .resolve(p) .then((res )=> { resolve(res) }) .catch(e => { reject(e) }) } }) }
手写promise.any
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Promise .any = function (iterators ) { const promises = Array .from(iterators); const num = promises.length; const rejectedList = new Array (num); let rejectedNum = 0 ; return new Promise ((resolve, reject ) => { promises.forEach((promise, index ) => { Promise .resolve(promise) .then(value => resolve(value)) .catch(error => { rejectedList[index] = error; if (++rejectedNum === num) { reject(rejectedList); } }); }); }); };
手写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 26 27 28 29 const formatSettledResult = (success, value ) => success ? { status : "fulfilled" , value } : { status : "rejected" , reason : value }; Promise .allSettled = function (iterators ) { const promises = Array .from(iterators); const num = promises.length; const settledList = new Array (num); let settledNum = 0 ; return new Promise (resolve => { promises.forEach((promise, index ) => { Promise .resolve(promise) .then(value => { settledList[index] = formatSettledResult(true , value); if (++settledNum === num) { resolve(settledList); } }) .catch(error => { settledList[index] = formatSettledResult(false , error); if (++settledNum === num) { resolve(settledList); } }); }); }); };
Promise并发限制 使用promise.all能够确保所有调用的promise对象全部达到resolve状态执行then回调。
但是如果请求的promise对象太多,瞬间发出很多http请求,tcp连接数不足可能造成等待,或者堆积了无数调用栈导致内存溢出。
此时就对http连接数进行限制。
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 function loadImg (url ) { return new Promise ((resolve,reject )=> { const img = new Image(); img.onload = function ( ) { console .log("加载完成" ), resolve(); } img.onerror = reject; img.src = url; }) } function limitload (urls, handler,limit ) { const sequence = [].concat(urls); let promises = []; promises = sequence.splice(0 ,limit).map((url,index )=> { return handler(url).then(() => { return index }) }) (async function loop ( ) { let p = Promise .race(promises); for (let i = 0 ;i< sequence.length;i++){ p = p.then((res )=> { promises[res] = handler(sequence[i]).then(() => { return res }) return Promise .race(promises); }) } }) } limitLoad(urls,loadImg,3 );
项目中也可以直接使用npm包,比如async-pool,es6-promise-pool,p-limit。
实现一个lazy_man 题目:
实现一个LazyMan,可以按照以下方式调用: LazyMan(“Hank”)输出: Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出 Hi! This is Hank! //等待10秒.. Wake up after 10 Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出 Hi This is Hank! Eat dinner~ Eat supper~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出 //等待5秒 Wake up after 5 Hi This is Hank! Eat supper 以此类推。
考察知识点:闭包 ,事件轮询机制 ,链式调用 ,队列 ,ES6实现方式
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 class _LazyMan { constructor (name) { this .tasks = []; const task = () => { console .log(`Hi! This is ${name} ` ); this .next(); } this .tasks.push(task); setTimeout(() => { this .next(); }, 0 ); } next() { const task = this .tasks.shift(); task && task(); } sleep(time) { this ._sleepWrapper(time, false ); return this ; } sleepFirst(time) { this ._sleepWrapper(time, true ); return this ; } _sleepWrapper(time, first) { const task = () => { setTimeout(() => { console .log(`Wake up after ${time} ` ); this .next(); }, time * 1000 ) } if (first) { this .tasks.unshift(task); } else { this .tasks.push(task); } } eat(name) { const task = () => { console .log(`Eat ${name} ` ); this .next(); } this .tasks.push(task); return this ; } } function LazyMan (name ) { return new _LazyMan(name); }
如果用 ES5 来写要在维护 this
方面多写一些代码。
js队列实现 基本队列、优先队列和循环队列
基本队列的六个方法:
①向队列(尾部)中添加元素(enqueue)、②(从队列头部)删除元素(dequeue)、③查看队列头部的元素(front)、④查看队列是否为空(isEmpty)、⑤查看队列的长度(size)、⑥查看队列(print) 等 6 个方法
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 function Queue ( ) { var items = []; this .enqueue = function (element ) { items.push(element); } this .dequeue = function ( ) { return items.shift(); } this .front = function ( ) { return items[0 ]; } this .isEmpty = function ( ) { return items.length == 0 ; } this .size = function ( ) { return items.length; } this .print = function ( ) { return items.toString(); } }
优先队列
在优先队列中,元素的添加或者删除是基于优先级的。实现优先队列有两种方式:①优先添加,正常出列;②正常添加,优先出列、
优先添加,正常出列的例子
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 function PriorityQueue ( ) { var items = []; function QueueElement (element, priority ) { this .element = element; this .priority = priority; } this .enqueue = function (element, priority ) { var queueElement = new QueueElement(element, priority); if (this .isEmpty()) { items.push(queueElement); }else { var added = false ; for (var i = 0 ; i < items.length; i++){ if (queueElement.priority < items[i].priority) { items.splice(i, 0 , queueElement); added = true ; break ; } } if (!added){ items.push(queueElement); } } } this .isEmpty = function ( ) { return items.length == 0 ; } this .print = function ( ) { return items; } } var priorityQueue = new PriorityQueue();
循环队列的经典问题:
约瑟夫环问题:
一群孩子围成一圈,每次传递 n 个数,停下来时手里拿花的孩子被淘汰,直到队伍中只剩下一个孩子,即胜利者。
循环队列,每次循环的时候(从队列头部)弹出一个孩子,再把这个孩子加入到队列的尾部,循环 n 次,循环停止时弹出队列头部的孩子(被淘汰),直到队列中只剩下一个孩子。
基于基本队列实现循环队列
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 hotPotato (nameList, num ) { var queue = new Queue(); for (var i = 0 ; i < nameList.length; i++) { queue.enqueue(nameList[i]); } var eliminated = '' ; while (queue.size() > 1 ) { for (var i = 0 ; i < num; i++) { queue.enqueue(queue.dequeue()); } eliminated = queue.dequeue(); console .log(eliminated + '被淘汰' ); } return queue.dequeue(); } var names = ['dee' , 'death mask' , 'saga' , 'mu' , 'alexis' ]; var winner = hotPotato(names, 7 ); console .log('胜利者是' + winner);
基于generator实现async/await 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function asyncToGenerator (generatorFunc ) { return function ( ) { const gen = generatorFunc.apply(this , arguments ) 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" ) }) } }
版本排序 1 2 3 4 5 6 7 8 9 10 11 12 13 function sortVersion (versions ) { if (!versions || !versions.length) return []; const result = versions.sort((a, b ) => { const arrA = a.split('.' ), arrB = b.split('.' ); const length = Math .max(a.length, b.length); for (let i = 0 ; i < length; i++) { const x = Number (arrA[i] || 0 ); const y = Number (arrB[i] || 0 ); if (x - y !== 0 ) return x - y; } }); return result; }
大数相加 补0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function add (str1,str2 ) { let maxLength = Math .max(str1.length,str2.length); var a = str1.padStart(maxLength,0 ); var b = str2.padStart(maxLength,0 ); let t = 0 ; let f = 0 ; let sum = '' ; for (let i = maxLength -1 ;i >= 0 ;i--){ t = parseInt (a[i]) + parseInt (b[i]) + f; f = Math .floor(t / 10 ); sum = t % 10 + sum; } if (f == 1 ){ sum = "1" + sum; } return sum }
json下划线与驼峰格式切换格式 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 function underline2Hump (s ) { return s.replace(/_(\w)/g , function (all, letter ) { return letter.toUpperCase() }) } function hump2Underline (s ) { return s.replace(/([A-Z])/g , '_$1' ).toLowerCase() } obj = JSONparse(ojb) function jsonToHump (obj ) { if (obj instanceof Array ) { obj.forEach(function (v, i ) { jsonToHump(v) }) } else if (obj instanceof Object ) { Object .keys(obj).forEach(function (key ) { var newKey = underline2Hump(key) if (newKey !== key) { obj[newKey] = obj[key] delete obj[key] } jsonToHump(obj[newKey]) }) } } function jsonToUnderline (obj ) { if (obj instanceof Array ) { obj.forEach(function (v, i ) { jsonToUnderline(v) }) } else if (obj instanceof Object ) { Object .keys(obj).forEach(function (key ) { var newKey = hump2Underline(key) if (newKey !== key) { obj[newKey] = obj[key] delete obj[key] } jsonToUnderline(obj[newKey]) }) } }
预定问题 选择最低价格预定,如果价格相同,优先选择时间
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 105 106 107 108 const firstbook = [ { name: 'a' time: '08:00' }, { name: 'b' , time: '12:15' } ] const secondbook = [ { name: 'c' , time: '12:00' }, { name: 'd' , time: '15:25' } ] const bookprice = { 'man' : { 'normal' :{ 'a' : 1 , 'b' : 2 , 'c' : 3 , 'd' : 4 , }, 'vip' : { 'a' : 2 , 'b' : 1 , 'c' : 3 , 'd' : 4 , } }, 'woman' : { 'normal' :{ 'a' : 1 , 'b' : 2 , 'c' : 3 , 'd' : 4 , }, 'vip' : { 'a' : 2 , 'b' : 1 , 'c' : 3 , 'd' : 4 , } } } function judgeDatetype (date ) { if (date == 'SUN' || date == 'SAT' ){ return 'WEEKENDS' }else { return 'WEEKDAYS' } } function durationTotime (clock,standard ) { const clockarray = clock.split(':' ) const standardarray = standard.split(':' ) moment.duration('01:01:01' ).as('seconds' ) moment({h :hours, m :minutes, s :seconds}).format('HH:mm:ss' ) return Math .abs(new Date ().setUTCHours(clockarray[0 ],clockarray[1 ]) - new Date ().setUTCHours(standardarray[0 ],standardarray[1 ]))/1000 ; } function sorted (array ) { return array.sort(function (a,b ) { if (a.price != b.price) { return a.price - b.price }else { return a.duration - b.duration } }) } function bestbook (type,time,book ) { const date = time.slice(0 ,8 ) const dateType = judgeDateType(time.replace(date,'' )) book.forEach(element => { element.duration = durationTotime(element.time,'12:00' ) element.price = bookprice[type][dateType][element.name] }) return sorted(book) } function bookPlan (type,gotime,backtime ) { const sortedgobook = bestbook(type,gotime,firstbook); const sortedbackbook = bestbook(type,backtime,secondbook); console .log(sortedgobook[0 ].name) console .log(sortedbackbook[0 ].name) return [sortedgobook[0 ].name,sortedbackbook[0 ].name] } function isDuplicate (array ) { const set = new Set(array) return set .size !== array.length } function findKey(obj,value, compare= (a,b) => a === b){ return Object .keys(obj).find(k => compare(obj[k],value)) }
两数平均值(无穷大)
数组根据某属性去重 1 2 3 4 5 6 7 function arrayUnique (arr,property ) { let hash = {}; return arr.reduce(function (item,next ) { hash[next[property]]? '' :hash[next[property]] = true && item.push(next); return item; },[]); }
数组根据某属性去重,保留后面的 for循环从后往前
一维数组变二维数组 转为子数组长度为n的二维数组,push方法,
1 2 3 4 5 6 7 8 9 10 11 function conversion (arr,n ) { let len = arr.length let lineNum = len % n === 0 ? len / n : Math .floor(len / n + 1 ) let res = [] for (let i = 0 ; i < lineNum; i++) { let temp = baseArray.slice(i * n, i * n + n) res.push(temp) } return res }
reduce方法
1 2 3 4 5 6 7 8 9 10 11 12 function oneTransTwo (source, num ) { return source.reduce((v, item, index ) => { let r = null ; if (index % num === 0 ) { r = [...v, [JSON .parse(JSON .stringify(item))]]; } else { v[v.length - 1 ].push(item); r = v; } return r; }, []); };
object-fit各属性实现 object-fit总共有fill、contain、cover、none、scale-down五种方式
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 function cover (containerSize, elementSize ) { const containerRatio = containerSize.width / containerSize.height; const elementRatio = elementSize.width / elementSize.height; let width, height if (containerRatio > elementRatio) { width = containerSize.width; height = containerSize.width / elementRadio } else { width = containerSize.height * elementRadio; height = containerSize.height; } return { width, height } } function contain (containerSize, elementSize ) { const containerRatio = containerSize.width / containerSize.height; const elementRatio = elementSize.width / elementSize.height; let width, height if (containerRatio > elementRatio) { width = containerSize.width; height = containerSize.width / elementRadio } else { width = containerSize.height * elementRadio; height = containerSize.height; } return { width, height } } function fill (containerSize ) { return containerSize } function none (elementSize ) { return elementSize } function scaleDown (containerSize, elementSize ) { if (elementSize.width > containerSize.width || elementSize.height > containerSize.height) { return contain(containerSize, elementSize) } else { return none(containerSize) } }
Https://xiaojun1994.top/posts/a97a42fc.html
js执行计算密集型任务 根据w3c性能小组,超过50ms的任务就是长任务
由于js是单线程运行,并且js引擎与UI渲染引擎互斥,所以在运行计算密集型任务时,容易造成页面假死的状态。虽然我们可以将任务放到任务队列中,通过异步的方式执行,但这并不能改变js执行时的本质
为了改变这种问题,可以使用一些手段避免
1.使用web-worker执行
2.使用时间分片进行任务分割
时间分片的核心思想是,如果任务不能在不影响用户体验的时间内执行完成,为了不阻塞主线程,这个任务应该让出主线程的控制权,以使浏览器可以处理其他任务,让出控制权意味着停止执行当前任务,让浏览器执行其他任务,随后再回来执行没有执行完的任务。
所以时间分片的目的是为了不阻塞主线程,而实现目的的技术手段是将任务拆分为很多个不超过50ms的小任务分散在宏任务队列中执行
使用requestAnimationFrame和requestIdleCallback。由系统本身去调度异步执行
3.大量渲染DOM使用documentFragments
图片懒加载 1 2 3 4 5 6 7 8 let imgList = [...document.querySelectorAll('img' )];let length = imgList.lengthconst imgLazyLoad = (function ( ) { let count = 0 ; return })
js实现LRU 数组实现
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 let LRU = function (capacity ) { this .capacity = capacity; this .cache = []; } LRU.prototype.get = function (key ) { let index = this .cache.findIndex((item ) => item.key === key); if (key === -1 ){ return -1 ; } let value = this .cache[index].value; this .cache.splice(index,1 ); this .cache.unshift({ key, value, }); return value; } LRU.prototype.put = function (key,value ) { let index = this .cache.findIndex((item ) => item.key === key); if (index>-1 ){ this .cache.splice(index,1 ) }else if (this .cache.length >= this .capacity){ this .cache.pop() } this .cache.unshift({key, value}) }
map数据结构实现
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 var LRUCache = function (capacity ) { this .cache = new Map (); this .capacity = capacity; }; LRUCache.prototype.get = function (key ) { if (this .cache.has(key)) { let temp = this .cache.get(key); this .cache.delete(key); this .cache.set(key, temp); return temp; } return -1 ; }; LRUCache.prototype.put = function (key, value ) { if (this .cache.has(key)) { this .cache.delete(key); } else if (this .cache.size >= this .capacity) { this .cache.delete(this .cache.keys().next().value); } this .cache.set(key, value); };
js实现正则表达式通配符 get/post请求实现下载文件 下载文件可以通过get请求或者post请求
get请求直接创建一个a标签
1 2 3 4 5 var blob=new Blob([result]);var link=document .createElement('a' );link.href=window .URL.createObjectURL(blob); link.download="myFileName.txt" ; link.click();
post请求的设置
1.后端设置响应头
Content-disposition: attachment; filename=数据报表.xlsx(表示会直接下载文件,文件名为‘数据报表’)
Content-Type:application/octet-stream
2.写好响应函数
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 axios({ method: 'post' , url: '/api/main/manage/memberLogAttendance/findPage' , responseType: 'arraybuffer' , data: para, headers: { token: this .token } }).then(res => { console .log('111' ) this .download(res.data) console .log(err) }) download(data) { if (!data) { return } const url = window .URL.createObjectURL(new Blob([data])) let link = document .createElement('a' ) link.style.display = 'none' link.href = url link.setAttribute('download' , '考勤统计.xlsx' ) document .body.appendChild(link) link.click() URL.revokeObjectURL(link.href) document .body.removeChild(link) link = null },
Node 定时堵塞线程 Node中常常会遇到需要延迟的任务,Java中有类似Thread.sleep()实现,在Nodejs中有以下方式实现
1.循环空转(不建议使用)
1 2 const start = new Date ();while (new Date () - start < 2000 ){}
2.Promise+定时器实现sleep
通过Promise+定时器延时执行函数setTimeout的链式依赖实现,本质是创建一个新的Promise对象,待到定时器时间到了执行resolve函数这是then才会执行。这里Nodejs是没有睡眠的,事件循环和V8都是正常运行的,这也是目前比较常用的方式
1 const sleep = ms => new Promise (resolve => setTimeout(resolve,ms));
3.Node中还能通过util模块提供的promisify方法实现,一种快捷方式
1 2 const { promisify } = require ("util" );const sleep = promisify(setTimeout);
libuv的执行流程 libuv是一个跨平台异步IO库。因为Nodejs是单线程的,作为服务器,他涉及到IO,而IO是会阻塞的,从而影响性能。所以Nodejs把IO操作交给libuv,保证主线程可以继续处理其他事情。Libuv主要是利用系统提供的事件驱动模块解决网络异步IO,利用线程池解决文件IO。另外还实现了定时器,对进程,线程等使用进行了封装。
libuv执行流程:
1.初始化libuv loop,新建一个uv_loop_t* loop。loop中保存了各个阶段对应的数据结构。
2.初始化tcp,建立、绑定、监听
3.设置客户端连接后的回调函数,再设置接收到客户端数据之后的回调函数
4.执行uv_run函数进入死循环。
5.libuv在每一轮循环里处理各个阶段。
Node的启动流程 1 注册内置c++模块(通过process.binding函数使用内置c++模块)。
2 新建process的c++对象,设置一系列属性(binding),然后挂载到全局。
3 执行bootstrap_node.js,初始化和挂载nextTick,setTimeout等函数,然后加载用户js,编译执行。
4 调用libuv开始事件循环。
1.nodejs启动的时候注册了check阶段的一个c++层回调是CheckImmediate,该函数再执行js回调processImmediate
2 用户调用setImmediate,生成一个节点插到双向链表。返回。
3 uv_run在check阶段。执行回调。setImmediate和setTimeout的关系这两个其实没什么关系,对应的阶段也不一样。
cluster模块原理 cluster可以以master-worker模式启动多个应用实例。
特点:端口只占用一个。
对于端口只用一个,cluster源码中有cluster.getServer方法
该方法向master进程注册worker。若master进程第一次接收到监听此端口下的worker,则起一个内部TCP服务器,来承担监听该端口的职责,并在master中记录worker
Hack掉worker进程中的listen方法中的监听端口的服务,使其不再承担该责任
cluster默认的复杂均衡策略为轮训
master是如何将接收的请求传递至worker中进行处理然后响应?
所有请求先统一经过内部TCP服务器
在内部TCP服务器的请求处理逻辑中,由负载均衡挑选出一个worker进程,将其发送一个newcon内部消息,随消息发送客户端句柄
Worker进程接收到此内部消息,根据客户端句柄创建socket实例,执行具体业务逻辑
Node高并发原理 Nodejs利用单线程模型省去了系统维护和切换多进程/线程的开销,同时多路复用的I/O模型可以让Nodejs的单线程不会堵塞在某一个连接上。在高并发场景下,nodejs应用只需要创建和管理多个客户端连接对应的socket描述符而不需要创建对应的线程/进程,系统开销大大减少,所以能同时处理更多的客户端连接
Nodejs并不能提升底层的真正I/O操作的效率,如果底层I/O成为系统的性能瓶颈,Nodejs依然无法解决。在这种情况下, Nodejs依然可以接收高并发请求,但如果需要处理大量慢IO操作,比如读写磁盘等,仍然可能造成系统过载,因此并不能简单地通过单线程+非阻塞IO模型来解决
CPU密集型应用可能会让nodejs的单线程模型成为性能瓶颈
Nodejs适合高并发处理少量业务逻辑或者快I/O
express与koa比较 koa使用async-await执行异步,并使用ctx上下文执行req和response
express使用传统的call back回调函数,和node原生的req和res
中间件响应机制
koa在中间件处理完成后的最外层响应,express是立即响应
npm install安装机制和流程 npm安装机制:
1.发出npm install命令
2.查询node_modules目录之中是否已经存在指定模块,若存在,不再重新安装
若不存在,npm 向 registry 查询模块压缩包的网址下载压缩包,存放在根目录下的.npm目录里,并解压压缩包到当前项目的node_modules目录
npm实现原理:
输入 npm install 命令并敲下回车后,会经历如下几个阶段:
1.执行工程自身preinstall。如果工程定义了preinstall钩子会被执行。
2.确定首层依赖模块。dependencies 和 devDependencies 属性中直接指定的模块,工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。
3.获取模块。
获取模块是一个递归的过程,分为以下几步:
获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semantic version(semver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如 packaeg.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。
获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载
查找该模块依赖,如果有依赖则回到第1步,如果没有则停止。
4.模块扁平化。上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。
从 npm3 开始默认加入了一个 dedupe 的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块 时,则将其丢弃。
重复模块 的定义,它指的是模块名相同 且 semver 兼容。\ 每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个*兼容* 版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。
5.安装模块。更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。
6.执行工程自身生命周期。当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。
7.更新或生成版本描述文件,npm install过程完成
--legacy-peer-deps
:安装时忽略所有 peerDependencies,采用 npm 版本 4 到版本 6 的样式。
--strict-peer-deps
:在遇到任何冲突的 peerDependencies 时失败并中止安装过程。默认情况下,npm 只会因根项目直接依赖导致的 peerDependencies 冲突而崩溃。
package-lock.json的作用 package-lock.json的官方定义:
package-lock.json它会在npm更改node_modules目录树或者package.json时自动生成的,它准确的描述了当前项目npm包的依赖树,并且在随后的安装中会根据package-lock.json来安装,保证是相同的一个依赖树,不考虑这个过程中是否有某个依赖有小版本的更新。
如果你有浏览它,会发现它长得类似package.json的依赖,但是比它复杂多了。package-lock.json是一个包含你所有依赖的巨大列表, 它包含明确的版本号, 依赖的获取地址, 一个用于验证完整性和正确性的哈希值, 以及这个依赖本身所需要的依赖
生成:
默认,当我们在一个项目中npm install时候(前提该项目有package.json文件),安装完成后,会自动生成一个package-lock.json文件(位置和之前的package.json文件同级)。该文件里面记录了package.json依赖的模块,以及依赖的依赖。并且给每个依赖标明了版本, 获取地址和哈希值, 使得每次安装都会出现相同的结果. 不管你在什么机器上面或什么时候安装。
当我们下次再npm install时候,npm发现如果项目中有package-lock.json文件,会根据package-lock.json里的内容来处理和安装依赖而不再根据package.json.
没有package-lock.json带来的问题
执行npm init
后,安装express:npm install express — save
。我撰文时express最高版本是4.15.4。所以“express”: “^4.15.4”被写进package.json里,依赖也已经装好了。不过第二天,可能express的维护者发布了一个bugfix版本,所以最新版本又变成了4.15.5。如果这时我同事clone了这个项目,并且执行npm install
,因为4.15.5的大版本号没变,所以他装的版本就会是最新的4.15.5。我们都安装了express,不过是不同的版本。按道理讲他们是互相兼容的,但是可能好巧不巧,这个被修复的bugfix刚好就影响到我们使用的功能了,那么我们分别运行自己的项目时就会出现不同的结果。这是个问题!
npm5.x.x以前,package.json是项目的唯一真理来源。package.json说啥就是啥!npm使用者喜欢并习惯仅维护package.json。但是,当package-lock出现后,它和很多人期望的背道而驰了!如果有同一份package和package-lock,package.json的修改并不会影响到package-lock。
于是就出现了更改package.json后,安装未生效的情况,需要移除package-lock再生成一份,由此引发了很多争论。可以从这份有趣的reop时间线 上看出端倪。有的人觉得还是得以package.json为准,有的人又觉得既然package-lock被创造出来了,那就以新的为准呗。最后这份争论以PR#17508 划上句号。npm决定,如果package.json上发生过改动,安装时package.json的权重就会超过package-lock 。这个决定随着npm v5.1.0发布,自2017年7月5日起即日执行~