强类型语言和弱类型语言的区别
- 强类型语言:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的应用要严格合乎定义,所有变量都必须先定义后应用。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
不会转换被比拟的两个值的类型,这点和===
更为类似,他们之间也存在一些区别。
NaN
在===
中是不相等的,而在Object.is
中是相等的+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 指向的:
- 执行 db1()时,this 指向全局作用域,所以 window.number 4 = 8,而后执行匿名函数,所以 window.number 5 = 40;
- 执行 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
代码执行过程如下:
- 首先遇到定时器,将其退出到宏工作队列;
- 遇到 Promise,首先执行外面的同步代码,打印出 2,遇到 resolve,将其退出到微工作队列,执行前面同步代码,打印出 3;
- 继续执行 script 中的代码,打印出 7 和 8,至此第一轮代码执行实现;
- 执行微工作队列中的代码,首先打印出 4,如遇到 Promise,执行其中的同步代码,打印出 5,遇到定时器,将其退出到宏工作队列中,此时宏工作队列中有两个定时器;
- 执行宏工作队列中的代码,这里咱们须要留神是的第一个定时器的工夫为 100ms,第二个定时器的工夫为 10ms,所以先执行第二个定时器,打印出 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 种,按优先级从高到低别离是:
- Service Worker:Service Worker 运行在 JavaScript 主线程之外,尽管因为脱离了浏览器窗体无奈间接拜访 DOM,然而它能够实现离线缓存、音讯推送、网络代理等性能。它能够让咱们 自在管制 缓存哪些文件、如何匹配缓存、如何读取缓存,并且 缓存是持续性的 。当 Service Worker 没有命中缓存的时候,须要去调用
fetch
函数获取 数据。也就是说,如果没有在 Service Worker 命中缓存,会依据缓存查找优先级去查找数据。 然而不论是从 Memory Cache 中还是从网络申请中获取的数据,浏览器都会显示是从 Service Worker 中获取的内容。 - Memory Cache: Memory Cache 就是内存缓存,它的效率最快,然而内存缓存尽管读取高效,可是缓存持续性很短,会随着过程的开释而开释。一旦咱们敞开 Tab 页面,内存中的缓存也就被开释了。
- Disk Cache: Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,然而什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。在所有浏览器缓存中,Disk Cache 覆盖面根本是最大的。它会依据 HTTP Herder 中的字段判断哪些资源须要缓存,哪些资源能够不申请间接应用,哪些资源曾经过期须要从新申请。并且即便在跨站点的状况下,雷同地址的资源一旦被硬盘缓存下来,就不会再次去申请数据。
Disk Cache: Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被应用。并且缓存工夫也很短暂,只在会话(Session)中存在,一旦会话完结就被开释。其具备以下特点:
- 所有的资源都能被推送,然而 Edge 和 Safari 浏览器兼容性不怎么好
- 能够推送
no-cache
和no-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
代码的执行过程如下:
- 首先执行函数中的同步代码
async1 start
,之后遇到了await
,它会阻塞async1
前面代码的执行,因而会先去执行async2
中的同步代码async2
,而后跳出async1
; - 跳出
async1
函数后,执行同步代码start
; - 在一轮宏工作全副执行完之后,再来执行
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.nextTick
,promise
,Object.observe
,MutationObserver
宏工作包含 script
,setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
很多人有个误区,认为微工作快于宏工作,其实是谬误的。因为宏工作中包含了 script
,浏览器会先执行一个宏工作,接下来有异步代码的话就先执行微工作。
所以正确的一次 Event loop 程序是这样的
- 执行同步代码,这属于宏工作
- 执行栈为空,查问是否有微工作须要执行
- 执行所有微工作
- 必要的话渲染 UI
- 而后开始下一轮 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 阶段会执行 setTimeout
和 setInterval
一个 timer
指定的工夫并不是精确工夫,而是在达到这个工夫后尽快执行回调,可能会因为零碎正在执行别的事务而提早。
上限的工夫有一个范畴:[1, 2147483647]
,如果设定的工夫不在这个范畴,将被设置为 1。
I/O
I/O 阶段会执行除了 close 事件,定时器和 setImmediate
的回调
idle, prepare
idle, prepare 阶段外部实现
poll
poll 阶段很重要,这一阶段中,零碎会做两件事件
- 执行到点的定时器
- 执行 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> 底部