面向对象 抽象 封装 继承 多态
工厂模式 1 2 3 4 5 6 7 8 9 10 11 12 function Factory ( ) { let obj = {} obj.prop = '' obj.fn = function ( ) {} return obj } let productA = Factory ()let productB = Factory ()
工厂模式解决了代码复用
的问题
但是却没有解决对象识别的问题
创建的实例都是 Object 类型, 不清楚是哪个工厂的实例
公共方法和属性会随着工厂方法的调用而重复占用内存
构造函数 1 2 3 4 5 6 7 8 9 10 function Factory ( ) { this .prop = '' } Factory .prototype = { constructor : Factory , fn : function ( ) {} } let productA = new Factory ()let productB = new Factory ()
原型 原型链 原型链
是指对象在访问属性或方法时的查找方式
当访问一个对象的属性或方法时,会先在对象自身上查找属性或方法是否存在,如果存在就使用对象自身的属性或方法;如果不存在就去创建对象的构造函数的原型
对象中查找,依此类推,直到找到为止。如果到顶层对象中还找不到,则返回 undefined
原型链最顶层为 Object 构造函数的 prototype 原型对象,给 Object.prototype 添加属性或方法可以被除 null 和 undefined 之外的所有数据类型对象使用
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 Animal ( ) {}const animal = new Animal animal.__proto__ === Animal .prototype Animal .prototype .constructor === Animal animal.constructor === Animal console .log (Animal .prototype .isPrototypeOf (animal))console .log (animal instanceof Animal )console .log ('name' in animal)console .log (animal.hasOwnProperty ('name' ))Animal .__proto__ === Function .prototype Function .prototype .__proto__ === Object .prototype Object .prototype .__proto__ === null Function .prototype .constructor === Function Object .prototype .constructor === Object Function .__proto__ === Function .prototype Object .__proto__ === Function .prototype
继承
constructor 指向问题
属性共享问题
子类不能定义自己的参数
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 inheritPrototype (Child, Parent ) { const protoType = Object .create (Parent .prototype , { constructor : { configurable : true , enumerable : false , writable : true , value : Child } }) Child .prototype = protoType } function Plane (color ) { this .color = color } Plane .prototype .fly = function ( ) { console .log ('flying' ) } function Fighter (color ) { Plane .call (this , color) this .bullets = [] } inheritPrototype (Fighter , Plane )Fighter .prototype .shoot = function ( ) { console .log ('biu biu biu' ) } const fighter = new Fighter ('黑色' )if (const staticAttribute in Parent ) { if (Parent .hasOwnProperty (staticAttribute) && !(staticAttribute in Child )) { Child [staticAttribute] = Parent [staticAttribute] } }
类型判断 instanceof 通过原型链
的方式来判断是否为构造函数的实例
1 2 3 4 5 6 7 8 9 10 11 12 let str = 'hello world' str instanceof String let str1 = new String ('hello world' )str1 instanceof String class PrimitiveString { static [Symbol .hasInstance ](str) { return typeof str === 'string' } } console .log ('hello world' instanceof PrimitiveString )
constructor 1 [].constructor === Array
Object.prototype.toString 常用于判断浏览器内置对象
1 2 Object .prototype .toString .call (null ) === '[object Null]' Object .prototype .toString .call (null ).slice (8 , -1 ).toLowerCase ()
is API 1 2 Array .isArray ([])Number .isNaN ('' )
class 类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Parent { constructor (value ) { this .val = value } getValue ( ) { console .log (this .val ) } } class Child extends Parent { constructor (value ) { super (value) } } let child = new Child (1 )child.getValue () child instanceof Parent Parent instanceof Function
作用域 闭包
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 const obj = { f1 ( ) { const fn = ( ) => { console .log ('f1' , this ) } fn () fn.call (window ) }, f2 : () => { function fn ( ) { console .log ('f2' , this ) } fn () fn.call (this ) } } obj.f1 () obj.f2 () class Foo { f1 ( ) { console .log ('f1' , this ) } f2 = () => { console .log ('f2' , this ) } static f3 ( ) { console .log ('f3' , this ) } } const f = new Foo ()f.f1 () f.f2 () Foo .f3 ()
在简单调用时,this 默认指向 window(浏览器)/global(node)/undefined(严格模式)
对象调用时,绑定在对象上
使用 bind call apply 时,绑定在指定参数上
使用 new 关键字时,绑定到新创建的对象上
使用箭头函数时,根据外层的规则决定
优先级: new > bind/call/apply > 对象调用
自由变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 let n = 10 function f1 () { n++ function f2 ( ) { function f3 ( ) { n++ } let n = 20 f3 () n++ } f2 () n++ } f1 ()console .log (n)
Closure 闭包
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 for (var i = 0 ; i < 10 ; i++) { setTimeout (() => { console .log (i) }) } for (var i = 0 ; i < 10 ; i++) { ((i ) => { setTimeout (() => { console .log (i) }) })(i) } function F1 ( ) { let a = 1 return function ( ) { console .log (a) } } let f1 = F1 ()let a = 2 f1 () function F2 (fn ) { let a = 3 fn () } F2 (f1) function foo ( ) { let i = 0 function bar ( ) { console .log (i++) } return bar } let bar = foo ()bar () bar () bar () function isFirstLoad ( ) { let _cache = [] return (id ) => { if (_cache.includes (id)) { return false } else { _cache.push (id) return true } } } let firstLoad = isFirstLoad ()firstLoad (1 ) firstLoad (1 ) firstLoad (2 )
let const 1 2 3 4 5 6 7 8 9 10 11 for (var i = 0 ; i < 10 ; i++) { setTimeout (() => { console .log (i) }) } for (let i = 0 ; i < 10 ; i++) { setTimeout (() => { console .log (i) }) }
var
全局/函数作用域
全局作用域下声明变量会挂载在 window 上
变量提升
let
块级作用域
不能重复声明
不会被预解析(暂时性死区
不能在声明前使用)
const
常量不能重新赋值
块级作用域
不能重复声明
不会被预解析(暂时性死区
不能在声明前使用)
箭头函数
没有 arguments 参数
无法通过 call apply bind 改变 this
简写的函数会变得难以阅读
不适用箭头函数的场景
对象方法
扩展对象原型(包括构造函数的原型)
构造函数
动态上下文中的回调函数addEventListener
Vue 生命周期和方法
class 中使用箭头函数没有问题,所以在 React 中可以使用箭头函数
Vue 组件是一个对象,而 React 组件是一个 class(如果不考虑 Composition API 和 Hooks)
Set Map
数组
有序结构,可排序的
中间插入删除比较慢
Set
有序结构,不可排序,没有 index
去重
对象
无序结构
key 两种类型 string symbol
Map
有序结构forEach
key 任意类型
Iterator 和 for…of 迭代器 1 2 3 4 5 6 7 8 9 10 obj[Symbol .iterator ] = function ( ) { return { next ( ) { return { value, done : true } } } }
迭代对象 实现了[Symbol.iterator]
方法
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 let obj = { a : 1 , b : 2 , c : 3 } obj[Symbol .iterator ] = function ( ) { let keys = Object .keys (obj) let index = 0 return { next ( ) { if (index >= keys.length ) { return { done : true } } else { return { done : false , value : { key : keys[index], value : obj[keys[index++]] } } } } } } for (let val of obj) { console .log (val) }
迭代语句 for…in 👎
以原始插入的顺序遍历对象的可枚举属性
用于可枚举数据 enumerable
对象
字符串 数组
Object.getOwnPropertyDescriptors( { a: 1 } )
得到 key 值
主要是为遍历对象而设计的,不适用于遍历数组
for…of 👍
根据迭代器具体实现遍历对象
用于可迭代数据 next
数组
字符串 Set Map 类数组 Generator对象
[Symbol.iterator]()
得到 value 值
可以使用break
和continue
提供了遍历所有数据结构的统一操作接口
for await…of 用于遍历异步请求的可迭代对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function createTimeoutPromise (val ) { return new Promise (resolve => { setTimeout (() => { resolve (val) }, 1000 ) }) } (async function ( ) { const list = [ createTimeoutPromise (10 ), createTimeoutPromise (20 ) ] for await (const p of list) { console .log (p) } })()
forEach 数组 对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function forEach (obj, fn ) { if (obj instanceof Array ) { obj.forEach ((item, index ) => { fn (index, item) }) } else { for (let key in obj) { if (obj.hasOwnProperty (key)) { fn (key, obj[key]) } } } } let arr = [1 , 2 , 3 ]forEach (arr, (index, item ) => { console .log (index, item) }) let obj = {x : 3 , y : 4 , z : 5 }forEach (obj, (key, value ) => { console .log (key, value) })
Generator 函数 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 * gen ( ) { yield new Promise ((resolve, reject ) => { setTimeout (() => { resolve (1 ) }, 500 ) }) yield 2 yield new Promise ((resolve, reject ) => { setTimeout (() => { resolve (3 ) }, 500 ) }) } co (gen)function co (gen ) { let fn = gen (), result (function iterate ( ) { result = fn.next () if (!result.done ) { if ('then' in Object (result.value )) { result.value .then (iterate) } else { iterate () } } })() }
模块化
IIFE 立即执行函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const iifeModule = ((window , jQuery, undefined , dependencyModule1 ) => { let count = 0 return { increase ( ) { ++count }, reset : () => (count = 0 ) } })(window , jQuery, undefined , dependencyModule1)
AMD 异步模块 RequireJS1 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 define ('amdModule' , ['dependencyModule1' , 'dependencyModule2' ], (dependencyModule1, dependencyModule2 ) => { let count = 0 const increase = ( ) => ++count const reset = ( ) => { count = 0 } }) require (['amdModule' ], amdModule => { amdModule.reset () }) define ('amdModule' , [], require => { const dependencyModule1 = require ('./dependencyModule1' ) const dependencyModule2 = require ('./dependencyModule2' ) let count = 0 const increase = ( ) => ++count const reset = ( ) => { count = 0 } export .increase = increase export .reset = reset return { increase, reset } })
CMD 通用模块 SeaJS 1 2 3 4 5 6 7 8 9 10 11 12 13 define ('cmdModule' , (require , exports , module ) => { const $ = require ('jquery' ) $('selector' ) })
动态语法,运行时加载,可以写在判断里
同步导入
单值导出,对模块的浅拷⻉
this 指向当前模块
应用于 Node、小程序、Webpack
1 2 3 4 5 6 7 8 9 10 module .exports = { data : 'data' } exports .data = 'data' let cjsModule = require ('./cjsModule' )console .log (cjsModule.data )
静态语法,编译时输出,必须放在文件开头
异步导入
多值导出,对模块的引用
this 等于 undefined
编译成 require / exports 执行
1 2 3 export default esModuleexport { esModule as default }
ESM 的模块化通过 Babel 编译 就是 UMD
模块规范, 它是一个兼容 CMD 和 CJS 的模块化规范, 同时还支持老式的”全局”变量规范
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (function (root, factory ) { if (typeof define === 'function' && define.amd ) { define (['jquery' ], factory) } else if (typeof exports === 'object' ) { module .exports = factory (require ('jquery' )) } else { root.returnExports = factory (root.jQuery ) } }(this , function ($ ) { function myFunc ( ) { } return myFunc }))
ES11 1 import ('./esModule.js' ).then (dynamicEsModule => {})
手写代码
代码规范性
ESLint 统一格式
代码可读: 命名语义化 函数抽离 注释
功能完整性
健壮性
new
创建一个空对象, 继承构造函数的原型
this 指向这个对象
执行构造函数, 对 this 赋值
隐式返回 this
1 2 3 4 5 function customNew<T>(Con : Function , ...args : any []): T { let obj = Object .create (Con .prototype ) let result = Con .apply (obj, args) return result instanceof Object ? result : obj }
Object.create 1 2 3 4 5 6 7 8 9 10 function create (proto ) { function F ( ) {} F.prototype = proto return new F () } const animal = new Animal () = Object .create (Animal .prototype )
instanceof 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function myInstanceof (L, R ) { if (L == null ) returnn false const type = typeof L if (type !== 'object' || type !== 'function' ) return let O = R.prototype while (true ) { L = L.__proto__ if (L === null ) return false if (O === L) return true } }
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 Function .prototype .bind = function (context, ...args ) { if (typeof this !== 'function' ) { throw new TypeError ('Type error.' ) } if (context == null ) context = globalThis if (typeof context !== 'object' ) context = new Object (context) const _this = this return function F ( ) { if (this instanceof F) { return new _this (...args, ...arguments ) } return _this.apply (context, args.concat (...arguments )) } } let fn2 = function fn1 ( ) { return function ( ) { return fn.apply (a, ['a' , 'b' , 'c' ]) }.apply (b, ['b' , 'c' ]) } fn2 ('c' )
call 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Function .prototype .call = function (context, ...args ) { if (typeof this !== 'function' ) { throw new TypeError ('Type error.' ) } if (context == null ) context = globalThis if (typeof context !== 'object' ) context = new Object (context) const fnKey = Symbol () context[fnKey] = this const result = context[fnKey](...args) delete context[fnKey] return result }
apply 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Function .prototype .apply = function (context, args ) { if (typeof this !== 'function' ) { throw new TypeError ('Type error.' ) } if (context == null ) context = globalThis if (typeof context !== 'object' ) context = new Object (context) const fnKey = Symbol () context[fnKey] = this const result = context[fnKey](...args) delete context[fnKey] return result }
throttle 节流 第一个人说了算 1 2 3 4 5 6 7 8 9 10 function throttle (fn, interval = 50 ) { let last = 0 return function (...args ) { let now = +new Date () if (now - last > interval) { last = now fn.apply (this , args) } } }
debounce 防抖 最后一个人说了算 1 2 3 4 5 6 7 8 9 function debounce (fn, delay = 100 ) { let timer = null return function (...args ) { if (timer) clearTimeout (timer) timer = setTimeout (() => { fn.apply (this , args) }, delay) } }
heartbeat 心跳 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function sleep (duration ) { return new Promise ((resolve ) => { setTimeout (resolve, duration) }) } async function * heartbeat ( ) { let i = 0 while (true ) { await sleep (1000 ) yield i++ } } for await (let i of heartbeat ()) console .log (i)
deepClone 深拷贝 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 let map = new WeakMap ()function deepClone (obj ) { if (obj instanceof Object ) { if (map.has (obj)) { return map.get (obj) } let newObj if (obj instanceof Array ) { newObj = [] } else if (obj instanceof Function ) { newObj = function ( ) { return obj.apply (this , arguments ) } } else if (obj instanceof RegExp ) { newobj = new RegExp (obj.source , obj.flags ) } else if (obj instanceof Date ) { newobj = new Date (obj) } else { newObj = {} } let desc = Object .getOwnPropertyDescriptors (obj) let clone = Object .create (Object .getPrototypeOf (obj), desc) map.set (obj, clone) for (let key in obj) { if (obj.hasOwnProperty (key)) { newObj[key] = deepClone (obj[key]) } } return newObj } return obj }
LRU 缓存 Least Recently Used 最近最少使用
Map
Object + Array
Object(查) + 双向链表(增 删 移动)
第三方 lib
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 class LRUCache { private length : number private data : Map <any , any > = new Map () constructor (length: number ) { if (length < 1 ) throw new Error ('Invalid length' ) this .length = length } set (key: any , value: any ) { const data = this .data if (data.has (key)) { data.delete (key) } data.set (key, value) if (data.size > this .length ) { const delKey = data.keys ().next ().value data.delete (delKey) } } get (key : any ): any { const data = this .data if (!data.has (key)) return const value = data.get (key) data.delete (key) data.set (key, value) return value } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const obj1 = { key : 'a' , value : 1 }const obj2 = { key : 'b' , value : 2 }const obj3 = { key : 'c' , value : 3 }const data = [obj1, obj2, obj3] const map = { 'a' : obj1, 'b' : obj2, 'c' : obj3 }
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 109 110 111 112 113 114 115 116 interface IListNode { value : any key : string prev?: IListNode next?: IListNode } export default class LRUCache { private length : number private data : { [key : string ]: IListNode } = {} private dataLength : number = 0 private listHead : IListNode | null = null private listTail : IListNode | null = null constructor (length: number ) { if (length < 1 ) throw new Error ('Invalid length' ) this .length = length } private moveToTail (currNode: IListNode ) { const tail = this .listTail if (tail === currNode) return const prevNode = currNode.prev const nextNode = currNode.next if (prevNode) { if (nextNode) { prevNode.next = nextNode } else { delete prevNode.next } } if (nextNode) { if (prevNode) { nextNode.prev = prevNode } else { delete nextNode.prev } if (this .listHead === currNode) this .listHead = nextNode } delete currNode.prev delete currNode.next if (tail) { tail.next = currNode currNode.prev = tail } this .listTail = currNode } private tryClean ( ) { while (this .dataLength > this .length ) { const head = this .listHead const headNext = head.next delete headNext.prev delete head.next this .listHead = headNext delete this .data [head.key ] this .dataLength -- } } get (key : string ): any { const data = this .data const currNode = data[key] if (currNode == null ) return if (this .listTail === currNode) { return currNode.value } this .moveToTail (currNode) return currNode.value } set (key: string , value: any ) { const data = this .data const currNode = data[key] if (currNode == null ) { const newNode : IListNode = { key, value } data[key] = newNode this .dataLength ++ if (this .dataLength === 1 ) { this .listHead = newNode this .listTail = newNode } this .moveToTail (newNode) } else { currNode.value = value this .moveToTail (currNode) } this .tryClean () } }
DOM-To-VNode render函数 1 2 3 4 5 <div id ="div" style ="border: 1px solid #ccc; padding: 10px;" > <p > 一行文字<a href ="abc.html" target ="_blank" > 链接</a > </p > <img src ="loading.png" alt ="加载中..." class ="image" /> <button click ="clickHandler" > 点击</button > </div >
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 const vnode = { tag : 'div' , props : { id : 'div' , style : { 'border' : '1px solid #ccc' , 'padding' : '10px' } }, children : [ { tag : 'p' , props : {}, children : [ '一行文字' , { tag : 'a' , props : { href : 'abc.html' , target : '_blank' }, children : ['链接' ] } ] }, { tag : 'img' , props : { className : 'image' , src : 'loading.png' , alt : '加载中...' } }, { tag : 'button' , props : { events : { click : clickHandler } }, children : ['点击' ] } ] }
Array-To-Tree 数组转树 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 interface IArrayItem { id : number name : string parentId : number } interface ITreeNode { id : number name : string children?: ITreeNode [] } function convert (arr: IArrayItem[] ): ITreeNode | null { const idToTreeNode : Map <number , ITreeNode > = new Map () let root = null arr.forEach (item => { const { id, name, parentId } = item const treeNode : ITreeNode = { id, name } idToTreeNode.set (id, treeNode) const parentNode = idToTreeNode.get (parentId) if (parentNode) { parentNode.children = parentNode.children || [] parentNode.children .push (treeNode) } if (parentId === 0 ) root = treeNode }) return root } function convert (arr ) { const res = [] const map = arr.reduce ((res, item ) => { res[item.id ] = item return res }, {}) for (const item of arr) { if (item.parentId === 0 ) { res.push (item) continue } if (item.parentId in map) { const parent = map[item.parentId ] parent.children = parent.children || [] parent.children .push (item) } } return res } const arr = [ { id : 1 , name : '部门 A' , parentId : 0 }, { id : 2 , name : '部门 B' , parentId : 1 }, { id : 3 , name : '部门 C' , parentId : 1 }, { id : 4 , name : '部门 D' , parentId : 2 }, { id : 5 , name : '部门 E' , parentId : 2 }, { id : 6 , name : '部门 F' , parentId : 3 } ] const tree = convert (arr)console .log (tree)
Tree-To-Array 树转数组 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 interface IArrayItem { id : number name : string parentId : number } interface ITreeNode { id : number name : string children?: ITreeNode [] } function convert (root: ITreeNode ): IArrayItem [] { const nodeToParent : Map <ITreeNode , ITreeNode > = new Map () const arr : IArrayItem [] = [] const queue : ITreeNode [] = [] queue.unshift (root) while (queue.length ) { const currNode = queue.pop () const { id, name, children = [] } = currNode const parentNode = nodeToParent.get (currNode) const parentId = parentNode?.id || 0 const item = { id, name, parentId } arr.push (item) children.forEach (child => { nodeToParent.set (child, currNode) queue.unshift (child) }) } return arr } const tree = { id : 1 , name : '部门 A' , children : [ { id : 2 , name : '部门 B' , children : [ { id : 4 , name : '部门 D' }, { id : 5 , name : '部门 E' } ] }, { id : 3 , name : '部门 C' , children : [ { id : 6 , name : '部门 F' } ] } ] } const arr = convert (tree)console .log (arr)
统计 SDK 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 const PV_URL_SET = new Set ()class MyStatistic { constructor (productId ) { this .productId = productId this .initPerformance () this .initError () } send (url, params = {} ) { params.productId = productId const paramArr = [] for (let key in params) { const value = params[key] paramArr.push (`${key} =${value} ` ) } const img = document .createElement ('img' ) img.src = `${url} ?${paramArr.join('&' )} ` } initPerformance ( ) { const url = '' this .send (url, performance.timing ) } initError ( ) { window .addEventListener ('error' , event => { const { error, lineno, colno } = event this .error (error, { lineno, colno }) }) window .addEventListener ('unhandledrejection' , event => { this .error (new Error (event.reason ), { type : 'unhandledrejection' }) }) } pv ( ) { const href = location.href if (PV_URL_SET .get (href)) return this .event ('pv' , {}) PV_URL_SET .add (href) } event (key, val ) { const url = '' this .send (url, {key, val}) } error (err, info = {} ) { const url = '' const { message, stack } = err this .send (url, { message, stack, ...info }) } }
图片懒加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function imgLazyLoad ( ) { const images = document .querySelectorAll ('img[data-src]' ) images.forEach (img => { const rect = img.getBoundingClientRect () if (rect.top < window .innerHeight ) { img.src = img.dataset .src img.removeAttribute ('data-src' ) } }) } window .addEventListener ('scroll' , throttle (() => { imgLazyLoad () })) imgLazyLoad ()
UUID 1 2 3 4 5 6 7 8 9 10 11 12 function generateUUID ( ) { let timestamp = new Date ().getTime () if (window .performance && typeof window .performance .now === 'function' ) { timestamp += performance.now () } const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' .replace (/[xy]/g , function (item ) { let random = (timestamp + Math .random () * 16 ) % 16 | 0 timestamp = Math .floor (timestamp / 16 ) return (item == 'x' ? random : (random & 0x3 ) | 0x8 ).toString (16 ) }) return uuid }
内存泄漏 垃圾回收 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 foo ( ) { globalVariable = '' this .bigData = {} } function fn ( ) { const obj = { v : 100 } window .obj = obj } function genDataFn ( ) { const data = {} return { get (key ) { return data[key] }, set (key, val ) { data[key] = val } } } const { get, set } = genDataFn ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const bigData = { js : { }, css : {}, html : {} } const _obj = bigDatabigData = 'other' const _class = _obj.js _obj = 'other' _class = null
垃圾回收机制 引用计数 ❌ 早期的垃圾回收算法,以数据是否被引用
用来判断要不要回收,这个算法有一个缺陷循环引用
1 2 3 4 5 6 7 function fn ( ) { const obj1 = {} const obj2 = {} obj1.a = obj2 obj2.b = obj1 } fn ()
早期 IE6、7 使用引用计数算法进行垃圾回收,常常因为循环引用导致 DOM 对象无法进行垃圾回收
1 2 3 4 5 6 7 let elemwindow .onload = function ( ) { elem = document .getElementById ('elem' ) elem.customAttribute = elem elem.someBigData = { ... } }
不希望它存在的,它却仍然存在 ,这是不符合预期的,这就是内存泄漏
标记清除 👍 基于上面的问题,现代浏览器使用标记清除算法,根据数据是否可获得
来判断是否回收
内存泄漏场景 Vue 组件中全局变量、定时器、注册全局事件、自定义事件,组件销毁时要记得清空解绑
闭包,变量销毁不了,是内存泄漏吗? 不一定 闭包它是符合开发者预期的,即本身就这么设计的;而内存泄漏是非预期的 但是,也有会认为不可被垃圾回收就是内存泄漏
🤷
可使用 Chrome devTools Performance 检测内存变化
早期前端不太关注内存泄漏,因为不会像服务端一样 7*24 运行 而随着现在富客户端系统不断出现,内存泄漏也在慢慢地被重视
WeakMap WeakSet 弱引用,不会影响垃圾回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const wMap = new WeakMap ()function fn ( ) { const obj = { v : 100 } wMap.set (obj, 100 ) } fn ()const wSet = new WeakSet ()function fn ( ) { const obj = { v : 100 } wSet.add (obj) } fn ()
Event Loop 事件循环
Call Stack 清空
执行微任务
尝试 DOM 渲染
触发 Event Loop
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 Promise .resolve ().then (() => { console .log (0 ) return Promise .resolve (4 ) }).then ((res ) => { console .log (res) }).then (() => { console .log (6 ) }) Promise .resolve ().then (() => { console .log (1 ) }).then (() => { console .log (2 ) }).then (() => { console .log (3 ) }).then (() => { console .log (5 ) }).then (() => { console .log (7 ) }) Promise .resolve ().then (() => { console .log ('第一拍' ) const p = Promise .resolve (4 ) Promise .resolve ().then (() => { console .log ('第二拍' ) p.then (res => { console .log (res, '慢两拍' ) }).then (() => { console .log (6 , '慢两拍' ) }) }) })
异步 单线程
JS 是单线程的,浏览器中 JS 和 DOM 渲染线程互斥
解决方案: 异步
实现原理: Event Loop
宏任务 微任务 在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task。
宏任务(ES6语法规定的)Callback Queue
script
setTimeout、setInterval
setImmediate
requestAnimationFrame、requestIdleCallback
UI渲染、 网络请求、I/O、DOM事件
微任务(浏览器规定的)MicroTask Queue
promise.then、async/await
queueMicrotask
Object.observe
MutationObserver
process.nextTick(Nodejs)
微任务在 DOM 渲染前执行,而宏任务在 DOM 显示后执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const p = document .createElement ('p' )p.innerHTML = 'new paragraph' document .body .appendChild (p)const list = document .getElementsByTagName ('p' )console .log (list.length )console .log ('start' )setTimeout (() => { console .log ('timeout ' , list.length ) alert ('timeout 阻塞' ) }) Promise .resolve ().then (() => { console .log ('promise.then ' , list.length ) alert ('promise.then 阻塞' ) }) console .log ('end' )
浏览器的 Event-Loop 由各个浏览器自己实现;而 Node 的 Event-Loop 由 libuv 来实现
浏览器
Node
执行异步任务都是以批量的形式,一队一队
地执行
循环形式为:宏任务队列 -> 微任务队列 -> 宏任务队列 -> 微任务队列 … 这样交替进行
Node 11 事件循环已与浏览器事件循环机制趋同
宏任务
微任务
process.nextTick优先级高
promise.then 和 async/await
process.nextTick
是在当前帧结束后立即执行,会阻断 IO 并且有最大数量限制
setImmediate
不会阻断 IO ,更像是setTimeout(fun, 0)
,推荐使用 👍
Hybrid App Hybrid App 兼具Native App 良好用户交互体验
和Web App 跨平台开发
的优势
自定义 Schema 协议 类似weixin://
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const sdk = { invoke (url, data, success, err ) { const iframe = document .createElement ('iframe' ) iframe.style .visibility = 'hidden' document .body .appendChild (iframe) iframe.onload = () => { const content = iframe.contentWindow .document .body .innerHTML success (JSON .parse (content)) iframe.remove () } iframe.onerror = () => { err () iframe.remove () } iframe.src = `my-app-name://${url} ?data=${JSON .string(data)} ` } }
单点登录 cookie 如果业务系统都在同一主域名下,比如 wenku.baidu.com
tieba.baidu.com
可以把 cookie domain
设置为主域名 .baidu.com
SSO 复杂一点的,滴滴 同时拥有 didichuxing.com
xiaojukeji.com
didiglobal.com
等域名,则需要使用 SSO 技术方案
SSO 是 oAuth 的实际案例,其他常见的还有微信登录、github 登录等。当涉及到第三方用户登录校验时,都会使用 OAuth2.0 标准
Cookie
http 请求是无状态的,每次请求之后都会断开连接
所以,每次请求时,都可以携带一段信息发送到服务端,以表明客户端的用户身份。服务端也可以通过 set-cookie
向客户端设置 cookie 内容
由于每次请求都携带 cookie,所以 cookie 大小限制 4kb 以内
本地存储 cookie 作为本地存储,并不完全合适
localStorage
sessionStorage
跨域限制 浏览器存储 cookie 是按照域名区分的,浏览器无法通过 JS document.cookie
获取到其他域名的 cookie
http 请求传递 cookie 默认有跨域限制,如果想要开启,需要客户端和服务器同时设置允许
客户端:使用 fetch 和 XMLHttpRequest 或者 axios 需要配置 withCredentials
服务端:需要配置 header Access-Control-Allow-Credentials
禁用第三方 cookie 现代浏览器都开始禁用第三方 cookie(第三方 js 设置 cookie),打击第三方广告,保护用户个人隐私
例如一个电商网站 A 引用了淘宝广告的 js
当访问 A 时,淘宝 js 设置 cookie,记录下商品信息
你再次访问淘宝时,淘宝即可获取这个 cookie 内容
并和你的个人信息一起发送到服务端,方便精准推荐
cookie + session 登录校验
前端输入用户名密码,传给后端
后端验证成功,返回信息时 set-cookie
客户端 cookie 存储 sessionId,不含用户敏感信息
用户信息存储在 session 中,session 是服务端的一个 hash 表
JSON Web Token
cookie
http 规范;默认存储;有跨域限制;配合 session 实现登录;只支持浏览器;有 CSRF 风险
token
自定义标准;自定义存储;无跨域限制;可用于 JWT 登录;还支持 App;无 CSRF 风险
Session
占用服务端内存,有硬件成本
多服务器 Redis 同步成本高
跨域传递 cookie,需要特殊配置
用户信息存储在服务端,可以快速封禁用户
JWT
不占用服务器内存
多进程、多服务器,不受影响
不受跨域限制
无法快速封禁登录用户
虚拟列表
URL URLSearchParams
1 2 new URLSearchParams (location.search ).get ('token' )new URL (location.href ).searchParams .get ('language' )