乐趣区

关于前端:一篇搞定前端高频手撕算法题36道

关注公众号“执鸢者 ”,获取大量教学视频及 私人总结面筋 并进入 业余交换群.

目前互联网行业目前正在处于内卷状态,各个大厂一直进步招人门槛,前端工程师找工作也越发艰巨,为了助力各位老铁可能在面试过程中怀才不遇,我联合本人的面试教训,筹备了这三十六道面试过程中的手撕算法题,与各位共享。

一、冒泡排序

冒泡排序的思路:遍历数组,而后将最大数沉到最底部;<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 和 concat
Array.prototype.flat1 = function () {return this.reduce((acc, val) => acc.concat(val), []);
}
// 应用 reduce + concat + isArray +recursivity
Array.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;
}
// 应用堆栈 stack
Array.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();
    })
}

欢送大家关注公众号(回复“书籍”获取大量前端学习材料, 回复“前端视频”获取大量前端教学视频)

退出移动版