乐趣区

关于前端:那些高级前端是如何回答面试题的

防抖节流

题目形容: 手写防抖节流

实现代码如下:

// 防抖
function debounce(fn, delay = 300) {
  // 默认 300 毫秒
  let timer;
  return function () {
    const args = arguments;
    if (timer) {clearTimeout(timer);
    }
    timer = setTimeout(() => {fn.apply(this, args); // 扭转 this 指向为调用 debounce 所指的对象
    }, delay);
  };
}

window.addEventListener(
  "scroll",
  debounce(() => {console.log(111);
  }, 1000)
);

// 节流
// 设置一个标记
function throttle(fn, delay) {
  let flag = true;
  return () => {if (!flag) return;
    flag = false;
    timer = setTimeout(() => {fn();
      flag = true;
    }, delay);
  };
}

window.addEventListener(
  "scroll",
  throttle(() => {console.log(111);
  }, 1000)
);

你在工作终于到那些问题,解决办法是什么

常常遇到的问题就是 Cannot read property‘prototype’of undefined
解决办法通过浏览器报错提醒代码定位问题,解决问题

Vue 我的项目中遇到视图不更新,办法不执行,埋点不触发等问题
个别解决方案查看浏览器报错,查看代码运行到那个阶段未之行完结,浏览源码以及相干文档等
而后举进去最近开发的我的项目中遇到的算是两个比拟大的问题。

数组扁平化

题目形容: 实现一个办法使多维数组变成一维数组

最常见的递归版本如下:

function flatter(arr) {if (!arr.length) return;
  return arr.reduce((pre, cur) =>
      Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur],
    []);
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

扩大思考:能用迭代的思路去实现吗?

实现代码如下:

function flatter(arr) {if (!arr.length) return;
  while (arr.some((item) => Array.isArray(item))) {arr = [].concat(...arr);
  }
  return arr;
}
// console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

instance 如何应用

右边能够是任意值,左边只能是函数

'hello tuture' instanceof String // false

10 个 Ajax 同时发动申请,全副返回展现后果,并且至少容许三次失败,说出设计思路

这个问题置信很多人会第一工夫想到 Promise.all,然而这个函数有一个局限在于如果失败一次就返回了,间接这样实现会有点问题,须要变通下。以下是两种实现思路

// 以下是不残缺代码,着重于思路 非 Promise 写法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) => {if (success) {
         success++
         if (success + errorCount === 10) {console.log(datas)
         } else {datas.push(res.data)
         }
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失败次数大于 3 次就应该报错了
             throw Error('失败三次')
         }
     }
})
// Promise 写法
let errorCount = 0
let p = new Promise((resolve, reject) => {if (success) {resolve(res.data)
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失败次数大于 3 次就应该报错了
            reject(error)
         } else {resolve(error)
         }
     }
})
Promise.all([p]).then(v => {console.log(v);
});

函数防抖

触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会从新计时。

简略版:函数外部反对应用 this 和 event 对象;

function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        clearTimeout(timeout)
        timeout = setTimeout(function(){func.apply(context, args)
        }, wait);
    }
}

应用:

var node = document.getElementById('layout')
function getUserAction(e) {console.log(this, e)  // 别离打印:node 这个节点 和 MouseEvent
    node.innerHTML = count++;
};
node.onmousemove = debounce(getUserAction, 1000)

最终版:除了反对 this 和 event 外,还反对以下性能:

  • 反对立刻执行;
  • 函数可能有返回值;
  • 反对勾销性能;
function debounce(func, wait, immediate) {
    var timeout, result;

    var debounced = function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果曾经执行过,不再执行
            var callNow = !timeout;
            timeout = setTimeout(function(){timeout = null;}, wait)
            if (callNow) result = func.apply(context, args)
        } else {timeout = setTimeout(function(){func.apply(context, args)
            }, wait);
        }
        return result;
    };

    debounced.cancel = function() {clearTimeout(timeout);
        timeout = null;
    };

    return debounced;
}

应用:

var setUseAction = debounce(getUserAction, 10000, true);
// 应用防抖
node.onmousemove = setUseAction

// 勾销防抖
setUseAction.cancel()

参考 前端进阶面试题具体解答

为什么须要革除浮动?革除浮动的形式

浮动的定义: 非 IE 浏览器下,容器不设高度且子元素浮动时,容器高度不能被内容撑开。此时,内容会溢出到容器里面而影响布局。这种景象被称为浮动(溢出)。

浮动的工作原理:

  • 浮动元素脱离文档流,不占据空间(引起“高度塌陷”景象)
  • 浮动元素碰到蕴含它的边框或者其余浮动元素的边框停留

浮动元素能够左右挪动,直到遇到另一个浮动元素或者遇到它外边缘的蕴含框。浮动框不属于文档流中的一般流,当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。此时文档流中的一般流就会体现得该浮动框不存在一样的布局模式。当蕴含框的高度小于浮动框的时候,此时就会呈现“高度塌陷”。

浮动元素引起的问题?

  • 父元素的高度无奈被撑开,影响与父元素同级的元素
  • 与浮动元素同级的非浮动元素会追随其后
  • 若浮动的元素不是第一个元素,则该元素之前的元素也要浮动,否则会影响页面的显示构造

革除浮动的形式如下:

  • 给父级 div 定义 height 属性
  • 最初一个浮动元素之后增加一个空的 div 标签,并增加 clear:both 款式
  • 蕴含浮动元素的父级标签增加 overflow:hidden 或者overflow:auto
  • 应用 :after 伪元素。因为 IE6- 7 不反对 :after,应用 zoom:1 触发 hasLayout**
.clearfix:after{
    content: "\200B";
    display: table; 
    height: 0;
    clear: both;
  }
  .clearfix{*zoom: 1;}

动静布局求解硬币找零问题

题目形容: 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算能够凑成总金额所需的起码的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

示例 1:输出: coins = [1, 2, 5], amount = 11
输入: 3
解释: 11 = 5 + 5 + 1

示例 2:输出: coins = [2], amount = 3
输入: -1

实现代码如下:

const coinChange = function (coins, amount) {
  // 用于保留每个指标总额对应的最小硬币个数
  const f = [];
  // 提前定义已知状况
  f[0] = 0;
  // 遍历 [1, amount] 这个区间的硬币总额
  for (let i = 1; i <= amount; i++) {
    // 求的是最小值,因而咱们预设为无穷大,确保它肯定会被更小的数更新
    f[i] = Infinity;
    // 循环遍历每个可用硬币的面额
    for (let j = 0; j < coins.length; j++) {
      // 若硬币面额小于指标总额,则问题成立
      if (i - coins[j] >= 0) {
        // 状态转移方程
        f[i] = Math.min(f[i], f[i - coins[j]] + 1);
      }
    }
  }
  // 若指标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回 -1
  if (f[amount] === Infinity) {return -1;}
  // 若有解,间接返回解的内容
  return f[amount];
};

实现函数原型办法

call

应用一个指定的 this 值和一个或多个参数来调用一个函数。

实现要点:

  • this 可能传入 null;
  • 传入不固定个数的参数;
  • 函数可能有返回值;
Function.prototype.call2 = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

apply

apply 和 call 一样,惟一的区别就是 call 是传入不固定个数的参数,而 apply 是传入一个数组。

实现要点:

  • this 可能传入 null;
  • 传入一个数组;
  • 函数可能有返回值;
Function.prototype.apply2 = function (context, arr) {
    var context = context || window;
    context.fn = this;

    var result;
    if (!arr) {result = context.fn();
    } else {var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

bind

bind 办法会创立一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时应用。

实现要点:

  • bind() 除了 this 外,还可传入多个参数;
  • bing 创立的新函数可能传入多个参数;
  • 新函数可能被当做结构函数调用;
  • 函数可能有返回值;
Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

实现 new 关键字

new 运算符用来创立用户自定义的对象类型的实例或者具备构造函数的内置对象的实例。

实现要点:

  • new 会产生一个新对象;
  • 新对象须要可能拜访到构造函数的属性,所以须要从新指定它的原型;
  • 构造函数可能会显示返回;
function objectFactory() {var obj = new Object()
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    var ret = Constructor.apply(obj, arguments);

    // ret || obj 这里这么写思考了构造函数显示返回 null 的状况
    return typeof ret === 'object' ? ret || obj : obj;
};

应用:

function person(name, age) {
    this.name = name
    this.age = age
}
let p = objectFactory(person, '布兰', 12)
console.log(p)  // {name: '布兰', age: 12}

实现 instanceof 关键字

instanceof 就是判断构造函数的 prototype 属性是否呈现在实例的原型链上。

function instanceOf(left, right) {
    let proto = left.__proto__
    while (true) {if (proto === null) return false
        if (proto === right.prototype) {return true}
        proto = proto.__proto__
    }
}

下面的 left.proto 这种写法能够换成 Object.getPrototypeOf(left)。

实现 Object.create

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。

Object.create2 = function(proto, propertyObject = undefined) {if (typeof proto !== 'object' && typeof proto !== 'function') {throw new TypeError('Object prototype may only be an Object or null.')
    if (propertyObject == null) {new TypeError('Cannot convert undefined or null to object')
    }
    function F() {}
    F.prototype = proto
    const obj = new F()
    if (propertyObject != undefined) {Object.defineProperties(obj, propertyObject)
    }
    if (proto === null) {// 创立一个没有原型对象的对象,Object.create(null)
        obj.__proto__ = null
    }
    return obj
}

实现 Object.assign

Object.assign2 = function(target, ...source) {if (target == null) {throw new TypeError('Cannot convert undefined or null to object')
    }
    let ret = Object(target) 
    source.forEach(function(obj) {if (obj != null) {for (let key in obj) {if (obj.hasOwnProperty(key)) {ret[key] = obj[key]
                }
            }
        }
    })
    return ret
}

实现 JSON.stringify

JSON.stringify([, replacer [, space]) 办法是将一个 JavaScript 值 (对象或者数组) 转换为一个 JSON 字符串。此处模仿实现,不思考可选的第二个参数 replacer 和第三个参数 space

  1. 根本数据类型:

    • undefined 转换之后仍是 undefined(类型也是 undefined)
    • boolean 值转换之后是字符串 “false”/”true”
    • number 类型 (除了 NaN 和 Infinity) 转换之后是字符串类型的数值
    • symbol 转换之后是 undefined
    • null 转换之后是字符串 “null”
    • string 转换之后仍是 string
    • NaN 和 Infinity 转换之后是字符串 “null”
  2. 函数类型:转换之后是 undefined
  3. 如果是对象类型(非函数)

    • 如果是一个数组:如果属性值中呈现了 undefined、任意的函数以及 symbol,转换成字符串 “null”;
    • 如果是 RegExp 对象:返回 {} (类型是 string);
    • 如果是 Date 对象,返回 Date 的 toJSON 字符串值;
    • 如果是一般对象;

      • 如果有 toJSON() 办法,那么序列化 toJSON() 的返回值。
      • 如果属性值中呈现了 undefined、任意的函数以及 symbol 值,疏忽。
      • 所有以 symbol 为属性键的属性都会被齐全疏忽掉。
  4. 对蕴含循环援用的对象(对象之间互相援用,造成有限循环)执行此办法,会抛出谬误。
function jsonStringify(data) {
    let dataType = typeof data;

    if (dataType !== 'object') {
        let result = data;
        //data 可能是 string/number/null/undefined/boolean
        if (Number.isNaN(data) || data === Infinity) {
            //NaN 和 Infinity 序列化返回 "null"
            result = "null";
        } else if (dataType === 'function' || dataType === 'undefined' || dataType === 'symbol') {
            //function、undefined、symbol 序列化返回 undefined
            return undefined;
        } else if (dataType === 'string') {result = '"'+ data +'"';}
        //boolean 返回 String()
        return String(result);
    } else if (dataType === 'object') {if (data === null) {return "null"} else if (data.toJSON && typeof data.toJSON === 'function') {return jsonStringify(data.toJSON());
        } else if (data instanceof Array) {let result = [];
            // 如果是数组
            //toJSON 办法能够存在于原型链中
            data.forEach((item, index) => {if (typeof item === 'undefined' || typeof item === 'function' || typeof item === 'symbol') {result[index] = "null";
                } else {result[index] = jsonStringify(item);
                }
            });
            result = "[" + result + "]";
            return result.replace(/'/g,'"');

        } else {
            // 一般对象
            /**             * 循环援用抛错(暂未检测,循环援用时,堆栈溢出)             * symbol key 疏忽             * undefined、函数、symbol 为属性值,被疏忽             */
            let result = [];
            Object.keys(data).forEach((item, index) => {if (typeof item !== 'symbol') {
                    //key 如果是 symbol 对象,疏忽
                    if (data[item] !== undefined && typeof data[item] !== 'function'
                        && typeof data[item] !== 'symbol') {
                        // 键值如果是 undefined、函数、symbol 为属性值,疏忽
                        result.push('"'+ item +'"' + ":" + jsonStringify(data[item]));
                    }
                }
            });
            return ("{" + result + "}").replace(/'/g,'"');
        }
    }
}

实现 JSON.parse

介绍 2 种办法实现:

  • eval 实现;
  • new Function 实现;

eval 实现

第一种形式最简略,也最直观,就是间接调用 eval,代码如下:

var json = '{"a":"1","b":2}';
var obj = eval("(" + json + ")");  // obj 就是 json 反序列化之后失去的对象

然而间接调用 eval 会存在平安问题,如果数据中可能不是 json 数据,而是可执行的 JavaScript 代码,那很可能会造成 XSS 攻打。因而,在调用 eval 之前,须要对数据进行校验。

var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;

if (
    rx_one.test(json.replace(rx_two, "@")
            .replace(rx_three, "]")
            .replace(rx_four, "")
    )
) {var obj = eval("(" +json + ")");
}

new Function 实现

Function 与 eval 有雷同的字符串参数个性。

var json = '{"name":" 小姐姐 ","age":20}';
var obj = (new Function('return' + json))();

实现 Promise

实现 Promise 须要齐全读懂 Promise A+ 标准,不过从总体的实现上看,有如下几个点须要思考到:

  • then 须要反对链式调用,所以得返回一个新的 Promise;
  • 解决异步问题,所以得先用 onResolvedCallbacks 和 onRejectedCallbacks 别离把胜利和失败的回调存起来;
  • 为了让链式调用失常进行上来,须要判断 onFulfilled 和 onRejected 的类型;
  • onFulfilled 和 onRejected 须要被异步调用,这里用 setTimeout 模仿异步;
  • 解决 Promise 的 resolve;
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        let resolve = (value) = > {if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach((fn) = > fn());
            }
        };

        let reject = (reason) = > {if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach((fn) = > fn());
            }
        };

        try {executor(resolve, reject);
        } catch (error) {reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        // 解决 onFufilled,onRejected 没有传值的问题
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) = > v;
        // 因为谬误的值要让前面拜访到,所以这里也要抛出谬误,不然会在之后 then 的 resolve 中捕捉
        onRejected = typeof onRejected === "function" ? onRejected : (err) = > {throw err;};
        // 每次调用 then 都返回一个新的 promise
        let promise2 = new Promise((resolve, reject) = > {if (this.status === FULFILLED) {
                //Promise/A+ 2.2.4 --- setTimeout
                setTimeout(() = > {
                    try {let x = onFulfilled(this.value);
                        // x 可能是一个 proimise
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {reject(e);
                    }
                }, 0);
            }

            if (this.status === REJECTED) {
                //Promise/A+ 2.2.3
                setTimeout(() = > {
                    try {let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {reject(e);
                    }
                }, 0);
            }

            if (this.status === PENDING) {this.onResolvedCallbacks.push(() = > {setTimeout(() = > {
                        try {let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {reject(e);
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(() = > {setTimeout(() = > {
                        try {let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {reject(e);
                        }
                    }, 0);
                });
            }
        });

        return promise2;
    }
}
const resolvePromise = (promise2, x, resolve, reject) = > {
    // 本人期待本人实现是谬误的实现,用一个类型谬误,完结掉 promise  Promise/A+ 2.3.1
    if (promise2 === x) {
        return reject(new TypeError("Chaining cycle detected for promise #<Promise>"));
    }
    // Promise/A+ 2.3.3.3.3 只能调用一次
    let called;
    // 后续的条件要严格判断 保障代码能和别的库一起应用
    if ((typeof x === "object" && x != null) || typeof x === "function") {
        try {
            // 为了判断 resolve 过的就不必再 reject 了(比方 reject 和 resolve 同时调用的时候)Promise/A+ 2.3.3.1
            let then = x.then;
            if (typeof then === "function") {
            // 不要写成 x.then,间接 then.call 就能够了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
                then.call(x, (y) = > {
                        // 依据 promise 的状态决定是胜利还是失败
                        if (called) return;
                        called = true;
                        // 递归解析的过程(因为可能 promise 中还有 promise)Promise/A+ 2.3.3.3.1
                        resolvePromise(promise2, y, resolve, reject);
                    }, (r) = > {
                        // 只有失败就失败 Promise/A+ 2.3.3.3.2
                        if (called) return;
                        called = true;
                        reject(r);
                    });
            } else {
                // 如果 x.then 是个一般值就间接返回 resolve 作为后果  Promise/A+ 2.3.3.4
                resolve(x);
            }
        } catch (e) {
            // Promise/A+ 2.3.3.2
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        // 如果 x 是个一般值就间接返回 resolve 作为后果  Promise/A+ 2.3.4
        resolve(x);
    }
};

Promise 写完之后能够通过 promises-aplus-tests 这个包对咱们写的代码进行测试,看是否合乎 A+ 标准。不过测试前还得加一段代码:

// promise.js
// 这里是下面写的 Promise 全副代码
Promise.defer = Promise.deferred = function () {let dfd = {}
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
module.exports = Promise;

全局装置:

npm i promises-aplus-tests -g

终端下执行验证命令:

promises-aplus-tests promise.js

下面写的代码能够顺利通过全副 872 个测试用例。

Promise.resolve

Promsie.resolve(value) 能够将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值自身是 Promise 则会原样返回它。

Promise.resolve = function(value) {
    // 如果是 Promsie,则间接输入它
    if(value instanceof Promise){return value}
    return new Promise(resolve => resolve(value))
}

Promise.reject

和 Promise.resolve() 相似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。

Promise.reject = function(reason) {return new Promise((resolve, reject) => reject(reason))
}

Promise.all

Promise.all 的规定是这样的:

  • 传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
  • 只有有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;
  • 只有有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
Promise.all = function(promiseArr) {let index = 0, result = []
    return new Promise((resolve, reject) => {promiseArr.forEach((p, i) => {Promise.resolve(p).then(val => {
                index++
                result[i] = val
                if (index === promiseArr.length) {resolve(result)
                }
            }, err => {reject(err)
            })
        })
    })
}

Promise.race

Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。

Promise.race = function(promiseArr) {return new Promise((resolve, reject) => {
        promiseArr.forEach(p => {Promise.resolve(p).then(val => {resolve(val)
            }, err => {rejecte(err)
            })
        })
    })
}

Promise.allSettled

Promise.allSettled 的规定是这样:

  • 所有 Promise 的状态都变动了,那么新返回一个状态是 fulfilled 的 Promise,且它的值是一个数组,数组的每项由所有 Promise 的值和状态组成的对象;
  • 如果有一个是 pending 的 Promise,则返回一个状态是 pending 的新实例;
Promise.allSettled = function(promiseArr) {let result = []

    return new Promise((resolve, reject) => {promiseArr.forEach((p, i) => {Promise.resolve(p).then(val => {
                result.push({
                    status: 'fulfilled',
                    value: val
                })
                if (result.length === promiseArr.length) {resolve(result) 
                }
            }, err => {
                result.push({
                    status: 'rejected',
                    reason: err
                })
                if (result.length === promiseArr.length) {resolve(result) 
                }
            })
        })  
    })   
}

Promise.any

Promise.any 的规定是这样:

  • 空数组或者所有 Promise 都是 rejected,则返回状态是 rejected 的新 Promsie,且值为 AggregateError 的谬误;
  • 只有有一个是 fulfilled 状态的,则返回第一个是 fulfilled 的新实例;
  • 其余状况都会返回一个 pending 的新实例;
Promise.any = function(promiseArr) {
    let index = 0
    return new Promise((resolve, reject) => {if (promiseArr.length === 0) return 
        promiseArr.forEach((p, i) => {Promise.resolve(p).then(val => {resolve(val)

            }, err => {
                index++
                if (index === promiseArr.length) {reject(new AggregateError('All promises were rejected'))
                }
            })
        })
    })
}

虚构 DOM 转换成实在 DOM

形容 :将如下 JSON 格局的 虚构 DOM构造转换成 实在 DOM构造。

// vnode 构造
{
    tag: 'DIV',
    attrs: {id: "app"},
    children: [
        {
            tag: 'SPAN',
            children: [
                {
                    tag: 'A',
                    children: []}
            ]
        }
    ]
}
// 实在 DOM 构造
<div id="app">
    <span>
        <a></a>
    </span>
</div>

实现

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) => {dom.setAttribute(key, vnode.attrs[key]);
        });
    }
    // 子数组进行递归操作
    vnode.children.forEach((child) => dom.appendChild(_render(child)));
    return dom;
}

// 测试
let vnode = {
    tag: "DIV",
    attrs: {id: "app",},
    children: [
        {
            tag: "SPAN",
            children: [
                {
                    tag: "A",
                    children: [],},
            ],
        },
    ],
};
console.log(_render(vnode)); // <div id="app"><span><a></a></span></div>

Set 和 Map 有什么区别?

1、Map 是键值对,Set 是值得汇合,当然键和值能够是任何得值
2、Map 能够通过 get 办法获取值,而 set 不能因为它只有值
3、都能通过迭代器进行 for...of 遍历
4、Set 的值是惟一的能够做数组去重,而 Map 因为没有格局限度,能够做数据存储

说一下 for…in 和 for…of 的区别?

for...of 遍历获取的是对象的键值, for...in 获取的是对象的键名;
for...in 会遍历对象的整个原型链, 性能十分差不举荐应用, 而 for...of 只遍历以后对象不会遍历原型链;
对于数组的遍历,for...in 会返回数组中所有可枚举的属性(包含原型链上可枚举的属性),for...of 只返回数组的下标对应的属性值;
总结:for...in 循环次要是为了遍历对象而生, 不实用遍历数组; for....of 循环能够用来遍历数组、类数组对象、字符串、Set、Map 以及 Generator 对象

公布订阅模式(事件总线)

形容:实现一个公布订阅模式,领有 on, emit, once, off 办法

class EventEmitter {constructor() {
        // 蕴含所有监听器函数的容器对象
        // 内部结构: {msg1: [listener1, listener2], msg2: [listener3]}
        this.cache = {};}
    // 实现订阅
    on(name, callback) {if(this.cache[name]) {this.cache[name].push(callback);
        }
        else {this.cache[name] = [callback];
        }
    }
    // 删除订阅
    off(name, callback) {if(this.cache[name]) {this.cache[name] = this.cache[name].filter(item => item !== callback);
        }
        if(this.cache[name].length === 0) delete this.cache[name];
    }
    // 只执行一次订阅事件
    once(name, callback) {callback();
        this.off(name, callback);
    }
    // 触发事件
    emit(name, ...data) {if(this.cache[name]) {
            // 创立正本,如果回调函数内持续注册雷同事件,会造成死循环
            let tasks = this.cache[name].slice();
            for(let fn of tasks) {fn(...data);
            }
        }
    }
}

PWA 应用过吗?serviceWorker 的应用原理是啥?

渐进式网络应用(PWA)是谷歌在 2015 年底提出的概念。基本上算是 web 应用程序,但在外观和感觉上与 原生 app相似。反对 PWA 的网站能够提供脱机工作、推送告诉和设施硬件拜访等性能。

Service Worker是浏览器在后盾独立于网页运行的脚本,它关上了通向不须要网页或用户交互的性能的大门。当初,它们已包含如推送告诉和后盾同步等性能。未来,Service Worker将会反对如定期同步或天文围栏等其余性能。本教程探讨的外围性能是拦挡和解决网络申请,包含通过程序来治理缓存中的响应。

原函数形参不定长(此时 fn.length 为 0)

function curry(fn) {
    // 保留参数,除去第一个函数参数
    let args = [].slice.call(arguments, 1);
    // 返回一个新函数
    let curried = function () {
        // 新函数调用时会持续传参
        let allArgs = [...args, ...arguments];
        return curry(fn, ...allArgs);
    };
    // 利用 toString 隐式转换的个性,当最初执行函数时,会隐式转换
    curried.toString = function () {return fn(...args);
    };
    return curried;
}

// 测试
function add(...args) {return args.reduce((pre, cur) => pre + cur, 0);
}
console.log(add(1, 2, 3, 4));
let addCurry = curry(add);
console.log(addCurry(1)(2)(3) == 6); // true
console.log(addCurry(1, 2, 3)(4) == 10); // true
console.log(addCurry(2, 6)(1).toString()); // 9
console.log(addCurry(2, 6)(1, 8)); // 打印 curried 函数

说一下常见的 HTTP 状态码? 说一下状态码是 302 和 304 是什么意思?你在我的项目中呈现过么?你是怎么解决的?

    <!-- 状态码:由 3 位数字组成,第一个数字定义了响应的类别 -->
    <!-- 1xx:批示音讯, 示意申请已接管,持续解决 -->
    <!-- 2xx:胜利, 示意申请已被胜利接管,解决 -->
    <!-- 200 OK:客户端申请胜利
         204 No Content:无内容。服务器胜利解决,但未返回内容。个别用在只是客户端向服务器发送信息,而服务器不必向客户端返回什么信息的状况。不会刷新页面。206 Partial Content:服务器曾经实现了局部 GET 申请(客户端进行了范畴申请)。响应报文中蕴含 Content-Range 指定范畴的实体内容
 -->
    <!-- 3xx 重定向 -->
    <!-- 301 Moved Permanently:永恒重定向,示意申请的资源曾经永恒的搬到了其余地位。302 Found:长期重定向,示意申请的资源长期搬到了其余地位
         303 See Other:长期重定向,应应用 GET 定向获取申请资源。303 性能与 302 一样,区别只是 303 明确客户端应该应用 GET 拜访
         307 Temporary Redirect:长期重定向,和 302 有着雷同含意。POST 不会变成 GET
         304 Not Modified:示意客户端发送附带条件的申请(GET 办法申请报文中的 IF…)时,条件不满足。返回 304 时,不蕴含任何响应主体。尽管 304 被划分在 3XX,但和重定向一毛钱关系都没有
 -->
    <!-- 4xx:客户端谬误 -->
    <!-- 400 Bad Request:客户端申请有语法错误,服务器无奈了解。401 Unauthorized:申请未经受权,这个状态代码必须和 WWW-Authenticate 报头域一起应用。403 Forbidden:服务器收到申请,然而回绝提供服务
         404 Not Found:申请资源不存在。比方,输出了谬误的 url
         415 Unsupported media type:不反对的媒体类型
 -->
    <!-- 5xx:服务器端谬误,服务器未能实现非法的申请。-->
    <!-- 500 Internal Server Error:服务器产生不可预期的谬误。503 Server Unavailable:服务器以后不能解决客户端的申请,一段时间后可能恢复正常,-->

说一下常见的 git 操作

git branch 查看本地所有分支
git status 查看以后状态 
git commit 提交 
git branch -a 查看所有的分支
git branch -r 查看近程所有分支
git commit -am "nit" 提交并且加正文 
git remote add origin git@192.168.1.119:ndshow
git push origin master 将文件给推到服务器上 
git remote show origin 显示近程库 origin 里的资源 
git push origin master:develop
git push origin master:hb-dev 将本地库与服务器上的库进行关联 
git checkout --track origin/dev 切换到近程 dev 分支
git branch -D master develop 删除本地库 develop
git checkout -b dev 建设一个新的本地分支 dev
git merge origin/dev 将分支 dev 与以后分支进行合并
git checkout dev 切换到本地 dev 分支
git remote show 查看近程库
git add .
git rm 文件名(包含门路) 从 git 中删除指定文件
git clone git://github.com/schacon/grit.git 从服务器上将代码给拉下来
git config --list 看所有用户
git ls-files 看曾经被提交的
git rm [file name] 删除一个文件
git commit -a 提交以后 repos 的所有的扭转
git add [file name] 增加一个文件到 git index
git commit -v 当你用-v 参数的时候能够看 commit 的差别
git commit -m "This is the message describing the commit" 增加 commit 信息
git commit -a - a 是代表 add,把所有的 change 加到 git index 里而后再 commit
git commit -a -v 个别提交命令
git log 看你 commit 的日志
git diff 查看尚未暂存的更新
git rm a.a 移除文件(从暂存区和工作区中删除)
git rm --cached a.a 移除文件(只从暂存区中删除)
git commit -m "remove" 移除文件(从 Git 中删除)
git rm -f a.a 强行移除批改后文件(从暂存区和工作区中删除)
git diff --cached 或 $ git diff --staged 查看尚未提交的更新
git stash push 将文件给 push 到一个长期空间中
git stash pop 将文件从长期空间 pop 下来

Promise.reject

Promise.reject = function(reason) {return new Promise((resolve, reject) => reject(reason));
}

Vuex 有哪些根本属性? 为什么 Vuex 的 mutation 中不能做异步操作?

有五种,别离是 State、Getter、Mutation、Action、Module
1、state => 根本数据(数据源寄存地)
2、getters => 从根本数据派生进去的数据
3、mutations => 提交更改数据的办法,同步
4、actions => 像一个装璜器,包裹 mutations,使之能够异步。5、modules => 模块化 Vuex

1、Vuex 中所有的状态更新的惟一路径都是 mutation,异步操作通过 Action 来提交 mutation 实现,这样能够不便地跟踪每一个状态的变动,从而可能实现一些工具帮忙更好地理解咱们的利用。2、每个 mutation 执行实现后都会对应到一个新的状态变更,这样 devtools 就能够打个快照存下来,而后就能够实现 time-travel 了。如果 mutation 反对异步操作,就没有方法晓得状态是何时更新的,无奈很好的进行状态的追踪,给调试带来艰难。

具体阐明 Event loop

家喻户晓 JS 是门非阻塞单线程语言,因为在最后 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,咱们在多个线程中解决 DOM 就可能会产生问题(一个线程中新加节点,另一个线程中删除节点),当然能够引入读写锁解决这个问题。

JS 在执行的过程中会产生执行环境,这些执行环境会被程序的退出到执行栈中。如果遇到异步的代码,会被挂起并退出到 Task(有多种 task)队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出须要执行的代码并放入执行栈中执行,所以实质上来说 JS 中的异步还是同步行为。

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

console.log('script end');

以上代码尽管 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,有余会主动减少。所以 setTimeout 还是会在 script end 之后打印。

不同的工作源会被调配到不同的 Task 队列中,工作源能够分为 微工作(microtask)和 宏工作(macrotask)。在 ES6 标准中,microtask 称为 jobs,macrotask 称为 task

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

new Promise((resolve) => {console.log('Promise')
    resolve()}).then(function() {console.log('promise1');
}).then(function() {console.log('promise2');
});

console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout

以上代码尽管 setTimeout 写在 Promise 之前,然而因为 Promise 属于微工作而 setTimeout 属于宏工作,所以会有以上的打印。

微工作包含 process.nextTickpromiseObject.observeMutationObserver

宏工作包含 scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

很多人有个误区,认为微工作快于宏工作,其实是谬误的。因为宏工作中包含了 script,浏览器会先执行一个宏工作,接下来有异步代码的话就先执行微工作。

所以正确的一次 Event loop 程序是这样的

  1. 执行同步代码,这属于宏工作
  2. 执行栈为空,查问是否有微工作须要执行
  3. 执行所有微工作
  4. 必要的话渲染 UI
  5. 而后开始下一轮 Event loop,执行宏工作中的异步代码

通过上述的 Event loop 程序可知,如果宏工作中的异步代码有大量的计算并且须要操作 DOM 的话,为了更快的 界面响应,咱们能够把操作 DOM 放入微工作中。

Node 中的 Event loop

Node 中的 Event loop 和浏览器中的不雷同。

Node 的 Event loop 分为 6 个阶段,它们会依照程序重复运行

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
timer

timers 阶段会执行 setTimeoutsetInterval

一个 timer 指定的工夫并不是精确工夫,而是在达到这个工夫后尽快执行回调,可能会因为零碎正在执行别的事务而提早。

上限的工夫有一个范畴:[1, 2147483647],如果设定的工夫不在这个范畴,将被设置为 1。

I/O

I/O 阶段会执行除了 close 事件,定时器和 setImmediate 的回调

idle, prepare

idle, prepare 阶段外部实现

poll

poll 阶段很重要,这一阶段中,零碎会做两件事件

  1. 执行到点的定时器
  2. 执行 poll 队列中的事件

并且当 poll 中没有定时器的状况下,会发现以下两件事件

  • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者零碎限度
  • 如果 poll 队列为空,会有两件事产生

    • 如果有 setImmediate 须要执行,poll 阶段会进行并且进入到 check 阶段执行 setImmediate
    • 如果没有 setImmediate 须要执行,会期待回调被退出到队列中并立刻执行回调

如果有别的定时器须要被执行,会回到 timer 阶段执行回调。

check

check 阶段执行 setImmediate

close callbacks

close callbacks 阶段执行 close 事件

并且在 Node 中,有些状况下的定时器执行程序是随机的

setTimeout(() => {console.log('setTimeout');
}, 0);
setImmediate(() => {console.log('setImmediate');
})
// 这里可能会输入 setTimeout,setImmediate
// 可能也会相同的输入,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout

当然在这种状况下,执行程序是雷同的

var fs = require('fs')

fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
    }, 0);
    setImmediate(() => {console.log('immediate');
    });
});
// 因为 readFile 的回调在 poll 中执行
// 发现有 setImmediate,所以会立刻跳到 check 阶段执行回调
// 再去 timer 阶段执行 setTimeout
// 所以以上输入肯定是 setImmediate,setTimeout

下面介绍的都是 macrotask 的执行状况,microtask 会在以上每个阶段实现后立刻执行。

setTimeout(()=>{console.log('timer1')

    Promise.resolve().then(function() {console.log('promise1')
    })
}, 0)

setTimeout(()=>{console.log('timer2')

    Promise.resolve().then(function() {console.log('promise2')
    })
}, 0)

// 以上代码在浏览器和 node 中打印状况是不同的
// 浏览器中打印 timer1, promise1, timer2, promise2
// node 中打印 timer1, timer2, promise1, promise2

Node 中的 process.nextTick 会先于其余 microtask 执行。

setTimeout(() => {console.log("timer1");

  Promise.resolve().then(function() {console.log("promise1");
  });
}, 0);

process.nextTick(() => {console.log("nextTick");
});
// nextTick, timer1, promise1
退出移动版