说明 给自己过知识点看的,查漏补缺,建议看原作。前端内参
es新特性 es7 1 2 3 4 5 6 7 8 9 10 11 let arr = [1 ,2 ,NaN ];console .log (arr.includes (NaN )); console .log (arr.indexOf (NaN ) != -1 ) console .log (arr.includes (1 )); console .log (2 **3 )
es8 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 let obj1 = {x : 'xxx' , y : 'yyy' }Object .values (obj1)let arr1 = ['e' , 's' , '8' ]Object .values (arr1)let obj2 = {10 : 'xxx' , 2 : 'yyy' , 1 : 'zzz' }Object .values (obj2)Object .entries (obj1); Object .entries (arr1); Object .entries (obj2); Object .entries ('es8' ); for (const [key, value] of Object .entries ('es8' )){ console .log (key, value); } const values = Object .entries ('es8' ).map (([k, v] ) => v); Object .entries ('es8' ).forEach (([key, value] ) => { console .log (key, value); }); 'es8' .padStart (7 , 0 )'es8' .padEnd (7 , 0 )let obj3 = { get es8 (){} } Object .getOwnPropertyDescriptors (obj3)
es9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 async function func (array ) { for await (let i of array) { someFunc (i); } } let s = `\u{54}` console .log (s); let str = String .raw `\u{54}` ; console .log (str);
es10
es11 1 2 3 4 5 6 7 8 9 10 11 12 let second = obj?.first ?.second ;let a = 0 ; let v = a ?? "some value" ;console .log (v);
函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (function (i ){ console .log ("函数名为" +func.name +",第" +i+"次调用" ) if (i<3 ){ arguments .callee (++i); } })(1 );
this
this的指向,是在函数被调用的时候确定的,也就是执行上下文被创建时确定的。
this 的指向和函数声明的位置没有任何关系,只取决于函数的调用位置(也即由谁、在什么地方调用这个函数)。
正因为在执行上下文的创建阶段this的指向就已经被确定了,在执行阶段this指向不可再被更改。
this指向最靠近被调用函数的对象。
this显式指向:call、apply、 bind。
具体怎么判断好呢?
函数是否在new 中被调用(new 操作符指向),如果是的话,this 绑定的是新创建的对象。
函数是否被call、apply、bind 改变过this指向,如果是的话,this 绑定的是改变后的对象。
函数是否被当做某个对象的方法而调用(隐式指向)?如果是的话,this指向的是这个对象。
若以上都不是的话,使用默认绑定。
null或者undefined作为this指向的对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认指向规则。
this 默认指向window,但是严格模式下this指向undefined。隐式指向之隐式丢失
函数是否被bind改变过this指向,如果是的话,this 绑定的是改变后的对象。
1 2 3 4 5 6 7 8 9 10 function func ( ) { console .log (this .a ); } var a = 2 ;var o = { a : 3 , func : func };var p = { a : 4 };o.func (); (p.func = o.func )();
箭头函数的this指向外部函数的this。被绑定之后无法修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function func ( ) { return a => { console .log (this .a ); }; } var obj1 = { a : 2 }; var obj2 = { a : 3 }; var bar = func.call (obj1);bar.call (obj2);
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 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 func.call (thisArg, param1, param2, ...) func.apply (thisArg, [param1,param2,...]) func.bind (thisArg, param1, param2, ...) let domNodes = Array .prototype .slice .call (document .getElementsByTagName ("h1" )); function isType (data, type ) { const typeObj = { "[object String]" : "string" , "[object Number]" : "number" , "[object Boolean]" : "boolean" , "[object Null]" : "null" , "[object Undefined]" : "undefined" , "[object Object]" : "object" , "[object Array]" : "array" , "[object Function]" : "function" , "[object Date]" : "date" , "[object RegExp]" : "regExp" , "[object Map]" : "map" , "[object Set]" : "set" , "[object HTMLDivElement]" : "dom" , "[object WeakMap]" : "weakMap" , "[object Window]" : "window" , "[object Error]" : "error" , "[object Arguments]" : "arguments" }; let name = Object .prototype .toString .call (data); let typeName = typeObj[name] || "未知类型" ; return typeName === type; } console .log ( isType ({}, "object" ), isType ([], "array" ), isType (new Date (), "object" ), isType (new Date (), "date" ) ); var arrayLike = { 0 : "OB" , 1 : "Koro1" , length : 2 }; Array .prototype .push .call (arrayLike, "添加数组项1" , "添加数组项2" );console .log (arrayLike);const arr = [15 , 6 , 12 , 13 , 16 ];const max = Math .max .apply (Math , arr); const min = Math .min .apply (Math , arr);
call、apply应该用哪个? call,apply的效果完全一样,它们的区别也在于 参数数量/顺序确定就用call,参数数量/顺序不确定的话就用apply。 考虑可读性:参数数量不多就用call,参数数量比较多的话,把参数整合成数组,使用apply。 参数集合已经是一个数组的情况,用apply,比如上文的获取数组最大值/最小值。
插一嘴: ** 页面通信,一般传递回调函数的时候会容易丢失this的值。
手写call、bind、apply 实现apply/call/bind apply 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 Function .prototype .myApply = function (thisArg ) { if (thisArg === null || thisArg === undefined ) { thisArg = window ; } else { thisArg = Object (thisArg); } function isArrayLike (o ) { if ( o && typeof o === "object" && isFinite (o.length ) && o.length >= 0 && o.length === Math .floor (o.length ) && o.length < 4294967296 ) return true ; else return false ; } const specialMethod = Symbol ("anything" ); thisArg[specialMethod] = this ; let args = arguments [1 ]; let result; if (args) { if (!Array .isArray (args) && !isArrayLike (args)) { throw new TypeError ( "第二个参数既不为数组,也不为类数组对象。抛出错误" ); } else { args = Array .from (args); result = thisArg[specialMethod](...args); } } else { result = thisArg[specialMethod](); } delete thisArg[specialMethod]; return result; }; Function .prototype .apply = function (thisArg, argsArray ){ if (typeof this !== 'function' ){ throw new TypeError ('Function.prototype.apply called on non-function' ) } if (thisArg === undefined || thisArg === null ){ thisArg = window ; }else { thisArg = Object (thisArg) } const func = Symbol ('func' ); thisArg[func] = this ; let result; if (argArray && typeof argArray === 'obejct' && 'length' in argsArray){ result = thisArg[func](...Array .from (argsArray)) } else if (argsArray && undefined || argsArray === null ){ result = thisArg[func]() } else { throw new TypeError ('argsArray must be an array-like object' ) } delete thisArg (func) return result; }
call 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 Function .prototype .call = function (thisArg, ...argsArray ){ if (typeof this !== 'function' ){ throw new TypeError ('function.prototype.call called on non-function' ) } if (thisArg === undefined || thisArg === null ){ thisArg = window ; } else { thisArg = Object (thisArg) } const func = Symbol ('func' ); thisArg[func] = this ; let result; if (argsArray.length ){ result = thisArg[func](...argsArray) }else { result = thisArg[func]() } delete thisArg[func]; return result; }
bind 常见面试题 1 2 3 4 5 6 for (var i=0 ;i<=5 ;i++){ setTimeout (function (i ){ console .log (i) }.bind (null , i), i*1000 ) }
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 Function .prototype .bind = function (thisArg, ...argsArray ){ if (typeof this !== 'function' ){ throw new TypeError ('function.prototype.bind called on non-function' ) }; if (thisArg === undefined || thisArg === null ){ thisArg = window ; }else { thisArg = Object (thisArg) } const func = this ; const bound = function (...boundArgsArray ){ let isNew = false ; try { isNew = this instanceof func; }catch (e){ } return func.apply (isNew ? this : thisArg, argsArray.concat (boundArgsArray)) } const Empty = function ( ){} Empty .prototype = this .prototype ; bound.prototype = new Empty (); return bound; } Function .prototype .myBind = function (objThis, ...params ) { const thisFn = this ; let funcForBind = function (...secondParams ) { const isNew = this instanceof funcForBind; const thisArg = isNew ? this : Object (objThis); return thisFn.call (thisArg, ...params, ...secondParams); }; funcForBind.prototype = Object .create (thisFn.prototype ); return funcForBind; }; Function .prototype .myBind1 = function (thisArg, ...args ){ if (typeof this !== "function" ){ throw new Error ("is not callable" ) } if (thisArg === undefined || thisArg === null ){ thisArg = window } else { thisArg = Object (thisArg) } let funcSym = this let functionForBind = function ( ){ return thisArg.apply (funcSym, ...args, ...arguments ) } functionForBind.prototype = Object .create (thisArg.prototype ) return functionForBind }
javascript手写call,apply,bind函数,尤其是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 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 Function .prototype .myCall = function (thisArg, ...arr ){ if (thisArg === null || thisArg === undefined ){ thisArg = window ; } else { thisArg = Object (thisArg); } const specialMethod = Symbol ('anything' ); thisArg[specialMethod] = this ; let result = thisArg[specialMethod](...arr); delete thisArg[specialMethod]; return result; }; let obj = { name : "coffe1891" }; function func ( ){ console .log (this .name ) } func.myCall (obj) Function .prototype .myApply = function (thisArg ){ if (thisArg === null || thisArg === undefined ){ thisArg = window ; } else { thisArg = Object (thisArg) } function isArrayLike (obj ){ if (o && typeof o === "object" && isFinite (o.length ) && o.length >= 0 && o.length === Math .floor (o.length ) && o.length < 4294967296 ) return true ; else return false } const specialMethod = Symbol ("anything" ) thisArg[specialMethod] = this ; let args = arguments [1 ] let result; if (args){ if (!Array .isArray (args) && !isArrayLike (args)){ throw new TypeError ("args must be an array-like object" ) } else { args = Array .from (args); result = thisArg[specialMethod](...args); } else { result = thisArg[speacialMethod] } delete thisArg[specialMethod] return result; } Function .prototype .myBind = function (objThis, ...params ){ const thisFn = this ; let funcForBind = function (...secondParams ){ const isNew = this instanceof funcForBind; const thisArg = isNew ? this : objThis; return thisFn.call (thisArg, ...params, ...secondParams) }; funcForBind.prototype = Object .create (thisFn.prototype ) return funcForBind; } let func = function (p,secondParams ){ console .log (p.name ); console .log (this .name ); console .log (secondParams); } let obj={ name :"1891" } func.myBind (obj,{name :"coffe" })("二次传参" );
闭包 1 2 3 4 5 6 7 8 9 var report = (function ( ) { var imgs = []; return function (src ) { var img = new Image (); imgs.push (img); img.src = src; } }()); report ('http://www.xxx.com/getClientInfo' );
原型和原型链
ES6实现的继承,本质仍是基于原型和原型链。一般函数有prototype属性。
每个对象(实例)都有一个属性proto ,指向他的构造函数(constructor)的prototype属性。
一个对象的原型就是它的构造函数的prototype属性的值,因此proto 也即原型的代名词。
对象的proto 也有自己的proto ,层层向上,直到proto 为null。换句话说,原型本身也有自己的原型。这种由原型层层链接起来的数据结构称为原型链。因为null不再有原型,所以原型链的末端是null。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var a = function ( ){};var b=[1 ,2 ,3 ];console .log (a.__proto__ == Function .prototype );console .log (b.__proto__ == Array .prototype );console .log (a.__proto__ .__proto__ === Object .prototype );console .log (b.__proto__ .__proto__ === Object .prototype );console .log (new Object ().__proto__ .__proto__ );console .log (Object .prototype .__proto__ );
使用最新的方法Object.setPrototypeOf(类似Reflect.setPrototypeOf)可以很方便地给对象设置原型,这个对象会继承该原型所有属性和方法。
但是,setPrototypeOf的性能很差,我们应该尽量使用 Object.create()来为某个对象设置原型。1 2 3 4 5 6 7 8 9 10 11 var obj={ methodA ( ){ console .log ("coffe" ); } } var newObj = Object .create (obj);newObj.methodA ();
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 class A {} A===A.prototype .constructor ; class A { constructor ( ) { } toString ( ) { } toValue ( ) { } } A.prototype = { constructor ( ) {}, toString ( ) {}, toValue ( ) {}, };
class 作为构造函数的语法糖,同时有prototype属性和proto 属性,因此同时存在两条继承链。
子类的proto 属性,表示构造函数的继承,总是指向父类。
子类prototype属性的proto 属性,表示方法的继承,总是指向父类的prototype属性。
1 2 3 4 5 6 7 8 class A {} class B extends A {} B.__proto__ === A B.prototype .__proto__ === A.prototype
1 2 3 4 5 6 7 8 9 10 console .log ("methodA" in newObj); if (newObj.methodA ) { newObj.methodA (); } console .log (Object .getPrototypeOf (newObj).hasOwnProperty ("methodA" ));
线程,EventLoop
浏览器的线程 JS引擎是单线程的,但是浏览器是多线程的。现代浏览器的一个 tab ,其中的线程包括但不局限于: GUI 渲染线程 JS引擎线程 事件触发线程 定时器触发线程 异步http请求线程 JavaScript中的异步回调是通过 WebAPIs 去支持的,常见的有 XMLHttpRequest,setTimeout,事件回调(onclik, onscroll等)。而这几个 API 浏览器都提供了单独的线程去运行,所以才会有合适的地方去处理定时器的计时、各种请求的回调。即当代码中出现这几个定义的异步任务,是由浏览器实现了它们与JS引擎的通信,与JS引擎不在同一个线程。 另外,GUI 渲染和JavaScript执行是互斥的。虽然两者属于不同的线程,但是由于JavaScript执行结果可能会对页面产生影响,所以浏览器对此做了处理,大部分情况下JavaScript线程执行,执行渲染(render)的线程就会暂停,JavaScript的同步代码执行完再去渲染。
Event Loop的作用很简单: 监控调用栈和任务队列,如果调用栈是空的,它就会取出队列中的第一个”callback函数”,然后将它压入到调用栈中,然后执行它。 总的来说,Event Loop 是实现异步回调的一种机制而已。
微任务队列一次只有一个,先清空微任务队列再做宏任务队列,然后微任务又进来,又继续清空微任务队列。微任务队列:promise等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 setTimeout (function ( ) { console .log (1 ); }); new Promise (function (resolve, reject ) { console .log (2 ); resolve (3 ); }).then (function (val ) { console .log (val); }); console .log (4 );
循环 for,forEach,map 如果使用箭头函数表达式来传入thisArg 参数会被忽略,因为箭头函数在词法上绑定了this值。 for循环满足特定条件时跳出循环体,或者跳出本次循环直接进入下一次循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let arr = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ];for (let i = 0 ; i < arr.length ; i++) { if (i == 3 ) continue ; console .log (arr[i]); if (i > 7 ) break ; }
for..in & for..of 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 Array .prototype .getLength = function ( ) { return this .length ; } var arr = [1 , 2 , 4 , 5 , 6 , 7 ]arr.name = "coffe1981" ; console .log ("-------for...of--------" );for (var value of arr) { console .log (value); } console .log ("-------for...in--------" );for (var key in arr) { console .log (key); }
filter,find,some,sort,reduce reduce initialValue可选作为第一次调用callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素(也即针对数组的arr循环计算少一次,千万要注意这点)。 在没有初始值的空数组上调用reduce将报错。
数组—->对象
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 const arr = [ { username : 'makai' , displayname : '馆长' , email : 'guanzhang@coffe1891.com' }, { username : 'xiaoer' , displayname : '小二' , email : 'xiaoer@coffe1891.com' }, { username : 'zhanggui' , displayname : '掌柜' , email : null }, ]; function cb1 (acc, person ){ return {...acc, [person.username ]: person} } const obj = arr.reduce (cb1, {})const emptyArray = [];const result = emptyArray.reduce ((acc, curr ) => acc + curr, 0 );console .log (result); const result1 = emptyArray.reduce ((acc, curr ) => acc + curr);const arrA = [0.3 , 1.2 , 3.4 , 0.2 , 3.2 , 5.5 , 0.4 ];function callback (acc, reading ) { return { minReading : Math .min (acc.minReading , reading), maxReading : Math .max (acc.maxReading , reading), }; } const initMinMax = { minReading : Number .MAX_VALUE , maxReading : Number .MIN_VALUE }; const result = arrA.reduce (callback, initMinMax);console .log (result);const arr8 = [1 ,2 ,3 ]const sum1 = arr8.reduce ((acc, cur ) => acc + cur,0 )
深拷贝 缺点:
会忽略undefined;
会忽略symbol;
如果对象的属性为Function,因为JSON格式字符串不支持Function,在序列化的时候会自动删除;
诸如 Map, Set, RegExp, Date, ArrayBuffer和其他内置类型在进行序列化时会丢失;
不支持循环引用对象的拷贝。1 2 let a = {x : 2 }let b = JSON .sringiy (a)
缺点: 对象嵌套层次超过2层,就会出现浅拷贝的状况; 非可枚举的属性无法被拷贝。
1 2 3 let a = {x :3 }let b = Object .assign ({}, a)
缺点: 这个方法是异步的; 拷贝有函数的对象时,还是会报错。
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 let obj = { a : 1 , b : { c : 2 , d : 3 , }, f : undefined } obj.c = obj.b ; obj.e = obj.a ; obj.b .c = obj.c ; obj.b .d = obj.b ; obj.b .e = obj.b .c ; function deepClone (obj ){ return new Promise ((resolve ) => { const {port1, port2} = new MessageChannel (); port2.onmessage = ev => resolve (ev.data ); port1.postMessage (obj); }) } deepClone (obj).then ((copy ) => { let copyObj = copy; console .log (copyObj, obj) console .log (copyObj == obj) })
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 function deepClone (obj, parent = null ){ let result = {}; let keys = Object .keys (obj), key = null , temp = null , _parent = parent; while (_parent){ if (_parent.originalParent === obj){ return _parent.currentParent } _parent = _parent.parent } for (let i=0 ; i<keys.length ; i++){ key = keys[i]; temp = obj[key] if (temp && typeof temp === 'object' ){ result[key] = deepClone (temp, { originalParent : obj, currentParent : result, parent : parent }); }else { result[key] = temp } } return result; } function deepCloneWeakMapVersion (obj, cache = new WeakMap () ) { if (obj && typeof obj === 'object' ) { if (cache.has (obj)) return cache.get (obj); const clone = Array .isArray (obj) ? [] : {}; cache.set (obj, clone); for (let key in obj) { clone[key] = deepCloneWeakMapVersion (obj[key], cache); } return clone; } return obj; } function deepCloneMapVersion (obj, map = new Map () ) { if (obj && typeof obj === 'object' ) { if (map.has (obj)) return map.get (obj); const result = Array .isArray (obj) ? [] : {}; map.set (obj, result); for (let key in obj) { result[key] = deepCloneMapVersion (obj[key], map); } return result; } return obj; }
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 deepClone (obj ){ let map = new WeakMap (); function dp (obj ){ let result = null ; let keys = null , key = null , temp = null , existsObj = null ; existObj = map.get (obj) if (existObj){ reurn existObj } keys = Object .keys (obj); result = {}; map.set (obj, result); for (let i=0 ; i<keys.length ; i++){ key = keys[i]; temp = obj[key]; if (temp && typeof temp === 'object' ){ result[key] = dp (temp) }else { result[key] = temp; } } return result; return dp (obj) } }
柯里化 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 .prototype .bind = function (context ) { if (typeof this !== 'function' ) { throw new TypeError ('Function.prototype.bind called on incompatible ' + this ); } const originalFunc = this ; const bindArgs = Array .prototype .slice .call (arguments , 1 ); const boundFunc = function ( ) { const callArgs = Array .prototype .slice .call (arguments ); const isConstructorCall = this instanceof boundFunc; const thisContext = isConstructorCall ? this : context; return originalFunc.apply (thisContext, bindArgs.concat (callArgs)); }; const F = function ( ) {}; if (this .prototype ) { F.prototype = this .prototype ; } boundFunc.prototype = new F (); return boundFunc; };
给定一个函数fn,设计一个通用封装(currying函数)作用于这个fn,让这个fn可以支持柯里化,该怎么实现呢?思路:
要让fn(a,b)等价于fn(a)(b),那么fn(a)要返回一个函数,这个函数接受参数b,并返回与fn(a,b)相同的结果。
设计一个currying函数,它接受第一个参数是要被柯里化的函数fn,第2~N个参数是原本要传递给fn的参数(这个可用rest操作符实现)。
柯里化主要围绕“处理参数”思考,不管怎么变化传参形式,柯里化之后的函数要把之前函数的参数都统统处理完毕才算合格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let promaryCurrying = function (fn, length ){ length = length || fn.length ; return function (...args2 ){ if (args2.length < length){ var combinedArgs = [fn].concat (args2) return curry (primaryCurrying.apply (this , coombinedArgs), length - args2.length ); } else { return fn.apply (this , args2) } }; }
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 var primaryCurrying = function (fn,...args ) { return function (...args2 ) { var newArgs = args.concat (args2); return fn.apply (this , newArgs); } } function curry (fn, length ) { length = length || fn.length ; return function (...args2 ) { if (args2.length < length) { var combinedArgs = [fn].concat (args2); return curry (primaryCurrying.apply (this , combinedArgs), length - args2.length ); } else { return fn.apply (this , args2); } }; }
// 实现一个add方法,使计算结果能够满足如下预期: add(1)(2)(3) = 6; add(1, 2, 3)(4) = 10; add(1)(2)(3)(4)(5) = 15;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function add ( ){ var _args = Array .prototype .slice .call (arguments ); var _addr = function ( ){ _args.push (...arguments ) return _addr; } _adder.toString = function ( ){ return _args.reduce (function (acc, cur ){ return acc + cur; }, 0 ) } return _adder; }
reflect和proxy eval就是元编程,只是现在不让用了。
数据绑定和响应式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function createReactive (obj ){ return new Proxy (obj, { get (target, prop ){ console .log ('reading' + prop) return target[prop] }, set (target, prop, value ){ console .log ('setting' , value) target[prop] = value; return true ; } }); } const data = createReactive ({ count : 0 });data.count ; data.count = 1
日志记录 1 2 3 4 5 6 7 8 9 10 11 12 13 function createLoggingProxy (obj ){ return new Proxy (obj, { get (target, prop ){ console .log ('get' , [prop]) return target[prop] } set (target, prop, value ){ console .log (`setting ${prop} = ${value} ` ) return Reflect .set (target, prop, value) } }); }
权限控制 1 2 3 4 5 6 7 8 9 10 11 function createSecureProxy (obj, allowedProps ){ return new Proxy (obj, { get (target, prop ) { if (allowedProps.includes (prop)){ return target[prop] } throw new Error (`Not allowed to access ${prop} ` ) } }) }
网络 常见面试题
为什么连接的时候是三次握手,关闭的时候却是四次握手? 答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态? 答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
为什么不能用两次握手进行连接? 答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
如果已经建立了连接,但是客户端突然出现故障了怎么办? TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
http HTTP/2 和 HTTP/1.1 的对比 相关问题
了解 HTTP/2 吗
HTTP/1.0、HTTP/1.1 和 HTTP/2 的区别回答关键点 队头阻塞,持久连接,二进制分帧层,多路复用,服务端推送
HTTP/1.1 相较 HTTP/1.0 的改进和优化:
持久连接
HTTP管道化
分块编码传输
新增Host头处理
更多缓存处理
新增更多状态码
断点续传、并行下载
HTTP/1.1 的缺点:
队头阻塞(Head-of-line blocking)
头部冗余
TCP连接数限制
HTTP/2 的优点:
二进制分帧层
多路复用
Header压缩
服务端推送
知识点深入
HTTP/1.1
1 相较 HTTP/1.0 的改进和优化 HTTP/1.1 相比于 HTTP/1.0 的改进和优化主要包含:持久连接、HTTP管道化请求、分块编码传输、新增host头字段、缓存支持、更多状态码等。
持久连接 在HTTP/1.0时期,每进行一次HTTP通信,都需要经过TCP三次握手建立连接。若一个页面引用了多个资源文件,就会极大地增加服务器负担,拉长请求时间,降低用户体验。
HTTP/1.1中增加了持久连接,可以在一次TCP连接中发送和接收多个HTTP请求/响应。只要浏览器和服务器没有明确断开连接,那么该TCP连接会一直保持下去。
持久连接在 HTTP/1.1 中默认开启(请求头中带有 Connection: keep-alive),若不需要开启持久连接,可以在请求头中加上 Connection: close。
HTTP管道化 HTTP管道化是指将多个HTTP请求同时发送给服务器的技术,但是响应必须按照请求发出的顺序依次返回。 但是由于HTTP管道化仍存在诸多问题:
第一个响应慢仍会阻塞后续响应 服务器为了保证能按序返回需要缓存提前完成的响应而占用更多资源 需要客户端 、代理和服务器都支持管道化 所以目前大部分主流浏览器默认关闭HTTP管线化功能。
分块编码传输 在HTTP/1.1协议里,允许在响应头中指定Transfer-Encoding: chunked标识当前为分块编码传输,可以将内容实体分装成一个个块进行传输。
新增Host头处理 在HTTP/1.0中认为每台服务器都绑定一个唯一的IP地址,因此一台服务器也无法搭建多个Web站点。 在HTTP/1.1中新增了host字段,可以指定请求将要发送到的服务器主机名和端口号。
断点续传、并行下载 在HTTP/1.1中,新增了请求头字段Range和响应头字段Content-Range。
前者是用来告知服务器应该返回文件的哪一部分,后者则是用来表示返回的数据片段在整个文件中的位置,可以借助这两个字段实现断点续传和并行下载。
HTTP/1.1 的缺点 队头阻塞 虽然在 HTTP1.1 中增加了持久连接,能在一次 TCP 连接中发送和接收多个 HTTP 请求/响应,但是在一个管道中同一时刻只能处理一个请求,所以如果上一个请求未完成,后续的请求都会被阻塞。
头部冗余 HTTP 请求每次都会带上请求头,若此时 cookie 也携带大量数据时,就会使得请求头部变得臃肿。 TCP 连接数限制
浏览器对于同一个域名,只允许同时存在若干个 TCP 连接(根据浏览器内核有所差异),若超过浏览器最大连接数限制,后续请求就会被阻塞。
HTTP/2 HTTP/2 的优点 二进制分帧层 在HTTP/1.x中传输数据使用的是纯文本形式的报文,需要不断地读入字节直到遇到分隔符为止。而HTTP/2则是采用二进制编码,将请求和响应数据分割为一个或多个的体积小的帧。
多路复用 HTTP/2允许在单个TCP连接上并行地处理多个请求和响应,真正解决了HTTP/1.1的队头阻塞和TCP连接数限制问题。
Header压缩 使用HPACK算法来压缩头部内容,包体积缩小,在网络上传输变快。
服务端推送 允许服务端主动向浏览器推送额外的资源,不再是完全被动地响应请求。例如客户端请求HTML文件时,服务端可以同时将JS和CSS文件发送给客户端。
HTTP和HTTPS的基本概念
HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 HTTPS协议的主要作用可以分为两种:
一种是建立一个信息安全通道,来保证数据传输的安全
另一种就是确认网站的真实性
xss和csrf XSS原理:攻击者把恶意脚本注入到受信任页面并被浏览器执行,脚本利用页面的信任上下文(cookies、localStorage、DOM)窃取数据或者劫持会话来获取敏感信息。
常见类型:反射型(参数或者路径直接反射到页面并执行);存储型:(恶意内容存储在服务器,其他用户访问时触发)
xss的预防方法是设置csp 通过 Content-Security-PolicyHTTP头来开启CSP: 只允许加载本站资源Content-Security-Policy: default-src ‘self’。 只允许加载HTTPS协议图片 Content-Security-Policy: img-src https://*。 允许加载任何来源框架 Content-Security-Policy: child-src ‘none’。
httponly开头的cookie Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly。
过滤decoding map
CSRF的防范 防范CSRF可遵循以下几种规则:
Get 请求不对数据进行修改。
不让第三方网站访问到用户Cookie。
阻止第三方网站请求接口。
请求时附带验证信息,如验证码或Token。
十大排序算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 排序算法 ├─ O(n²) 简单排序 │ ├─ 冒泡 Bubble │ ├─ 选择 Selection │ └─ 插入 Insertion │ ├─ O(n log n) 高级排序(基于分治) │ ├─ 归并 Merge —— 非原地 + 稳定 │ └─ 快排 Quick —— 原地 + 不稳定 │ ├─ O(n log n) 不基于分治 │ └─ 堆排序 Heap — 原地 + 不稳定 │ └─ O(n) 非比较排序(限定数据范围) ├─ 计数排序 Counting ├─ 桶排序 Bucket └─ 基数排序 Radix
冒泡排序 两两比较,大的往后沉,双重循环,交换!
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 bubbleSort (arr ){ for (let i = 0 ; i< arr.length ; i++){ for (let j = 0 ; j< arr.length - i - 1 ; j++){ if (arr[j] > arr[j+1 ]){ const temp = arr[j] arr[j] = arr[j+1 ] arr[j+1 ] = temp } } } return arr } function bubbleSort1 (arr ){ for (let i = 0 ; i< arr.length ; i++){ for (let j = 0 ; j< arr.length - i - 1 ; j++){ let flag = false if (arr[j] > arr[j+1 ]){ const temp = arr[j] arr[j] = arr[j+1 ] arr[j+1 ] = temp flag = true } if (!flag) break } } return arr }
选择排序 每次选最小,放到前面,固定位置,找最小index
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function selectionSort (arr ) { let length = arr.length ; let indexMin; for (let i = 0 ; i < length - 1 ; i++) { indexMin = i; for (let j = i + 1 ; j < length; j++) { if (arr[indexMin] > arr[j]) { indexMin = j; } } if (i !== indexMin) { let temp = arr[i]; arr[i] = arr[indexMin]; arr[indexMin] = temp; } } return arr; }
插入排序 前面是有序区,把当前数插进去,适合几乎有序数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ((arr ) => { for (let i=1 ; i<arr.length ; i++){ let current = arr[i]; let = j = i-1 ; while (j>=0 && arr[j] > current){ arr[j+1 ] = arr[j] j-- } arr[j+1 ] = current } return arr })([1 ,5 ,4 ,2 ,9 ,7 ,8 ])
*** 快速排序(Top1 高频) 快速排序其实是分治法,将待排序数组里的项和基准数对比,比基准数大的放在一边,小的放另一边,然后再对左右两边的子数组重复使用这个思路,直到整个数组排序完毕。 选基准,比它小的放左边,比它大的放右边。里面比较的是合法下标哦,是合法下标的区间哦!
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 function quickSort (arr, left, right ) { if (left >= right) return ; let i = left; let j = right; let pivot = arr[left]; while (i < j) { while (arr[j] >= pivot && i < j) j--; while (arr[i] <= pivot && i < j) i++; if (i < j) { [arr[i], arr[j]] = [arr[j], arr[i]]; } } arr[left] = arr[i]; arr[i] = pivot; quickSort (arr, left, i - 1 ); quickSort (arr, i + 1 , right); return arr; } console .log (quickSort ([1 ,5 ,4 ,2 ,9 ,7 ,8 ], 0 , 6 ));
*** 堆排序(面试经常问“Top K”) 建立大顶堆,堆顶最大,不断弹出再重建。 React Fiber = 一棵被不断“重新排序”的最小堆结构 → 面试官特别爱问堆。 React Fiber 的本体结构是一串 单向链表 + 树: React Fiber 的调度器(Scheduler)用的是“最小堆(min-heap)”结构; React Fiber 的任务执行顺序(Fiber 树构建 / Reconciliation)本身是用“可中断的链表(队列)结构”。 所以: Fiber = 链表结构(队列式遍历) Scheduler = 用最小堆来决定“下一件事做谁” 这两者完全不冲突,只是层级不一样。
React Fiber 本身不是堆,它是一套链表结构的 Fiber 树,用来支持可中断更新。 但 React 的调度器 Scheduler 为了按优先级选择下一个任务,使用了“最小堆”作为优先级队列。 所以:Fiber = 链表;调度 = 堆。
堆的特性: 必须是完全二叉树; 任一结点的值是其子树所有结点的最大值或最小值。 最大值时,称为”最大堆”,也称大顶堆; 最小值时,称为”最小堆”,也称小顶堆。 堆排序主要用到最大堆/最小堆的删除操作,也即根节点已经是最大/小的值,排序的话,只需要把根结点拿(删)掉,放入有序的新数组里,然后用下沉算法处理剩余的结点以便组成新的最大堆/最小堆……如此循环。 所谓下沉算法,拿最小堆来举例说(最大堆同理),就是把完全二叉树根结点R和该树第二层左右子结点的值比较,如果大,结点就互换位置(”下沉”),以此逐层递归,直到处理完整棵树,此时,根节点值最小。
堆的本质是完全二叉树,而完全二叉树最典型的表示方式就是“连续数组”。 只要数组里没有空洞(每个 index 都能访问到),它天然可以作为堆结构。 只要保证数组连续,堆排序一定能用。
1 2 3 4 5 6 function isContinuousArray (arr ) { for (let i = 0 ; i < arr.length ; i++) { if (!(i in arr)) return false ; } return true ; }
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 ((arr ) => { let result = [] buildMinHeap (arr) for (let i = 0 , length = arr.length ; i< length; i++){ result.push (arr[0 ]) swap (0 , length-result.length ) sink (arr, 0 , length-result.length ) } function buildMinHeap (arr ){ let length = arr.length ; let currentIndex; for (currentIndex = Math .floor ((length - 2 ) / 2 ); currentIndex >= 0 ; currentIndex--){ console .log ('正在处理的下沉结点索引' + currentIndex) sink (arr, currentIndex, length) } } function sink (arr, currentIndex, length ){ let minIndex = currentIndex let leftChildIndex = 2 *currentIndex + 1 let rightChildIndex = 2 *currentIndex + 2 if (leftChildIndex < length && arr[leftChildIndex] < arr[minIndex]){ minIndex = leftChildIndex } if (rightChildIndex < length && arr[rightChildIndex] < arr[minIndex]){ minIndex = rightChildIndex } if (minIndex !== currentIndex){ swap (minIndex, currentIndex) sink (arr, minIndex, length) } } function swap (x, y ){ let temp = arr[x] arr[x] = arr[y] arr[y] = temp } return result })([1 ,5 ,4 ,2 ,9 ,7 ,8 ])
计数排序(不太会) 统计次数,再按次数依次输出,只适合整数小范围。
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 ((arr ) => { function countingSort (arr ) { const n = arr.length ; let max = arr[0 ]; let min = arr[0 ]; for (let i = 1 ; i < n; i++) { if (arr[i] > max) max = arr[i]; if (arr[i] < min) min = arr[i]; } const range = max - min + 1 ; const count = new Array (range).fill (0 ); const output = new Array (n); for (let i = 0 ; i < n; i++) { count[arr[i] - min]++; } for (let i = 1 ; i < range; i++) { count[i] += count[i - 1 ]; } for (let i = n - 1 ; i >= 0 ; i--) { output[count[arr[i] - min] - 1 ] = arr[i]; count[arr[i] - min]--; } for (let i = 0 ; i < n; i++) { arr[i] = output[i]; } return arr; } const sortedArr = countingSort (arr); console .log (sortedArr); })([1 , 5 , 4 , 2 , 9 , 7 , 8 ]);
桶排序 分桶 → 各桶排序 → 合并 适合均匀分布的数据。
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 ((arr ) => { function bucketSort (arr, bucketSize = 5 ) { if (arr.length === 0 ) return arr; let min = arr[0 ]; let max = arr[0 ]; for (let i = 1 ; i < arr.length ; i++) { if (arr[i] < min) min = arr[i]; if (arr[i] > max) max = arr[i]; } const bucketCount = Math .floor ((max - min) / bucketSize) + 1 ; const buckets = new Array (bucketCount); for (let i = 0 ; i < bucketCount; i++) { buckets[i] = []; } for (let i = 0 ; i < arr.length ; i++) { const bucketIndex = Math .floor ((arr[i] - min) / bucketSize); buckets[bucketIndex].push (arr[i]); } for (let i = 0 ; i < buckets.length ; i++) { buckets[i].sort ((a, b ) => a - b); } let index = 0 ; for (let i = 0 ; i < buckets.length ; i++) { for (let j = 0 ; j < buckets[i].length ; j++) { arr[index++] = buckets[i][j]; } } return arr; } const sortedArr = bucketSort (arr); console .log (sortedArr); })([1 , 5 , 4 , 2 , 9 , 7 , 8 ]);
基数排序 按个位、十位、百位依次排 用桶 + 多次扫描。
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 ((arr ) => { function radixSort (arr ) { let max = arr[0 ]; for (let i = 1 ; i < arr.length ; i++) { if (arr[i] > max) max = arr[i]; } let maxDigit = 1 ; while (Math .floor (max / 10 ) > 0 ) { maxDigit++; max = Math .floor (max / 10 ); } for (let digit = 0 ; digit < maxDigit; digit++) { const buckets = Array .from ({length : 10 }, () => []); for (let i = 0 ; i < arr.length ; i++) { const bucketIndex = Math .floor (arr[i] / Math .pow (10 , digit)) % 10 ; buckets[bucketIndex].push (arr[i]); } let index = 0 ; for (let i = 0 ; i < 10 ; i++) { for (let j = 0 ; j < buckets[i].length ; j++) { arr[index++] = buckets[i][j]; } } } return arr; } const sortedArr = radixSort (arr); console .log (sortedArr); })([1 , 5 , 4 , 2 , 9 , 7 , 8 ]);
*** 归并排序(分治 + 面试能问很深) 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 function mergeSort (arr ){ let array = mergeSortRec (arr) return array } function mergeSortRec (arr ){ let length = arr.length ; if (length === 1 ){ return arr; } let mid = Math .floor (length/2 ), left = arr.slice (0 , mid), right = arr.slice (mid, length) return merge (mergeSortRec (left), mergeSortRec (right)) } function merge (left, right ){ let result = [], ileft = 0 , iright=0 while (ileft<left.length && iright<right.length ){ if (left[ileft] < right[iright]){ result.push (left[ileft++]) } else { result.push (right[iright++]) } } while (ileft<left.length ){ result.push (left[ileft++]) } while (iright<right.length ){ result.push (right[iright++]) } return result }
希尔排序 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 ((arr ) => { function shellSort (arr ) { const n = arr.length ; for (let gap = Math .floor (n / 2 ); gap > 0 ; gap = Math .floor (gap / 2 )) { for (let i = gap; i < n; i++) { let temp = arr[i]; let j; for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) { arr[j] = arr[j - gap]; } arr[j] = temp; } } return arr; } const sortedArr = shellSort (arr); console .log (sortedArr); })([1 , 5 , 4 , 2 , 9 , 7 , 8 ]);
后面单调栈的代码实现有点问题,粗看了一下后面几个题,先不按照这里的算法题复习了哦!转去瓶子算法那,感觉更好懂。 三大框架和webpack部分跳过,写的不好。 发布订阅和观察者模式 观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。 发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
差异: 在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。