手写js高级知识点
实现原生的 AJAX 请求 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const ajax = { get(url, fn) { const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); // 第三个参数异步与否 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { fn(xhr.responeText); } }; xhr.send(); }, post(url, data, fn) { const xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { fn(xhr.responeText); } }; xhr.send(data); }, }; 手写 new 的过程 # js 复制 1 2 3 4 5 6 7 8 9 function myNew(fn, ...args) { const obj = {}; obj.__proto__ = fn.prototype; fn.apply(obj, args); return obj; } instanceof 关键字 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 function instanceOf(father, child) { const fp = father.prototype; var cp = child.__proto__; while (cp) { if (cp === fp) { return true; } cp = cp.__proto__; } return false; } 实现防抖函数 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 function debounce(fn, delay = 500) { let timer; return function () { if (timer) { clearTimeout(timer); } const args = arguments; timer = setTimeout(() => { fn.apply(this, args); // 改变this指向为调用debounce所指的对象 }, delay); }; } 实现节流函数 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 function throttle(fn, delay = 200) { let flag = true; return function () { if (!flag) return; flag = false; const args = arguments; setTimeout(() => { fn.apply(this, args); flag = true; }, delay); }; } 实现数组去重 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 第一种:Map记录 function quchong1(arr) { const newArr = []; arr.reduce((pre, next) => { if (!pre.has(next)) { pre.set(next, 1); newArr.push(next); } return pre; }, new Map()); return newArr; } // 第二种:Set去重 function quchong2(arr) { return [...new Set(arr)]; } 用 setTimeout 实现 setInterval # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function mySetTimout(fn, delay) { let timer = null; const interval = () => { fn(); timer = setTimeout(interval, delay); }; setTimeout(interval, delay); return { cancel: () => { clearTimeout(timer); }, }; } // 测试 const { cancel } = mySetTimout(() => console.log(888), 1000); setTimeout(() => { cancel(); }, 4000); 用 setInterval 实现 setTimeout # js 复制 1 2 3 4 5 6 7 8 9 function mySetInterval(fn, delay) { const timer = setInterval(() => { fn(); clearInterval(timer); }, delay); } // 测试 mySetInterval(() => console.log(888), 1000); 实现一个 compose 函数 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function fn1(x) { return x + 1; } function fn2(x) { return x + 2; } function fn3(x) { return x + 3; } function fn4(x) { return x + 4; } const a = compose(fn1, fn2, fn3, fn4); console.log(a); console.log(a(1)); // 1+2+3+4=11 // 实现如下: function compose(...fn) { if (fn.length === 0) return (num) => num; if (fn.length === 1) return fn[0]; return fn.reduce((pre, next) => { return (num) => { return next(pre(num)); }; }); } 实现一个科里化函数 # js 复制 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 add = (a, b, c) => a + b + c; const a = currying(add, 1); console.log(a(2, 3)); // + + 3=6 // 实现如下: function currying(fn, ...args1) { // 获取fn参数有几个 const length = fn.length; let allArgs = [...args1]; const res = (...arg2) => { allArgs = [...allArgs, ...arg2]; // 长度相等就返回执行结果 if (allArgs.length === length) { return fn(...allArgs); } else { // 不相等继续返回函数 return res; } }; return res; } // 测试: const add = (a, b, c) => a + b + c; const a = currying(add, 1); console.log(a(2, 3)); 实现一个 LRU 缓存函数 # js 复制 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 // 实现如下: class LRUCache { constructor(size) { this.size = size; this.cache = new Map(); } get(key) { const hasKey = this.cache.has(key); if (hasKey) { const val = this.cache.get(key); this.cache.delete(key); this.cache.set(key, val); return val; } else { return -1; } } put(key, val) { const hasKey = this.cache.has(key); if (hasKey) { this.cache.delete(key); } this.cache.set(key, val); if (this.cache.size > this.size) { this.cache.delete(this.cache.keys().next().value); } } } 简单实现 发布订阅模式 # js 复制 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 class EventEmitter { constructor() { this.cache = {}; } on(name, fn) { const tasks = this.cache[name]; if (tasks) { this.cache[name].push(fn); } else { this.cache[name] = [fn]; } } off(name, fn) { const tasks = this.cache[name]; if (task) { const index = tasks.findIndex((item) => item === fn); if (index >= 0) { this.cache[name].splice(index, 1); } } } emit(name, ...args) { // 复制一份。防止回调里继续on,导致死循环 const tasks = this.cache[name].slice(); if (tasks) { for (let fn of tasks) { fn(...args); } } } once(name, cb) { function fn(...args) { cb(args); this.off(name, fn); } this.on(name, fn); } } 实现 JSON.parse # js 复制 1 2 3 function parse(json) { return eval("(" + json + ")"); } 将 DOM 转化成树结构对象 # html 复制 1 2 3 4 5 6 7 <div> <span></span> <ul> <li></li> <li></li> </ul> </div> js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 将上方的DOM转化为下面的树结构对象 { tag: 'DIV', children: [ { tag: 'SPAN', children: [] }, { tag: 'UL', children: [ { tag: 'LI', children: [] }, { tag: 'LI', children: [] } ] } ] } // 实现如下: function dom2tree(dom) { const obj = {} obj.tag = dom.tagName obj.children = [] dom.childNodes.forEach(child => obj.children.push(dom2tree(child))) return obj } 将树结构转换为 DOM # js 复制 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 // 树结构 {tag: 'DIV', children: [ { tag: 'SPAN', children: [] }, { tag: 'UL', children: [ { tag: 'LI', children: [] }, { tag: 'LI', children: [] } ] } ] } // 实现如下: // 真正的渲染函数 function _render(vnode) { // 如果是数字类型转化为字符串 if (typeof vnode === "number") { vnode = String(vnode); } // 字符串类型直接就是文本节点 if (typeof vnode === "string") { return document.createTextNode(vnode); } // 普通DOM const dom = document.createElement(vnode.tag); if (vnode.attrs) { // 遍历属性 Object.keys(vnode.attrs).forEach((key) => { const value = vnode.attrs[key]; dom.setAttribute(key, value); }); } // 子数组进行递归操作 vnode.children.forEach((child) => dom.appendChild(_render(child))); return dom; } html 复制 1 2 3 4 5 6 7 8 <!-- 转换后的dom --> <div> <span></span> <ul> <li></li> <li></li> </ul> </div> 判断一个对象有环引用 # js 复制 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 var obj = { a: { c: [1, 2], }, b: 1, }; obj.a.c.d = obj; console.log(cycleDetector(obj)); // true // 实现思路:用一个数组存储每一个遍历过的对象,下次找到数组中存在,则说明环引用 function cycleDetector(obj) { const arr = [obj]; let flag = false; function cycle(o) { const keys = Object.keys(o); for (const key of keys) { const temp = o[key]; if (typeof temp === "object" && temp !== null) { if (arr.indexOf(temp) >= 0) { flag = true; return; } arr.push(temp); cycle(temp); } } } cycle(obj); return flag; } 计算一个对象的层数 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const obj = { a: { b: [1] }, c: { d: { e: { f: 1 } } }, }; console.log(loopGetLevel(obj)); // 4 // 实现如下: function loopGetLevel(obj) { var res = 1; function computedLevel(obj, level) { var level = level ? level : 0; if (typeof obj === "object") { for (var key in obj) { if (typeof obj[key] === "object") { computedLevel(obj[key], level + 1); } else { res = level + 1 > res ? level + 1 : res; } } } else { res = level > res ? level : res; } } computedLevel(obj); return res; } 对象的扁平化 # js 复制 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 const obj = { a: { b: 1, c: 2, d: { e: 5 }, }, b: [1, 3, { a: 2, b: 3 }], c: 3, }; flatten(obj); // 结果返回如下 // { // 'a.b': 1, // 'a.c': 2, // 'a.d.e': 5, // 'b[0]': 1, // 'b[1]': 3, // 'b[2].a': 2, // 'b[2].b': 3 // c: 3 // } // 实现如下: const isObject = (val) => typeof val === "object" && val !== null; function flatten(obj) { if (!isObject(obj)) return; const res = {}; const dfs = (cur, prefix) => { if (isObject(cur)) { if (Array.isArray(cur)) { cur.forEach((item, index) => { dfs(item, `${prefix}[${index}]`); }); } else { for (let key in cur) { dfs(cur[key], `${prefix}${prefix ? "." : ""}${key}`); } } } else { res[prefix] = cur; } }; dfs(obj, ""); return res; } // 测试 console.log(flatten(obj)); 实现(a == && a == && a == 3)为 true # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 第一种方法 var a = { i: 1, toString: function () { return a.i++; }, }; console.log(a == 1 && a == 2 && a == 3); // true // 第二种方法 var a = [1, 2, 3]; a.join = a.shift; console.log(a == 1 && a == 2 && a == 3); // true // 第三种方法 var val = 0; Object.defineProperty(window, "a", { get: function () { return ++val; }, }); console.log(a == 1 && a == 2 && a == 3); // true 实现限制并发的 Promise 调度器 # js 复制 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 addTask(1000, "1"); addTask(500, "2"); addTask(300, "3"); addTask(400, "4"); // 的输出顺序是:4 // 整个的完整执行流程: // 一开始1,2两个任务开始执行 // 500ms时,2任务执行完毕,输出2,任务3开始执行 // 800ms时,3任务执行完毕,输出3,任务4开始执行 // 1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行 // 1200ms时,4任务执行完毕,输出4 // 实现如下: class Scheduler { constructor(limit) { this.queue = []; this.limit = limit; this.count = 0; } add(time, order) { const promiseCreator = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log(order); resolve(); }, time); }); }; this.queue.push(promiseCreator); } taskStart() { for (let i = 0; i < this.limit; i++) { this.request(); } } request() { if (!this.queue.length || this.count >= this.limit) return; this.count++; this.queue .shift()() .then(() => { this.count--; this.request(); }); } } // 测试 const scheduler = new Scheduler(2); const addTask = (time, order) => { scheduler.add(time, order); }; addTask(1000, "1"); addTask(500, "2"); addTask(300, "3"); addTask(400, "4"); scheduler.taskStart(); 实现 lazyMan 函数 # js 复制 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 // 实现一个LazyMan,可以按照以下方式调用: LazyMan(“Hank”) /** 输出: * Hi! This is Hank! */ LazyMan(“Hank”).sleep(10).eat(“dinner”) /** 输出: * Hi! This is Hank! * //等待10秒.. * Wake up after 10 * Eat dinner~ */ LazyMan(“Hank”).eat(“dinner”).eat(“supper”) /** 输出: * Hi This is Hank! * Eat dinner~ * Eat supper~ */ LazyMan(“Hank”).eat(“supper”).sleepFirst(5) /** 输出: * //等待5秒 * Wake up after 5 * Hi This is Hank! * Eat supper */ // 实现如下: class _LazyMan { constructor(name) { this.tasks = [] const task = () => { console.log(`Hi! This is ${name}`) this.next() } this.tasks.push(task) setTimeout(() => { this.next() }, 0) } next() { const task = this.tasks.shift() task && task() } sleep(time) { this.sleepWrapper(time, false) return this } sleepFirst(time) { this.sleepWrapper(time, true) return this } sleepWrapper(time, first) { const task = () => { setTimeout(() => { console.log(`Wake up after ${time}`) this.next() }, time * 1000) } if (first) { this.tasks.unshift(task) } else { this.tasks.push(task) } } eat(food) { const task = () => { console.log(`Eat ${food}`); this.next(); }; this.tasks.push(task); return this; } } // 测试 const lazyMan = (name) => new _LazyMan(name) lazyMan('Hank').sleep(1).eat('dinner') lazyMan('Hank').eat('dinner').eat('supper') lazyMan('Hank').eat('supper').sleepFirst(5) 实现 add 函数 # js 复制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 add(1)(2)(3)()=6 add(1,2,3)(4)()=10 function add(...args1) { let allArgs = [...args1] function fn(...args2) { if (!args2.length) return fn.toString() allArgs = [...allArgs, ...args2] return fn } fn.toString = function () { return allArgs.reduce((pre, next) => pre + next) } return fn } // 测试 console.log(add(1)(2)(3)()) console.log(add(1, 2)(3)()) 实现一个合格的深拷贝 # 深拷贝有这 个段位? ...