关于javascript:诚意满满的前端面试总结

6次阅读

共计 17556 个字符,预计需要花费 44 分钟才能阅读完成。

强类型语言和弱类型语言的区别

  • 强类型语言:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的应用要严格合乎定义,所有变量都必须先定义后应用。Java 和 C ++ 等语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不通过强制转换,那么它就永远是这个数据类型了。例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。
  • 弱类型语言:弱类型语言也称为弱类型定义语言,与强类型定义相同。JavaScript 语言就属于弱类型语言。简略了解就是一种变量类型能够被疏忽的语言。比方 JavaScript 是弱类型定义的,在 JavaScript 中就能够将字符串 ’12’ 和整数 3 进行连贯失去字符串 ’123’,在相加的时候会进行强制类型转换。

两者比照:强类型语言在速度上可能略逊色于弱类型语言,然而强类型语言带来的严谨性能够无效地帮忙防止许多谬误。

说一下 HTTP 和 HTTPS 协定的区别?

1、HTTPS 协定须要 CA 证书, 费用较高; 而 HTTP 协定不须要
2、HTTP 协定是超文本传输协定, 信息是明文传输的,HTTPS 则是具备安全性的 SSL 加密传输协定;
3、应用不同的连贯形式, 端口也不同,HTTP 协定端口是 80,HTTPS 协定端口是 443;
4、HTTP 协定连贯很简略, 是无状态的;HTTPS 协定是具备 SSL 和 HTTP 协定构建的可进行加密传输、身份认证的网络协议, 比 HTTP 更加平安

说一下购物车的逻辑?

//vue 中购物车逻辑的实现
1. 购物车信息用一个数组来存储,数组中保留对象,对象中有 id 和 count 属性

2. 在 vuex 中 state 中增加一个数据 cartList 用来保留这个数组

3. 因为商品详情页须要用到退出购物车性能,所以咱们须要提供一个 mutation, 用来将购物车信息退出 cartList 中

4. 退出购物车信息的时候,遵循如下规定:如果购物车中曾经有了该商品信息,则数量累加,如果没有该商品信息,则新增一个对象

5. 在商品详情页,点击退出购物车按钮的时候,调用 vuex 提供的 addToCart 这个 mutation 将以后的商品信息(id count)传给 addTocart  this.$store.commit("addToCart", {id:  , count:})

// js 中购物车逻辑的实现
1. 商品页点击“退出购物车”按钮,触发事件

2. 事件调用购物车“减少商品”的 Js 程序(函数、对象办法)3. 向 Js 程序传递传递“商品 id”、“商品数量”等数据

4. 存储“商品 id”、“商品数量”到浏览器的 localStorage 中

** 展现购物车中的商品 ******

1. 关上购物车页面

2. 从 localStorage 中取出“商品 Id”、“商品数量”等信息。3. 调用服务器端“取得商品详情”的接口失去购物车中的商品信息(参数为商品 Id)4. 将取得的商品信息显示在购物车页面。** 实现购物车中商品的购买 ******

1. 用户对购物车中的商品实现购买流程,产生购物订单

2. 革除 localStorage 中存储的曾经购买的商品信息

备注 1:购物车中商品存储的数据除了“商品 id”、“商品数量”之外,依据产品要求还能够有其余的信息,例如残缺的商品详情(这样就不必掉服务器接口取得详情了)、购物车商品的过期工夫,超过工夫的购物车商品在下次关上网站或者购物车页面时被革除。备注 2:购物车商品除了存储在 localStorage 中,依据产品的需要不同,也能够存储在 sessionStorage、cookie、session 中,或者间接向服务器接口发动申请存储在服务器上。何种状况应用哪种形式存储、有啥区别请本人剖析。

实现数组原型办法

forEach

语法:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

参数:

callback:为数组中每个元素执行的函数,该函数承受 1 - 3 个参数currentValue: 数组中正在解决的以后元素index(可选): 数组中正在解决的以后元素的索引array(可选): forEach() 办法正在操作的数组 thisArg(可选): 当执行回调函数 callback 时,用作 this 的值。

返回值:undefined

Array.prototype.forEach1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    // 创立一个新的 Object 对象。该对象将会包裹 (wrapper) 传入的参数 this(以后数组)。const O = Object(this);
    // O.length >>> 0 无符号右移 0 位
    // 意义:为了保障转换后的值为正整数。// 其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {if(k in O) {callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}

map

语法:arr.map(callback(currentValue [, index [, array]])[, thisArg])

参数:与 forEach() 办法一样

返回值:一个由原数组每个元素执行回调函数的后果组成的新数组。

Array.prototype.map1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新数组
    let k = 0;
    while(k < len) {if(k in O) {newArr[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
    return newArr;
}

filter

语法:arr.filter(callback(element [, index [, array]])[, thisArg])

参数:

callback: 用来测试数组的每个元素的函数。返回 true 示意该元素通过测试,保留该元素,false 则不保留。它承受以下三个参数:element、index、array,参数的意义与 forEach 一样。

thisArg(可选): 执行 callback 时,用于 this 的值。

返回值:一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

Array.prototype.filter1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let newArr = [];  // 返回的新数组
    let k = 0;
    while(k < len) {if(k in O) {if(callback.call(thisArg, O[k], k, O)) {newArr.push(O[k]);
            }
        }
        k++;
    }
    return newArr;
}

some

语法:arr.some(callback(element [, index [, array]])[, thisArg])

参数:

callback: 用来测试数组的每个元素的函数。承受以下三个参数:element、index、array,参数的意义与 forEach 一样。

thisArg(可选): 执行 callback 时,用于 this 的值。
返回值:数组中有至多一个元素通过回调函数的测试就会返回 true;所有元素都没有通过回调函数的测试返回值才会为 false

Array.prototype.some1 = function(callback, thisArg) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this); 
    const len = O.length >>> 0;
    let k = 0;
    while(k < len) {if(k in O) {if(callback.call(thisArg, O[k], k, O)) {return true}
        }
        k++;
    }
    return false;
}

reduce

语法:arr.reduce(callback(preVal, curVal[, curIndex [, array]])[, initialValue])

参数:

callback: 一个“reducer”函数,蕴含四个参数:

preVal:上一次调用 callback 时的返回值。在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]

curVal:数组中正在解决的元素。在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],否则为 array[1]

curIndex(可选):数组中正在解决的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。

array(可选):用于遍历的数组。
initialValue(可选): 作为第一次调用 callback 函数时参数 preVal 的值。若指定了初始值 initialValue,则 curVal 则将应用数组第一个元素;否则 preVal 将应用数组第一个元素,而 curVal 将应用数组第二个元素。
返回值:应用“reducer”回调函数遍历整个数组后的后果。

Array.prototype.reduce1 = function(callback, initialValue) {if(this == null) {throw new TypeError('this is null or not defined');
    }
    if(typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
    }
    const O = Object(this);
    const len = O.length >>> 0;
    let k = 0;
    let accumulator = initialValue;
    // 如果第二个参数为 undefined 的状况下,则数组的第一个有效值(非 empty)作为累加器的初始值
    if(accumulator === undefined) {while(k < len && !(k in O)) {k++;}
        // 如果超出数组界线还没有找到累加器的初始值,则 TypeError
        if(k >= len) {throw new TypeError('Reduce of empty array with no initial value');
        }
        accumulator = O[k++];
    }
    while(k < len) {if(k in O) {accumulator = callback(accumulator, O[k], k, O);
        }
        k++;
    }
    return accumulator;
}

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

Object.is()

形容 Object.is 不会转换被比拟的两个值的类型,这点和=== 更为类似,他们之间也存在一些区别。

  1. NaN=== 中是不相等的,而在 Object.is 中是相等的
  2. +0-0=== 中是相等的,而在 Object.is 中是不相等的

实现:利用 ===

Object.is = function(x, y) {if(x === y) {
        // 当前情况下,只有一种状况是非凡的,即 +0 -0
        // 如果 x !== 0,则返回 true
        // 如果 x === 0,则须要判断 + 0 和 -0,则能够间接应用 1/+0 === Infinity 和 1/-0 === -Infinity 来进行判断
        return x !== 0 || 1 / x === 1 / y;
    }
    // x !== y 的状况下,只须要判断是否为 NaN,如果 x!==x,则阐明 x 是 NaN,同理 y 也一样
    // x 和 y 同时为 NaN 时,返回 true
    return x !== x && y !== y;
}

代码输入问题

window.number = 2;
var obj = {
 number: 3,
 db1: (function(){console.log(this);
   this.number *= 4;
   return function(){console.log(this);
     this.number *= 5;
   }
 })()}
var db1 = obj.db1;
db1();
obj.db1();
console.log(obj.number);     // 15
console.log(window.number);  // 40

这道题目看清起来有点乱,然而实际上是考查 this 指向的:

  1. 执行 db1()时,this 指向全局作用域,所以 window.number 4 = 8,而后执行匿名函数,所以 window.number 5 = 40;
  2. 执行 obj.db1(); 时,this 指向 obj 对象,执行匿名函数,所以 obj.numer * 5 = 15。

单例模式

用意:保障一个类仅有一个实例,并提供一个拜访它的全局拜访点。

次要解决:一个全局应用的类频繁地创立与销毁。

何时应用:当您想管制实例数目,节俭系统资源的时候。

如何解决:判断零碎是否曾经有这个单例,如果有则返回,如果没有则创立。

实现

var Singleton = (function() {
    // 如果在外部申明 SingletonClass 对象,则无奈在内部间接调用
    var SingletonClass = function() {}; 
    var instance;
    return function() {
        // 如果已存在,则返回 instance
        if(instance) return instance;
        // 如果不存在,则 new 一个 SingletonClass 对象
        instance = new SingletonClass();
        return instance;
    }
})();

// 测试
var a = new Singleton();
var b = new Singleton();
console.log(a === b);  // true

Promise 以及相干办法的实现

题目形容: 手写 Promise 以及 Promise.all Promise.race 的实现

实现代码如下:

class Mypromise {constructor(fn) {
    // 示意状态
    this.state = "pending";
    // 示意 then 注册的胜利函数
    this.successFun = [];
    // 示意 then 注册的失败函数
    this.failFun = [];

    let resolve = (val) => {
      // 放弃状态扭转不可变(resolve 和 reject 只准触发一种)if (this.state !== "pending") return;

      // 胜利触发机会  扭转状态 同时执行在 then 注册的回调事件
      this.state = "success";
      // 为了保障 then 事件先注册(次要是思考在 promise 外面写同步代码)promise 标准 这里为模仿异步
      setTimeout(() => {
        // 执行以后事件外面所有的注册函数
        this.successFun.forEach((item) => item.call(this, val));
      });
    };

    let reject = (err) => {if (this.state !== "pending") return;
      // 失败触发机会  扭转状态 同时执行在 then 注册的回调事件
      this.state = "fail";
      // 为了保障 then 事件先注册(次要是思考在 promise 外面写同步代码)promise 标准 这里模仿异步
      setTimeout(() => {this.failFun.forEach((item) => item.call(this, err));
      });
    };
    // 调用函数
    try {fn(resolve, reject);
    } catch (error) {reject(error);
    }
  }

  // 实例办法 then

  then(resolveCallback, rejectCallback) {
    // 判断回调是否是函数
    resolveCallback =
      typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
    rejectCallback =
      typeof rejectCallback !== "function"
        ? (err) => {throw err;}
        : rejectCallback;
    // 为了放弃链式调用  持续返回 promise
    return new Mypromise((resolve, reject) => {
      // 将回调注册到 successFun 事件汇合外面去
      this.successFun.push((val) => {
        try {
          //    执行回调函数
          let x = resolveCallback(val);
          //(最难的一点)// 如果回调函数后果是一般值 那么就 resolve 进来给下一个 then 链式调用  如果是一个 promise 对象(代表又是一个异步)那么调用 x 的 then 办法 将 resolve 和 reject 传进去 等到 x 外部的异步 执行结束的时候(状态实现)就会主动执行传入的 resolve 这样就管制了链式调用的程序
          x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
        } catch (error) {reject(error);
        }
      });

      this.failFun.push((val) => {
        try {
          //    执行回调函数
          let x = rejectCallback(val);
          x instanceof Mypromise ? x.then(resolve, reject) : reject(x);
        } catch (error) {reject(error);
        }
      });
    });
  }
  // 静态方法
  static all(promiseArr) {let result = [];
    // 申明一个计数器 每一个 promise 返回就加一
    let count = 0;
    return new Mypromise((resolve, reject) => {for (let i = 0; i < promiseArr.length; i++) {
      // 这里用 Promise.resolve 包装一下 避免不是 Promise 类型传进来
        Promise.resolve(promiseArr[i]).then((res) => {// 这里不能间接 push 数组  因为要管制程序一一对应(感激评论区斧正)
            result[i] = res;
            count++;
            // 只有全副的 promise 执行胜利之后才 resolve 进来
            if (count === promiseArr.length) {resolve(result);
            }
          },
          (err) => {reject(err);
          }
        );
      }
    });
  }
  // 静态方法
  static race(promiseArr) {return new Mypromise((resolve, reject) => {for (let i = 0; i < promiseArr.length; i++) {Promise.resolve(promiseArr[i]).then((res) => {
            //promise 数组只有有任何一个 promise 状态变更  就能够返回
            resolve(res);
          },
          (err) => {reject(err);
          }
        );
      }
    });
  }
}

// 应用
// let promise1 = new Mypromise((resolve, reject) => {//   setTimeout(() => {//     resolve(123);
//   }, 2000);
// });
// let promise2 = new Mypromise((resolve, reject) => {//   setTimeout(() => {//     resolve(1234);
//   }, 1000);
// });

// Mypromise.all([promise1,promise2]).then(res=>{//   console.log(res);
// })

// Mypromise.race([promise1, promise2]).then(res => {//   console.log(res);
// });

// promise1
//   .then(
//     res => {//       console.log(res); // 过两秒输入 123
//       return new Mypromise((resolve, reject) => {//         setTimeout(() => {//           resolve("success");
//         }, 1000);
//       });
//     },
//     err => {//       console.log(err);
//     }
//   )
//   .then(
//     res => {//       console.log(res); // 再过一秒输入 success
//     },
//     err => {//       console.log(err);
//     }
//   );

扩大思考: 如何勾销 promise

Promise.race()办法能够用来竞争 Promise
能够借助这个个性 本人包装一个 空的 Promise 与要发动的 Promise 来实现

function wrap(pro) {let obj = {};
  // 结构一个新的 promise 用来竞争
  let p1 = new Promise((resolve, reject) => {
    obj.resolve = resolve;
    obj.reject = reject;
  });

  obj.promise = Promise.race([p1, pro]);
  return obj;
}

let testPro = new Promise((resolve, reject) => {setTimeout(() => {resolve(123);
  }, 1000);
});

let wrapPro = wrap(testPro);
wrapPro.promise.then((res) => {console.log(res);
});
wrapPro.resolve("被拦挡了");

代码输入后果

setTimeout(function () {console.log(1);
}, 100);

new Promise(function (resolve) {console.log(2);
  resolve();
  console.log(3);
}).then(function () {console.log(4);
  new Promise((resove, reject) => {console.log(5);
    setTimeout(() =>  {console.log(6);
    }, 10);
  })
});
console.log(7);
console.log(8);

输入后果为:

2
3
7
8
4
5
6
1

代码执行过程如下:

  1. 首先遇到定时器,将其退出到宏工作队列;
  2. 遇到 Promise,首先执行外面的同步代码,打印出 2,遇到 resolve,将其退出到微工作队列,执行前面同步代码,打印出 3;
  3. 继续执行 script 中的代码,打印出 7 和 8,至此第一轮代码执行实现;
  4. 执行微工作队列中的代码,首先打印出 4,如遇到 Promise,执行其中的同步代码,打印出 5,遇到定时器,将其退出到宏工作队列中,此时宏工作队列中有两个定时器;
  5. 执行宏工作队列中的代码,这里咱们须要留神是的第一个定时器的工夫为 100ms,第二个定时器的工夫为 10ms,所以先执行第二个定时器,打印出 6;
  6. 此时微工作队列为空,继续执行宏工作队列,打印出 1。

做完这道题目,咱们就须要分外留神,每个定时器的工夫,并不是所有定时器的工夫都为 0 哦。

说一下前端登录的流程?

首次登录的时候,前端调后调的登录接口,发送用户名和明码,后端收到申请,验证用户名和明码,验证胜利,就给前端返回一个 token,和一个用户信息的值,前端拿到 token,将 token 贮存到 Vuex 中,而后从 Vuex 中把 token 的值存入浏览器 Cookies 中。把用户信息存到 Vuex 而后再存储到 LocalStroage 中, 而后跳转到下一个页面,依据后端接口的要求,只有不登录就不能拜访的页面须要在前端每次跳转页面师判断 Cookies 中是否有 token,没有就跳转到登录页,有就跳转到相应的页面,咱们应该再每次发送 post/get 申请的时候应该退出 token,罕用办法再我的项目 utils/service.js 中增加全局拦截器,将 token 的值放入申请头中 后端判断申请头中有无 token,有 token,就拿到 token 并验证 token 是否过期,在这里过期会返回有效的 token 而后有个跳回登录页面从新登录并且革除本地用户的信息

浏览器资源缓存的地位有哪些?

资源缓存的地位一共有 3 种,按优先级从高到低别离是:

  1. Service Worker:Service Worker 运行在 JavaScript 主线程之外,尽管因为脱离了浏览器窗体无奈间接拜访 DOM,然而它能够实现离线缓存、音讯推送、网络代理等性能。它能够让咱们 自在管制 缓存哪些文件、如何匹配缓存、如何读取缓存,并且 缓存是持续性的 。当 Service Worker 没有命中缓存的时候,须要去调用 fetch 函数获取 数据。也就是说,如果没有在 Service Worker 命中缓存,会依据缓存查找优先级去查找数据。 然而不论是从 Memory Cache 中还是从网络申请中获取的数据,浏览器都会显示是从 Service Worker 中获取的内容。
  2. Memory Cache: Memory Cache 就是内存缓存,它的效率最快,然而内存缓存尽管读取高效,可是缓存持续性很短,会随着过程的开释而开释。一旦咱们敞开 Tab 页面,内存中的缓存也就被开释了。
  3. Disk Cache: Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,然而什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。在所有浏览器缓存中,Disk Cache 覆盖面根本是最大的。它会依据 HTTP Herder 中的字段判断哪些资源须要缓存,哪些资源能够不申请间接应用,哪些资源曾经过期须要从新申请。并且即便在跨站点的状况下,雷同地址的资源一旦被硬盘缓存下来,就不会再次去申请数据。

Disk Cache: Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被应用。并且缓存工夫也很短暂,只在会话(Session)中存在,一旦会话完结就被开释。其具备以下特点:

  • 所有的资源都能被推送,然而 Edge 和 Safari 浏览器兼容性不怎么好
  • 能够推送 no-cacheno-store 的资源
  • 一旦连贯被敞开,Push Cache 就被开释
  • 多个页面能够应用雷同的 HTTP/2 连贯,也就是说能应用同样的缓存
  • Push Cache 中的缓存只能被应用一次
  • 浏览器能够拒绝接受曾经存在的资源推送
  • 能够给其余域名推送资源

什么是执行栈

能够把执行栈认为是一个存储函数调用的 栈构造,遵循先进后出的准则。当开始执行 JS 代码时,依据先进后出的准则,后执行的函数会先弹出栈,能够看到,foo 函数后执行,当执行结束后就从栈中弹出了。

平时在开发中,能够在报错中找到执行栈的痕迹:

function foo() {throw new Error('error')
}
function bar() {foo()
}
bar()

能够看到报错在 foo 函数,foo 函数又是在 bar 函数中调用的。当应用递归时,因为栈可寄存的函数是有 限度 的,一旦寄存了过多的函数且没有失去开释的话,就会呈现爆栈的问题

function bar() {  bar()}bar()

代码输入后果

Promise.resolve('1')
  .then(res => {console.log(res)
  })
  .finally(() => {console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {console.log('finally2')
      return '我是 finally2 返回的值'
  })
  .then(res => {console.log('finally2 前面的 then 函数', res)
  })

输入后果如下:

1
finally2
finally
finally2 前面的 then 函数 2

.finally()个别用的很少,只有记住以下几点就能够了:

  • .finally()办法不论 Promise 对象最初的状态如何都会执行
  • .finally()办法的回调函数不承受任何的参数,也就是说你在 .finally() 函数中是无奈晓得 Promise 最终的状态是 resolved 还是 rejected
  • 它最终返回的默认会是一个上一次的 Promise 对象值,不过如果抛出的是一个异样则返回异样的 Promise 对象。
  • finally 实质上是 then 办法的特例

.finally()的谬误捕捉:

Promise.resolve('1')
  .finally(() => {console.log('finally1')
    throw new Error('我是 finally 中抛出的异样')
  })
  .then(res => {console.log('finally 前面的 then 函数', res)
  })
  .catch(err => {console.log('捕捉谬误', err)
  })

输入后果为:

'finally1'
'捕捉谬误' Error: 我是 finally 中抛出的异样

代码输入后果

async function async1() {console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {console.log("async2");
}
async1();
console.log('start')

输入后果如下:

async1 start
async2
start
async1 end

代码的执行过程如下:

  1. 首先执行函数中的同步代码 async1 start,之后遇到了await,它会阻塞async1 前面代码的执行,因而会先去执行 async2 中的同步代码async2,而后跳出async1
  2. 跳出 async1 函数后,执行同步代码start
  3. 在一轮宏工作全副执行完之后,再来执行 await 前面的内容async1 end

这里能够了解为 await 前面的语句相当于放到了 new Promise 中,下一行及之后的语句相当于放在 Promise.then 中。

Promise.reject

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

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

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

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

Promise.allSettled

形容 :等到所有promise 都返回后果,就返回一个 promise 实例。

实现

Promise.allSettled = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = {
                            status: 'fulfilled',
                            value: value
                        };
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => {
                        count++;
                        result[index] = {
                            status: 'rejected'.
                            reason: reason
                        };
                        if(count === promises.length) resolve(result);
                    }
                );
            });
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

代码输入后果

(function(){var x = y = 1;})();
var z;

console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined

这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行 y = 1, 因为 y 没有应用 var 申明,所以它是一个全局变量,而后第二步是将 y 赋值给 x,讲一个全局变量赋值给了一个局部变量,最终,x 是一个局部变量,y 是一个全局变量,所以打印 x 是报错。

具体阐明 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

对 HTML 语义化的了解

语义化是指依据内容的结构化(内容语义化),抉择适合的标签(代码语义化)。艰深来讲就是用正确的标签做正确的事件。

语义化的长处如下:

  • 对机器敌对,带有语义的文字表现力丰盛,更适宜搜索引擎的爬虫爬取无效信息,有利于 SEO。除此之外,语义类还反对读屏软件,依据文章能够主动生成目录;
  • 对开发者敌对,应用语义类标签加强了可读性,构造更加清晰,开发者能清晰的看出网页的构造,便于团队的开发与保护。

常见的语义化标签:

<header></header>  头部

<nav></nav>  导航栏

<section></section>  区块(有语义化的 div)<main></main>  次要区域

<article></article>  次要内容

<aside></aside>  侧边栏

<footer></footer>  底部
正文完
 0