共计 13222 个字符,预计需要花费 34 分钟才能阅读完成。
说一下 web worker
在 HTML 页面中,如果在执行脚本时,页面的状态是不可相应的,直到脚本执行实现后,页面才变成可相应。web worker 是运行在后盾的 js,独立于其余脚本,不会影响页面的性能。并且通过 postMessage 将后果回传到主线程。这样在进行简单操作的时候,就不会阻塞主线程了。
如何创立 web worker:
- 检测浏览器对于 web worker 的支持性
- 创立 web worker 文件(js,回传函数等)
- 创立 web worker 对象
代码输入后果
const async1 = async () => {console.log('async1');
setTimeout(() => {console.log('timer1')
}, 2000)
await new Promise(resolve => {console.log('promise1')
})
console.log('async1 end')
return 'async1 success'
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.catch(4)
.then(res => console.log(res))
setTimeout(() => {console.log('timer2')
}, 1000)
输入后果如下:
script start
async1
promise1
script end
1
timer2
timer1
代码的执行过程如下:
- 首先执行同步带吗,打印出 script start;
- 遇到定时器 timer1 将其退出宏工作队列;
- 之后是执行 Promise,打印出 promise1,因为 Promise 没有返回值,所以前面的代码不会执行;
- 而后执行同步代码,打印出 script end;
- 继续执行上面的 Promise,.then 和.catch 冀望参数是一个函数,这里传入的是一个数字,因而就会产生值浸透,将 resolve(1)的值传到最初一个 then,间接打印出 1;
- 遇到第二个定时器,将其退出到微工作队列,执行微工作队列,按程序顺次执行两个定时器,然而因为定时器工夫的起因,会在两秒后先打印出 timer2,在四秒后打印出 timer1。
同步和异步的区别
- 同步 指的是当一个过程在执行某个申请时,如果这个申请须要期待一段时间能力返回,那么这个过程会始终期待上来,直到音讯返回为止再持续向下执行。
- 异步 指的是当一个过程在执行某个申请时,如果这个申请须要期待一段时间能力返回,这个时候过程会持续往下执行,不会阻塞期待音讯的返回,当音讯返回时零碎再告诉过程进行解决。
三栏布局的实现
三栏布局个别指的是页面中一共有三栏,左右两栏宽度固定,两头自适应的布局,三栏布局的具体实现:
- 利用 相对定位,左右两栏设置为相对定位,两头设置对应方向大小的 margin 的值。
.outer {
position: relative;
height: 100px;
}
.left {
position: absolute;
width: 100px;
height: 100px;
background: tomato;
}
.right {
position: absolute;
top: 0;
right: 0;
width: 200px;
height: 100px;
background: gold;
}
.center {
margin-left: 100px;
margin-right: 200px;
height: 100px;
background: lightgreen;
}
- 利用 flex 布局,左右两栏设置固定大小,两头一栏设置为 flex:1。
.outer {
display: flex;
height: 100px;
}
.left {
width: 100px;
background: tomato;
}
.right {
width: 100px;
background: gold;
}
.center {
flex: 1;
background: lightgreen;
}
- 利用浮动,左右两栏设置固定大小,并设置对应方向的浮动。两头一栏设置左右两个方向的 margin 值,留神这种形式,两头一栏必须放到最初:
.outer {height: 100px;}
.left {
float: left;
width: 100px;
height: 100px;
background: tomato;
}
.right {
float: right;
width: 200px;
height: 100px;
background: gold;
}
.center {
height: 100px;
margin-left: 100px;
margin-right: 200px;
background: lightgreen;
}
- 圣杯布局,利用浮动和负边距来实现。父级元素设置左右的 padding,三列均设置向左浮动,两头一列放在最后面,宽度设置为父级元素的宽度,因而前面两列都被挤到了下一行,通过设置 margin 负值将其挪动到上一行,再利用绝对定位,定位到两边。
.outer {
height: 100px;
padding-left: 100px;
padding-right: 200px;
}
.left {
position: relative;
left: -100px;
float: left;
margin-left: -100%;
width: 100px;
height: 100px;
background: tomato;
}
.right {
position: relative;
left: 200px;
float: right;
margin-left: -200px;
width: 200px;
height: 100px;
background: gold;
}
.center {
float: left;
width: 100%;
height: 100px;
background: lightgreen;
}
- 双飞翼布局,双飞翼布局绝对于圣杯布局来说,左右地位的保留是通过两头列的 margin 值来实现的,而不是通过父元素的 padding 来实现的。实质上来说,也是通过浮动和外边距负值来实现的。
.outer {height: 100px;}
.left {
float: left;
margin-left: -100%;
width: 100px;
height: 100px;
background: tomato;
}
.right {
float: left;
margin-left: -200px;
width: 200px;
height: 100px;
background: gold;
}
.wrapper {
float: left;
width: 100%;
height: 100px;
background: lightgreen;
}
.center {
margin-left: 100px;
margin-right: 200px;
height: 100px;
}
什么是尾调用,应用尾调用有什么益处?
尾调用指的是函数的最初一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留以后的执行上下文,而后再新建另外一个执行上下文退出栈中。应用尾调用的话,因为曾经是函数的最初一步,所以这时能够不用再保留以后的执行上下文,从而节俭了内存,这就是尾调用优化。然而 ES6 的尾调用优化只在严格模式下开启,失常模式是有效的。
手写题:数组扁平化
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));
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;
}
对 CSS 工程化的了解
CSS 工程化是为了解决以下问题:
- 宏观设计:CSS 代码如何组织、如何拆分、模块构造怎么设计?
- 编码优化:怎么写出更好的 CSS?
- 构建:如何解决我的 CSS,能力让它的打包后果最优?
- 可维护性:代码写完了,如何最小化它后续的变更老本?如何确保任何一个共事都能轻松接手?
以下三个方向都是时下比拟风行的、普适性十分好的 CSS 工程化实际:
- 预处理器:Less、Sass 等;
- 重要的工程化插件:PostCss;
- Webpack loader 等。
基于这三个方向,能够衍生出一些具备典型意义的子问题,这里咱们一一来看:
(1)预处理器:为什么要用预处理器?它的呈现是为了解决什么问题?
预处理器,其实就是 CSS 世界的“轮子”。预处理器反对咱们写一种相似 CSS、但理论并不是 CSS 的语言,而后把它编译成 CSS 代码:那为什么写 CSS 代码写得好好的,偏偏要转去写“类 CSS”呢?这就和原本用 JS 也能够实现所有性能,但最初却写 React 的 jsx 或者 Vue 的模板语法一样——为了爽!要想晓得有了预处理器有多爽,首先要晓得的是传统 CSS 有多不爽。随着前端业务复杂度的进步,前端工程中对 CSS 提出了以下的诉求:
- 宏观设计上:咱们心愿能优化 CSS 文件的目录构造,对现有的 CSS 文件实现复用;
- 编码优化上:咱们心愿能写出构造清晰、扼要易懂的 CSS,须要它具备高深莫测的嵌套层级关系,而不是无差别的一铺到底写法;咱们心愿它具备变量特色、计算能力、循环能力等等更强的可编程性,这样咱们能够少写一些无用的代码;
- 可维护性上:更强的可编程性意味着更优质的代码构造,实现复用意味着更简略的目录构造和更强的拓展能力,这两点如果能做到,天然会带来更强的可维护性。
这三点是传统 CSS 所做不到的,也正是预处理器所解决掉的问题。预处理器广泛会具备这样的个性:
- 嵌套代码的能力,通过嵌套来反映不同 css 属性之间的层级关系;
- 反对定义 css 变量;
- 提供计算函数;
- 容许对代码片段进行 extend 和 mixin;
- 反对循环语句的应用;
- 反对将 CSS 文件模块化,实现复用。
(2)PostCss:PostCss 是如何工作的?咱们在什么场景下会应用 PostCss?
它和预处理器的不同就在于,预处理器解决的是 类 CSS,而 PostCss 解决的就是 CSS 自身。Babel 能够将高版本的 JS 代码转换为低版本的 JS 代码。PostCss 做的是相似的事件:它能够编译尚未被浏览器广泛支持的先进的 CSS 语法,还能够主动为一些须要额定兼容的语法减少前缀。更强的是,因为 PostCss 有着弱小的插件机制,反对各种各样的扩大,极大地强化了 CSS 的能力。
PostCss 在业务中的应用场景十分多:
- 进步 CSS 代码的可读性:PostCss 其实能够做相似预处理器能做的工作;
- 当咱们的 CSS 代码须要适配低版本浏览器时,PostCss 的 Autoprefixer 插件能够帮忙咱们主动减少浏览器前缀;
- 容许咱们编写面向未来的 CSS:PostCss 可能帮忙咱们编译 CSS next 代码;
(3)Webpack 能解决 CSS 吗?如何实现? Webpack 能解决 CSS 吗:
- Webpack 在裸奔的状态下,是不能解决 CSS 的,Webpack 自身是一个面向 JavaScript 且只能解决 JavaScript 代码的模块化打包工具;
- Webpack 在 loader 的辅助下,是能够解决 CSS 的。
如何用 Webpack 实现对 CSS 的解决:
- Webpack 中操作 CSS 须要应用的两个要害的 loader:css-loader 和 style-loader
-
留神,答出“用什么”有时候可能还不够,面试官会狐疑你是不是在背答案,所以你还须要理解每个 loader 都做了什么事件:
- css-loader:导入 CSS 模块,对 CSS 代码进行编译解决;
- style-loader:创立 style 标签,把 CSS 内容写入标签。
在理论应用中,css-loader 的执行程序肯定要安顿在 style-loader 的后面。因为只有实现了编译过程,才能够对 css 代码进行插入;若提前插入了未编译的代码,那么 webpack 是无奈了解这坨货色的,它会无情报错。
HTTP 1.0 和 HTTP 1.1 之间有哪些区别?
HTTP 1.0 和 HTTP 1.1 有以下区别:
- 连贯方面,http1.0 默认应用非长久连贯,而 http1.1 默认应用长久连贯。http1.1 通过应用长久连贯来使多个 http 申请复用同一个 TCP 连贯,以此来防止应用非长久连贯时每次须要建设连贯的时延。
- 资源申请方面,在 http1.0 中,存在一些节约带宽的景象,例如客户端只是须要某个对象的一部分,而服务器却将整个对象送过来了,并且不反对断点续传性能,http1.1 则在申请头引入了 range 头域,它容许只申请资源的某个局部,即返回码是 206(Partial Content),这样就不便了开发者自在的抉择以便于充分利用带宽和连贯。
- 缓存方面,在 http1.0 中次要应用 header 里的 If-Modified-Since、Expires 来做为缓存判断的规范,http1.1 则引入了更多的缓存控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来管制缓存策略。
- http1.1 中 新增了 host 字段,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个惟一的 IP 地址,因而,申请音讯中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的倒退,在一台物理服务器上能够存在多个虚拟主机,并且它们共享一个 IP 地址。因而有了 host 字段,这样就能够将申请发往到同一台服务器上的不同网站。
- http1.1 绝对于 http1.0 还新增了很多 申请办法,如 PUT、HEAD、OPTIONS 等。
当在浏览器中输出 Google.com 并且按下回车之后产生了什么?
(1)解析 URL: 首先会对 URL 进行解析,剖析所须要应用的传输协定和申请的资源的门路。如果输出的 URL 中的协定或者主机名不非法,将会把地址栏中输出的内容传递给搜索引擎。如果没有问题,浏览器会查看 URL 中是否呈现了非法字符,如果存在非法字符,则对非法字符进行本义后再进行下一过程。
(2)缓存判断: 浏览器会判断所申请的资源是否在缓存里,如果申请的资源在缓存里并且没有生效,那么就间接应用,否则向服务器发动新的申请。
(3)DNS 解析: 下一步首先须要获取的是输出的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则应用,如果没有则向本地 DNS 服务器发动申请。本地 DNS 服务器也会先查看是否存在缓存,如果没有就会先向根域名服务器发动申请,取得负责的顶级域名服务器的地址后,再向顶级域名服务器申请,而后取得负责的权威域名服务器的地址后,再向权威域名服务器发动申请,最终取得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给申请的用户。用户向本地 DNS 服务器发动申请属于递归申请,本地 DNS 服务器向各级域名服务器发动申请属于迭代申请。
(4)获取 MAC 地址: 当浏览器失去 IP 地址后,数据传输还须要晓得目标主机 MAC 地址,因为应用层下发数据给传输层,TCP 协定会指定源端口号和目标端口号,而后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目标地址。而后将下发给数据链路层,数据链路层的发送须要退出通信单方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目标 MAC 地址须要分状况解决。通过将 IP 地址与本机的子网掩码相与,能够判断是否与申请主机在同一个子网里,如果在同一个子网里,能够应用 APR 协定获取到目标主机的 MAC 地址,如果不在一个子网里,那么申请应该转发给网关,由它代为转发,此时同样能够通过 ARP 协定来获取网关的 MAC 地址,此时目标主机的 MAC 地址应该为网关的地址。
(5)TCP 三次握手: 上面是 TCP 建设连贯的三次握手的过程,首先客户端向服务器发送一个 SYN 连贯申请报文段和一个随机序号,服务端接管到申请后向服务器端发送一个 SYN ACK 报文段,确认连贯申请,并且也向客户端发送一个随机序号。客户端接管服务器的确认应答后,进入连贯建设的状态,同时向服务器也发送一个 ACK 确认报文段,服务器端接管到确认后,也进入连贯建设状态,此时单方的连贯就建设起来了。
(6)HTTPS 握手: 如果应用的是 HTTPS 协定,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送应用的协定的版本号、一个随机数和能够应用的加密办法。服务器端收到后,确认加密的办法,也向客户端发送一个随机数和本人的数字证书。客户端收到后,首先查看数字证书是否无效,如果无效,则再生成一个随机数,并应用证书中的公钥对随机数加密,而后发送给服务器端,并且还会提供一个后面所有内容的 hash 值供服务器端测验。服务器端接管后,应用本人的私钥对数据解密,同时向客户端发送一个后面所有内容的 hash 值供客户端测验。这个时候单方都有了三个随机数,依照之前所约定的加密办法,应用这三个随机数生成一把秘钥,当前单方通信前,就应用这个秘钥对数据进行加密后再传输。
(7)返回数据: 当页面申请发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接管到响应后,开始对 html 文件进行解析,开始页面的渲染过程。
(8)页面渲染: 浏览器首先会依据 html 文件构建 DOM 树,依据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建设好后,依据它们来构建渲染树。渲染树构建好后,会依据渲染树来进行布局。布局实现后,最初应用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示进去了。
(9)TCP 四次挥手: 最初一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送实现,则它须要向服务端发送连贯开释申请。服务端收到连贯开释申请后,会通知应用层要开释 TCP 链接。而后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连贯曾经开释,不再接管客户端发的数据了。然而因为 TCP 连贯是双向的,所以服务端仍旧能够发送数据给客户端。服务端如果此时还有没发完的数据会持续发送,结束后会向客户端发送连贯开释申请,而后服务端便进入 LAST-ACK 状态。客户端收到开释申请后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会继续 2MSL(最大段生存期,指报文段在网络中生存的工夫,超时会被摈弃)工夫,若该时间段内没有服务端的重发申请的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。
代码输入后果
async function async1 () {console.log('async1 start');
await new Promise(resolve => {console.log('promise1')
})
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
输入后果如下:
script start
async1 start
promise1
script end
这里须要留神的是在 async1
中await
前面的 Promise 是没有返回值的,也就是它的状态始终是 pending
状态,所以在 await
之后的内容是不会执行的,包含 async1
前面的 .then
。
公布订阅模式
题目形容: 实现一个公布订阅模式领有 on emit once off 办法
实现代码如下:
class EventEmitter {constructor() {this.events = {};
}
// 实现订阅
on(type, callBack) {if (!this.events[type]) {this.events[type] = [callBack];
} else {this.events[type].push(callBack);
}
}
// 删除订阅
off(type, callBack) {if (!this.events[type]) return;
this.events[type] = this.events[type].filter((item) => {return item !== callBack;});
}
// 只执行一次订阅事件
once(type, callBack) {function fn() {callBack();
this.off(type, fn);
}
this.on(type, fn);
}
// 触发事件
emit(type, ...rest) {this.events[type] &&
this.events[type].forEach((fn) => fn.apply(this, rest));
}
}
// 应用如下
// const event = new EventEmitter();
// const handle = (...rest) => {// console.log(rest);
// };
// event.on("click", handle);
// event.emit("click", 1, 2, 3, 4);
// event.off("click", handle);
// event.emit("click", 1, 2);
// event.once("dbClick", () => {// console.log(123456);
// });
// event.emit("dbClick");
// event.emit("dbClick");
JavaScript 有哪些数据类型,它们的区别?
JavaScript 共有八种数据类型,别离是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是 ES6 中新增的数据类型:
- Symbol 代表创立后举世无双且不可变的数据类型,它次要是为了解决可能呈现的全局变量抵触的问题。
- BigInt 是一种数字类型的数据,它能够示意任意精度格局的整数,应用 BigInt 能够平安地存储和操作大整数,即便这个数曾经超出了 Number 可能示意的平安整数范畴。
这些数据能够分为原始数据类型和援用数据类型:
- 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
- 堆:援用数据类型(对象、数组和函数)
两种类型的区别在于 存储地位的不同:
- 原始数据类型间接存储在栈(stack)中的简略数据段,占据空间小、大小固定,属于被频繁应用数据,所以放入栈中存储;
- 援用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;援用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找援用值时,会首先检索其在栈中的地址,获得地址后从堆中取得实体。
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
- 在数据结构中,栈中数据的存取形式为先进后出。
- 堆是一个优先队列,是按优先级来进行排序的,优先级能够依照大小来规定。
在操作系统中,内存被分为栈区和堆区:
- 栈区内存由编译器主动调配开释,寄存函数的参数值,局部变量的值等。其操作形式相似于数据结构中的栈。
- 堆区内存个别由开发着调配开释,若开发者不开释,程序完结时可能由垃圾回收机制回收。
对对象与数组的解构的了解
解构是 ES6 提供的一种新的提取数据的模式,这种模式可能从对象或数组里有针对性地拿到想要的数值。1)数组的解构 在解构数组时,以元素的地位为匹配条件来提取想要的数据的:
const [a, b, c] = [1, 2, 3]
最终,a、b、c 别离被赋予了数组第 0、1、2 个索引位的值:
数组里的 0、1、2 索引位的元素值,精准地被映射到了左侧的第 0、1、2 个变量里去,这就是数组解构的工作模式。还能够通过给左侧变量数组设置空占位的形式,实现对数组中某几个元素的精准提取:
const [a,,c] = [1,2,3]
通过把两头位留空,能够顺利地把数组第一位和最初一位的值赋给 a、c 两个变量:
2)对象的解构 对象解构比数组构造略微简单一些,也更显弱小。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。当初定义一个对象:
const stu = {
name: 'Bob',
age: 24
}
如果想要解构它的两个自有属性,能够这样:
const {name, age} = stu
这样就失去了 name 和 age 两个和 stu 平级的变量:
留神,对象解构严格以属性名作为定位根据,所以就算调换了 name 和 age 的地位,后果也是一样的:
const {age, name} = stu
代码输入后果
console.log('1');
setTimeout(function() {console.log('2');
process.nextTick(function() {console.log('3');
})
new Promise(function(resolve) {console.log('4');
resolve();}).then(function() {console.log('5')
})
})
process.nextTick(function() {console.log('6');
})
new Promise(function(resolve) {console.log('7');
resolve();}).then(function() {console.log('8')
})
setTimeout(function() {console.log('9');
process.nextTick(function() {console.log('10');
})
new Promise(function(resolve) {console.log('11');
resolve();}).then(function() {console.log('12')
})
})
输入后果如下:
1
7
6
8
2
4
3
5
9
11
10
12
(1)第一轮事件循环流程剖析如下:
- 整体 script 作为第一个宏工作进入主线程,遇到
console.log
,输入 1。 - 遇到
setTimeout
,其回调函数被散发到宏工作 Event Queue 中。暂且记为setTimeout1
。 - 遇到
process.nextTick()
,其回调函数被散发到微工作 Event Queue 中。记为process1
。 - 遇到
Promise
,new Promise
间接执行,输入 7。then
被散发到微工作 Event Queue 中。记为then1
。 - 又遇到了
setTimeout
,其回调函数被散发到宏工作 Event Queue 中,记为setTimeout2
。
宏工作 Event Queue | 微工作 Event Queue |
---|---|
setTimeout1 | process1 |
setTimeout2 | then1 |
上表是第一轮事件循环宏工作完结时各 Event Queue 的状况,此时曾经输入了 1 和 7。发现了 process1
和then1
两个微工作:
- 执行
process1
,输入 6。 - 执行
then1
,输入 8。
第一轮事件循环正式完结,这一轮的后果是输入 1,7,6,8。
(2)第二轮工夫循环从 **setTimeout1**
宏工作开始:
- 首先输入 2。接下来遇到了
process.nextTick()
,同样将其散发到微工作 Event Queue 中,记为process2
。 new Promise
立刻执行输入 4,then
也散发到微工作 Event Queue 中,记为then2
。
宏工作 Event Queue | 微工作 Event Queue |
---|---|
setTimeout2 | process2 |
then2 |
第二轮事件循环宏工作完结,发现有 process2
和then2
两个微工作能够执行:
- 输入 3。
- 输入 5。
第二轮事件循环完结,第二轮输入 2,4,3,5。
(3)第三轮事件循环开始,此时只剩 setTimeout2 了,执行。
- 间接输入 9。
- 将
process.nextTick()
散发到微工作 Event Queue 中。记为process3
。 - 间接执行
new Promise
,输入 11。 - 将
then
散发到微工作 Event Queue 中,记为then3
。
宏工作 Event Queue | 微工作 Event Queue |
---|---|
process3 | |
then3 |
第三轮事件循环宏工作执行完结,执行两个微工作 process3
和then3
:
- 输入 10。
- 输入 12。
第三轮事件循环完结,第三轮输入 9,11,10,12。
整段代码,共进行了三次事件循环,残缺的输入为 1,7,6,8,2,4,3,5,9,11,10,12。
代码输入后果
function fn1(){console.log('fn1')
}
var fn2
fn1()
fn2()
fn2 = function() {console.log('fn2')
}
fn2()
输入后果:
fn1
Uncaught TypeError: fn2 is not a function
fn2
这里也是在考查变量晋升,关键在于第一个 fn2(),这时 fn2 仍是一个 undefined 的变量,所以会报错 fn2 不是一个函数。
代码输入后果
var obj = {say: function() {var f1 = () => {console.log("1111", this);
}
f1();},
pro: {getPro:() => {console.log(this);
}
}
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();
输入后果:
1111 window 对象
1111 obj 对象
window 对象
解析:
- o(),o 是在全局执行的,而 f1 是箭头函数,它是没有绑定 this 的,它的 this 指向其父级的 this,其父级 say 办法的 this 指向的是全局作用域,所以会打印出 window;
- obj.say(),谁调用 say,say 的 this 就指向谁,所以此时 this 指向的是 obj 对象;
- obj.pro.getPro(),咱们晓得,箭头函数时不绑定 this 的,getPro 处于 pro 中,而对象不形成独自的作用域,所以箭头的函数的 this 就指向了全局作用域 window。
代码输入后果
var A = {n: 4399};
var B = function(){this.n = 9999};
var C = function(){var n = 8888};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++
console.log(b.n);
console.log(c.n);
输入后果:9999 4400
解析:
- console.log(b.n),在查找 b.n 是首先查找 b 对象本身有没有 n 属性,如果没有会去原型(prototype)上查找,当执行 var b = new B()时,函数外部 this.n=9999(此时 this 指向 b) 返回 b 对象,b 对象有本身的 n 属性,所以返回 9999。
- console.log(c.n),同理,当执行 var c = new C()时,c 对象没有本身的 n 属性,向上查找,找到原型(prototype)上的 n 属性,因为 A.n++(此时对象 A 中的 n 为 4400),所以返回 4400。
页面有多张图片,HTTP 是怎么的加载体现?
- 在
HTTP 1
下,浏览器对一个域名下最大 TCP 连接数为 6,所以会申请屡次。能够用 多域名部署 解决。这样能够进步同时申请的数目,放慢页面图片的获取速度。 - 在
HTTP 2
下,能够一瞬间加载进去很多资源,因为,HTTP2 反对多路复用,能够在一个 TCP 连贯中发送多个 HTTP 申请。