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

  • 强类型语言:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的应用要严格合乎定义,所有变量都必须先定义后应用。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);     // 15console.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);

输入后果为:

23784561

代码执行过程如下:

  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)  })

输入后果如下:

1finally2finallyfinally2前面的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 startasync2startasync1 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); // 1console.log(z); // undefinedconsole.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>  底部