应用Promise封装AJAX申请
// promise 封装实现:function getJSON(url) { // 创立一个 promise 对象 let promise = new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest(); // 新建一个 http 申请 xhr.open("GET", url, true); // 设置状态的监听函数 xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 当申请胜利或失败时,扭转 promise 的状态 if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; // 设置谬误监听函数 xhr.onerror = function() { reject(new Error(this.statusText)); }; // 设置响应的数据类型 xhr.responseType = "json"; // 设置申请头信息 xhr.setRequestHeader("Accept", "application/json"); // 发送 http 申请 xhr.send(null); }); return promise;}
树形构造转成列表(解决菜单)
[ { id: 1, text: '节点1', parentId: 0, children: [ { id:2, text: '节点1_1', parentId:1 } ] }]转成[ { id: 1, text: '节点1', parentId: 0 //这里用0示意为顶级节点 }, { id: 2, text: '节点1_1', parentId: 1 //通过这个字段来确定子父级 } ...]
实现代码如下:
function treeToList(data) { let res = []; const dfs = (tree) => { tree.forEach((item) => { if (item.children) { dfs(item.children); delete item.children; } res.push(item); }); }; dfs(data); return res;}
实现prototype继承
所谓的原型链继承就是让新实例的原型等于父类的实例:
//父办法function SupperFunction(flag1){ this.flag1 = flag1;}//子办法function SubFunction(flag2){ this.flag2 = flag2;}//父实例var superInstance = new SupperFunction(true);//子继承父SubFunction.prototype = superInstance;//子实例var subInstance = new SubFunction(false);//子调用本人和父的属性subInstance.flag1; // truesubInstance.flag2; // false
将VirtualDom转化为实在DOM构造
这是以后SPA利用的外围概念之一
// vnode构造:// {// tag,// attrs,// children,// }//Virtual DOM => DOMfunction render(vnode, container) { container.appendChild(_render(vnode));}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 => render(child, dom)); return dom;}
Promise
// 模仿实现Promise// Promise利用三大伎俩解决回调天堂:// 1. 回调函数提早绑定// 2. 返回值穿透// 3. 谬误冒泡// 定义三种状态const PENDING = 'PENDING'; // 进行中const FULFILLED = 'FULFILLED'; // 已胜利const REJECTED = 'REJECTED'; // 已失败class Promise { constructor(exector) { // 初始化状态 this.status = PENDING; // 将胜利、失败后果放在this上,便于then、catch拜访 this.value = undefined; this.reason = undefined; // 胜利态回调函数队列 this.onFulfilledCallbacks = []; // 失败态回调函数队列 this.onRejectedCallbacks = []; const resolve = value => { // 只有进行中状态能力更改状态 if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 胜利态函数顺次执行 this.onFulfilledCallbacks.forEach(fn => fn(this.value)); } } const reject = reason => { // 只有进行中状态能力更改状态 if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 失败态函数顺次执行 this.onRejectedCallbacks.forEach(fn => fn(this.reason)) } } try { // 立刻执行executor // 把外部的resolve和reject传入executor,用户可调用resolve和reject exector(resolve, reject); } catch(e) { // executor执行出错,将谬误内容reject抛出去 reject(e); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function'? onRejected : reason => { throw new Error(reason instanceof Error ? reason.message : reason) } // 保留this const self = this; return new Promise((resolve, reject) => { if (self.status === PENDING) { self.onFulfilledCallbacks.push(() => { // try捕捉谬误 try { // 模仿微工作 setTimeout(() => { const result = onFulfilled(self.value); // 分两种状况: // 1. 回调函数返回值是Promise,执行then操作 // 2. 如果不是Promise,调用新Promise的resolve函数 result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } }); self.onRejectedCallbacks.push(() => { // 以下同理 try { setTimeout(() => { const result = onRejected(self.reason); // 不同点:此时是reject result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } }) } else if (self.status === FULFILLED) { try { setTimeout(() => { const result = onFulfilled(self.value); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }); } catch(e) { reject(e); } } else if (self.status === REJECTED) { try { setTimeout(() => { const result = onRejected(self.reason); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } } }); } catch(onRejected) { return this.then(null, onRejected); } static resolve(value) { if (value instanceof Promise) { // 如果是Promise实例,间接返回 return value; } else { // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED return new Promise((resolve, reject) => resolve(value)); } } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } static all(promiseArr) { const len = promiseArr.length; const values = new Array(len); // 记录曾经胜利执行的promise个数 let count = 0; return new Promise((resolve, reject) => { for (let i = 0; i < len; i++) { // Promise.resolve()解决,确保每一个都是promise实例 Promise.resolve(promiseArr[i]).then( val => { values[i] = val; count++; // 如果全副执行完,返回promise的状态就能够扭转了 if (count === len) resolve(values); }, err => reject(err), ); } }) } static race(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { Promise.resolve(p).then( val => resolve(val), err => reject(err), ) }) }) }}
封装异步的fetch,应用async await形式来应用
(async () => { class HttpRequestUtil { async get(url) { const res = await fetch(url); const data = await res.json(); return data; } async post(url, data) { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await res.json(); return result; } async put(url, data) { const res = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data) }); const result = await res.json(); return result; } async delete(url, data) { const res = await fetch(url, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data) }); const result = await res.json(); return result; } } const httpRequestUtil = new HttpRequestUtil(); const res = await httpRequestUtil.get('http://golderbrother.cn/'); console.log(res);})();
参考 前端进阶面试题具体解答
深拷贝
递归的残缺版本(思考到了Symbol属性):
const cloneDeep1 = (target, hash = new WeakMap()) => { // 对于传入参数解决 if (typeof target !== 'object' || target === null) { return target; } // 哈希表中存在间接返回 if (hash.has(target)) return hash.get(target); const cloneTarget = Array.isArray(target) ? [] : {}; hash.set(target, cloneTarget); // 针对Symbol属性 const symKeys = Object.getOwnPropertySymbols(target); if (symKeys.length) { symKeys.forEach(symKey => { if (typeof target[symKey] === 'object' && target[symKey] !== null) { cloneTarget[symKey] = cloneDeep1(target[symKey]); } else { cloneTarget[symKey] = target[symKey]; } }) } for (const i in target) { if (Object.prototype.hasOwnProperty.call(target, i)) { cloneTarget[i] = typeof target[i] === 'object' && target[i] !== null ? cloneDeep1(target[i], hash) : target[i]; } } return cloneTarget;}
实现一个函数判断数据类型
function getType(obj) { if (obj === null) return String(obj); return typeof obj === 'object' ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase() : typeof obj;}// 调用getType(null); // -> nullgetType(undefined); // -> undefinedgetType({}); // -> objectgetType([]); // -> arraygetType(123); // -> numbergetType(true); // -> booleangetType('123'); // -> stringgetType(/123/); // -> regexpgetType(new Date()); // -> date
Array.prototype.reduce()
Array.prototype.reduce = function(callback, initialValue) { if (this == undefined) { throw new TypeError('this is null or not defined'); } if (typeof callback !== 'function') { throw new TypeError(callbackfn + ' is not a function'); } const O = Object(this); const len = this.length >>> 0; let accumulator = initialValue; let k = 0; // 如果第二个参数为undefined的状况下 // 则数组的第一个有效值作为累加器的初始值 if (accumulator === undefined) { while (k < len && !(k in O)) { k++; } // 如果超出数组界线还没有找到累加器的初始值,则TypeError if (k >= len) { throw new TypeError('Reduce of empty array with no initial value'); } accumulator = O[k++]; } while (k < len) { if (k in O) { accumulator = callback.call(undefined, accumulator, O[k], k, O); } k++; } return accumulator;}
二叉树档次遍历
// 二叉树档次遍历class Node { constructor(element, parent) { this.parent = parent // 父节点 this.element = element // 以后存储内容 this.left = null // 左子树 this.right = null // 右子树 }}class BST { constructor(compare) { this.root = null // 树根 this.size = 0 // 树中的节点个数 this.compare = compare || this.compare } compare(a,b) { return a - b } add(element) { if(this.root === null) { this.root = new Node(element, null) this.size++ return } // 获取根节点 用以后增加的进行判断 放右边还是放左边 let currentNode = this.root let compare let parent = null while (currentNode) { compare = this.compare(element, currentNode.element) parent = currentNode // 先将父亲保存起来 // currentNode要不停的变动 if(compare > 0) { currentNode = currentNode.right } else if(compare < 0) { currentNode = currentNode.left } else { currentNode.element = element // 相等时 先笼罩后续解决 } } let newNode = new Node(element, parent) if(compare > 0) { parent.right = newNode } else if(compare < 0) { parent.left = newNode } this.size++ } // 档次遍历 队列 levelOrderTraversal(visitor) { if(this.root == null) { return } let stack = [this.root] let index = 0 // 指针 指向0 let currentNode while (currentNode = stack[index++]) { // 反转二叉树 let tmp = currentNode.left currentNode.left = currentNode.right currentNode.right = tmp visitor.visit(currentNode.element) if(currentNode.left) { stack.push(currentNode.left) } if(currentNode.right) { stack.push(currentNode.right) } } }}
// 测试var bst = new BST((a,b)=>a.age-b.age) // 模仿sort办法// // bst.add({age: 10})bst.add({age: 8})bst.add({age:19})bst.add({age:6})bst.add({age: 15})bst.add({age: 22})bst.add({age: 20})// 应用访问者模式class Visitor { constructor() { this.visit = function (elem) { elem.age = elem.age*2 } }}// console.log(bst.levelOrderTraversal(new Visitor()))
应用 setTimeout 实现 setInterval
setInterval 的作用是每隔一段指定工夫执行一个函数,然而这个执行不是真的到了工夫立刻执行,它真正的作用是每隔一段时间将事件退出事件队列中去,只有当以后的执行栈为空的时候,能力去从事件队列中取出事件执行。所以可能会呈现这样的状况,就是以后执行栈执行的工夫很长,导致事件队列里边积攒多个定时器退出的事件,当执行栈完结的时候,这些事件会顺次执行,因而就不能到距离一段时间执行的成果。
针对 setInterval 的这个毛病,咱们能够应用 setTimeout 递归调用来模仿 setInterval,这样咱们就确保了只有一个事件完结了,咱们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是应用递归函数,一直地去执行 setTimeout 从而达到 setInterval 的成果
function mySetInterval(fn, timeout) { // 控制器,管制定时器是否继续执行 var timer = { flag: true }; // 设置递归函数,模仿定时器执行。 function interval() { if (timer.flag) { fn(); setTimeout(interval, timeout); } } // 启动定时器 setTimeout(interval, timeout); // 返回控制器 return timer;}
Array.prototype.forEach()
Array.prototype.forEach = function(callback, thisArg) { if (this == null) { throw new TypeError('this is null or not defined'); } if (typeof callback !== "function") { throw new TypeError(callback + ' is not a function'); } const O = Object(this); const len = O.length >>> 0; let k = 0; while (k < len) { if (k in O) { callback.call(thisArg, O[k], k, O); } k++; }}
实现防抖函数(debounce)
防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则从新计时。
那么与节流函数的区别间接看这个动画实现即可。
手写简化版:
// 防抖函数const debounce = (fn, delay) => { let timer = null; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); };};
实用场景:
- 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
- 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似
生存环境请用lodash.debounce
实现字符串的repeat办法
输出字符串s,以及其反复的次数,输入反复的后果,例如输出abc,2,输入abcabc。
function repeat(s, n) { return (new Array(n + 1)).join(s);}
递归:
function repeat(s, n) { return (n > 0) ? s.concat(repeat(s, --n)) : "";}
对象数组列表转成树形构造(解决菜单)
[ { id: 1, text: '节点1', parentId: 0 //这里用0示意为顶级节点 }, { id: 2, text: '节点1_1', parentId: 1 //通过这个字段来确定子父级 } ...]转成[ { id: 1, text: '节点1', parentId: 0, children: [ { id:2, text: '节点1_1', parentId:1 } ] }]
实现代码如下:
function listToTree(data) { let temp = {}; let treeData = []; for (let i = 0; i < data.length; i++) { temp[data[i].id] = data[i]; } for (let i in temp) { if (+temp[i].parentId != 0) { if (!temp[temp[i].parentId].children) { temp[temp[i].parentId].children = []; } temp[temp[i].parentId].children.push(temp[i]); } else { treeData.push(temp[i]); } } return treeData;}
Function.prototype.bind
Function.prototype.bind = function(context, ...args) { if (typeof this !== 'function') { throw new Error("Type Error"); } // 保留this的值 var self = this; return function F() { // 思考new的状况 if(this instanceof F) { return new self(...args, ...arguments) } return self.apply(context, [...args, ...arguments]) }}
实现一个链表构造
链表构造
看图了解next层级
// 链表 从头尾删除、减少 性能比拟好// 分为很多类 罕用单向链表、双向链表// js模仿链表构造:增删改查// node节点class Node { constructor(element,next) { this.element = element this.next = next } }class LinkedList { constructor() { this.head = null // 默认应该指向第一个节点 this.size = 0 // 通过这个长度能够遍历这个链表 } // 减少O(n) add(index,element) { if(arguments.length === 1) { // 向开端增加 element = index // 以后元素等于传递的第一项 index = this.size // 索引指向最初一个元素 } if(index < 0 || index > this.size) { throw new Error('增加的索引不失常') } if(index === 0) { // 间接找到头部 把头部改掉 性能更好 let head = this.head this.head = new Node(element,head) } else { // 获取以后头指针 let current = this.head // 不停遍历 直到找到最初一项 增加的索引是1就找到第0个的next赋值 for (let i = 0; i < index-1; i++) { // 找到它的前一个 current = current.next } // 让创立的元素指向上一个元素的下一个 // 看图了解next层级 current.next = new Node(element,current.next) // 让以后元素指向下一个元素的next } this.size++; } // 删除O(n) remove(index) { if(index < 0 || index >= this.size) { throw new Error('删除的索引不失常') } this.size-- if(index === 0) { let head = this.head this.head = this.head.next // 挪动指针地位 return head // 返回删除的元素 }else { let current = this.head for (let i = 0; i < index-1; i++) { // index-1找到它的前一个 current = current.next } let returnVal = current.next // 返回删除的元素 // 找到待删除的指针的上一个 current.next.next // 如删除200, 100=>200=>300 找到200的上一个100的next的next为300,把300赋值给100的next即可 current.next = current.next.next return returnVal } } // 查找O(n) get(index) { if(index < 0 || index >= this.size) { throw new Error('查找的索引不失常') } let current = this.head for (let i = 0; i < index; i++) { current = current.next } return current }}var ll = new LinkedList()ll.add(0,100) // Node { ellement: 100, next: null }ll.add(0,200) // Node { element: 200, next: Node { element: 100, next: null } }ll.add(1,500) // Node {element: 200,next: Node { element: 100, next: Node { element: 500, next: null } } }ll.add(300)ll.remove(0)console.log(ll.get(2),'get')console.log(ll.head)module.exports = LinkedList
替换a,b的值,不能用长期变量
奇妙的利用两个数的和、差:
a = a + bb = a - ba = a - b
实现instanceOf
// 模仿 instanceoffunction instance_of(L, R) { //L 示意左表达式,R 示意右表达式 var O = R.prototype; // 取 R 的显示原型 L = L.__proto__; // 取 L 的隐式原型 while (true) { if (L === null) return false; if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true return true; L = L.__proto__; }}
实现一个迷你版的vue
入口
// js/vue.jsclass Vue { constructor (options) { // 1. 通过属性保留选项的数据 this.$options = options || {} this.$data = options.data || {} this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el // 2. 把data中的成员转换成getter和setter,注入到vue实例中 this._proxyData(this.$data) // 3. 调用observer对象,监听数据的变动 new Observer(this.$data) // 4. 调用compiler对象,解析指令和差值表达式 new Compiler(this) } _proxyData (data) { // 遍历data中的所有属性 Object.keys(data).forEach(key => { // 把data的属性注入到vue实例中 Object.defineProperty(this, key, { enumerable: true, configurable: true, get () { return data[key] }, set (newValue) { if (newValue === data[key]) { return } data[key] = newValue } }) }) }}
实现Dep
class Dep { constructor () { // 存储所有的观察者 this.subs = [] } // 增加观察者 addSub (sub) { if (sub && sub.update) { this.subs.push(sub) } } // 发送告诉 notify () { this.subs.forEach(sub => { sub.update() }) }}
实现watcher
class Watcher { constructor (vm, key, cb) { this.vm = vm // data中的属性名称 this.key = key // 回调函数负责更新视图 this.cb = cb // 把watcher对象记录到Dep类的动态属性target Dep.target = this // 触发get办法,在get办法中会调用addSub this.oldValue = vm[key] Dep.target = null } // 当数据发生变化的时候更新视图 update () { let newValue = this.vm[this.key] if (this.oldValue === newValue) { return } this.cb(newValue) }}
实现compiler
class Compiler { constructor (vm) { this.el = vm.$el this.vm = vm this.compile(this.el) } // 编译模板,解决文本节点和元素节点 compile (el) { let childNodes = el.childNodes Array.from(childNodes).forEach(node => { // 解决文本节点 if (this.isTextNode(node)) { this.compileText(node) } else if (this.isElementNode(node)) { // 解决元素节点 this.compileElement(node) } // 判断node节点,是否有子节点,如果有子节点,要递归调用compile if (node.childNodes && node.childNodes.length) { this.compile(node) } }) } // 编译元素节点,解决指令 compileElement (node) { // console.log(node.attributes) // 遍历所有的属性节点 Array.from(node.attributes).forEach(attr => { // 判断是否是指令 let attrName = attr.name if (this.isDirective(attrName)) { // v-text --> text attrName = attrName.substr(2) let key = attr.value this.update(node, key, attrName) } }) } update (node, key, attrName) { let updateFn = this[attrName + 'Updater'] updateFn && updateFn.call(this, node, this.vm[key], key) } // 解决 v-text 指令 textUpdater (node, value, key) { node.textContent = value new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } // v-model modelUpdater (node, value, key) { node.value = value new Watcher(this.vm, key, (newValue) => { node.value = newValue }) // 双向绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) } // 编译文本节点,解决差值表达式 compileText (node) { // console.dir(node) // {{ msg }} let reg = /\{\{(.+?)\}\}/ let value = node.textContent if (reg.test(value)) { let key = RegExp.$1.trim() node.textContent = value.replace(reg, this.vm[key]) // 创立watcher对象,当数据扭转更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } } // 判断元素属性是否是指令 isDirective (attrName) { return attrName.startsWith('v-') } // 判断节点是否是文本节点 isTextNode (node) { return node.nodeType === 3 } // 判断节点是否是元素节点 isElementNode (node) { return node.nodeType === 1 }}
实现Observer
class Observer { constructor (data) { this.walk(data) } walk (data) { // 1. 判断data是否是对象 if (!data || typeof data !== 'object') { return } // 2. 遍历data对象的所有属性 Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) }) } defineReactive (obj, key, val) { let that = this // 负责收集依赖,并发送告诉 let dep = new Dep() // 如果val是对象,把val外部的属性转换成响应式数据 this.walk(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { // 收集依赖 Dep.target && dep.addSub(Dep.target) return val }, set (newValue) { if (newValue === val) { return } val = newValue that.walk(newValue) // 发送告诉 dep.notify() } }) }}
应用
<!DOCTYPE html><html lang="cn"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Mini Vue</title></head><body> <div id="app"> <h1>差值表达式</h1> <h3>{{ msg }}</h3> <h3>{{ count }}</h3> <h1>v-text</h1> <div v-text="msg"></div> <h1>v-model</h1> <input type="text" v-model="msg"> <input type="text" v-model="count"> </div> <script src="./js/dep.js"></script> <script src="./js/watcher.js"></script> <script src="./js/compiler.js"></script> <script src="./js/observer.js"></script> <script src="./js/vue.js"></script> <script> let vm = new Vue({ el: '#app', data: { msg: 'Hello Vue', count: 100, person: { name: 'zs' } } }) console.log(vm.msg) // vm.msg = { test: 'Hello' } vm.test = 'abc' </script></body></html>