关注公众号“执鸢者”,获取大量教学视频及私人总结面筋并进入业余交换群.目前互联网行业目前正在处于内卷状态,各个大厂一直进步招人门槛,前端工程师找工作也越发艰巨,为了助力各位老铁可能在面试过程中怀才不遇,我联合本人的面试教训,筹备了这三十六道面试过程中的手撕算法题,与各位共享。
一、冒泡排序
冒泡排序的思路:遍历数组,而后将最大数沉到最底部;<br/>工夫复杂度:O(N^2);<br/>空间复杂度:O(1)
function BubbleSort(arr) { if(arr == null || arr.length <= 0){ return []; } var len = arr.length; for(var end = len - 1; end > 0; end--){ for(var i = 0; i < end; i++) { if(arr[i] > arr[i + 1]){ swap(arr, i, i + 1); } } } return arr;}function swap(arr, i, j){ // var temp = arr[i]; // arr[i] = arr[j]; // arr[j] = temp; //替换也能够用异或运算符 arr[i] = arr[i] ^ arr[j]; arr[j] = arr[i] ^ arr[j]; arr[i] = arr[i] ^ arr[j];}
二、抉择排序
抉择排序的实现思路:遍历数组,把最小数放在头部;<br/>工夫复杂度:O(N^2);<br/>空间复杂度:O(1)
function SelectionSort(arr) { if(arr == null || arr.length < 0) { return []; } for(var i = 0; i < arr.length - 1; i++) { var minIndex = i; for(var j = i + 1; j < arr.length; j++) { minIndex = arr[j] < arr[minIndex] ? j : minIndex; } swap(arr, i, minIndex); } return arr;}function swap(arr, i, j) { arr[i] = arr[i] ^ arr[j]; arr[j] = arr[i] ^ arr[j]; arr[i] = arr[i] ^ arr[j];}
三、插入排序
插入排序实现思路:将一个新的数,和后面的比拟,只有以后数小于前一个则和前一个替换地位,否则终止;<br/>工夫复杂度:O(N^2);<br/>空间复杂度:O(1)
function insertSort(arr) { if(arr == null || arr.length <= 0){ return []; } var len = arr.length; for(var i = 1; i < len; i++) { for(var j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { swap(arr, j, j + 1); } } return arr;}function swap(arr, i, j){ var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}
四、归并排序
归并排序的思路:<br/>1.先左侧局部排好序<br/>2.再右侧局部排好序<br/>3.再筹备一个辅助数组,用外排的形式,小的开始填,直到有个动到开端,将另一个数组残余局部拷贝到开端<br/>4.再将辅助数组拷贝回原数组<br/>工夫复杂度:O(N * logN)<br/>空间复杂度:O(N)
// 递归实现function mergeSort(arr){ if(arr == null || arr.length <= 0){ return []; } sortProcess(arr, 0, arr.length - 1); return arr;}function sortProcess(arr, L, R){ //递归的终止条件,就是左右边界索引一样 if(L == R){ return; } var middle = L + ((R - L) >> 1);//找出两头值 sortProcess(arr, L, middle);//对左侧局部进行递归 sortProcess(arr, middle + 1, R);//对右侧局部进行递归 merge(arr, L, middle, R);//而后利用外排形式进行联合}function merge(arr, L, middle, R){ var help = []; var l = L; var r = middle + 1; var index = 0; //利用外排形式进行 while(l <= middle && r <= R){ help[index++] = arr[l] < arr[r] ? arr[l++] : arr[r++]; } while(l <= middle){ help.push(arr[l++]); } while(r <= R){ help.push(arr[r++]); } for(var i = 0; i < help.length; i++) { arr[L + i] = help[i]; } //arr.splice(L, help.length, ...help);//这个利用了ES6的语法}
// 循环实现function mergeSort(arr){ if(arr ==null || arr.length <= 0){ return []; } var len = arr.length; //i每次乘2,是因为每次合并当前小组元素就变成两倍个了 for(var i = 1; i < len; i *= 2){ var index = 0;//第一组的起始索引 while( 2 * i + index <= len){ index += 2 * i; merge(arr, index - 2 * i, index - i, index); } //阐明残余两个小组,但其中一个小组数据的数量曾经有余2的幂次方个 if(index + i < len){ merge(arr, index, index + i, len); } } return arr;}//利用外排的形式进行联合function merge(arr, start, mid, end){ //新建一个辅助数组 var help = []; var l = start, r = mid; var i = 0; while(l < mid && r < end){ help[i++] = arr[l] < arr[r] ? arr[l++] : arr[r++]; } while(l < mid){ help[i++] = arr[l++]; } while(r < end){ help[i++] = arr[r++]; } for(var j = 0; j < help.length; j++){ arr[start + j] = help[j]; }}
五、疾速排序
疾速排序实现思路:随机取出一个值进行划分,大于该值放左边,小于该值放右边(该算法在经典快排的根底上通过荷兰国旗思维和随机思维进行了革新)<br/>工夫复杂度:O(N*logN) <br/>空间复杂度:O(logN)
function quickSort(arr) { if(arr == null || arr.length <= 0){ return []; } quick(arr, 0, arr.length - 1);}function quick(arr, L, R){ //递归完结条件是L >= R if(L < R){ //随机找一个值,而后和最初一个值进行替换,将经典排序变为疾速排序 swap(arr, L + Math.floor(Math.random() * (R - L + 1)), R); //利用荷兰国旗问题取得划分的边界,返回的值是小于区域的最大索引和大于区域的最小索引,在这利用荷兰国旗问题将等于区域局部就不必动了 var tempArr = partition(arr, L, R, arr[R]); quick(arr, L, tempArr[0]); quick(arr, tempArr[1], R); }}//返回值是小于区域最初的索引和大于区域的第一个索引function partition(arr, L, R, num){ var less = L - 1; var more = R + 1; var cur = L; while(cur < more){ if(arr[cur] < num){ swap(arr, ++less, cur++); }else if(arr[cur] > num) { swap(arr, --more, cur); }else{ cur++; } } return [less, more];}function swap(arr, i, j){ var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}
六、堆排序
堆排序思路:<br/>1.让数组变成大根堆<br/>2.把最初一个地位和堆顶做替换<br/>3.则最大值在最初,则剩下局部做heapify,则从新调整为大根堆,则堆顶地位和该局部最初地位做替换<br/>4.反复进行,直到减完,则这样最初就调整结束,整个数组排完序(为一个升序)<br/>工夫复杂度:O(N * logN)<br/>空间复杂度:O(1)
function heapSort(arr) { if(arr == null || arr.length <= 0) { return []; } //首先是建设大顶堆的过程 for(var i = 0; i < arr.length; i++) { heapInsert(arr, i); } var size = arr.length;//这个值用来指定多少个数组成堆,当失去一个排序的值后这个值减一 //将堆顶和最初一个地位替换 /** * 当大顶堆建设实现后,而后一直将最初一个地位和堆顶替换; * 这样最大值就到了最初,则剩下局部做heapify,从新调整为大根堆,则堆顶地位和倒数第二个地位替换,反复进行,直到全副排序结束*/ //因为后面曾经是大顶堆,所以间接替换 swap(arr, 0, --size); while(size > 0) { //从新变成大顶堆 heapify(arr, 0, size); //进行替换 swap(arr, 0, --size); }}//加堆过程中function heapInsert(arr, index) { //比拟以后地位和其父地位,若大于其父地位,则进行替换,并将索引挪动到其父地位进行循环,否则跳过 //完结条件是比父地位小或者达到根节点处 while(arr[index] > arr[parseInt((index - 1) / 2)]){ //进行替换 swap(arr, index, parseInt((index - 1) / 2)); index = parseInt((index - 1) / 2); }}//减堆过程/** * size指的是这个数组前多少个数形成一个堆 * 如果你想把堆顶弹出,则把堆顶和最初一个数替换,把size减1,而后从0地位经验一次heapify,调整一下,残余局部变成大顶堆*/function heapify(arr, index, size) { var left = 2 * index + 1; while(left < size) { var largest = (left + 1 < size && arr[left] < arr[left + 1]) ? left + 1 : left; largest = arr[index] > arr[largest] ? index : largest; //如果最大值索引和传进来索引一样,则该值达到指定地位,间接完结循环 if(index == largest) { break; } //进行替换,并扭转索引和其左子节点 swap(arr, index, largest); index = largest; left = 2 * index + 1; }}function swap(arr, i, j) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}
七、桶排序
桶排序会经验三次遍历:筹备一个数组、遍历一遍数组、重构一遍数组,是非基于比拟的排序,上面以一个问题来论述其思路。<br/>问题:<br/> 给定一个数组,求如果排序之后,相邻两个数的最大差值,要求工夫复杂度O(N),且要求不能用基于比拟的排序<br/>
思路:<br/>1.筹备桶:数组中有N个数就筹备N+1个桶<br/>2.遍历一遍数组,找到最大值max和最小值min
。若min = max,则差值=0;若min≠max,则最小值放在0号桶,最大值放在N号桶,剩下的数属于哪个范畴就进哪个桶<br/>3.依据鸽笼原理,则必定有一个桶为空桶,设计该桶的目标是为了否定最大值在一个桶中,则最大差值的两个数肯定来自于两个桶,但空桶两侧并不一定是最大值<br/>4.所以只记录所有进入该桶的最小值min和最大值max和一个布尔值示意该桶有没有值<br/>5.而后遍历这个数组,如果桶是空的,则跳到下一个数,如果桶非空,则找前一个非空桶,则最大差值=以后桶min - 上一个非空桶max,用全局变量更新最大值<br/>工夫复杂度:O(N)<br/>空间复杂度:O(N)
function maxGap(arr) { if(arr == null || arr.length <= 0) { return 0; } var len = arr.length; var max = -Infinity, min = Infinity; //遍历一遍数组,找到最大值max和最小值min for(var i = 0; i < len; i++) { max = max > arr[i] ? max : arr[i]; min = min > arr[i] ? arr[i] : min; } //若min = max,则差值为0; if(min == max) { return 0; } var hasNum = new Array(len + 1); var mins = new Array(len + 1); var maxs = new Array(len + 1); var bid = 0;//指定桶的编号 for(var i = 0; i < len; i++) { bid = bucket(arr[i], min, max, len);//取得该值是在哪个桶//因为有N+1个桶,所以距离就是N个,所以此处除以的是len,而后通过这个函数失去应该放到哪个桶里 maxs[bid] = hasNum[bid] ? Math.max(arr[i], maxs[bid]) : arr[i]; mins[bid] = hasNum[bid] ? Math.min(arr[i], mins[bid]) : arr[i]; hasNum[bid] = true; } var res = 0; var lastMax = maxs[0]; for(var i = 0; i < len + 1; i++) { if(hasNum[i]) { res = Math.max(mins[i] - lastMax, res); lastMax = maxs[i]; } } return res;}//取得桶号//这个函数用于判断在哪个桶中,参数别离为值、最小值、最大值、桶距离function bucket(value, min, max, len) { return parseInt((value - min) / ((max - min) / len));}
八、new
function New (Fn, ...arg) { // 一个新的对象被创立 const result = {}; // 该对象的__proto__属性指向该构造函数的原型 if (Fn.prototype !== null) { Object.setPrototypeOf(result, Fn.prototype); } // 将执行上下文(this)绑定到新创建的对象中 const returnResult = Fn.apply(result, arg); // 如果构造函数有返回值,那么这个返回值将取代第一步中新创建的对象。否则返回该对象 if ((typeof returnResult === "object" || typeof returnResult === "function") && returnResult !== null) { return returnResult; } return result;}
九、instanceof
function Instanceof(left, right) { let leftVal = Object.getPrototypeOf(left); const rightVal = right.prototype; while (leftVal !== null) { if (leftVal === rightVal) return true; leftVal = Object.getPrototypeOf(leftVal); } return false;}
十、 Object.create()
Object.ObjectCreate = (proto, propertiesObject)=> { // 对输出进行检测 if (typeof proto !== 'object' && typeof proto !== 'function' && proto !== null) { throw new Error(`Object prototype may only be an Object or null:${proto}`); } // 新建一个对象 const result = {}; // 将该对象的原型设置为proto Object.setPrototypeOf(result, proto); // 将属性赋值给该对象 Object.defineProperties(result, propertiesObject); // 返回该对象 return result;}
十一、 Object assign()
function ObjectAssign(target, ...sources) { // 对第一个参数的判断,不能为undefined和null if (target === undefined || target === null) { throw new TypeError('cannot convert first argument to object'); } // 将第一个参数转换为对象(不是对象转换为对象) const targetObj = Object(target); // 将源对象(source)本身的所有可枚举属性复制到指标对象(target) for (let i = 0; i < sources.length; i++) { let source = sources[i]; // 对于undefined和null在源角色中不会报错,会间接跳过 if (source !== undefined && source !== null) { // 将源角色转换成对象 // 须要将源角色本身的可枚举属性(蕴含Symbol值的属性)进行复制 // Reflect.ownKeys(obj) 返回一个数组,蕴含对象本身的所有属性,不论属性名是Symbol还是字符串,也不论是否可枚举 const keysArray = Reflect.ownKeys(Object(source)); for (let nextIndex = 0; nextIndex < keysArray.length; nextIndex ++) { const nextKey = keysArray[nextIndex]; // 去除不可枚举属性 const desc = Object.getOwnPropertyDescriptor(source, nextKey); if (desc !== undefined && desc.enumerable) { // 前面的属性会笼罩后面的属性 targetObj[nextKey] = source[nextKey]; } } } } return targetObj;}// 因为挂载到Object的assign是不可枚举的,间接挂载下来是可枚举的,所以采纳这种形式if (typeof Object.myAssign !== 'function') { Object.defineProperty(Object, "myAssign", { value : ObjectAssign, writable: true, enumerable: false, configurable: true });}
十二、 map
Array.prototype.myMap = function(fn) { // 判断输出的第一个参数是不是函数 if (typeof fn !== 'function') { throw new TypeError(fn + 'is not a function'); } // 获取须要解决的数组内容 const arr = this; const len = arr.length; // 新建一个空数组用于装载新的内容 const temp = new Array(len); // 对数组中每个值进行解决 for (let i = 0; i < len; i++) { // 获取第二个参数,扭转this指向 let result = fn.call(arguments[1], arr[i], i, arr); temp[i] = result; } // 返回新的后果 return temp;}
十三、 filter
Array.prototype.myFilter = function (fn) { if (typeof fn !== 'function') { throw new TypeError(`${fn} is not a function`); } // 获取该数组 const arr = this; // 获取该数组长度 const len = this.length >>> 0; // 新建一个新的数组用于搁置该内容 const temp = []; // 对数组中每个值进行解决 for (let i = 0; i < len; i++) { // 解决时留神this指向 const result = fn.call(arguments[1], arr[i], i, arr); result && temp.push(arr[i]); } return temp;}
十四、 reduce
Array.prototype.myReduce = function(fn) { if (typeof fn !== 'function') { throw new TypeError(`${fn} is not a function`); } const arr = this; const len = arr.length >>> 0; let value;// 最终返回的值 let k = 0;// 以后索引 if (arguments.length >= 2) { value = arguments[1]; } else { // 当数组为稠密数组时,判断数组以后是否有元素,如果没有索引加一 while (k < len && !( k in arr)) { k++; } // 如果数组为空且初始值不存在则报错 if (k >= len) { throw new TypeError('Reduce of empty array with no initial value'); } value = arr[k++]; } while (k < len) { if (k in arr) { value = fn(value, arr[k], k, arr); } k++; } return value;}
十五、 flat
// 应用reduce和concatArray.prototype.flat1 = function () { return this.reduce((acc, val) => acc.concat(val), []);}
// 应用reduce + concat + isArray +recursivityArray.prototype.flat2 = function (deep = 1) { const flatDeep = (arr, deep = 1) => { // return arr.reduce((acc, val) => Array.isArray(val) && deep > 0 ? [...acc, ...flatDeep(val, deep - 1)] : [...acc, val], []); return deep > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, deep - 1) : val), []) : arr.slice(); } return flatDeep(this, deep);}
// 应用forEach + concat + isArray +recursivity// forEach 遍历数组会主动跳过空元素Array.prototype.flat3 = function (deep = 1) { const result = []; (function flat(arr, deep) { arr.forEach((item) => { if (Array.isArray(item) && deep > 0) { flat(item, deep - 1); } else { result.push(item); } }) })(this, deep); return result;}
// 应用for of + concat + isArray +recursivity// for of 遍历数组会主动跳过空元素Array.prototype.flat4 = function (deep = 1) { const result = []; (function flat(arr, deep) { for(let item of arr) { if (Array.isArray(item) && deep > 0) { flat(item, deep - 1); } else { // 去除空元素,因为void 表达式返回的都是undefined,不实用undefined是因为undefined在局部变量会被重写 item !== void 0 && result.push(item); } } })(this, deep); return result;}
// 应用堆栈stackArray.prototype.flat5 = function(deep = 1) { const stack = [...this]; const result = []; while (stack.length > 0) { const next = stack.pop(); if (Array.isArray(next)) { stack.push(...next); } else { result.push(next); } } // 反转复原原来程序 return result.reverse();}
十六、 call
Function.prototype.call1 = function(context, ...args) { // 获取第一个参数(留神第一个参数为null或undefined是,this指向window),构建对象 context = context ? Object(context) : window; // 将对应函数传入该对象中 context.fn = this; // 获取参数并执行相应函数 let result = context.fn(...args); delete context.fn;
十七、 apply
Function.prototype.apply1 = function(context, arr) { context = context ? Object(context) : window; context.fn = this; let result = arr ? context.fn(...arr) : context.fn(); delete context.fn; return result;}
十八、 bind
Function.prototype.bind1 = function (context, ...args) { if (typeof this !== 'function') { throw new TypeError('The bound object needs to be a function'); } const self = this; const fNOP = function() {}; const fBound = function(...fBoundArgs) { // 指定this // 当作为构造函数时,this 指向实例,此时 this instanceof fBound 后果为 true return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]); } // 批改返回函数的 prototype 为绑定函数的 prototype,为了防止间接批改this的原型,所以新建了一个fNOP函数作为中介 if (this.prototype) { fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound;}
十九、 防抖
function debounce(fn, wait, immediate) { let timer = null; return function(...args) { // 立刻执行的性能(timer为空示意首次触发) if (immediate && !timer) { fn.apply(this, args); } // 有新的触发,则把定时器清空 timer && clearTimeout(timer); // 从新计时 timer = setTimeout(() => { fn.apply(this, args); }, wait) }}
二十、 节流
// 工夫戳版本function throttle(fn, wait) { // 上一次执行工夫 let previous = 0; return function(...args) { // 以后工夫 let now = +new Date(); if (now - previous > wait) { previous = now; fn.apply(this, args); } }}
// 定时器版本function throttle(fn, wait) { let timer = null; return function(...args) { if (!timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; }, wait) } }}
二十一、深拷贝
// 乞巧版function cloneDeep1(source) { return JSON.parse(JSON.stringify(source));}
// 递归版function cloneDeep2(source) { // 如果输出的为根本类型,间接返回 if (!(typeof source === 'object' && source !== null)) { return source; } // 判断输出的为数组函数对象,进行相应的构建 const target = Array.isArray(source) ? [] : {}; for (let key in source) { // 判断是否是本身属性 if (Object.prototype.hasOwnProperty.call(source, key)) { if (typeof source === 'object' && source !== null) { target[key] = cloneDeep2(source[key]); } else { target[key] = source[key]; } } } return target;}
// 循环形式function cloneDeep3(source) { if (!(typeof source === 'object' && source !== null)) { return source; } const root = Array.isArray(source) ? [] : {}; // 定义一个栈 const loopList = [{ parent: root, key: undefined, data: source, }]; while (loopList.length > 0) { // 深度优先 const node = loopList.pop(); const parent = node.parent; const key = node.key; const data = node.data; // 初始化赋值指标,key为undefined则拷贝到父元素,否则拷贝到子元素 let res = parent; if (typeof key !== 'undefined') { res = parent[key] = Array.isArray(data) ? [] : {}; } for (let key in data) { if (data.hasOwnProperty(key)) { if (typeof data[key] === 'object' && data !== null) { loopList.push({ parent: res, key: key, data: data[key], }); } else { res[key] = data[key]; } } } } return root;}
二十二、 依据Promise/A+标准实现Promise
人家有相干规范,咱们就要恪守,毕竟遵纪守法才是好公民,当初只能硬着头皮把这个规范过一遍。
上面就是基于Promise/A+标准实现的代码,曾经通过promises-aplus-tests库进行了验证。
const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';/** * Promise构造函数 * excutor: 外部同步执行的函数 */class Promise { constructor(excutor) { const self = this; self.status = PENDING; self.onFulfilled = [];// 胜利的回调 self.onRejected = [];// 失败的回调 // 异步解决胜利调用的函数 // PromiseA+ 2.1 状态只能由Pending转为fulfilled或rejected;fulfilled状态必须有一个value值;rejected状态必须有一个reason值。 function resolve(value) { if (self.status === PENDING) { self.status = FULFILLED; self.value = value; // PromiseA+ 2.2.6.1 雷同promise的then能够被调用屡次,当promise变为fulfilled状态,全副的onFulfilled回调依照原始调用then的程序执行 self.onFulfilled.forEach(fn => fn()); } } function reject(reason) { if (self.status === PENDING) { self.status = REJECTED; self.reason = reason; // PromiseA+ 2.2.6.2 雷同promise的then能够被调用屡次,当promise变为rejected状态,全副的onRejected回调依照原始调用then的程序执行 self.onRejected.forEach(fn => fn()); } } try { excutor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { // PromiseA+ 2.2.1 onFulfilled和onRejected是可选参数 // PromiseA+ 2.2.5 onFulfilled和onRejected必须被作为函数调用 // PromiseA+ 2.2.7.3 如果onFulfilled不是函数且promise1状态是fulfilled,则promise2有雷同的值且也是fulfilled状态 // PromiseA+ 2.2.7.4 如果onRejected不是函数且promise1状态是rejected,则promise2有雷同的值且也是rejected状态 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }; const self = this; const promise = new Promise((resolve, reject) => { const handle = (callback, data) => { // PromiseA+ 2.2.4 onFulfilled或者onRejected须要在本人的执行上下文栈里被调用,所以此处用setTimeout setTimeout(() => { try { // PromiseA+ 2.2.2 如果onFulfilled是函数,则在fulfilled状态之后调用,第一个参数为value // PromiseA+ 2.2.3 如果onRejected是函数,则在rejected状态之后调用,第一个参数为reason const x = callback(data); // PromiseA+ 2.2.7.1 如果onFulfilled或onRejected返回一个x值,运行这[[Resolve]](promise2, x) resolvePromise(promise, x, resolve, reject); } catch (e) { // PromiseA+ 2.2.7.2 onFulfilled或onRejected抛出一个异样e,promise2必须以e的理由失败 reject(e); } }) } if (self.status === PENDING) { self.onFulfilled.push(() => { handle(onFulfilled, self.value); }); self.onRejected.push(() => { handle(onRejected, self.reason); }) } else if (self.status === FULFILLED) { setTimeout(() => { handle(onFulfilled, self.value); }) } else if (self.status === REJECTED) { setTimeout(() => { handle(onRejected, self.reason); }) } }) return promise; }}function resolvePromise(promise, x, resolve, reject) { // PromiseA+ 2.3.1 如果promise和x援用同一对象,会以TypeError谬误reject promise if (promise === x) { reject(new TypeError('Chaining Cycle')); } if (x && typeof x === 'object' || typeof x === 'function') { // PromiseA+ 2.3.3.3.3 如果resolvePromise和rejectPromise都被调用,或者对同一个参数进行屡次调用,那么第一次调用优先,当前的调用都会被疏忽。 let used; try { // PromiseA+ 2.3.3.1 let then be x.then // PromiseA+ 2.3.2 调用then办法曾经蕴含了该条(该条是x是promise的解决)。 let then = x.then; if (typeof then === 'function') { // PromiseA+ 2.3.3.3如果then是一个函数,用x作为this调用它。第一个参数是resolvePromise,第二个参数是rejectPromise // PromiseA+ 2.3.3.3.1 如果resolvePromise用一个值y调用,运行[[Resolve]](promise, y) // PromiseA+ 2.3.3.3.2 如果rejectPromise用一个起因r调用,用r回绝promise。 then.call(x, (y) => { if (used) return; used = true; resolvePromise(promise, y, resolve, reject) }, (r) => { if (used) return; used = true; reject(r); }) } else { // PromiseA+ 如果then不是一个函数,变为fulfilled状态并传值为x if (used) return; used = true; resolve(x); } } catch (e) { // PromiseA+ 2.3.3.2 如果检索属性x.then抛出异样e,则以e为起因回绝promise // PromiseA+ 2.3.3.4 如果调用then抛出异样,然而resolvePromise或rejectPromise曾经执行,则疏忽它 if (used) return; used = true; reject(e); } } else { // PromiseA+ 2.3.4 如果x不是一个对象或函数,状态变为fulfilled并传值x resolve(x); }}
二十三、 Promise.resolve()
class Promise { // ... // 将现有对象转为 Promise 对象 static resolve(value) { // 如果参数是 Promise 实例,那么Promise.resolve将不做任何批改、一成不变地返回这个实例。 if (value instanceof Promise) return value; // 参数是一个thenable对象(具备then办法的对象),Promise.resolve办法会将这个对象转为 Promise 对象,而后就立刻执行thenable对象的then办法。 if (typeof value === 'object' || typeof value === 'function') { try { let then = value.then; if (typeof then === 'function') { return new Promise(then.bind(value)); } } catch (e) { return new Promise((resolve, reject) => { reject(e); }) } } // 参数不是具备then办法的对象,或基本就不是对象,Promise.resolve办法返回一个新的 Promise 对象,状态为resolved。 return new Promise((resolve, reject) => { resolve(value); }) }}
二十四、 Promise.reject()
class Promise { // ... // 返回一个新的 Promise 实例,该实例的状态为rejected。 static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) }}
二十五、 Promise.all()
class Promise { // ... // 用于将多个 Promise 实例,包装成一个新的 Promise 实例。只有所有状态都变为fulfilled,p的状态才会是fulfilled static all(promises) { const values = []; let resolvedCount = 0; return new Promise((resolve, reject) => { promises.forEach((p, index) => { Promise.resolve(p).then(value => { resolvedCount++; values[index] = value; if (resolvedCount === promises.length) { resolve(values); } }, reason => { reject(reason); }) }) }) }}
二十六、 Promise.race()
class Promise { // ... // 只有有一个实例率先扭转状态,状态就跟着扭转。那个率先扭转的 Promise 实例的返回值,就传递给回调函数。 static race(promises) { return new Promise((resolve, reject) => { promises.forEach((p, index) => { Promise.resolve(p).then(value => { resolve(value); }, reason => { reject(reason); }) }) }) }}
二十七、 Promise.catch()
class Promise { // ... // 是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定产生谬误时的回调函数。 catch(onRejected) { return this.then(undefined, onRejected); }}
二十八、 Promise.finally()
class Promise { // ... // 用于指定不论 Promise 对象最初状态如何,都会执行的操作。 finally(callback) { return this.then( value => Promise.resolve(callback()).then(() => value), reason => Promise.resolve(callback()).then(() => { throw reason }) ) }}
二十九、Async实现原理
这是Async的实现原理,行将Generator函数作为参数放入run函数中,最终实现主动执行并返回Promise对象。
function run(genF) { // 返回值是Promise return new Promise((resolve, reject) => { const gen = genF(); function step(nextF) { let next; try { // 执行该函数,获取一个有着value和done两个属性的对象 next = nextF(); } catch (e) { // 出现异常则将该Promise变为rejected状态 reject(e); } // 判断是否达到开端,Generator函数达到开端则将该Promise变为fulfilled状态 if (next.done) { return resolve(next.value); } // 没达到开端,则利用Promise封装该value,直到执行结束,重复调用step函数,实现主动执行 Promise.resolve(next.value).then((v) => { step(() => gen.next(v)) }, (e) => { step(() => gen.throw(e)) }) } step(() => gen.next(undefined)); })}
三十、公布订阅模式
// 公布订阅(TypeScript版)interface Publish { registerObserver(eventType : string, subscribe : Subscribe) : void; remove(eventType : string, subscribe ?: Subscribe) : void; notifyObservers(eventType : string) : void;}interface SubscribesObject{ [key : string] : Array<Subscribe>}class ConcretePublish implements Publish { private subscribes : SubscribesObject; constructor() { this.subscribes = {}; } registerObserver(eventType : string, subscribe : Subscribe) : void { if (!this.subscribes[eventType]) { this.subscribes[eventType] = []; } this.subscribes[eventType].push(subscribe); } remove(eventType : string, subscribe ?: Subscribe) : void { const subscribeArray = this.subscribes[eventType]; if (subscribeArray) { if (!subscribe) { delete this.subscribes[eventType]; } else { for (let i = 0; i < subscribeArray.length; i++) { if (subscribe === subscribeArray[i]) { subscribeArray.splice(i, 1); } } } } } notifyObservers(eventType : string, ...args : any[]) : void { const subscribes = this.subscribes[eventType]; if (subscribes) { subscribes.forEach(subscribe => subscribe.update(...args)) } }}interface Subscribe { update(...value : any[]) : void;}class ConcreteSubscribe1 implements Subscribe { public update(...value : any[]) : void { console.log('曾经执行更新操作1,值为', ...value); }}class ConcreteSubscribe2 implements Subscribe { public update(...value : any[]) : void { console.log('曾经执行更新操作2,值为', ...value); }}function main() { const publish = new ConcretePublish(); const subscribe1 = new ConcreteSubscribe1(); const subscribe2 = new ConcreteSubscribe2(); publish.registerObserver('1', subscribe1); publish.registerObserver('2', subscribe2); publish.notifyObservers('2', '22222');}main();
三十一、懒加载
1)首先,不要将图片地址放到src属性中,而是放到其它属性(data-original)中。<br/>
2)页面加载实现后,依据scrollTop判断图片是否在用户的视线内,如果在,则将data-original属性中的值取出寄存到src属性中。<br/>
3)在滚动事件中反复判断图片是否进入视线,如果进入,则将data-original属性中的值取出寄存到src属性中。<br/>
elementNode.getAttribute(name):办法通过名称获取属性的值。<br/>
elementNode.setAttribute(name, value):办法创立或扭转某个新属性。<br/>
elementNode.removeAttribute(name):办法通过名称删除属性的值。
//懒加载代码实现var viewHeight = document.documentElement.clientHeight;//可视化区域的高度function lazyload () { //获取所有要进行懒加载的图片 let eles = document.querySelectorAll('img[data-original][lazyload]');//获取属性名中有data-original的 Array.prototype.forEach.call(eles, function(item, index) { let rect; if(item.dataset.original === '') { return; } rect = item.getBoundingClientRect(); //图片一进入可视区,动静加载 if(rect.bottom >= 0 && rect.top < viewHeight) { !function () { let img = new Image(); img.src = item.dataset.original; img.onload = function () { item.src = img.src; } item.removeAttribute('data-original'); item.removeAttribute('lazyload'); }(); } })}lazyload();document.addEventListener('scroll', lazyload);
三十二、FileReader应用
function uploadMulFile(uploadFile) { return new Promise((resolve, reject) => { let fileLength = 0; let reader = new FileReader(); reader.readAsText(uploadFile[fileLength]); reader.onabort = function(e) { console.log("文件读取异样"); } reader.onerror = function(e) { console.log("文件读取谬误"); } reader.onload = function(e){ if(e.target.result) { fileLength++; if(fileLength < uploadFile.length) { reader.readAsText(uploadFile[fileLength]); }else{ resolve({ carArr, crossArr, roadArr }) } } } })}
三十三、Ajax应用(非Promise版)
function ajax(url) { var XHR; //进行性能检测 if(window.ActiveXObject) { XHR = new ActiveXObject("Microsoft.XMLHTTP");//兼容IE//IE浏览器中应用的申请的办法,需实例化 }else if(window.XMLHttpRequest) { XHR = new XMLHttpRequest();//规范浏览器中的应用的申请办法,需实例化 }else{ XHR = null; } if(XHR) { XHR.onreadystatechange = function() { //readyState:Ajax申请服务的状态 if(XHR.readyState == 4) { //status:页面的响应码 if(XHR.status == 200) { //返回的数据以string的模式返回 console.log(XHR.responseText); } } }; XHR.open("get", url);//open(‘method’,‘url’,boolean);参数1:申请形式;参数2:申请文件的地址;参数3:设置是否异步,true示意异步, 默认值为 true 所以能够不写。 XHR.send(); }}
三十四、Ajax应用(Promise版)
//Promise模式function ajaxPromise(method, url, data) { var xhr = null; if(window.ActiveXObject) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }else{ xhr = new XMLHttpRequest(); } return new Promise(function(resolve, reject) { xhr.onreadystatechange = function(){ if(xhr.readyState === 4 ) { if(xhr.status === 200) { resolve(JSON.parse(xhr.responseText)); }else{ reject(xhr.status); } } }; if(method.toUpperCase() == "GET") { var arr = []; for(var key in data) { arr.push(key + '=' + data[key]); } var getData = arr.join('&'); xhr.open("GET", url + "?" + getData, true);//true示意异步 xhr.send(null); }else if(method.toUpperCase() == "POST") { xhr.open("POST", url, true); xhr.responseType = "json"; xhr.setRequestHeader('Content', 'application/x-www-form-urlencoded;charset=utf-8'); xhr.send(data); } })}
三十五、JsonP
function jsonp(url, onsuccess, onerror, charset) { var hash = Math.random().toString().slice(2); window['jsonp' + hash] = function(data) { if(onsuccess && typeof onsuccess == 'function') { onsuccess(data); } } var script = createScript(url + "?callback=jsonp" + hash, charset); // 动静产检一个script标签 //监听加载胜利的事件,获取数据,这个地位用了两个事件onload和onreadystatechange是为了兼容IE,因为IE9之前不反对onload事件,只反对onreadystatechange事件 script.onload = script.onreadystatechange = function() { //若不存在readyState事件则证实不是IE浏览器,能够间接执行,若是的话,必须等到状态变为loaded或complete才能够执行 if(!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') { script.onload = script.onreadystatechange = null; // 移除该script的DOM对象 if(script.parentNode) { script.parentNode.removeChild(script); } //删除函数或变量 window['jsonp' + hash] = null; } } script.onerror = function () { if(onerror && typeof onerror == 'function') { onerror(); } } document.getElementsByTagName('head')[0].appendChild(script);//往html中减少这个标签,目标是把申请发送进来}function createScript(url, charset) { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); charset && script.setAttribute('charset', charset); script.setAttribute('src', url); script.async = true;}
三十六、将一个字符串转换为驼峰模式
//形式一:操作字符串数组function transformStr2Hump1(str) { if(str == null) { return ""; } var strArr = str.split('-'); for(var i = 1; i < strArr.length; i++) { strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1); } return strArr.join('');}//形式二:操作字符数组function transformStr2Hump2(str) { if(str == null) { return ""; } var strArr =str.split(''); for(var i = 0; i < strArr.length; i++) { if(strArr[i] == "-"){ //删除- strArr.splice(i, 1); //将该处改为大写 if(i < strArr.length) { strArr[i] = strArr[i].toUpperCase(); } } } return strArr.join("");}//形式三:利用正则function transformStr2Hump3(str) { if(str == null) { return ""; } var reg = /-(\w)/g;//匹配字母或数字或下划线或汉字 return str.replace(reg, function($0, $1) { return $1.toUpperCase(); })}
欢送大家关注公众号(回复“书籍”获取大量前端学习材料,回复“前端视频”获取大量前端教学视频)