├── .gitignore ├── README.md ├── dataStructure ├── Queue │ ├── index.js │ └── test.js └── Stack │ ├── index.js │ └── test.js ├── feature ├── attribute │ └── MyPromise.js └── method │ ├── apply.js │ ├── bind.js │ ├── call.js │ ├── instanceof.js │ └── new.js ├── framework ├── react │ └── .gitkeep └── vue │ ├── mini-vue-router │ ├── components │ │ ├── Link.js │ │ └── View.js │ ├── create-macher.js │ ├── create-route-map.js │ ├── history │ │ ├── base.js │ │ ├── hash.js │ │ └── html5.js │ ├── index.js │ ├── install.js │ └── util │ │ └── route.js │ └── mini-vue │ ├── compiler.js │ ├── dep.js │ ├── index.html │ ├── observer.js │ ├── vue.js │ └── watcher.js ├── functions ├── Array │ ├── every.js │ ├── filter.js │ ├── find.js │ ├── flat.js │ ├── forEach.js │ ├── includes.js │ ├── map.js │ ├── reduce.js │ └── some.js ├── Object │ ├── assign.js │ ├── orderAssign.js │ └── reverseAssign.js └── utils │ ├── compose.js │ ├── curry.js │ ├── debounce.js │ ├── deepClone.js │ ├── getType.js │ ├── getUrlData.js │ ├── isEqual.js │ ├── memoize.js │ └── throttle.js ├── upload.sh └── work └── performance ├── FCP.js └── FMP.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | 16 | # fackAchieve 17 | 18 | 手写 es6 函数,Promise 特性,lodash 库的函数实现,模拟 vue,React 等前端框架的实现和原理的理解。 19 | 20 | ## functions 文件夹 21 | 22 | 手动实现各种函数,包括不限于 ES6 等函数的方法 23 | 24 | ### 内容对应表 25 | 26 | #### Array 27 | 28 | | 方法名称(name) | 位置(position) | 作用(effect) | 29 | | :------------- | :--------------------------------------------------------- | :----------: | 30 | | forEach | [functions/Array/forEach.js](functions/Array/forEach.js) | -- | 31 | | every | [functions/Array/every.js](functions/Array/every.js) | -- | 32 | | some | [functions/Array/some.js](functions/Array/some.js) | -- | 33 | | filter | [functions/Array/filter.js](functions/Array/filter.js) | -- | 34 | | find | [functions/Array/find.js](functions/Array/find.js) | -- | 35 | | reduce | [functions/Array/reduce.js](functions/Array/reduce.js) | -- | 36 | | map | [functions/Array/map.js](functions/Array/map.js) | -- | 37 | | flat | [functions/Array/flat.js](functions/Array/flat.js) | -- | 38 | | includes | [functions/Array/includes.js](functions/Array/includes.js) | -- | 39 | 40 | #### Object 41 | 42 | | 方法名称(name) | 位置(position) | 作用(effect) | 43 | | :------------- | :--------------------------------------------------------------------- | :----------------: | 44 | | assign | [functions/Object/assign.js](functions/Object/assign.js) | -- | 45 | | reverseAssign | [functions/Object/reverseAssign.js](functions/Object/reverseAssign.js) | 逆向 assign | 46 | | orderAssign | [functions/Object/orderAssign.js](functions/Object/orderAssign.js) | 逆向 reverseAssign | 47 | 48 | #### utils 49 | 50 | | 方法名称(name) | 位置(position) | 作用(effect) | 51 | | :------------- | :------------------------------------------------------------- | :-----------: | 52 | | memoize | [functions/utils/memoize.js](functions/utils/memoize.js) | 缓存结果 | 53 | | curry | [functions/utils/curry.js](functions/utils/curry.js) | 柯里化 | 54 | | compose | [functions/utils/compose.js](functions/utils/compose.js) | 合并函数 | 55 | | getType | [functions/utils/getType.js](functions/utils/getType.js) | 判断类型 | 56 | | isEqual | [functions/utils/isEqual.js](functions/utils/isEqual.js) | 判断值相等 | 57 | | deepClone | [functions/utils/deepClone.js](functions/utils/deepClone.js) | 深拷贝 | 58 | | getUrlData | [functions/utils/getUrlData.js](functions/utils/getUrlData.js) | 获取 url 参数 | 59 | | debounce | [functions/utils/debounce.js](functions/utils/debounce.js) | 函数防抖 | 60 | | throttle | [functions/utils/throttle.js](functions/utils/throttle.js) | 函数节流 | 61 | 62 | ## feature 文件夹 63 | 64 | 手动实现各种 ES6 新特性 65 | 66 | | 方法名称(name) | 位置(position) | 描述(desc) | 67 | | :------------- | :--------------------------------------------------------------- | :------------------------: | 68 | | MyPromise | [feature/attribute/MyPromise.js](feature/attribute/MyPromise.js) | 符合 A+规范的 Promise 实现 | 69 | | call | [feature/method/call.js](feature/method/call.js) | 手写 call 函数 | 70 | | apply | [feature/method/apply.js](feature/method/apply.js) | 手写 apply 函数 | 71 | | bind | [feature/method/bind.js](feature/method/bind.js) | 手写 bind 函数 | 72 | | new | [feature/method/new.js](feature/method/new.js) | new 构造方法 | 73 | | instanceof | [feature/method/instanceof.js](feature/method/instanceof.js) | instanceof 方法 | 74 | 75 | ## framework 文件夹 76 | 77 | 模拟 vue,React 等前端框架,了解原理 78 | 79 | | 方法名称(name) | 位置(position) | 描述(desc) | 80 | | :------------- | :---------------------------------------------------------------------- | :-------------: | 81 | | vue | [framework/vue/mini-vue](framework/vue/mini-vue/vue.js) | mini vue | 82 | | vue-router | [framework/vue/mini-vue-router](framework/vue/mini-vue-router/index.js) | mini vue-router | 83 | | react | [framework/react](framework/react) | mini react | 84 | 85 | ## dataStructure 文件夹 86 | 87 | 手动实现各种数据结构 88 | 89 | | 结构名称(name) | 位置(position) | 描述(desc) | 90 | | :------------- | :----------------------------------------------------------- | :--------: | 91 | | Stack | [dataStructure/Stack/index.js](dataStructure/Stack/index.js) | 栈结构 | 92 | | Queue | [dataStructure/Queue/index.js](dataStructure/Queue/index.js) | 队列结构 | 93 | 94 | ## work 文件夹 95 | 96 | 工作中,各种场景会碰到的常用方法或类实现 97 | 98 | | 名称(name) | 位置(position) | 描述(desc) | 99 | | :--------- | :------------------------------------------------- | :--------------: | 100 | | FMP | [work/performance/FMP.js](work/performance/FMP.js) | 首次有效绘制时间 | 101 | | FCP | [work/performance/FCP.js](work/performance/FCP.js) | 首屏时间 | 102 | -------------------------------------------------------------------------------- /dataStructure/Queue/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ES6语法构建队数据结构 3 | */ 4 | class Queue{ 5 | constructor() { 6 | this._queue = []; 7 | } 8 | // 查询队顶元素 9 | top() { 10 | return this._queue[0]; 11 | } 12 | // 元素出队 13 | pop() { 14 | this._queue.shift(); 15 | } 16 | // 元素入队 17 | push(item) { 18 | this._queue.push(item); 19 | } 20 | // 查询队尾元素 21 | peek() { 22 | return this._queue[this._queue.length - 1]; 23 | } 24 | // 查询队元素总数 25 | length() { 26 | return this._queue.length; 27 | } 28 | // 清空队 29 | clear() { 30 | this._queue = []; 31 | } 32 | // 队是否为空 33 | isEmpty() { 34 | return this._queue.length === 0; 35 | } 36 | // 获取当前队 37 | getStack() { 38 | return this._queue; 39 | } 40 | // 获取队无符号间隔的字符串 41 | getString() { 42 | let str = ""; 43 | this._queue.map((item) => (str += item)); 44 | return str; 45 | } 46 | reverseStack() { 47 | return this._queue.reverse(); 48 | } 49 | // 获取队无符号间隔的反向字符串 50 | getReverseString() { 51 | let str = ""; 52 | this._queue.map((item) => (str += item)); 53 | return str.split('').reverse().join('') 54 | } 55 | print(){ 56 | console.log(this._queue) 57 | } 58 | } 59 | module.exports = Queue -------------------------------------------------------------------------------- /dataStructure/Queue/test.js: -------------------------------------------------------------------------------- 1 | const Queue = require("./index") 2 | 3 | 4 | let q = new Queue() 5 | q.push('1') 6 | q.push('2') 7 | q.push('3') 8 | q.push('4') 9 | q.push('5') 10 | 11 | q.pop() 12 | q.pop() 13 | console.log(q.getString()) 14 | q.print() -------------------------------------------------------------------------------- /dataStructure/Stack/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ES6语法构建栈数据结构 3 | */ 4 | class Stack { 5 | constructor() { 6 | this._stack = []; 7 | } 8 | // 查询栈顶元素 9 | top() { 10 | return this._stack[this._stack.length - 1]; 11 | } 12 | // 元素出栈 13 | pop() { 14 | this._stack.pop(); 15 | } 16 | // 元素入栈 17 | push(item) { 18 | this._stack.push(item); 19 | } 20 | // 查询栈底元素 21 | peek() { 22 | return this._stack[0]; 23 | } 24 | // 查询栈元素总数 25 | length() { 26 | return this._stack.length; 27 | } 28 | // 清空栈 29 | clear() { 30 | this._stack = []; 31 | } 32 | // 是否为空 33 | isEmpty() { 34 | return this._stack.length === 0; 35 | } 36 | // 获取当前栈 37 | getStack() { 38 | return this._stack; 39 | } 40 | // 获取栈无符号间隔的字符串 41 | getString() { 42 | let str = ""; 43 | this._stack.map((item) => (str += item)); 44 | return str; 45 | } 46 | reverseStack() { 47 | return this._stack.reverse(); 48 | } 49 | // 获取栈无符号间隔的反向字符串 50 | getReverseString() { 51 | let str = ""; 52 | this._stack.map((item) => (str += item)); 53 | return str.split("").reverse().join(""); 54 | } 55 | print() { 56 | console.log(this._stack); 57 | } 58 | } 59 | 60 | module.exports = Stack; 61 | -------------------------------------------------------------------------------- /dataStructure/Stack/test.js: -------------------------------------------------------------------------------- 1 | const Stack = require("./index") 2 | 3 | 4 | /** 5 | * 进制转换 6 | * @param {*} num 需要转换的10进制数 7 | * @param {*} base 需要转换的进制 8 | * @returns 9 | */ 10 | function scale(num, base) { 11 | let s = new Stack(); 12 | while (num > 0) { 13 | s.push(num % base); 14 | num = Math.floor((num /= base)); 15 | } 16 | return s.getReverseString(); 17 | } 18 | console.log(scale(125, 2)); // 1111101 19 | console.log(scale(125, 8)); // 175 20 | -------------------------------------------------------------------------------- /feature/attribute/MyPromise.js: -------------------------------------------------------------------------------- 1 | // 用常量定义promise的三种状态 2 | const PENDING = 'pending'; 3 | const FULFILLED = 'fulfilled'; 4 | const REJECTED = 'rejected'; 5 | 6 | class MyPromise { 7 | constructor(execurte) { 8 | // 默认状态是等待 9 | this.status = PENDING 10 | // 成功的回调默认值 11 | this.value = undefined 12 | // 失败的回调默认值 13 | this.resaon = undefined 14 | // 成功的回调队列,可以多次then,所以存在多个定义为数组 15 | this.resolveCallBacks = [] 16 | // 失败的回调 17 | this.rejecteCallBacks = [] 18 | // 针对执行器进行异常处理 19 | try { 20 | execurte(this.resolve, this.reject) 21 | } catch (error) { 22 | this.reject(error) 23 | } 24 | } 25 | 26 | // 成功时候的回调 27 | resolve = (value) => { 28 | queueMicrotask(() => { 29 | if (this.status === PENDING) { 30 | this.status = FULFILLED; // 修改状态 31 | this.value = value; 32 | this.resolveCallBacks.forEach((fn) => fn(this.value)); // 成功的回调 33 | } 34 | }) 35 | } 36 | // 失败时候的回调 37 | reject = (resaon) => { 38 | queueMicrotask(() => { 39 | if (this.status === PENDING) { 40 | this.status = REJECTED; // 修改状态 41 | this.resaon = resaon; 42 | this.rejecteCallBacks.forEach((fn) => fn(this.resaon)); // 失败的回调 43 | } 44 | }) 45 | } 46 | // then方法 47 | then = (resolveCallBack, rejecteCallBack) => { 48 | // 如果传递空值,则默认向后传递所以添加一个默认情况 49 | resolveCallBack = resolveCallBack ? resolveCallBack : value => value; 50 | // 参数可选 51 | rejecteCallBack = rejecteCallBack ? rejecteCallBack : reason => { throw reason }; 52 | let p = new MyPromise((resolve, reject) => { 53 | // 处理不同的返回,如果是正常值直接返回,如果是Promise对象,则返回一个Promise供继续调用 54 | // 成功 55 | if (this.status === FULFILLED) { 56 | // 开启一个微任务,等待p结果的返回。否则程序限制性后返回p的值 57 | // 针对执行的函数进行异常处理 58 | queueMicrotask(() => { 59 | try { 60 | let callbackValue = resolveCallBack(this.value) 61 | this._returnValue(p, callbackValue, resolve, reject) 62 | } catch (error) { 63 | reject(error) 64 | } 65 | }) 66 | // 失败 67 | } else if (this.status === REJECTED) { 68 | queueMicrotask(() => { 69 | try { 70 | let callbackValue = rejecteCallBack(this.resaon) 71 | this._returnValue(p, callbackValue, resolve, reject) 72 | } catch (error) { 73 | reject(error) 74 | } 75 | }) 76 | // 等待过程 77 | } else { 78 | // 判断为等待状态的情况,存储任务然后后续执行 79 | // 存储成功的任务 80 | this.resolveCallBacks.push(() => { 81 | queueMicrotask(() => { 82 | try { 83 | let callbackValue = resolveCallBack(this.value) 84 | this._returnValue(p, callbackValue, resolve, reject) 85 | } catch (error) { 86 | reject(error) 87 | } 88 | }) 89 | }) 90 | // 存储失败的情况 91 | this.rejecteCallBacks.push(() => { 92 | queueMicrotask(() => { 93 | try { 94 | let callbackValue = rejecteCallBack(this.resaon) 95 | this._returnValue(p, callbackValue, resolve, reject) 96 | } catch (error) { 97 | reject(error) 98 | } 99 | }) 100 | }) 101 | } 102 | }) 103 | return p 104 | } 105 | 106 | // 注册一个非静态的方法,catch收集错误信息 107 | catch(rejecteCallBack) { 108 | return this.then(undefined, rejecteCallBack) 109 | } 110 | 111 | // 注册一个非静态的方法,无论成功或者失败finally都会执行 112 | finally(callback) { 113 | return this.then((value) => { 114 | return MyPromise.resolve(callback()).then(() => value) 115 | }, (resaon) => { 116 | return MyPromise.resolve(callback()).then(() => { throw resaon }) 117 | }) 118 | } 119 | 120 | // then可能返回一个普通值,也可能返回一个 Promise,一个内置工具 121 | /** 122 | * 123 | * @param {*} p 当前在运行的Promise 124 | * @param {*} callbackValue 返回值(then出来的值) 125 | * @param {*} resolve 成功回调 126 | * @param {*} reject 失败回调 127 | * @returns 128 | */ 129 | _returnValue(p, callbackValue, resolve, reject) { 130 | // 如果p和callbackValue相等,则说明产生了循环引用 131 | if (p === callbackValue) { 132 | return reject(new TypeError('靓仔,你的代码循环引用了')) 133 | } 134 | // 判断callbackValue是不是Promise类型 135 | if (callbackValue instanceof MyPromise) { 136 | callbackValue.then(value => resolve(value), resaon => reject(resaon)) 137 | } else { 138 | resolve(callbackValue) 139 | } 140 | } 141 | 142 | // 直接注册resolve方法,表示直接只返回一个成功的结果 143 | static resolve(value) { 144 | // 如果是promise对象则直接返回 145 | if (value instanceof MyPromise) { 146 | return value 147 | } else { 148 | // 如果不是promise对象,则重新创建一个 149 | return new MyPromise((resolve) => { 150 | resolve(value) 151 | }) 152 | } 153 | } 154 | 155 | // 静态方法,返回错误的Promise 156 | static reject(resaon) { 157 | if (resaon instanceof MyPromise) { 158 | return this.reject('[object Promise]') 159 | } else { 160 | // 如果不是promise对象,则重新创建一个 161 | return new MyPromise((resolve, reject) => { 162 | reject(resaon) 163 | }) 164 | } 165 | } 166 | 167 | // all静态方法,有一个失败,直接返回失败,结果是按照传入的顺序返回 168 | static all(promises) { 169 | // 保存回调结果的数组 170 | let result = []; 171 | // 累加器,用来判断执行的方法队列是否执行完成 172 | let count = 0; 173 | // all 方法也返回一个promise对象 174 | return new MyPromise((resolve, reject) => { 175 | function pushResult(key, value) { 176 | result[key] = value 177 | count++ 178 | // 如果累加器和执行的任务列表长度相等,则说明已经完成了整个任务 179 | if (count === promises.length) { 180 | resolve(result) 181 | } 182 | } 183 | // 循环处理要执行的任务 184 | promises.forEach((task, index) => { 185 | if (task instanceof MyPromise) { 186 | task.then((v) => pushResult(index, v), (resaon) => reject(resaon)) 187 | } else { 188 | pushResult(index, promises[index]) 189 | } 190 | }) 191 | }) 192 | } 193 | 194 | 195 | // 所有 Promises 都完成后(包含成功和失败) 196 | static allSettled(promises) { 197 | return new MyPromise((resolve) => { 198 | let results = [] 199 | let count = 0 200 | promises.forEach((task, index) => { 201 | if (task instanceof MyPromise) { 202 | task.finally(_ => { 203 | count++ 204 | results[index] = { 205 | status: task.status, 206 | value: task.value || task.resaon 207 | } 208 | if (count === promises.length) { 209 | resolve(results) 210 | } 211 | }) 212 | } else { 213 | count++ 214 | results[index] = { 215 | status: 'fulfilled', 216 | value: task 217 | } 218 | if (count === promises.length) { 219 | resolve(results) 220 | } 221 | } 222 | }) 223 | }) 224 | } 225 | 226 | // 有一个成功就返回 227 | static any(promises) { 228 | return new MyPromise((resolve) => { 229 | promises.forEach((task) => { 230 | if (task instanceof MyPromise) { 231 | task.then(_ => { 232 | resolve(task.value) 233 | }) 234 | } else { 235 | resolve(task) 236 | } 237 | }) 238 | }) 239 | } 240 | 241 | // 有一个改变状态(成功或者失败)就返回 242 | static race(promises) { 243 | return new MyPromise((resolve) => { 244 | promises.forEach((task) => { 245 | if (task instanceof MyPromise) { 246 | task.finally(_ => { 247 | resolve(task.value || task.resaon) 248 | }) 249 | } else { 250 | resolve(task) 251 | } 252 | }) 253 | }) 254 | } 255 | } 256 | 257 | MyPromise.defer = MyPromise.deferred = function () { 258 | let testObj = {} 259 | testObj.promise = new Promise((resolve, reject) => { 260 | testObj.resolve = resolve 261 | testObj.reject = reject 262 | }) 263 | return testObj 264 | } 265 | 266 | module.exports = MyPromise -------------------------------------------------------------------------------- /feature/method/apply.js: -------------------------------------------------------------------------------- 1 | // 思路同call,参数不一样 2 | const arr = [1, 2, 3, 4, 5] 3 | function fackApply(context, arg) { 4 | if (typeof this !== 'function') { 5 | throw new TypeError('当前调用apply方法的不是函数!') 6 | } 7 | const flag = Symbol('function') 8 | const callback = context || (typeof window !== 'undefined' ? window : globalThis) 9 | callback[flag] = this 10 | // 同call一样,只是参数需要展开 11 | const result = callback[flag](...arg) 12 | delete callback[flag] 13 | return result 14 | } 15 | 16 | Function.prototype.fackApply = fackApply 17 | 18 | // const max = Math.max(arr); 19 | 20 | // const max = Math.max.apply(null, arr); 21 | 22 | const max = Math.max.fackApply(null, arr); 23 | 24 | console.log(max) -------------------------------------------------------------------------------- /feature/method/bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 编写思路 3 | * 1.返回一个原返回一个函数 4 | * 2.传递参数并绑定传入的this 5 | * 3.可以通过new调用 6 | */ 7 | const user = { 8 | x: 42, 9 | getX: function () { 10 | return this.x; 11 | } 12 | }; 13 | 14 | function fackBind(thisArg, ...args) { 15 | if (typeof this !== 'function') { 16 | throw new TypeError('当前调用bind方法的不是函数!') 17 | } 18 | const callback = thisArg || (typeof window !== 'undefined' ? window : globalThis) 19 | return () => this.apply(callback, args) 20 | } 21 | 22 | // MDN版本 23 | function fackBind2(otherThis) { 24 | if (typeof this !== 'function') { 25 | throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); 26 | } 27 | var ArrayPrototypeSlice = Array.prototype.slice; 28 | var baseArgs = ArrayPrototypeSlice.call(arguments, 1), 29 | baseArgsLength = baseArgs.length, 30 | fToBind = this, 31 | fNOP = function () { }, 32 | fBound = function () { 33 | baseArgs.length = baseArgsLength; // reset to default base arguments 34 | baseArgs.push.apply(baseArgs, arguments); 35 | return fToBind.apply( 36 | fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs 37 | ); 38 | }; 39 | 40 | if (this.prototype) { 41 | fNOP.prototype = this.prototype; 42 | } 43 | fBound.prototype = new fNOP(); 44 | 45 | return fBound; 46 | }; 47 | 48 | Function.prototype.fackBind = fackBind 49 | Function.prototype.fackBind2 = fackBind2 50 | 51 | const generator = user.getX; 52 | 53 | const a = generator.bind(user); 54 | console.log(a()); 55 | 56 | const b = generator.fackBind(user); 57 | console.log(b()); 58 | 59 | const c = generator.fackBind2(user); 60 | console.log(c()); 61 | -------------------------------------------------------------------------------- /feature/method/call.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 编写思路 3 | * 1.确定函数的作用域 4 | * 2.存储执行函数的执行结果 5 | * 3.删除零时函数并返回 6 | */ 7 | 8 | const fackCall = function (context, ...args) { 9 | if (typeof this !== 'function') { 10 | throw new TypeError('当前调用call方法的不是函数!') 11 | } 12 | // 定义一个私有标识符 13 | const flag = Symbol('function') 14 | // 确定返回函数的作用域, 默认是window,区分node和浏览器环境 15 | const callback = context || (typeof window !== 'undefined' ? window : globalThis) 16 | // 把要执行函数的函数体 复制到临时的函数中 17 | callback[flag] = this 18 | // 保存返回的结果 19 | const result = callback[flag](...args) 20 | // 删除临时变量 21 | delete callback[flag] 22 | // 返回结果 23 | return result 24 | } 25 | 26 | 27 | Function.prototype.fackCall = fackCall 28 | 29 | function Product(name, price) { 30 | this.name = name; 31 | this.price = price; 32 | } 33 | function Food(name, price) { 34 | // Product(name, price) 没有this会找不到 35 | // Product.call(this, name, price); 36 | Product.fackCall(this, name, price); 37 | } 38 | 39 | console.log(new Food('张三', 5).name); 40 | console.log(new Food('张三', 5).price); 41 | -------------------------------------------------------------------------------- /feature/method/instanceof.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @description 编写思路 4 | * 1. 不支持运算符重载,通过函数穿参数解决 5 | * 2. 判断对象A是否在对象B的原型链上 6 | * 3. 如果不存在就一直查询,一直到顶层 7 | * 3. 返回一个布尔值 8 | */ 9 | function Car(make, model, year) { 10 | this.make = make; 11 | this.model = model; 12 | this.year = year; 13 | } 14 | 15 | const auto = new Car('Honda', 'Accord', 1998); 16 | 17 | 18 | function fackInstanceof(detectObject, souceObject) { 19 | let leftObj = Object.getPrototypeOf(detectObject) // 相当于detectObject.__proto__ 20 | let rightObj = souceObject.prototype // 获取源对象原型 21 | // 循环获取detectObject对象的原型 22 | while (true) { 23 | // 如果是null直接返回(第一次或者原型链顶) 24 | if (Object.is(leftObj, null)) return false 25 | // 如果第一次相等直接返回 26 | if (Object.is(leftObj, rightObj)) return true 27 | // 不相等,继续向上找 28 | leftObj = Object.getPrototypeOf(leftObj) 29 | } 30 | } 31 | 32 | console.log(auto instanceof Car); 33 | 34 | console.log(auto instanceof Object); 35 | 36 | console.log(fackInstanceof(auto, Car)); 37 | 38 | console.log(fackInstanceof(auto, Object)); -------------------------------------------------------------------------------- /feature/method/new.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 编写思路 3 | * 1. js不支持运算符重载,所以第一个参数是constructor,剩余的参数是arguments 4 | * 2. 创建一个新对象,并链接构造函数到运算的对象 5 | * 3. 绑定对象的this 6 | * 4. 如果没对象返回this,有对象返回新对象 7 | */ 8 | 9 | const fackNew = function (thisArg, ...args) { 10 | if (typeof thisArg !== 'function') { 11 | throw new TypeError('当前调用fackNew的不是函数!') 12 | } 13 | // 定义一个空对象 14 | let temoObj = {} 15 | // 链接该对象(设置该对象的constructor)到另一个对象 ,继承旧对象的原型 16 | // Object.setPrototypeOf(temoObj, thisArg.prototype); 17 | temoObj = Object.create(thisArg.prototype); // 推荐这种 18 | // 设置临时对象的this 19 | thisArg.apply(temoObj, args) 20 | // 定义要返回的对象 21 | let returnObj = temoObj 22 | // 如果函数没有返回对象,则返回this 23 | if (typeof thisArg === 'object') { 24 | returnObj = this 25 | } 26 | return returnObj 27 | } 28 | 29 | // 精简代码版本 30 | const _new = function (thisArg, ...args) { 31 | const temoObj = Object.create(thisArg.prototype) 32 | thisArg.apply(temoObj, args) 33 | return typeof thisArg === 'object' ? this : temoObj 34 | } 35 | 36 | 37 | 38 | function Car(name, age) { 39 | this.name = name; 40 | this.age = age; 41 | } 42 | 43 | const car1 = new Car('张三', 1993); 44 | 45 | console.log(car1.name); 46 | 47 | 48 | const car2 = _new(Car, '李四', 1998); 49 | 50 | console.log(car2.name); -------------------------------------------------------------------------------- /framework/react/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gzg1023/fackAchieve/472a586e6c201c2e8a93f9fadd42cb9b428e6c38/framework/react/.gitkeep -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/components/Link.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'routerLink', 3 | props: { 4 | to: { 5 | type: String, 6 | require: true 7 | } 8 | }, 9 | render(h) { 10 | return h('a', 11 | { 12 | attrs: { 13 | href: this.$router.mode === 'hash' ? `#${this.to}` : `${this.to}` 14 | } 15 | }, this.$slots.default) 16 | } 17 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/components/View.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'routerView', 3 | render(h) { 4 | const route = this.$route 5 | let depth = 0; 6 | this.isRouterVirw = true 7 | let parent = this.$parent 8 | while (parent) { 9 | if(parent.isRouterVirw){ 10 | depth ++ 11 | } 12 | parent = parent.$parent 13 | } 14 | const record = route.matched[depth] 15 | if(record){ 16 | return h(record.component) 17 | } 18 | return h() 19 | } 20 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/create-macher.js: -------------------------------------------------------------------------------- 1 | import createRouteMap from "./create-route-map"; 2 | import createRoute from "./util/route"; 3 | export default function createMacher(routes) { 4 | const { pathList, pathMap } = createRouteMap(routes) 5 | 6 | // 匹配路由的函数,通过路径拿到组件 7 | function match(path) { 8 | const record = pathMap[path] 9 | if(record){ 10 | return createRoute(record,path) 11 | } 12 | return createRoute(null,path) 13 | } 14 | // 动态添加路由 15 | function addRoutes(routes) { 16 | createRouteMap(routes, pathList, pathMap) 17 | } 18 | return{ 19 | match, 20 | addRoutes 21 | } 22 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/create-route-map.js: -------------------------------------------------------------------------------- 1 | // 解析routers,生成路由表 2 | export default function createRouteMap(routes, pathList, pathMap) { 3 | // 路由路径表 4 | pathList = pathList ?? [] 5 | // 路径映射表 6 | pathMap = pathMap ?? {} 7 | 8 | routes.forEach(route => { 9 | addRouteRecord(route, pathList, pathMap) 10 | }); 11 | 12 | return { 13 | pathList, 14 | pathMap 15 | } 16 | } 17 | 18 | function addRouteRecord(route, pathList, pathMap, parentRecord) { 19 | // 处理父子路由 20 | let path = parentRecord ? `${parentRecord.path}/${route.path}` : route.path 21 | let record = { 22 | path: path, 23 | component: route.component, 24 | parentRecord: parentRecord 25 | } 26 | // 如果不存在则添加 27 | if (!pathMap[path]) { 28 | pathList.push(path) 29 | pathMap[path] = record 30 | } 31 | // 处理子路由 32 | if (route.children) { 33 | route.children.forEach((subRoute) => { 34 | addRouteRecord(subRoute, pathList, pathMap, record) 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/history/base.js: -------------------------------------------------------------------------------- 1 | import createRoute from "../util/route"; 2 | export default class History { 3 | constructor(router) { 4 | this.router = router 5 | // 当前路由对象。通过match创建的对象 6 | this.current = createRoute(null, '/') 7 | this.callback = null 8 | } 9 | 10 | /** 11 | * 路由跳转函数 12 | * @param {*} path 要跳转的路径 13 | * @param {*} onComplete 回调函数 14 | */ 15 | transitionTo(path, onComplete) { 16 | this.current = this.router.matcher.match(path) 17 | this.callback && this.callback(this.current) 18 | onComplete && onComplete() 19 | } 20 | 21 | 22 | listen(callback){ 23 | this.callback = callback 24 | } 25 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/history/hash.js: -------------------------------------------------------------------------------- 1 | import History from "./base" 2 | 3 | export default class hashHistory extends History{ 4 | constructor(router){ 5 | super(router) 6 | // 修正浏览器地址栏。改为/#/ 7 | this.ensureSlash() 8 | } 9 | 10 | 11 | ensureSlash(){ 12 | if(location.hash){ 13 | return 14 | }else{ 15 | location.hash = '/' 16 | } 17 | } 18 | // 返回当前路由地址,去除#符号 19 | getCurrentLocation(){ 20 | return location.hash.substr(1) 21 | } 22 | 23 | // 设置hash监听 24 | setUpListener(){ 25 | window.addEventListener('hashchange',() =>{ 26 | this.transitionTo(this.getCurrentLocation()) 27 | }) 28 | } 29 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/history/html5.js: -------------------------------------------------------------------------------- 1 | import History from "./base" 2 | 3 | export default class HTML5History extends History{ 4 | constructor(router){ 5 | super(router) 6 | } 7 | 8 | 9 | // 返回当前路由地址,去除#符号 10 | getCurrentLocation(){ 11 | return location.pathname 12 | } 13 | 14 | // 设置history监听 15 | setUpListener(){ 16 | window.addEventListener('popstate',() =>{ 17 | this.transitionTo(this.getCurrentLocation()) 18 | }) 19 | } 20 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/index.js: -------------------------------------------------------------------------------- 1 | 2 | import install from './install' 3 | import createMacher from './create-macher'; 4 | import HTML5History from "./history/html5"; 5 | import HashHistory from "./history/hash"; 6 | export default class VueRouter { 7 | constructor(options) { 8 | this._routes = options.routes || [] 9 | this.matcher = createMacher(this._routes) 10 | let mode = options.mode || 'hash' 11 | this.mode = mode 12 | this.push = this.push 13 | switch (mode) { 14 | case 'history': 15 | this.history = new HTML5History(this, options.base) 16 | break; 17 | case 'hash': 18 | this.history = new HashHistory(this, options.base, this.feedback) 19 | break; 20 | default: 21 | throw new Error('type Error: mode is not a goog value') 22 | } 23 | } 24 | 25 | init(app) { 26 | const history = this.history 27 | 28 | history.listen((current)=>{ 29 | app._route = current 30 | }) 31 | 32 | history.transitionTo( 33 | history.getCurrentLocation(), 34 | () => { 35 | history.setUpListener() 36 | } 37 | ) 38 | } 39 | 40 | push(path) { 41 | if (this.mode === 'hash') { 42 | location.hash = '/' + path 43 | } else { 44 | location.pathname = path 45 | } 46 | } 47 | } 48 | VueRouter.install = install -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/install.js: -------------------------------------------------------------------------------- 1 | 2 | // 保存传入的Vue实例 3 | export let _Vue = null 4 | import Link from "./components/Link"; 5 | import View from "./components/View"; 6 | export default function install(Vue) { 7 | _Vue = Vue 8 | _Vue.mixin({ 9 | beforeCreate() { 10 | // 跟实例 11 | if (this.$options.router) { 12 | this._routerRoot = this 13 | this._router = this.$options.router 14 | this._router.init(this) 15 | _Vue.util.defineReactive(this, '_route', this._router.history.current) 16 | } else /* 组件*/ { 17 | this._routerRoot = this.$parent && this.$parent._routerRoot 18 | } 19 | } 20 | }) 21 | _Vue.component(View.name,View) 22 | _Vue.component(Link.name,Link) 23 | Object.defineProperty(Vue.prototype, '$router', { 24 | get () { return this._routerRoot._router } 25 | }) 26 | 27 | Object.defineProperty(Vue.prototype, '$route', { 28 | get () { return this._routerRoot._route } 29 | }) 30 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue-router/util/route.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 匹配路由对象 3 | * @param {*} record 4 | * @param {*} path 5 | * 6 | */ 7 | export default function createRoute(record, path) { 8 | const matched = [] 9 | while (record) { 10 | // 使用unshift是因为先拿到子路由,父路由要排到前面 11 | matched.unshift(record) 12 | record = record.parentRecord 13 | } 14 | return { 15 | path, 16 | matched 17 | } 18 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue/compiler.js: -------------------------------------------------------------------------------- 1 | class Compiler { 2 | constructor(vm) { 3 | this.el = vm.$el 4 | this.vm = vm 5 | this.compiler(this.el) 6 | } 7 | // 编译模版,处理各种节点 8 | compiler(el) { 9 | const childNodes = el.childNodes 10 | Array.from(childNodes).forEach((node) => { 11 | if (this.isTextNode(node)) { 12 | // 处理文本 13 | this.compilerText(node) 14 | } else if (this.isElementNode(node)) { 15 | // 处理元素 16 | this.compilerElement(node) 17 | } 18 | // 处理多层节点 19 | if (node.childNodes && node.childNodes.length !== 0) { 20 | this.compiler(node) 21 | } 22 | }) 23 | } 24 | // 编译元素节点,处理指令 25 | compilerElement(node) { 26 | Array.from(node.attributes).forEach((attr) => { 27 | let attrName = attr.name 28 | // 判断是否为指令 29 | if (this.isDirective(attrName)) { 30 | // 转化指令 31 | attrName = attrName.substr(2) 32 | let key = attr.value 33 | this.update(node, key, attrName) 34 | } 35 | }) 36 | } 37 | // 编译文本节点,处理差值表达式 38 | compilerText(node) { 39 | let reg = /\{\{(.+?)}\}/ 40 | let content = node.textContent 41 | if (reg.test(content)) { 42 | // 获取正则匹配的第一个内容 43 | let key = RegExp.$1.trim() 44 | node.textContent = content.replace(reg, this.vm[key]) 45 | // 触发依赖 46 | new Watcher(this.vm, key, (newValue) => { 47 | node.textContent = newValue 48 | }) 49 | } 50 | } 51 | // 判断元素是否为指令 52 | isDirective(attrName) { 53 | return attrName.startsWith('v-') 54 | } 55 | // 判断元素是否为文本节点 56 | isTextNode(node) { 57 | return node.nodeType === 3 58 | } 59 | // 判断元素是否为元素节点 60 | isElementNode(node) { 61 | return node.nodeType === 1 62 | } 63 | // 更新指令数据 64 | update(node, key, attrName) { 65 | let updateFn 66 | if (attrName.indexOf(':') !== -1) { 67 | attrName = attrName.substr(3) 68 | updateFn = this.onUpdater 69 | updateFn && updateFn.call(this, node, key, this.vm[key], attrName) 70 | } else { 71 | updateFn = this[attrName + 'Updater'] 72 | // 此处的this的Compiler对象 73 | updateFn && updateFn.call(this, node, key, this.vm[key]) 74 | } 75 | 76 | } 77 | // 处理v-text指令 78 | textUpdater(node, key, value) { 79 | // 文本节点的值用textContent 80 | node.textContent = value 81 | // 收集依赖 82 | new Watcher(this.vm, key, (newValue) => { 83 | node.textContent = newValue 84 | }) 85 | } 86 | // 处理v-model指令 87 | modelUpdater(node, key, value) { 88 | // 表单的值是value 89 | node.value = value 90 | // 收集依赖 91 | new Watcher(this.vm, key, (newValue) => { 92 | node.value = newValue 93 | }) 94 | // 双向绑定 95 | node.addEventListener('input', (e) => { 96 | console.log(e) 97 | this.vm[key] = node.value 98 | }) 99 | } 100 | 101 | // 处理v-show 102 | showUpdater(node, key, value) { 103 | if (value) { 104 | node.style.display = 'block' 105 | } else { 106 | node.style.display = 'none' 107 | } 108 | new Watcher(this.vm, key, (newValue) => { 109 | node.style.display = newValue ? 'block' : 'none' 110 | }) 111 | 112 | } 113 | // 处理v-on 114 | onUpdater(node, key, value, handleType) { 115 | // value = value.substr(2) 116 | console.log("🚀 onUpdater", node, key, value) 117 | node.addEventListener(handleType, (e) => { 118 | this.vm[key]() 119 | }) 120 | } 121 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue/dep.js: -------------------------------------------------------------------------------- 1 | // 观察者模式的 发布者 2 | class Dep { 3 | constructor() { 4 | // 收集依赖对象 5 | this.subs = [] 6 | } 7 | // 添加依赖对象 8 | addSub(sub) { 9 | if (sub && sub.update) { 10 | this.subs.push(sub) 11 | } 12 | } 13 | // 通知方法 14 | notify() { 15 | this.subs.forEach((sub) => { 16 | sub.update() 17 | }) 18 | } 19 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | mini vue 7 | 8 | 9 |
10 |

差值表达式

11 |

{{ msg }}

12 |
13 |

{{ count }}

14 |

{{ person }}

15 |
16 |

v-text

17 |
18 |

v-model

19 | 20 | 21 |

v-if

22 | 23 |

v-on

24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 52 | 53 | -------------------------------------------------------------------------------- /framework/vue/mini-vue/observer.js: -------------------------------------------------------------------------------- 1 | class Observer { 2 | constructor(targetData) { 3 | this.walk(targetData) 4 | } 5 | // 遍历对象所有属性 6 | walk(targetData) { 7 | // 判断是否为对象 8 | if (!targetData || typeof targetData !== 'object') { 9 | return 10 | } 11 | // 遍历所有属性 12 | Object.keys(targetData).forEach(key => { 13 | this.defineReactive(targetData, key, targetData[key]) 14 | }) 15 | } 16 | 17 | // 定义响应式数据 18 | defineReactive(obj, key, value) { 19 | // 收集依赖,来统一更新 20 | let dep = new Dep() 21 | // 转化对象的内部属性 22 | this.walk(value) 23 | const _that = this 24 | Object.defineProperty(obj, key, { 25 | enumerable: true, 26 | configurable: true, 27 | // 不返回obj[key]的原因是会递归触发 28 | get() { 29 | // 收集依赖 30 | Dep.target && dep.addSub(Dep.target) 31 | return value 32 | }, 33 | set(newValue) { 34 | if (newValue === value) return 35 | value = newValue 36 | // 处理普通值转为对象的情况 37 | _that.walk(newValue) 38 | // 发生通知 39 | dep.notify() 40 | } 41 | 42 | }) 43 | } 44 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue/vue.js: -------------------------------------------------------------------------------- 1 | class Vue { 2 | constructor(options) { 3 | // 1. 通过属性保存选项的数据 4 | this.$options = options || {} 5 | this.$data = options.data || {} 6 | // 如果是字符串就说明是选择器 7 | this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el 8 | // 2. 把data的成员转化为getter和setter注入到vue实例 9 | this._proxyData(this.$data) 10 | // 3. 调用observer对象,把data属性转化为响应式数据,监听数据的变化 11 | new Observer(this.$data) 12 | // 4. 调用Compiler对象,处理模版编译 13 | new Compiler(this) 14 | } 15 | 16 | _proxyData(data) { 17 | // 遍历对象 18 | Object.keys(data).forEach((key) => { 19 | Object.defineProperty(this, key, { 20 | enumerable: true, 21 | configurable: true, 22 | get() { 23 | return data[key] 24 | }, 25 | set(newValue) { 26 | if (newValue === data[key]) return 27 | data[key] = newValue 28 | } 29 | 30 | }) 31 | }) 32 | } 33 | } -------------------------------------------------------------------------------- /framework/vue/mini-vue/watcher.js: -------------------------------------------------------------------------------- 1 | class Watcher { 2 | constructor(vm, key, cb) { 3 | this.vm = vm 4 | this.key = key 5 | this.cb = cb 6 | // 把watcher对象记录到Dep类的静态属性target 7 | Dep.target = this 8 | // 触发get方法,在get方法中会调用addSub 9 | this.oldValue = vm[key] 10 | // 重制依赖对象,防止数据混乱 11 | Dep.target = null 12 | } 13 | update() { 14 | let newValue = this.vm[this.key] 15 | if (this.oldValue === newValue) { 16 | return 17 | } 18 | this.cb(newValue) 19 | } 20 | } -------------------------------------------------------------------------------- /functions/Array/every.js: -------------------------------------------------------------------------------- 1 | // 手写实现every 2 | function fackEvery(callback, thisArg) { 3 | let tempFlag = true 4 | let array = this 5 | for (let index = 0; index < array.length; index++) { 6 | if (!callback.call(thisArg, array[index], index, array)) { 7 | tempFlag = false 8 | } 9 | } 10 | return tempFlag 11 | } 12 | 13 | // MDN官方every 14 | function fackEvery2(callback, thisArg) { 15 | var T, k; 16 | if (this == null) { 17 | throw new TypeError('this is null or not defined'); 18 | } 19 | var O = Object(this); 20 | var len = O.length >>> 0; 21 | if (typeof callback !== 'function') { 22 | throw new TypeError(); 23 | } 24 | if (arguments.length > 1) { 25 | T = thisArg; 26 | } 27 | k = 0; 28 | while (k < len) { 29 | var kValue; 30 | if (k in O) { 31 | kValue = O[k]; 32 | var testResult = callback.call(T, kValue, k, O); 33 | if (!testResult) { 34 | return false; 35 | } 36 | } 37 | k++; 38 | } 39 | return true; 40 | } 41 | 42 | Array.prototype.fackEvery = fackEvery 43 | Array.prototype.fackEvery2 = fackEvery2 44 | let arr = [1, 2, 3] 45 | 46 | function isHasBig(item){ 47 | return item >= 2 48 | } 49 | 50 | let S = arr.fackEvery(isHasBig) 51 | console.log(S) 52 | let T = arr.fackEvery2(isHasBig) 53 | console.log(T) 54 | let Z = arr.every(isHasBig) 55 | console.log(Z) -------------------------------------------------------------------------------- /functions/Array/filter.js: -------------------------------------------------------------------------------- 1 | function fackFiliter(callback, thisArg) { 2 | let array = this 3 | let tempArr = [] 4 | if (typeof callback !== "function") { 5 | throw "参数必须为函数"; 6 | } 7 | for (let index = 0; index < array.length; index++) { 8 | if (callback.call(thisArg, (array[index]), index, array)) { 9 | tempArr.push((array[index])) 10 | } 11 | } 12 | return tempArr 13 | } 14 | 15 | // MDN官方filter 16 | function fackFiliter2(callback, thisArg) { 17 | if (!((typeof callback === 'Function' || typeof callback === 'function') && this)) 18 | throw new TypeError(); 19 | var len = this.length >>> 0, 20 | res = new Array(len), 21 | t = this, c = 0, i = -1; 22 | if (thisArg === undefined) { 23 | while (++i !== len) { 24 | if (i in this) { 25 | if (callback(t[i], i, t)) { 26 | res[c++] = t[i]; 27 | } 28 | } 29 | } 30 | } 31 | else { 32 | while (++i !== len) { 33 | if (i in this) { 34 | if (callback.call(thisArg, t[i], i, t)) { 35 | res[c++] = t[i]; 36 | } 37 | } 38 | } 39 | } 40 | res.length = c; 41 | return res; 42 | } 43 | 44 | Array.prototype.fackFiliter = fackFiliter 45 | Array.prototype.fackFiliter2 = fackFiliter2 46 | 47 | let arr = [1, 2, 3] 48 | 49 | function filiterFunc(item){ 50 | return item >= 2 51 | } 52 | 53 | let S = arr.fackFiliter(filiterFunc) 54 | console.log(S) 55 | 56 | let T = arr.fackFiliter2(filiterFunc) 57 | console.log(T) 58 | 59 | let Z = arr.filter(filiterFunc) 60 | console.log(Z) -------------------------------------------------------------------------------- /functions/Array/find.js: -------------------------------------------------------------------------------- 1 | // 手写实现find 2 | function fackFind(callback, thisArg) { 3 | let temoItem = undefined 4 | let array = this 5 | for (let index = 0; index < array.length; index++) { 6 | if (callback.call(thisArg, array[index], index, array)) { 7 | temoItem = array[index] 8 | break; 9 | } 10 | } 11 | return temoItem 12 | } 13 | 14 | // MDN官方 15 | function fackFind2(callback) { 16 | if (this == null) { 17 | throw new TypeError('"this" is null or not defined'); 18 | } 19 | var o = Object(this); 20 | var len = o.length >>> 0; 21 | if (typeof callback !== 'function') { 22 | throw new TypeError('callback must be a function'); 23 | } 24 | var thisArg = arguments[1]; 25 | var k = 0; 26 | while (k < len) { 27 | var kValue = o[k]; 28 | if (callback.call(thisArg, kValue, k, o)) { 29 | return kValue; 30 | } 31 | k++; 32 | } 33 | return undefined; 34 | } 35 | 36 | Array.prototype.fackFind = fackFind 37 | Array.prototype.fackFind2 = fackFind2 38 | 39 | let arr = [1, 2, 3, 2, 5] 40 | 41 | function logFun(item) { 42 | return item == 2 43 | } 44 | 45 | let S = arr.fackFind(logFun) 46 | console.log(S) 47 | 48 | let T = arr.fackFind2(logFun) 49 | console.log(T) 50 | 51 | let Z = arr.find(logFun) 52 | console.log(Z) -------------------------------------------------------------------------------- /functions/Array/flat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 拍平数组 3 | * 编写思路 4 | * 1. 可指定的深度处理 5 | * 2. 递归遍历数组 6 | * 3. 会移除数组中的空项 7 | * 3. 返回结果是新数组 8 | */ 9 | 10 | let arr = [1, , 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]; 11 | 12 | 13 | // 深度默认为1 14 | function fackFlat(depth = 1) { 15 | const tempArr = [] 16 | function deepArr(arr, depth) { 17 | // forEach循环会自动过滤空值,如果使用其他循环,需要手动过滤空值 18 | arr.forEach(element => { 19 | // 如果是数组,而且深度还存在,则继续调用 20 | if (Array.isArray(element) && depth > 0) { 21 | deepArr(element, --depth) 22 | } else { 23 | tempArr.push(element) 24 | } 25 | }); 26 | } 27 | // 通过this拿到调用的数组 28 | deepArr(this, depth) 29 | return tempArr 30 | 31 | } 32 | 33 | // MDN reduce版本 34 | function fackFlat2(arr, d = 1) { 35 | return d > 0 ? arr.reduce((acc, val) => 36 | acc.concat(Array.isArray(val) ? fackFlat2(val, d - 1) : val), []) : arr.slice(); 37 | }; 38 | 39 | 40 | 41 | 42 | Array.prototype.fackFlat = fackFlat 43 | 44 | console.log(arr.flat(Infinity)) 45 | console.log(arr.fackFlat(Infinity)) 46 | console.log(fackFlat(arr, Infinity)); -------------------------------------------------------------------------------- /functions/Array/forEach.js: -------------------------------------------------------------------------------- 1 | // 手写实现forEach 2 | function fackForEach(callback, thisArg) { 3 | let array = this 4 | var _this; 5 | if (typeof callback !== "function") { 6 | throw "参数必须为函数"; 7 | } 8 | for (let index = 0; index < array.length; index++) { 9 | callback.call(thisArg, (array[index]), index, array) 10 | } 11 | } 12 | 13 | // MDN方法 14 | function fackForEach2(callback, thisArg) { 15 | var T, k; 16 | if (this == null) { 17 | throw new TypeError(' this is null or not defined'); 18 | } 19 | var O = Object(this); 20 | var len = O.length >>> 0; 21 | if (typeof callback !== "function") { 22 | throw new TypeError(callback + ' is not a function'); 23 | } 24 | if (arguments.length > 1) { 25 | T = thisArg; 26 | } 27 | k = 0; 28 | while (k < len) { 29 | var kValue; 30 | if (k in O) { 31 | kValue = O[k]; 32 | callback.call(T, kValue, k, O); 33 | } 34 | k++; 35 | } 36 | } 37 | 38 | Array.prototype.fackForEach = fackForEach 39 | 40 | Array.prototype.fackForEach2 = fackForEach2 41 | 42 | let arr = [1, 2, 3] 43 | 44 | arr.fackForEach((item, index, array) => { 45 | console.log('1111', item, index, array) 46 | }) 47 | 48 | arr.fackForEach2((item, index, array) => { 49 | console.log('2222', item, index, array) 50 | }) 51 | 52 | // 原生forEach 53 | arr.forEach((item, index, array) => { 54 | console.log("3333", item, index, array) 55 | }); -------------------------------------------------------------------------------- /functions/Array/includes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 判断数组中是否包含某个元素 3 | * 编写思路 4 | * 1. 传入需要判断的值在数组中判断 5 | * 2. 如果存在就返回true,不存在就返回false 6 | * 3. 从fromIndex 索引处开始查找 valueToFind,默认0 7 | */ 8 | 9 | let arr = [1, 2, 3, 4, 5]; 10 | 11 | let testItem = 1 12 | 13 | function fackIncludes(valueToFind, fromIndex = 0) { 14 | if (!Array.isArray(this)) { 15 | throw "非法调用"; 16 | } 17 | let findFlag = false 18 | for (let index = fromIndex; index < this.length; index++) { 19 | if (this[index] === valueToFind) { 20 | findFlag = true 21 | } 22 | } 23 | return findFlag 24 | } 25 | 26 | 27 | // MDN 实现 28 | function fackIncludes2(valueToFind, fromIndex = 0) { 29 | if (this == null) { 30 | throw new TypeError('"this" is null or not defined'); 31 | } 32 | var o = Object(this) 33 | var len = o.length >>> 0 34 | if (len === 0) { 35 | return false; 36 | } 37 | var n = fromIndex | 0; 38 | var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); 39 | function sameValueZero(x, y) { 40 | return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); 41 | } 42 | while (k < len) { 43 | if (sameValueZero(o[k], valueToFind)) { 44 | return true; 45 | } 46 | k++; 47 | } 48 | return false; 49 | } 50 | 51 | 52 | Array.prototype.fackIncludes = fackIncludes 53 | Array.prototype.fackIncludes2 = fackIncludes2 54 | 55 | console.log(arr.includes(testItem, 2)) 56 | console.log(arr.fackIncludes(testItem, 2)) 57 | console.log(arr.fackIncludes2(testItem, 2)) -------------------------------------------------------------------------------- /functions/Array/map.js: -------------------------------------------------------------------------------- 1 | 2 | function fackMap(callback, thisArg) { 3 | let array = this 4 | if (typeof callback !== "function") { 5 | throw "参数必须为函数"; 6 | } 7 | let tempArr = [] 8 | for (let index = 0; index < array.length; index++) { 9 | tempArr.push(callback.call(thisArg, (array[index]), index, array)) 10 | } 11 | return tempArr 12 | } 13 | 14 | // MDN方法 15 | function fackMap2(callback, thisArg) { 16 | var T, A, k; 17 | if (this == null) { 18 | throw new TypeError('this is null or not defined'); 19 | } 20 | var O = Object(this); 21 | var len = O.length >>> 0; 22 | if (typeof callback !== 'function') { 23 | throw new TypeError(callback + ' is not a function'); 24 | } 25 | if (arguments.length > 1) { 26 | T = arguments[1]; 27 | } 28 | A = new Array(len); 29 | k = 0; 30 | while (k < len) { 31 | var kValue, mappedValue; 32 | if (k in O) { 33 | kValue = O[k]; 34 | mappedValue = callback.call(T, kValue, k, O); 35 | A[k] = mappedValue; 36 | } 37 | k++; 38 | } 39 | return A; 40 | } 41 | 42 | Array.prototype.fackMap = fackMap 43 | Array.prototype.fackMap2 = fackMap2 44 | let arr = [1, 2, 3] 45 | 46 | function logFun(item, index, array) { 47 | console.log('1111', item, index, array) 48 | return item > 1 49 | } 50 | 51 | let S1 = arr.fackMap(logFun) 52 | let S2 = arr.fackMap2(logFun) 53 | let S3 = arr.map(logFun); 54 | console.log(S1) 55 | console.log(S2) 56 | console.log(S3) -------------------------------------------------------------------------------- /functions/Array/reduce.js: -------------------------------------------------------------------------------- 1 | // 手写实现find 2 | function fackReduce(callback, initialValue) { 3 | if (typeof callback !== "function") { 4 | throw "参数必须为函数"; 5 | } 6 | let array = this 7 | let index = 1; 8 | // MDN:没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。 9 | // 如果提供initialValue,从索引0开始 10 | initialValue ? index = 0 : initialValue = array[0] 11 | for (index; index < this.length; index++) { 12 | initialValue = callback.call(null, initialValue, array[index], index, array) 13 | } 14 | return initialValue 15 | } 16 | 17 | 18 | // MDN版本 19 | function fackReduce2(callback) { 20 | if (this === null) { 21 | throw new TypeError('Array.prototype.fackReduce2 ' + 22 | 'called on null or undefined'); 23 | } 24 | if (typeof callback !== 'function') { 25 | throw new TypeError(callback + 26 | ' is not a function'); 27 | } 28 | var o = Object(this); 29 | var len = o.length >>> 0; 30 | var k = 0; 31 | var value; 32 | if (arguments.length >= 2) { 33 | value = arguments[1]; 34 | } else { 35 | while (k < len && !(k in o)) { 36 | k++; 37 | } 38 | if (k >= len) { 39 | throw new TypeError('Reduce of empty array ' + 40 | 'with no initial value'); 41 | } 42 | value = o[k++]; 43 | } 44 | while (k < len) { 45 | if (k in o) { 46 | value = callback(value, o[k], k, o); 47 | } 48 | k++; 49 | } 50 | return value; 51 | } 52 | 53 | 54 | Array.prototype.fackReduce = fackReduce 55 | Array.prototype.fackReduce2 = fackReduce2 56 | 57 | 58 | let arr = [1, 2, 3, 4, 5, 20] 59 | 60 | function addFunc(a, b, index, array) { 61 | return a + b 62 | } 63 | 64 | let S = arr.reduce(addFunc, 2) 65 | let S2 = arr.fackReduce(addFunc, 2) 66 | let S3 = arr.fackReduce2(addFunc, 2) 67 | console.log('S', S) 68 | console.log('S2', S2) 69 | console.log('S3', S3) -------------------------------------------------------------------------------- /functions/Array/some.js: -------------------------------------------------------------------------------- 1 | function fackSome(callback, thisArg) { 2 | let tempFlag = false 3 | let array = this 4 | for (let index = 0; index < array.length; index++) { 5 | if (callback.call(thisArg, array[index], index, array)) { 6 | tempFlag = true 7 | } 8 | } 9 | return tempFlag 10 | } 11 | 12 | // MDN官方filter 13 | function fackSome2(callback, thisArg) { 14 | if (this == null) { 15 | throw new TypeError('Array.prototype.some called on null or undefined'); 16 | } 17 | 18 | if (typeof callback !== 'function') { 19 | throw new TypeError(); 20 | } 21 | 22 | var t = Object(this); 23 | var len = t.length >>> 0; 24 | 25 | var thisArg = arguments.length >= 2 ? arguments[1] : void 0; 26 | for (var i = 0; i < len; i++) { 27 | if (i in t && callback.call(thisArg, t[i], i, t)) { 28 | return true; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | 35 | Array.prototype.fackSome = fackSome 36 | Array.prototype.fackSome2 = fackSome2 37 | 38 | let arr = [1, 2, 3] 39 | 40 | function logFun(item) { 41 | return item == '3' 42 | } 43 | 44 | let S = arr.fackSome(logFun) 45 | console.log(S) 46 | 47 | let T = arr.fackSome2(logFun) 48 | console.log(T) 49 | 50 | let Z = arr.some(logFun) 51 | console.log(Z) -------------------------------------------------------------------------------- /functions/Object/assign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 分配对象 3 | * 编写思路 4 | * 1. 接受一个tatget对象和多个sources对象 5 | * 2. 将所有可枚举属性的值从一个或多个源对象分配到目标对象 6 | * 3. 返回目标对象。 7 | */ 8 | 9 | function fackAssign(target, ...source) { 10 | for (let index = 0; index < source.length; index++) { 11 | const element = source[index]; 12 | for (const key in element) { 13 | if (Object.hasOwnProperty.call(element, key)) { 14 | target[key] = element[key] 15 | } 16 | } 17 | } 18 | return target 19 | } 20 | 21 | Object.prototype.fackAssign = fackAssign 22 | 23 | const target = { a: 1, b: 2 }; 24 | const source = { b: 4, c: 5 }; 25 | const source2 = { d: 6, e: 7 }; 26 | 27 | 28 | const returnedTarget = Object.assign(target, source, source2); 29 | 30 | const returnedTarget2 = Object.fackAssign(target, source, source2); 31 | 32 | console.log(target); 33 | 34 | console.log(returnedTarget); 35 | 36 | console.log(returnedTarget2); 37 | -------------------------------------------------------------------------------- /functions/Object/orderAssign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 逆向分配对象 3 | * 编写思路 4 | * 同reverseAssign,作用是只覆盖源对象存在的属性,没有的属性不处理 5 | */ 6 | 7 | function orderAssign(target, ...source) { 8 | for (let index = 0; index < source.length; index++) { 9 | const element = source[index]; 10 | for (const key in element) { 11 | if (Object.hasOwnProperty.call(element, key)) { 12 | if (target[key]) { 13 | target[key] = element[key] 14 | } 15 | } 16 | } 17 | } 18 | return target 19 | } 20 | 21 | Object.prototype.orderAssign = orderAssign 22 | 23 | 24 | const target = { a: 1, b: 2, c: 8 }; 25 | const source = { a: 4, b: 5 }; 26 | const source2 = { d: 6, e: 7 }; 27 | 28 | 29 | const returnedTarget = Object.orderAssign(target, source, source2); 30 | 31 | 32 | console.log(returnedTarget); 33 | -------------------------------------------------------------------------------- /functions/Object/reverseAssign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 逆向分配对象 3 | * 编写思路 4 | * 同assign,作用是不覆盖源对象有的属性。只存没有的属性 5 | */ 6 | 7 | function reverseAssign(target, ...source) { 8 | for (let index = 0; index < source.length; index++) { 9 | const element = source[index]; 10 | for (const key in element) { 11 | if (Object.hasOwnProperty.call(element, key)) { 12 | if (!target[key]) { 13 | target[key] = element[key] 14 | } 15 | } 16 | } 17 | } 18 | return target 19 | } 20 | 21 | Object.prototype.reverseAssign = reverseAssign 22 | 23 | 24 | const target = { a: 1, b: 2, c: 8 }; 25 | const source = { a: 4, b: 5 }; 26 | const source2 = { d: 6, e: 7 }; 27 | 28 | const returnedTarget = Object.reverseAssign(target, source, source2); 29 | 30 | console.log(returnedTarget); 31 | -------------------------------------------------------------------------------- /functions/utils/compose.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | const toUpper = s => s.toUpperCase() 5 | const toLower = s => s.toLowerCase() 6 | const reverse = arr => arr.reverse() 7 | const first = arr => arr[0] 8 | // 从右到左运行 9 | function fackComposeRight (...callback) { 10 | return function (value){ 11 | return callback.reverse().reduce((item,fn)=>{ 12 | return fn(item) 13 | },value) 14 | } 15 | } 16 | // 从左到右运行 17 | function fackComposeLeft (...callback) { 18 | return function (value){ 19 | return callback.reduce((item,fn)=>{ 20 | return fn(item) 21 | },value) 22 | } 23 | } 24 | const f = fackComposeRight(toUpper, first, reverse) 25 | const f2 = fackComposeLeft(first, toLower) 26 | 27 | console.log(f(['oNe', 'tWo', 'thRee'])) 28 | 29 | console.log(f2(['oNe', 'tWo', 'thRee'])) 30 | -------------------------------------------------------------------------------- /functions/utils/curry.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function getSum(a, b, c) { 4 | return a + b + c 5 | } 6 | // es6 7 | function fackCurry3(callback) { 8 | return curryHandle = (...args) => { 9 | // 如果参数相同或超出,直接返回结果 10 | if (args.length >= callback.length) { 11 | return callback(...args) 12 | } else { 13 | // 参数不足,则返回函数,继续调用 14 | return (...args2) => curryHandle(...args.concat((args2))) 15 | } 16 | } 17 | } 18 | 19 | // es5 20 | function fackCurry2(callback) { 21 | return function curryHandle() { 22 | var args = Array.prototype.slice.call(arguments) 23 | if (args.length >= callback.length) { 24 | return callback.apply(null, args) 25 | } else { 26 | return function () { 27 | var args2 = Array.prototype.slice.call(arguments) 28 | return curryHandle.apply(null, args.concat(args2)) 29 | } 30 | } 31 | } 32 | } 33 | 34 | // 柯里化后的函数 35 | let curried = fackCurry2(getSum) // 测试 36 | 37 | console.log(curried(1, 2, 3)) 38 | console.log(curried(1)(2,3)) 39 | console.log(curried(1, 2)(3)) 40 | console.log(curried(1)(2)(3)) 41 | -------------------------------------------------------------------------------- /functions/utils/debounce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @desc 函数防抖 3 | * @param func 函数 4 | * @param wait 延迟执行毫秒数 5 | * @param immediate true 表立即执行,false 表非立即执行 6 | */ 7 | function debounce(func, wait, immediate) { 8 | let timeout 9 | return function() { 10 | const context = this 11 | const args = arguments 12 | if (timeout) clearTimeout(timeout) 13 | if (immediate) { 14 | const callNow = !timeout 15 | timeout = setTimeout(() => { 16 | timeout = null 17 | }, wait) 18 | if (callNow) func.apply(context, args) 19 | } else { 20 | timeout = setTimeout(() => { 21 | func.apply(context, args) 22 | }, wait) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /functions/utils/deepClone.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @description 深拷贝一个函数 4 | * 1. 先判断类型如果是基本类型直接返回 5 | * 2. 通过WeakMap结构 解决循环引用的问题 6 | * 3. 通过dp函数递归 如果是对象类型,递归拷贝交换通过key交换值 7 | * 4. 返回新对象 8 | */ 9 | 10 | function deepClone(source) { 11 | if (typeof source !== 'object') { 12 | return source 13 | } 14 | let objMap = new WeakMap(); 15 | function dp(){ 16 | if(objMap.has(source)){ 17 | return objMap.get(source) 18 | } 19 | 20 | let target = Array.isArray(source) ? [] : {}; // 判断对象是不是数组 21 | objMap.set(source,target) 22 | for (let key in source) { 23 | // 如果是对象就递归遍历每个key或者 24 | if (typeof source[key] === 'object' && source[key] != null) { 25 | target[key] = dp(source[key]); 26 | } else { 27 | // 如果是普通对象直接赋值 28 | target[key] = source[key]; 29 | } 30 | } 31 | return target; 32 | } 33 | return dp(source); 34 | } 35 | 36 | let obj1 = { 37 | a: 1, 38 | b: 2, 39 | c: { 40 | age: 18, 41 | alex: { 42 | '0': [1, 2, 3], 43 | '2': [4, 5, 6] 44 | } 45 | }, 46 | d: [ 47 | 1, 48 | 2, 49 | 3 50 | ], 51 | 52 | } 53 | 54 | 55 | let obj2 = {} 56 | 57 | let obj3 = {} 58 | 59 | obj2 = obj1 60 | 61 | obj3 = deepClone(obj1) 62 | 63 | obj1.a = 100; // 测试引用修改 64 | 65 | obj1.obj1 = obj1 // 测试循环引用 66 | 67 | console.log(obj1, obj2, obj3) -------------------------------------------------------------------------------- /functions/utils/getType.js: -------------------------------------------------------------------------------- 1 | let arr = BigInt(123); 2 | // 类型供参考 3 | let typeList = { 4 | '[object String]': 'String', 5 | '[object Number]': 'Number', 6 | '[object Array]': 'Array', 7 | '[object Function]': 'funtion', 8 | '[object Undefined]': 'Undefined', 9 | '[object Null]': 'Null', 10 | '[object Boolean]': 'Boolean', 11 | '[object Symbol]': 'Symbol', 12 | '[object Object]': 'Object', 13 | '[object Set]': 'Set', 14 | '[object Map]': 'Map', 15 | '[object WeakMap]': 'WeakMap', 16 | '[object WeakSet]': 'WeakSet', 17 | '[object ArrayBuffer]': 'ArrayBuffer', 18 | '[object BigInt]': 'BigInt' 19 | 20 | } 21 | 22 | function getType(val) { 23 | return typeList[Object.prototype.toString.call(val)] 24 | } 25 | 26 | console.log(getType(arr)) -------------------------------------------------------------------------------- /functions/utils/getUrlData.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @description 获取url的参数 4 | * 参数 可能是在index.html后面也可能是在路由#的后面 5 | */ 6 | 7 | function getUrlData(){ 8 | let url = window.location.search ? 9 | window.location.search.substr(1).split('&') : 10 | window.location.hash.substr(window.location.hash.indexOf('?')+1).split('&') 11 | let urlObj = {} 12 | for(let keys in url){ 13 | let urlItem = url[keys].split('=') 14 | urlObj[urlItem[0]] = decodeURI(urlItem[1]) 15 | } 16 | return urlObj 17 | } 18 | 19 | // 获取参数对象 20 | getUrlData() 21 | 22 | -------------------------------------------------------------------------------- /functions/utils/isEqual.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @description 判断两个数据是否相等(广义) 4 | * 1. 先处理基本类型 包括String,Number,Null,undefined,NaN,Boolean 不考虑Symbol 5 | * 2. 引用类型分情况处理 包括Object和Array 不考虑Function类型 6 | * 如果是基本类型,那么判断值是否为全等,认为NaN和NaN相等 7 | * 如果是数组,那么判断每个子项是否相同,子项包括 不同类型 8 | * 如果是对象, 9 | * 如果是空对象认为相等, 10 | * 如果对象里面的key和value相等(广义的值相等,内存地址不一样)且原型相同,那么认为是相等的 11 | * 如果是循环引用的对象,那么两个变量同时引用的对象相等,认为相等 12 | */ 13 | 14 | 15 | let obj1 = {} 16 | 17 | let obj2 = {} 18 | 19 | 20 | console.log(Object.is(obj1,obj2)) -------------------------------------------------------------------------------- /functions/utils/memoize.js: -------------------------------------------------------------------------------- 1 | function getArea(r) { 2 | console.log(r) 3 | return Math.PI * r * r 4 | } 5 | 6 | function fackMemoize(callback) { 7 | if (typeof callback != 'function') { 8 | throw new TypeError('传入的内容不是函数'); 9 | } 10 | let cache = {} 11 | return function (val) { 12 | let key = JSON.stringify(arguments) 13 | cache[key] = cache[key] || callback.call(callback, val) 14 | return cache[key] 15 | } 16 | } 17 | 18 | 19 | let getAreaWithMemory = fackMemoize(getArea) 20 | 21 | console.log(getAreaWithMemory(4)) 22 | console.log(getAreaWithMemory(4)) 23 | console.log(getAreaWithMemory(4)) 24 | 25 | 26 | -------------------------------------------------------------------------------- /functions/utils/throttle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @desc 函数节流 3 | * @param func 函数 4 | * @param delay 延迟执行毫秒数 5 | * @param mustRunDelay 需要等待的时间 6 | */ 7 | function throttle(fn, delay, mustRunDelay) { 8 | let timer = null 9 | let t_start 10 | return function() { 11 | const context = this 12 | const args = arguments 13 | const t_curr = +new Date() 14 | clearTimeout(timer) 15 | if (!t_start) { 16 | t_start = t_curr 17 | } 18 | if (t_curr - t_start >= mustRunDelay) { 19 | fn.apply(context, args) 20 | t_start = t_curr 21 | } else { 22 | timer = setTimeout(function() { 23 | fn.apply(context, args) 24 | }, delay) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | echo "开始上传" 2 | 3 | git add . 4 | git commit -am "$1" 5 | git push 6 | 7 | echo "上传完毕" -------------------------------------------------------------------------------- /work/performance/FCP.js: -------------------------------------------------------------------------------- 1 | /** 2 | * First Contentful Paint 3 | * 首屏时间计算 4 | */ 5 | class FCP { 6 | static details = [] 7 | static ignoreEleList = ['script', 'style', 'link', 'br'] 8 | constructor() { 9 | } 10 | static isEleInArray(target, arr) { 11 | if (!target || target === document.documentElement) { 12 | return false; 13 | } 14 | else if (arr.indexOf(target) !== -1) { 15 | return true; 16 | } 17 | else { 18 | return this.isEleInArray(target.parentElement, arr); 19 | } 20 | } 21 | static isInFirstScreen(target) { 22 | if (!target || !target.getBoundingClientRect) 23 | return false; 24 | var rect = target.getBoundingClientRect(), screenHeight = window.innerHeight, screenWidth = window.innerWidth; 25 | return rect.left >= 0 26 | && rect.left < screenWidth 27 | && rect.top >= 0 28 | && rect.top < screenHeight; 29 | } 30 | 31 | static getFCP() { 32 | return new Promise((resolve, reject) => { 33 | // 5s之内先收集所有的dom变化,并以key(时间戳)、value(dom list)的结构存起来。 34 | var observeDom = new MutationObserver((mutations) => { 35 | if (!mutations || !mutations.forEach) 36 | return; 37 | var detail = { 38 | time: performance.now(), 39 | roots: [] 40 | }; 41 | mutations.forEach((mutation) => { 42 | if (!mutation || !mutation.addedNodes || !mutation.addedNodes.forEach) 43 | return; 44 | mutation.addedNodes.forEach((ele) => { 45 | if (ele.nodeType === 1 && this.ignoreEleList.indexOf(ele.nodeName.toLocaleLowerCase()) === -1) { 46 | if (!this.isEleInArray(ele, detail.roots)) { 47 | detail.roots.push(ele); 48 | } 49 | } 50 | }); 51 | }); 52 | if (detail.roots.length) { 53 | this.details.push(detail); 54 | } 55 | }); 56 | observeDom.observe(document, { 57 | childList: true, 58 | subtree: true 59 | }); 60 | setTimeout(() => { 61 | observeDom.disconnect(); 62 | resolve(this.details); 63 | }, 5000); 64 | }).then((details) => { 65 | // 分析上面收集到的数据,返回最终的结果 66 | var result; 67 | details.forEach((detail) => { 68 | for (var i = 0; i < detail.roots.length; i++) { 69 | if (this.isInFirstScreen(detail.roots[i])) { 70 | result = detail.time; 71 | break; 72 | } 73 | } 74 | }); 75 | // 遍历当前请求的图片中,如果有开始请求时间在首屏dom渲染期间的,则表明该图片是首屏渲染中的一部分, 76 | // 所以dom渲染时间和图片返回时间中大的为首屏渲染时间 77 | window.performance.getEntriesByType('resource').forEach(function (resource) { 78 | if (resource.initiatorType === 'img' && (resource.fetchStart < result || resource.startTime < result) && resource.responseEnd > result) { 79 | result = resource.responseEnd; 80 | } 81 | }); 82 | return result; 83 | }); 84 | } 85 | } 86 | 87 | 88 | /** 89 | * FCP.getFCP().then(fst => { 90 | console.log('首屏时间' + fst + 'ms') 91 | }) 92 | */ -------------------------------------------------------------------------------- /work/performance/FMP.js: -------------------------------------------------------------------------------- 1 | class FMP { 2 | /** 3 | * get first-meaningful-paint 4 | * 首次有效绘制时间 5 | */ 6 | static getFmp(observeTime = 3000) { 7 | if (!Promise 8 | || !window.performance 9 | || !window.performance.timing 10 | || !window.requestAnimationFrame 11 | || !wiÎndow.MutationObserver) { 12 | console.log('fmp can not be retrieved'); 13 | Promise.reject(new Error('fmp can not be retrieved')); 14 | } 15 | 16 | const promise = new Promise((resolve) => { 17 | const observedPoints = []; 18 | const observer = new window.MutationObserver(() => { 19 | const innerHeight = window.innerHeight; 20 | function getDomMark(dom, level) { 21 | const length = dom.children ? dom.children.length : 0; 22 | let sum = 0; 23 | const tagName = dom.tagName; 24 | if (tagName !== 'SCRIPT' && tagName !== 'STYLE' && tagName !== 'META' && tagName !== 'HEAD') { 25 | if (dom.getBoundingClientRect && dom.getBoundingClientRect().top < innerHeight) { 26 | sum += (level * length); 27 | } 28 | if (length > 0) { 29 | const children = dom.children; 30 | for (let i = 0; i < length; i++) { 31 | sum += getDomMark(children[i], level + 1); 32 | } 33 | } 34 | } 35 | return sum; 36 | } 37 | window.requestAnimationFrame(() => { 38 | const timing = window.performance.timing; 39 | const startTime = timing.navigationStart || timing.fetchStart; 40 | const t = new Date().getTime() - startTime; 41 | const score = getDomMark(document, 1); 42 | observedPoints.push({ 43 | score, 44 | t, 45 | }); 46 | }); 47 | }); 48 | observer.observe(document, { 49 | childList: true, 50 | subtree: true, 51 | }); 52 | 53 | setTimeout(() => { 54 | observer.disconnect(); 55 | const rates = []; 56 | for (let i = 1; i < observedPoints.length; i++) { 57 | if (observedPoints[i].t !== observedPoints[i - 1].t) { 58 | rates.push({ 59 | t: observedPoints[i].t, 60 | rate: observedPoints[i].score - observedPoints[i - 1].score, 61 | }); 62 | } 63 | } 64 | rates.sort((a, b) => b.rate - a.rate); 65 | if (rates.length > 0) { 66 | resolve(rates[0].t); 67 | } else { 68 | resolve(observeTime); 69 | } 70 | }, observeTime); 71 | }); 72 | return promise; 73 | } 74 | } 75 | 76 | /** 77 | * FMP.getFmp().then(fst => { 78 | console.log('首屏时间' + fst + 'ms') 79 | }) 80 | */ --------------------------------------------------------------------------------