继承
原型继承
核心思想:子类的原型成为父类的实例
实现:
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 指回 SubTypeSubType.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); // nulla.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 functionvar 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); // undefinedfunction 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); // undefinedfunction 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); // 10086var 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的状态的惟一办法就是提交mutationaction: 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'