共计 16488 个字符,预计需要花费 42 分钟才能阅读完成。
继承
原型继承
核心思想:子类的原型成为父类的实例
实现:
function SuperType() {this.colors = ['red', 'green'];
}
function SubType() {}
// 原型继承要害: 子类的原型成为父类的实例
SubType.prototype = new SuperType();
// 测试
let instance1 = new SubType();
instance1.colors.push('blue');
let instance2 = new SubType();
console.log(instance2.colors); // ['red', 'green', 'blue']
原型继承存在的问题:
- 原型中蕴含的援用类型属性将被所有实例对象共享
- 子类在实例化时不能给父类构造函数传参
构造函数继承
核心思想:在子类构造函数中调用父类构造函数
实现:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'green'];
this.getName = function() {return this.name;}
}
function SubType(name) {
// 继承 SuperType 并传参
SuperType.call(this, name);
}
// 测试
let instance1 = new SubType('instance1');
instance1.colors.push('blue');
console.log(instance1.colors); // ['red','green','blue']
let instance2 = new SubType('instance2');
console.log(instance2.colors); // ['red', 'green']
构造函数继承 的呈现是为了解决了原型继承的援用值共享问题。长处是能够在子类构造函数中向父类构造函数传参。它存在的问题是:1)因为办法必须在构造函数中定义,因而办法不能重用。2)子类也不能拜访父类原型上定义的办法。
组合继承
核心思想:综合了原型链和构造函数,即,应用原型链继承原型上的办法,而通过构造函数继承实例属性。
实现:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {console.log(this.name);
}
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
// 实例属性
this.age = age;
}
// 继承办法
SubType.prototype = new SuperType();
// 测试
let instance1 = new SubType('instance1', 1);
instance1.sayName(); // "instance1"
instance1.colors.push("blue");
console.log(instance1.colors); // ['red','green','blue']
let instance2 = new SubType('instance2', 2);
instance2.sayName(); // "instance2"
console.log(instance2.colors); // ['red','green']
组合继承 存在的问题是:父类构造函数始终会被调用两次:一次是在创立子类原型时 new SuperType()
调用,另一次是在子类构造函数中 SuperType.call()
调用。
寄生式组合继承(最佳)
核心思想:通过构造函数继承属性,但应用混合式原型继承办法,即,不通过调用父类构造函数给子类原型赋值,而是获得父类原型的一个正本。
实现:
function SuperType(name) {
this.name = name;
this.colors = ['red', 'green'];
}
Super.prototype.sayName = function() {console.log(this.name);
}
function SubType(name, age) {
// 继承属性
SuperType.call(this, name);
this.age = age;
}
// 继承办法
SubType.prototype = Object.create(SuperType.prototype);
// 重写原型导致默认 constructor 失落,手动将 constructor 指回 SubType
SubType.prototype.constructor = SubType;
class 实现继承(ES6)
核心思想:通过 extends
来实现类的继承(相当于 ES5
的原型继承)。通过 super
调用父类的构造方法 (相当于 ES5
的构造函数继承)。
实现:
class SuperType {constructor(name) {this.name = name;}
sayName() {console.log(this.name);
}
}
class SubType extends SuperType {constructor(name, age) {super(name); // 继承属性
this.age = age;
}
}
// 测试
let instance = new SubType('instance', 0);
instance.sayName(); // "instance"
尽管类继承应用的是新语法,但背地仍旧应用的是原型链。
OPTIONS 申请办法及应用场景
OPTIONS 是除了 GET 和 POST 之外的其中一种 HTTP 申请办法。
OPTIONS 办法是用于申请取得由 Request-URI
标识的资源在申请 / 响应的通信过程中能够应用的性能选项。通过这个办法,客户端能够 在采取具体资源申请之前,决定对该资源采取何种必要措施,或者理解服务器的性能。该申请办法的响应不能缓存。
OPTIONS 申请办法的 主要用途 有两个:
- 获取服务器反对的所有 HTTP 申请办法;
- 用来查看拜访权限。例如:在进行 CORS 跨域资源共享时,对于简单申请,就是应用 OPTIONS 办法发送嗅探申请,以判断是否有对指定资源的拜访权限。
浏览器渲染过程的线程有哪些
浏览器的渲染过程的线程总共有五种:(1)GUI 渲染线程 负责渲染浏览器页面,解析 HTML、CSS,构建 DOM 树、构建 CSSOM 树、构建渲染树和绘制页面;当界面须要 重绘 或因为某种操作引发 回流 时,该线程就会执行。
留神:GUI 渲染线程和 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新会被保留在一个队列中等到 JS 引擎闲暇时立刻被执行。
(2)JS 引擎线程 JS 引擎线程也称为 JS 内核,负责解决 Javascript 脚本程序,解析 Javascript 脚本,运行代码;JS 引擎线程始终期待着工作队列中工作的到来,而后加以解决,一个 Tab 页中无论什么时候都只有一个 JS 引擎线程在运行 JS 程序;
留神:GUI 渲染线程与 JS 引擎线程的互斥关系,所以如果 JS 执行的工夫过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞。
(3)工夫触发线程 工夫触发线程 属于浏览器而不是 JS 引擎,用来管制事件循环;当 JS 引擎执行代码块如 setTimeOut 时(也可是来自浏览器内核的其余线程, 如鼠标点击、AJAX 异步申请等),会将对应工作增加到事件触发线程中;当对应的事件合乎触发条件被触发时,该线程会把事件增加到待处理队列的队尾,期待 JS 引擎的解决;
留神:因为 JS 的单线程关系,所以这些待处理队列中的事件都得排队期待 JS 引擎解决(当 JS 引擎闲暇时才会去执行);
(4)定时器触发过程 定时器触发过程 即 setInterval 与 setTimeout 所在线程;浏览器定时计数器并不是由 JS 引擎计数的,因为 JS 引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;因而应用独自线程来计时并触发定时器,计时结束后,增加到事件队列中,期待 JS 引擎闲暇后执行,所以定时器中的工作在设定的工夫点不肯定可能准时执行,定时器只是在指定工夫点将工作增加到事件队列中;
留神:W3C 在 HTML 规范中规定,定时器的定时工夫不能小于 4ms,如果是小于 4ms,则默认为 4ms。
(5)异步 http 申请线程
- XMLHttpRequest 连贯后通过浏览器新开一个线程申请;
- 检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,期待 JS 引擎闲暇后执行;
虚构 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>
如何判断一个对象是不是空对象?
Object.keys(obj).length === 0
手写题:在线编程,getUrlParams(url,key); 就是很简略的获取 url 的某个参数的问题,但要思考边界状况,多个返回值等等
代码输入后果
function a() {console.log(this);
}
a.call(null);
打印后果:window 对象
依据 ECMAScript262 标准规定:如果第一个参数传入的对象调用者是 null 或者 undefined,call 办法将把全局对象(浏览器上是 window 对象)作为 this 的值。所以,不论传入 null 还是 undefined,其 this 都是全局对象 window。所以,在浏览器上答案是输入 window 对象。
要留神的是,在严格模式中,null 就是 null,undefined 就是 undefined:
'use strict';
function a() {console.log(this);
}
a.call(null); // null
a.call(undefined); // undefined
代码输入后果
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
输入后果如下:
1
看到这个题目,好多的 then,实际上只须要记住一个准则:.then
或 .catch
的参数冀望是函数,传入非函数则会产生 值透传。
第一个 then 和第二个 then 中传入的都不是函数,一个是数字,一个是对象,因而产生了透传,将resolve(1)
的值间接传到最初一个 then 里,间接打印出 1。
参考:前端进阶面试题具体解答
JS 闭包,你理解多少?
应该有面试官问过你:
- 什么是闭包?
- 闭包有哪些理论使用场景?
- 闭包是如何产生的?
- 闭包产生的变量如何被回收?
这些问题其实都能够被看作是同一个问题,那就是面试官在问你:你对 JS 闭包理解多少?
来总结一下我听到过的答案,尽量齐全还原候选人面试的时候说的原话。
答案 1:
就是一个 function
外面 return
了一个子函数,子函数拜访了里面那个函数的变量。
答案 2:
for 循环外面能够用闭包来解决问题。
for(var i = 0; i < 10; i++){setTimeout(()=>console.log(i),0)
}
// 控制台输入 10 遍 10.
for(var i = 0; i < 10; i++){(function(a){setTimeout(()=>console.log(a),0)
})(i)
}
// 控制台输入 0 -9
答案 3:
以后作用域产产生了对父作用域的援用。
答案 4:
不晓得。是跟浏览器的垃圾回收机制无关吗?
开杠了。请问,小伙伴的答案和以上的内容有多少类似水平?
其实,拿着这些问题好好想想,你就会发现这些问题都只是为了最终那一个问题。
闭包的底层实现原理
1. JS 执行上下文
咱们都晓得,咱们手写的 js 代码是要通过浏览器 V8 进行预编译后能力真正的被执行。例如变量晋升、函数晋升。举个栗子。
// 栗子:var d = 'abc';
function a(){console.log("函数 a");
};
console.log(a); // ƒ a(){ console.log("函数 a"); }
a(); // '函数 a'
var a = "变量 a";
console.log(a); // '变量 a'
a(); // a is not a function
var c = 123;
// 输入后果及程序:// ƒ a(){ console.log("函数 a"); }
// '函数 a'
// '变量 a'
// a is not a function
// 栗子预编后相当于:function a(){console.log("函数 a");
};
var d;
console.log(a); // ƒ a(){ console.log("函数 a"); }
a(); // '函数 a'
a = "变量 a"; // 此时变量 a 赋值,函数申明被笼罩
console.log(a); // "变量 a"
a(); // a is not a function
那么问题来了。请问是谁来执行预编译操作的?那这个谁又是在哪里进行预编译的?
是的,你的纳闷没有错。js 代码运行须要一个运行环境,那这个环境就是 执行上下文。是的,js 运行前的预编译也是在这个环境中进行。
js 执行上下文分三种:
全局执行上下文
:代码开始执行时首先进入的环境。函数执行上下文
:函数调用时,会开始执行函数中的代码。eval 执行上下文
:不倡议应用,可疏忽。
那么,执行上下文的周期,分为两个阶段:
-
创立阶段
- 创立词法环境
- 生成变量对象 (
VO
),建设 作用域链 、 作用域链 、 作用域链(重要的事说三遍) - 确认
this
指向,并绑定this
执行阶段
。这个阶段进行变量赋值,函数援用及执行代码。
你当初猜猜看,预编译是产生在什么时候?
噢,我遗记说了,其实与编译还有另一个称说:执行期上下文
。
预编译产生在函数执行之前。预编译四部曲为:
- 创立
AO
对象 - 找形参和变量申明,将变量和形参作为 AO 属性名,值为
undefined
- 将实参和形参相对立
- 在函数体里找到函数申明,值赋予函数体。最初程序输入变量值的时候,就是从
AO
对象中拿。
所以,预编译真正的后果是:
var AO = {a = function a(){console.log("函数 a");};
d = 'abc'
}
咱们从新来。
1. 什么叫变量对象?
变量对象是 js
代码在进入执行上下文时,js
引擎在内存中建设的一个对象,用来寄存以后执行环境中的变量。
2. 变量对象 (VO) 的创立过程
变量对象的创立,是在执行上下文创立阶段,顺次通过以下三个过程:
-
创立
arguments
对象。对于函数执行环境,首先查问是否有传入的实参,如果有,则会将参数名是实参值组成的键值对放入
arguments
对象中。否则,将参数名和undefined
组成的键值对放入arguments
对象中。
// 举个栗子
function bar(a, b, c) {console.log(arguments); // [1, 2]
console.log(arguments[2]); // undefined
}
bar(1,2)
- 当遇到同名的函数时,前面的会笼罩后面的。
console.log(a); // function a() {console.log('Is a ?') }
function a() {console.log('Is a');
}
function a() {console.log('Is a ?')
}
/**ps: 在执行第一行代码之前,函数申明曾经创立实现. 前面的对之前的申明进行了笼罩。**/
- 查看以后环境中的变量申明并赋值为
undefined
。当遇到同名的函数申明, 为了防止函数被赋值为undefined
, 会疏忽此申明
console.log(a); // function a() {console.log('Is a ?') }
console.log(b); // undefined
function a() {console.log('Is a');
}
function a() {console.log('Is a ?');
}
var b = 'Is b';
var a = 10086;
/** 这段代码执行一下,你会发现 a 打印后果仍旧是一个函数,而 b 则是 undefined。**/
依据以上三个步骤,对于变量晋升也就晓得是怎么回事了。
3. 变量对象变为流动对象
执行上下文的第二个阶段,称为执行阶段,在此时,会进行变量赋值,函数援用并执行其余代码,此时,变量对象变为流动对象。
咱们还是举下面的例子:
console.log(a); // function a() {console.log('fjdsfs') }
console.log(b); // undefined
function a() {console.log('Is a');
}
function a() {console.log('Is a?');
}
var b = 'Is b';
console.log(b); // 'Is b'
var a = 10086;
console.log(a); // 10086
var b = 'Is b?';
console.log(b); // 'Is b?'
在下面的代码中,代码真正开始执行是从第一行 console.log() 开始的,自这之前,执行上下文是这样的:
// 创立过程
EC= {VO:{}; // 创立变量对象
scopeChain: {}; // 作用域链}
VO = {argument: {...}; // 以后为全局上下文,所以这个属性值是空的
a: <a reference> // 函数 a 的援用地址 b: undefiend // 见上文创立变量对象的第三步}
词法作用域(Lexical scope
)
这里想阐明,咱们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript
的一个简单之处在于它如何查找变量,如果在函数执行上下文中找不到变量,它将在调用上下文中寻找它,如果在它的调用上下文中没有找到,就始终往上一级,直到它在全局执行上下文中查找为止。(如果最初找不到,它就是 undefined
)。
再来举个栗子:
1: let top = 0; //
2: function createWarp() {3: function add(a, b) {
4: let ret = a + b
5: return ret
6: }
7: return add
8: }
9: let sum = createWarp()
10: let result = sum(top, 8)
11: console.log('result:',result)
剖析过程如下:
- 在全局上下文中申明变量
top
并赋值为 0. - 2 – 8 行。在全局执行上下文中申明了一个名为
createWarp
的变量,并为其调配了一个函数定义。其中第 3 - 7 行形容了其函数定义,并将函数定义存储到那个变量 (createWarp
) 中。 - 第 9 行。咱们在全局执行上下文中申明了一个名为
sum
的新变量,临时,值为undefined
。 - 第 9 行。遇到
()
,表明须要执行或调用一个函数。 那么查找全局执行上下文的内存并查找名为createWarp
的变量。 显著,曾经在步骤 2 中创立结束。接着,调用它。 - 调用函数时,回到第 2 行。创立一个新的
createWarp
执行上下文。咱们能够在createWarp
的执行上下文中创立自有变量。js
引擎createWarp
的上下文增加到调用堆栈(call stack
)。因为这个函数没有参数,间接跳到它的主体局部. - 3 – 6 行。咱们有一个新的函数申明,在
createWarp
执行上下文中创立一个变量add
。add
只存在于createWarp
执行上下文中, 其函数定义存储在名为add
的自有变量中。 - 第 7 行,咱们返回变量
add
的内容。js 引擎查找一个名为add
的变量并找到它. 第 4 行和第 5 行括号之间的内容形成该函数定义。 createWarp
调用结束,createWarp
执行上下文将被销毁。add 变量也跟着被销毁。 但add
函数定义依然存在,因为它返回并赋值给了sum
变量。(ps:这才是闭包产生的变量存于内存当中的假相
)- 接下来就是简略的执行过程,不再赘述。。
- ……
- 代码执行结束,全局执行上下文被销毁。sum 和 result 也跟着被销毁。
小结一下
当初,如果再让你答复什么是闭包,你能答出多少?
其实,大家说的都对。不论是函数返回一个函数,还是产生了内部作用域的援用,都是有情理的。
所以,什么是闭包?
- 解释一下作用域链是如何产生的。
- 解释一下 js 执行上下文的创立、执行过程。
- 解释一下闭包所产生的变量放在哪了。
- 最初请把以上 3 点联合起来说给面试官听。
图片懒加载
与一般的图片懒加载不同,如下这个多做了 2 个精心解决:
- 图片全副加载实现后移除事件监听;
- 加载完的图片,从 imgList 移除;
let imgList = [...document.querySelectorAll('img')]
let length = imgList.length
// 修改谬误,须要加上自执行
- const imgLazyLoad = function() {+ const imgLazyLoad = (function() {
let count = 0
return function() {let deleteIndexList = []
imgList.forEach((img, index) => {let rect = img.getBoundingClientRect()
if (rect.top < window.innerHeight) {
img.src = img.dataset.src
deleteIndexList.push(index)
count++
if (count === length) {document.removeEventListener('scroll', imgLazyLoad)
}
}
})
imgList = imgList.filter((img, index) => !deleteIndexList.includes(index))
}
- }
+ })()
// 这里最好加上防抖解决
document.addEventListener('scroll', imgLazyLoad)
手写题:数组扁平化
function flatten(arr) {let result = [];
for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {result = result.concat(flatten(arr[i]));
} else {result = result.concat(arr[i]);
}
}
return result;
}
const a = [1, [2, [3, 4]]];
console.log(flatten(a));
说一说 SessionStorage 和 localStorage 还有 cookie
共同点:都是保留在浏览器端、且同源的
不同点:1.cookie 数据始终在同源的 http 申请中携带(即便不须要),即 cookie 在浏览器和服务器间来回传递。cookie 数据还有门路(path)的概念,能够限度 cookie 只属于某个门路下
sessionStorage 和 localStorage 不会主动把数据发送给服务器,仅在本地保留。2. 存储大小限度也不同,cookie 数据不能超过 4K,sessionStorage 和 localStorage 能够达到 5M
3.sessionStorage:仅在以后浏览器窗口敞开之前无效;localStorage:始终无效,窗口或浏览器敞开也始终保留,本地存储,因而用作持久数据;cookie:只在设置的 cookie 过期工夫之前无效,即便窗口敞开或浏览器敞开
4. 作用域不同
sessionStorage:不在不同的浏览器窗口中共享,即便是同一个页面;localstorage:在所有同源窗口中都是共享的;也就是说只有浏览器不敞开,数据依然存在
cookie: 也是在所有同源窗口中都是共享的. 也就是说只有浏览器不敞开,数据依然存在
vuex
vuex 是一个专为 vue.js 利用程序开发的状态管理器,它采纳集中式存储管理利用的所有组件的状态,并且以相
应的规定保障状态以一种能够预测的形式发生变化。state: vuex 应用繁多状态树,用一个对象就蕴含来全副的利用层级状态
mutation: 更改 vuex 中 state 的状态的惟一办法就是提交 mutation
action: action 提交的是 mutation,而不是间接变更状态,action 能够蕴含任意异步操作
getter: 相当于 vue 中的 computed 计算属性
说说 Vue 原理
Vue 是采纳数据劫持配合发布者 - 订阅者模式,通过 Object.defineProperty 来 () 来劫持各个属性的 getter 和 setter
在数据发生变化的时候,公布音讯给依赖收集器,去告诉观察者,做出对应的回调函数去更新视图。具体就是:MVVM 作为绑定的入口,整合 Observe,Compil 和 Watcher 三者,通过 Observe 来监听 model 的变动
通过 Compil 来解析编译模版指令,最终利用 Watcher 搭起 Observe 和 Compil 之前的通信桥梁
从而达到数据变动 => 更新视图,视图交互变动(input) => 数据 model 变更的双向绑定成果。
说一说前端性能优化计划
三个方面来阐明前端性能优化
一:webapck 优化与开启 gzip 压缩
1.babel-loader 用 include 或 exclude 来帮咱们防止不必要的转译,不转译 node_moudules 中的 js 文件
其次在缓存以后转译的 js 文件,设置 loader: 'babel-loader?cacheDirectory=true' 2. 文件采纳按需加载等等 3. 具体的做法非常简单,只须要你在你的 request headers 中加上这么一句:accept-encoding:gzip 4. 图片优化,采纳 svg 图片或者字体图标 5. 浏览器缓存机制,它又分为强缓存和协商缓存二:本地存储——从 Cookie 到 Web Storage、IndexedDB 阐明一下 SessionStorage 和 localStorage 还有 cookie 的区别和优缺点三:代码优化 1. 事件代理 2. 事件的节流和防抖 3. 页面的回流和重绘 4.EventLoop 事件循环机制 5. 代码优化等等
什么是同源策略
跨域问题其实就是浏览器的同源策略造成的。
同源策略限度了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在歹意文件的重要的平安机制。同源指的是:协定 、 端口号 、 域名 必须统一。
同源策略:protocol(协定)、domain(域名)、port(端口)三者必须统一。
同源政策次要限度了三个方面:
- 以后域下的 js 脚本不可能拜访其余域下的 cookie、localStorage 和 indexDB。
- 以后域下的 js 脚本不可能操作拜访操作其余域下的 DOM。
- 以后域下 ajax 无奈发送跨域申请。
同源政策的目标次要是为了保障用户的信息安全,它只是对 js 脚本的一种限度,并不是对浏览器的限度,对于个别的 img、或者 script 脚本申请都不会有跨域的限度,这是因为这些操作都不会通过响应后果来进行可能呈现平安问题的操作。
网络劫持有哪几种,如何防备?
⽹络劫持分为两种:
(1)DNS 劫持: (输⼊京东被强制跳转到淘宝这就属于 dns 劫持)
- DNS 强制解析: 通过批改运营商的本地 DNS 记录,来疏导⽤户流量到缓存服务器
- 302 跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是能够进⾏劫持解决的, 再对劫持的内存发动 302 跳转的回复,疏导⽤户获取内容
(2)HTTP 劫持: (拜访⾕歌然而⼀直有贪玩蓝⽉的⼴告), 因为 http 明⽂传输, 运营商会批改你的 http 响应内容(即加⼴告)
DNS 劫持因为涉嫌守法,曾经被监管起来,当初很少会有 DNS 劫持,⽽ http 劫持仍然⾮常盛⾏,最无效的方法就是全站 HTTPS,将 HTTP 加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。
Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行程序?
Node 中的 Event Loop 和浏览器中的是齐全不雷同的货色。
Node 的 Event Loop 分为 6 个阶段,它们会依照 程序 重复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到零碎设定的阈值,就会进入下一阶段。
(1)Timers(计时器阶段):首次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(蕴含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Pending callbacks 阶段。
(2)Pending callbacks:执行推延到下一个循环迭代的 I / O 回调(零碎调用相干的回调)。
(3)Idle/Prepare:仅供外部应用。
(4)Poll(轮询阶段):
- 当回调队列不为空时:会执行回调,若回调中触发了相应的微工作,这里的微工作执行机会和其余中央有所不同,不会等到所有回调执行结束后才执行,而是针对每一个回调执行结束后,就执行相应微工作。执行完所有的回调后,变为上面的状况。
- 当回调队列为空时(没有回调或所有回调执行结束):但如果存在有计时器(setTimeout、setInterval 和 setImmediate)没有执行,会完结轮询阶段,进入 Check 阶段。否则会阻塞并期待任何正在执行的 I / O 操作实现,并马上执行相应的回调,直到所有回调执行结束。
(5)Check(查问阶段):会查看是否存在 setImmediate 相干的回调,如果存在则执行所有回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Close callbacks 阶段。
(6)Close callbacks:执行一些敞开回调,比方 socket.on(‘close’, …)等。
上面来看一个例子,首先在有些状况下,定时器的执行程序其实是 随机 的
setTimeout(() => { console.log('setTimeout')}, 0)setImmediate(() => { console.log('setImmediate')})
对于以上代码来说,setTimeout
可能执行在前,也可能执行在后
- 首先
setTimeout(fn, 0) === setTimeout(fn, 1)
,这是由源码决定的 - 进入事件循环也是须要老本的,如果在筹备时候破费了大于 1ms 的工夫,那么在 timer 阶段就会间接执行
setTimeout
回调 - 那么如果筹备工夫破费小于 1ms,那么就是
setImmediate
回调先执行了
当然在某些状况下,他们的执行程序肯定是固定的,比方以下代码:
const fs = require('fs')
fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
}, 0)
setImmediate(() => {console.log('immediate')
})
})
在上述代码中,setImmediate
永远 先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行结束后队列为空,发现存在 setImmediate
回调,所以就间接跳转到 check 阶段去执行回调了。
下面都是 macrotask 的执行状况,对于 microtask 来说,它会在以上每个阶段实现前 清空 microtask 队列,
setTimeout(() => {console.log('timer21')
}, 0)
Promise.resolve().then(function() {console.log('promise1')
})
对于以上代码来说,其实和浏览器中的输入是一样的,microtask 永远执行在 macrotask 后面。
最初来看 Node 中的 process.nextTick
,这个函数其实是独立于 Event Loop 之外的,它有一个本人的队列,当每个阶段实现后,如果存在 nextTick 队列,就会 清空队列中的所有回调函数,并且优先于其余 microtask 执行。
setTimeout(() => {console.log('timer1')
Promise.resolve().then(function() {console.log('promise1')
})
}, 0)
process.nextTick(() => {console.log('nextTick')
process.nextTick(() => {console.log('nextTick')
process.nextTick(() => {console.log('nextTick')
process.nextTick(() => {console.log('nextTick')
})
})
})
})
对于以上代码,永远都是先把 nextTick 全副打印进去。
call apply bind
题目形容: 手写 call apply bind 实现
实现代码如下:
Function.prototype.myCall = function (context, ...args) {if (!context || context === null) {context = window;}
// 发明惟一的 key 值 作为咱们结构的 context 外部办法名
let fn = Symbol();
context[fn] = this; //this 指向调用 call 的函数
// 执行函数并返回后果 相当于把本身作为传入的 context 的办法进行调用了
return context[fn](...args);
};
// apply 原理统一 只是第二个参数是传入的数组
Function.prototype.myApply = function (context, args) {if (!context || context === null) {context = window;}
// 发明惟一的 key 值 作为咱们结构的 context 外部办法名
let fn = Symbol();
context[fn] = this;
// 执行函数并返回后果
return context[fn](...args);
};
//bind 实现要简单一点 因为他思考的状况比拟多 还要波及到参数合并(相似函数柯里化)
Function.prototype.myBind = function (context, ...args) {if (!context || context === null) {context = window;}
// 发明惟一的 key 值 作为咱们结构的 context 外部办法名
let fn = Symbol();
context[fn] = this;
let _this = this;
// bind 状况要简单一点
const result = function (...innerArgs) {
// 第一种状况 : 若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符应用,则不绑定传入的 this,而是将 this 指向实例化进去的对象
// 此时因为 new 操作符作用 this 指向 result 实例对象 而 result 又继承自传入的_this 依据原型链常识可得出以下论断
// this.__proto__ === result.prototype //this instanceof result =>true
// this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
if (this instanceof _this === true) {
// 此时 this 指向指向 result 的实例 这时候不须要扭转 this 指向
this[fn] = _this;
this[fn](...[...args, ...innerArgs]); // 这里应用 es6 的办法让 bind 反对参数合并
} else {
// 如果只是作为一般函数调用 那就很简略了 间接扭转 this 指向为传入的 context
context[fn](...[...args, ...innerArgs]);
}
};
// 如果绑定的是构造函数 那么须要继承构造函数原型属性和办法
// 实现继承的形式: 应用 Object.create
result.prototype = Object.create(this.prototype);
return result;
};
// 用法如下
// function Person(name, age) {// console.log(name); //'我是参数传进来的 name'
// console.log(age); //'我是参数传进来的 age'
// console.log(this); // 构造函数 this 指向实例对象
// }
// // 构造函数原型的办法
// Person.prototype.say = function() {// console.log(123);
// }
// let obj = {
// objName: '我是 obj 传进来的 name',
// objAge: '我是 obj 传进来的 age'
// }
// // 一般函数
// function normalFun(name, age) {// console.log(name); //'我是参数传进来的 name'
// console.log(age); //'我是参数传进来的 age'
// console.log(this); // 一般函数 this 指向绑定 bind 的第一个参数 也就是例子中的 obj
// console.log(this.objName); //'我是 obj 传进来的 name'
// console.log(this.objAge); //'我是 obj 传进来的 age'
// }
// 先测试作为结构函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的 name')
// let a = new bindFun('我是参数传进来的 age')
// a.say() //123
// 再测试作为一般函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的 name')
// bindFun('我是参数传进来的 age')
什么状况会阻塞渲染?
首先渲染的前提是生成渲染树,所以 HTML 和 CSS 必定会阻塞渲染。如果你想渲染的越快,你越应该升高一开始须要渲染的文件大小,并且扁平层级,优化选择器。而后当浏览器在解析到 script 标签时,会暂停构建 DOM,实现后才会从暂停的中央从新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都倡议将 script 标签放在 body 标签底部的起因。
当然在当下,并不是说 script 标签必须放在底部,因为你能够给 script 标签增加 defer 或者 async 属性。当 script 标签加上 defer 属性当前,示意该 JS 文件会并行下载,然而会放到 HTML 解析实现后程序执行,所以对于这种状况你能够把 script 标签放在任意地位。对于没有任何依赖的 JS 文件能够加上 async 属性,示意 JS 文件下载和解析不会阻塞渲染。
NaN 是什么,用 typeof 会输入什么?
Not a Number,示意非数字,typeof NaN === ‘number’