好记性不如烂笔头,面试题看的再多不如自己总结,结合看过的面试题和 js 红宝书,根据自己的理解记录成章,方便后续面试前使用。因为是自学,内容有错或者不全面欢迎指正,感谢。
网络相关
浏览器地址栏输入文字或url后,都发生了什么
地址栏输入文字后会直接调用浏览器默认搜索引擎进行搜索,通过 TCP 握手建立连接,服务器响应返回资源,浏览器解析并渲染返回的资源并展示到页面。
地址栏输入 url 解析后会进行 DNS 查找服务器地址,通过 TCP 握手建立连接,服务器响应返回资源,浏览器解析并渲染返回的资源并展示到页面。
实际整个过程设计到的知识点很多,下面以输入 url 为例进行简要说明,可以参考扩展进行阅读:
-
url 编码传递给浏览器:通过
encodeURIComponent()/encodeURI()
对地址进行编码,传递给浏览器 -
DNS 域名解析得到 ip 地址:域名查找过程 本地host 文件 → 本地 DNS 解析器缓存 → 计算机设置的 DNS 服务器查询 → 根服务器查找
-
TCP 握手和服务器建立连接:TCP的”三次握手“技术通过 “SYN, SYN-ACK, ACK” 消息协商完成身份认证
-
发起 http 请求,服务器处理后返回资源和 http 报文,浏览器缓存策略如下:
强缓存:响应头中设置
Cache-Control/Expires
浏览器将信息缓存下来,在下一次请求会进行强缓存处理。协商缓存:在强缓存失效后会进行协商缓存,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
通过设置缓存达到优化加载速度的目的(关于强缓存和协议缓存具体的流程可以看参考一和二)
-
页面渲染:渲染机制
-
TCP 挥手和服务器断开连接:TCP 的四次“挥手”
参考:url输入到返回请求的过程 从输入URL开始建立前端知识体系 渲染页面:浏览器的工作原理 mdn 《JavaScript高级程序设计(第四版) 》5.4 章节
谈谈浏览器渲染机制,重绘,重排
浏览器的渲染机制说明的是浏览器怎么解析 HTML / CSS / JavaScript 文件,并将解析后得到的内容绘制成页面呈现出来。总的来说有5步:
- 构建 DOM Tree:解析 HTML 文件,读取页面的标签并生成 DOM Tree,因为有预加载扫描器的存在,对于如 CSS / JavaScript / web字体的请求,不必等到主 HTML 解析器到达请求的资源时,就已经发起请求。对于
<script>
标签(特别是没有async
或者defer
属性)会阻塞渲染并停止HTML的解析。 - 构建 CSS Tree:浏览器将CSS规则转换为可以理解和使用的样式映射,遍历 CSS 中的每一个规则集,根据 CSS 选择器创建具有父、子和兄弟关系的节点树。
- 构建 Render Tree:将 DOM Tree 和 CSS Tree 组合成 Render Tree。Render Tree 保存所有具有内容和计算样式的可见节点——将所有相关样式匹配到 DOM Tree 中的每个可见节点,并根据 CSS 级联确定每个节点的计算样式,但不标识每个节点的尺寸或位置。
- 布局(layout):根据 Render Tree 中的每个结点得到的计算样式,确定节点的尺寸和位置信息。
- 绘制(paint):将各个节点绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)等操作
这里还会涉及到两个概念:重绘(repaint)和重排(reflow),由于元素的大小位置的改变会触发重排(display: none
),仅是如颜色,边框等改变仅触发重绘(visibility: hidden
)
这里还有一个问题,如果 js 文件(位置可能在 body 中后)中有改动到 DOM ,渲染过程又是怎么样的,是触发 reflow/repaint 还是等 js 处理完将影响加入到 DOM Tree 中,才进行后面4步操作,又或者有其他的方式,如果有相关资料可以在文章后留言告知,感谢
参考:渲染页面:浏览器的工作原理 mdn 重排(reflow)和重绘(repaint)
跨域的原因及解决方法
浏览器出于安全的考虑存在同源策略,对于不同域的资源禁止解析。访问不同域名下的资源称为跨域。
同源:协议,主机,端口号三者相同,如果有任何一个或以上不同,则是不同源。
跨域的解决方式:
-
jsonp:原理是 script 标签不受同源策略影响,可以嵌入跨域脚本。实现方式:函数定义在前端,通过 script 标签,将函数作为 query 的 value 值传递给后端, 函数在后端执行,参数为需要传递的数据。只适用 get 请求。
-
CORS:CORS 标准新增了一组 HTTP 首部字段,允许服务器声明哪些源有权限访问服务器资源。对于不同的请求方式,CORS 处理方式也不同
-
简单请求:不会触发 CORS 预检请求。满足的条件可见 MDN 参考文档。使用
Origin
和Access-Control-Allow-Origin
就能完成最简单的访问控制。例如:请求头携带
Origin: https://foo.example
,服务端响应头返回Access-Control-Allow-Origin:*
,该资源可以被 任意外域访问 -
非简单请求:会触发 CORS 预检请求,预检请求通过后,才会发起主请求。预检响应没有检验通过,CORS 会阻止跨域访问,实际的请求永远不会被发送。
客户端会先使用
OPTIONS
方法发起一个预检请求,预检请求的请求头中同时携带了两个首部字段Access-Control-Request-Method Access-Control-Request-Headers
告知服务器请求方法和自定义请求首部字段。服务器返回一个没有 body 的 HTTP 响应,标记允许的请求方法和HTTP Header 字段预检请求示例
<!-- 请求头 --> OPTIONS https://api.mywebsite.com/user/1 HTTP/1.1 Origin: https://www.mywebsite.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: Content-Type <!-- 响应头 --> HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://www.mywebsite.com Access-Control-Request-Method: GET POST PUT Access-Control-Request-Headers: Content-Type
-
-
反向代理:服务器没有同源策略的限制,可由服务器向目标服务器发送请求,得到结果后再转发给客户端。
参考:浏览器的同源策略 mdn 跨源资源共享(CORS)MDN jsonp实现 动图讲解 CORS
前端常见的网络攻击及防护
-
XSS 攻击:跨站脚本攻击,指的是恶意脚本执行在被攻击的网站上。通过恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。XSS 攻击可以分为以下三种:
- 存储型 XSS:通过在评论,论坛发帖,用户私信等带有输入性质的元素上提交恶意代码,并保存在数据库内。
- 反射型 XSS:诱导用户点击恶意的 URL ,通过 URL 传递参数的功能,恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
- DOM 型 XSS:恶意脚本只发生在客户端,修改页面 DOM 结构
- 防御策略:可从两方面做防御。防止恶意脚本注入,如输入转义方式(把
& < > " ' /
这几个字符转义掉),控制输入内容长度;阻止恶意脚本执行,如输出转义,纯前端渲染,谨慎适用.innerHTML .outerHTML document.write()
,HTTP-only Cookie
-
CSRF 攻击:跨站请求伪造,指的是冒用被攻击者的登陆凭证 cookie ,向服务器发起非法请求或者攻击。CSRF 攻击形式主要如下:
攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。常见的 CSRF 攻击包括如下三种:
- GET 类型的 CSRF:利用图片 URL 发送非法的 HTTP请求
- POST类型的CSRF:利用一个自动提交的表单发送非法的 HTTP请求
- 链接类型的CSRF:图片或者广告中嵌入恶意链接发送非法的 HTTP请求
- 防御策略:可以从两方面做防御。阻止不明外域的访问,如同源检测;提交时要求附加本域才能获取的信息,如CSRF token 和双重Cookies认证
参考:XSS 攻击详解 CSRF 攻击详解 浅说 XSS 和 CSRF HTTP cookies MDN
CSS 相关
CSS 盒模型
CSS 盒模型由 margin/border/padding/context 4部分组成,作用在 block/inline-block 元素。盒模型下有2种,标准盒模型content-box
和 IE 盒模型(怪异盒模型)border-box
,这两种盒模型可以通过 box-sizing
设置。两者的区别在于计算盒子的宽高,标准盒模型 width 和 height 为 context 的大小, IE 盒模型 width 和 height 需要加上 padding 和 border。
谈谈对 BFC 的理解
BFC,全称 Block Formatting Context,块级格式化上下文,是Web页面的可视CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区 。官方解释太抽象,参考多份资料,总结如下:
-
BFC 是一种 CSS 的布局概念,触发了 BFC 的元素会提供了一个环境,其内部的元素布局不会影响到外部的元素布局
-
BFC 的触发条件
-
body 元素
-
开启浮动,不包括
float:none
-
设置定位,
position:absolute/fixed
-
设置 overflow,除了
visible
(默认值) -
display:flex/table-cell/inline-block
-
-
BFC 带来了什么问题,又解决了什么问题
带来的问题:
-
margin 垂直塌陷(垂直方向的距离由margin决定, 属于同一个 BFC 的两个相邻的标签外边距会发生重叠)
-
margin 包含塌陷,子元素设置 margin-top带着父元素一并下移
可以解决的问题:
- 使用Float脱离文档流,高度塌陷(计算 BFC 的高度时,浮动元素也参与计算)
- 两栏布局(左侧元素 float,右侧元素触发 BFC , BFC可以阻止标准流元素被浮动元素覆盖)
-
参考:BFC是什么?10 分钟讲透BFC 原理 面试官:请说说什么是BFC?大白话讲清楚 BFC mdn
CSS 样式的优先级
-
标签上存在单个选择器时,各选择器的优先级顺序:!important > 内联样式 > id 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器 > 通用选择器(*),选择器在文件中的书写顺序也会影响优先级,同一选择器顺序靠后的优先级越大。
-
当一个标签同时被多个选择符选中,需要计算选择符中类选择器、属性选择器以及伪类选择器的个数之和,方便理解可以按照权重相加(但是这种方式不是很严谨,在不同的浏览器中,这个权重不同,CSS 权重进制在 IE6 为 256,后来扩大到了 65536,现代浏览器则采用更大的数量)
参考:CSS 选择器 mdn CSS 优先级 mdn CSS 样式优先级
三栏布局怎么实现
常用的有浮动/定位,推荐的是flex,冷门的有 table 和 grid 布局
-
浮动的思路是有三种,使用时需要解决float脱离文档流导致的高度塌陷
-
HTML结构:左右中,左右两个元素分布设置左右浮动,中间元素,可以控制 margin-top 或者触发 BFC 不会左浮动元素重叠
-
双飞翼布局:(中)左右,三者浮动,中间元素包裹在浮动元素内外围浮动元素
width:100%
中间元素margin-left/right
等于左右元素的宽用来空出显示位置,左右元素设置margin-left
为负值 -
圣杯布局:中左右,思路和双飞翼差不多,重点是通过定位去除中间元素的包裹层
-
-
定位的思路:通过定位确定左中右的位置,但是绝对定位是脱离文档流,需要设置高度
-
flex 记住使用规则,应用起来就比较简单, IE8 以下不支持
参考:详解 CSS 七种三栏布局技巧 浅谈 margin 负值 Flex 布局教程
水平垂直居中
常用的有定位,推荐的是 flex ,冷门的有 table 和 grid
-
定位的思路:通过
top/left:50%
使得元素左上角顶点居中,然后使用顶点分布左移一半的宽/上移一半的高实现,实现方式有以下s三种- 负 margin
margin-left: -50px;margin-top: -50px;
- transform
transform: translate(-50%, -50%);
- auto margin
left: 0;top: 0;right: 0;bottom: 0;margin: auto;
还可以直接使用 calc 实现
- calc
left: calc(50% - 50px);top: calc(50% - 50px);
- 负 margin
-
flex
justify-content: center;align-items: center;
参考:面试指导
如何用 css 实现不同的形状:如三角形,圆形,椭圆,平行四边形
三角形利用 border 实现;
.triangle {
width: 0px;
height: 0px;
/* 需要哪条边的三角形就将另外三边设为 transparent */
border-top: 100px solid #111;
border-left: 100px solid #333;
border-right: 100px solid #666;
border-bottom: 100px solid transparent;
}
圆形,椭圆利用 border-radius 实现
.round{
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #000;
}
/* 1/4 圆 */
.round-1of4{
width: 100px;
height: 100px;
border-top-left-radius: 0% 0%;
border-top-right-radius: 100% 100%;
border-bottom-right-radius:0% 0%;
border-bottom-left-radius: 0% 0%;
/* 上面4行等价于 border-radius:0 100% 0 0 */
background-color: #000;
}
.ellipse{
width: 100px;
height: 50px;
border-radius: 50% 50%;
background-color: #999;
}
/* 1/4 椭圆 */
.ellipse-1of4{
width: 100px;
height: 50px;
border-top-left-radius: 0% 0%;
border-top-right-radius: 100% 100%;
border-bottom-right-radius:0% 0%;
border-bottom-left-radius: 0% 0%;
/* 上面4行等价于 border-radius:0 100% 0 0 */
background-color: #000;
}
平行四边形可以通过 transform:skewX(45deg)
实现,为了不让字体倾斜,使用伪元素进行倾斜
.parallelogram{
width: 100px;
height: 50px;
position: relative;
}
.parallelogram::before{
content: '';
display: block;
position: absolute;
z-index: -1;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #666;
transform: skewX(45deg);
}
参考:《css揭秘》 第三章
JavaScript 相关
var let const 声明变量有什么区别
- var 声明的变量会提升,变量可以在声明前就使用(值为 undefined)。var 声明变量的范围是函数作用域,在全局作用域声明的变量会成为 window 对象的属性;
- let 声明的变量不会提升,因为存在“暂时性死区”,变量未声明就使用会抛出
RefernceError
错误。let 声明的范围是块作用域,在全局作用域声明的变量不会成为 window 对象的属性; - const 的行为和 let 相当,唯一的区别在于声明变量的同时必须赋值,且无法修改。因为对象是按照引用传递的,const 声明的对象修改对象内的属性是不会报错的。
注意:在函数中变量未经声明直接使用,该变量会被提升到全局作用域中
参考:《JavaScript 高级程序设计(第四版)》3.3 章节 var 声明 mdn let 声明 mdn const 声明 mdn
JavaScript 的数据类型有哪些,如何判断
JavaScript 数据类型有两大类:简单数据类型 和 Object 类型
- 简单数据类型 6 种:null undefined String Number Boolean Symbol
- Object 类型:Function Array Date RegExp Map Set …
- 简单数据类型判断 :typeof,需要注意的是
typeof null = object typeof Function = function
,其他的返回的是其数据类型 - 数组的判断:
Array.isArray()
[] instanceof Array
[].constructor === Array
- 对象的判断:
对象 instanceof 构造函数
对象.constructor === 构造函数
a instanceof A
// 表示 A 的原型是否出现在 a 的原型链上,可以理解为如下(因为通过原型链可以一直向 Object 延长,以下方式并不严谨,实际实现需要向上递归)
a.__proto__ === A.prototype
注意:Number 类型有一个特别的值 NaN,NaN !== NaN ,判断值是否为 NaN 需使用 isNaN()
参考:《JavaScript 高级程序设计(第四版)》3.4 章节 数据类型判断 typeof mdn instanceof mdn
在 JavaScript 中,0.1 + 0.2 为什么不是 0.3
JavaScript 使用IEEE 754 格式表示整数和浮点值。该标准在浮点数计算时,由于这种微小的舍入错误,导致很难测试特定的浮点值。
简单的解决办法可以使用 toFixed()
对结果进行舍入,或者先将小数转成整数来运算,之后再转回小数。
参考:IEEE754详解 0.1+0.2!==0.3 各语言0.1+0.2结果
谈谈对作用域(执行上下文)的理解
-
定义:作用域(执行上下文)指是代码当前的运行环境。任何变量(不管包含的是原始值还是引用值)都存在于某个作用域(执行上下文)。这个作用域(执行上下文)决定了变量的生命周期,以及它们可以访问代码的哪些部分。
-
JavaScript作用域分为三类:全局作用域、函数作用域和块级作用域。
-
作用域链:代码执行流每进入一个新的作用域,都会创建一个作用域链。
- 函数或块级作用域通过作用域链访问全局作用域内的变量
- 全局作用域只能访问全局作用域中的变量
参考:《你不知道的JavaScript(上卷)》第一部分 《JavaScript 高级程序设计(第四版)》4.2章节 执行上下文 mdn 作用域 mdn 作用域讲解
数据类型转换(显式/隐式转换)
数据类型转换主要讨论的是其他的数据类型转换成 Number/String/Boolean 类型的过程。类型转换可以通过两种方式完成,一种是显式转换:调用专有的转换函数,一种是隐式转换:通常是和操作符相关的操作。
-
显示转换:
-
转换成数字:
Number()
parseInt()
parseFloat()
// Number()方法转换规则 /* 1. 布尔值,true 转换为1,false 转换为0。 2. 数值,直接返回。 3. null,返回0。 4. undefined,返回NaN。 5.字符串,应用以下规则。 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。 如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)。 如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值。 如果是空字符串(不包含字符),则返回0。 如果字符串包含除上述情况之外的其他字符,则返回NaN。 5.对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。 */ Number('123') // 123 Number('12.3') // 12.3 Number('12.00') // 12 Number('123e-1') // 12.3 Number('') // 0 Number(null) // 0 Number('0x11') // 17 Number('0b11') // 3 Number('0o11') // 9 Number('foo') // NaN Number('100a') // NaN Number('-Infinity') //-Infinity Number([1]) // 1 Number([1,2]) // NaN Number({a:1}) // NaN
-
转换成字符串:
toString()
和模板字符串// 模板字符串写法 `字符串${变量}`
-
转换成 Boolean:
Boolean()
,对于非空的字符串,非0,数组(包括空数组),对象(包括空对象)结果为 true。”“,0,NaN,undefined,null 结果为 false
-
-
隐式转换
-
一元/乘法/除法/取模/减法/加法操作符:设计到计算的操作符,会讲符号两边的变量隐式调用 Number() 方法转化为数字再进行计算
/* 加法操作符针对字符串和其他的略有不同 1. 如果有任一操作数是NaN,则返回NaN; 2. 如果是Infinity 加Infinity,则返回Infinity; 3. 如果是-Infinity 加-Infinity,则返回-Infinity; 4. 如果是Infinity 加-Infinity,则返回NaN; 5. 如果是+0 加+0,则返回+0; 6. 如果是-0 加+0,则返回+0; 7. 如果是-0 加-0,则返回-0。 8. 有一个操作数是字符串,则要应用如下规则: 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面; 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。 9. 如果有任一操作数是对象、数值或布尔值,则调用它们的toString()方法以获取字符串,然后再应用前面的关于字符串的规则 */
-
布尔操作符:会讲符号两边的变量隐式调用 Boolean() 方法转化成 Boolean 类型,
&& ||
后接的是表达式,这里是将其作为短路操作符使用 -
等于操作符():根据符号两边的数据类型,调用不同的方式进行比较,全等(=)不会进行类型转化
/* 1. 如果任一操作数是布尔值,则将其转换为数值再比较是否相等。false 转换为0,true 转换 为1。 2. 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否 相等。 3. 如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法取得其原始值,再 根据前面的规则进行比较。 4. null 和 undefined 的规则。 null 和undefined 相等。 null 和undefined 不能转换为其他类型的值再进行比较。 如果有任一操作数是NaN,则相等操作符返回false,不相等操作符返回true。记住:即使两个操作数都是NaN,相等操作符也返回 false,因为按照规则,NaN 不等于NaN。 5. 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回true。否则,两者不相等。 */
-
参考:《JavaScript 高级程序设计(第四版)》3.4和3.5 章节 valueof() mdn 数据类型转化一 数据类型转化二
数组去重
-
原理简单的双层 for 循环:数组中每一项和其他项逐一比较,去除相同项
-
数组的方法:利用数组的方法找出重复性并去除减去一轮循环,只循环一轮调用数组的方法进行重复性确认
-
indexOf:构建新数组 newArr,newArr.indexof(arr[i]) === -1,则将 arr[i] push 到 newArr
-
includes:思路和 indexof 相同,newArr.includes(arr[i]) 为假,则将 arr[i] push 到 newArr
-
sort:先排序,相邻项两两比较,去除相同项
-
reduce + includes/indexOf:思路和 indexOf/includes 相同,只是将 for 循环用 reduce 替代
//数组去重 function unique(ary) { // reduce : 第一个是函数,第二个参数会传给第一次回调的prev; return ary.reduce((prev,next)=>{ // 该函数返回值是下一次执行的prev; return prev.includes(next)?prev:[...prev,next]; },[]) } //数组对象去重 function uniqueFunc2(arr, uniId){ let hash = {} return arr.reduce((accum,item) => { hash[item[uniId]] ? '' : hash[item[uniId]] = true && accum.push(item) return accum },[]) }
-
filter + map/set :
//数组去重 function distinct(a, b) { let arr = a.concat(b); return arr.filter((item, index)=> { return arr.indexOf(item) === index }) } //数组对象去重 function uniqueFunc(arr, uniId){ const res = new Map(); return arr.filter((item) => !res.has(item[uniId]) && res.set(item[uniId], 1)); }
-
-
对象的属性名唯一性:利用空的 object 对象,把数组的值存成 Object 的 key 值,数组的 value 为 true,如果值再次出现,object[arr[i]]的值为 true ,不再加入对象
-
Set 对象:利用 Set 对象值唯一性,[…new Set(arr)]
原型对象和原型链
- 仅函数(除箭头函数)上存在 prototype 属性(这是一个显式原型属性),其指向原型对象,原型对象上存在两个属性:constructor 和 [[prototype]] (无法访问,部分浏览器可通过
__proto__
访问,这是一个隐性的原型属性)- constructor :指向构造函数,即是函数自身
- [[prototype]](
__proto__
):指向构造函数的原型对象,即 Object ,Object.__proto__ = null
- 当通过 new 调用这个函数时,得到的对象会存在 [[prototype]](
__proto__
),指向的是函数的 prototype 属性 - 所有的对象上都存在 [[prototype]](
__proto__
)属性 - 这种通过
__proto__
在各对象间建立链接的工具就是原型链,通过原型链可以访问到其他对象的属性和方法
参考:《JavaScript 高级程序设计(第四版)》8.2.4章节 原型链
new 操作符创建对象的过程及模拟 new 实现
new 操作符创建对象会执行以下5步:
- 在内存中创建一个新对象
- 这个新对象内部的 [[Prototype]] 特性被赋值为构造函数的 prototype 属性(
obj.__proto__ = fn.prototype
) - 构造函数内部的this 被赋值为这个新对象(即this 指向新对象)
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
// 模拟 new 实现
function newFn(fn,...args) {
const obj = Object.create(fn.prototype);
// 等价于如下两句
//const obj = new Object();
//obj.__proto__ = fn.prototype;
that = obj;
fn.apply(that,args)
return that;
}
function Fns(name){
this.name=name;
}
const fns = newFn(Fns,'fns');
const fns1 = newFn(Fns,'fns');
console.log("🚀 ~ file: 新建 文本文档.html ~ line 262 ~ fns",
fns,fns.__proto__,
fns.name === fns1.name,
fns.constructor === fns1.constructor,
fns.prototype === fns1.prototype)
参考:《JavaScript 高级程序设计(第四版)》8.2 章节 new 的模拟实现
JS中继承实现的方式有哪些
继承指一个对象直接使用另一对象的属性和方法。JavaScript 的继承是通过原型链实现的。实现属性继承的核心代码是SuperFn.call(this)
,实现方法继承的核心代码是 SubFn.prototype.__proto__ = SuperFn.prototype
,具体实现方式有以下6种:
- ES6 class类 extends 继承 :通过ES6 新增的语法糖实现继承
- 基于原型继承:原型赋值
SubFn.prototype = new SuperFn();
- 借用构造函数:构造函数内部执行
SuperFn.call(this)
,只能继承属性,不能继承原型链上的方法 - 组合继承:构造函数内部执行
SuperFn.call(this)
,原型赋值SubFn.prototype.__proto__ = SuperFn.prototype;
- 原型式继承:此种方式和
subObj = Object.create(superObj)
实现的功能相同 - 寄生式组合继承:构造函数内部执行
SuperFn.call(this)
,原型赋值SubFn.prototype = Object.create(SuperFn.prototype);
参考:《JavaScript 高级程序设计(第四版)》8.3 章节 继承 继承的8种方式
箭头函数和普通函数区别
箭头函数是 ES6 之后新增的语法糖,方便函数的简写,形式如下:
- 省略函数表达式:
(a,b)=>{...}
===function(a,b){...}
- 仅一个参数省略():
a=>{...}
===function(a){...}
- 函数只执行
return
:a=>a+1
===function(a){return a+1}
除了简写函数外,箭头函数不能使用arguments、super 和 new.target,也不能用作构造函数,也没有 prototype 属性 。箭头函数中的 this 的指向方面也和常规函数处理不同:在箭头函数中,this 绑定的是函数声明时所在的作用域的全局变量对象(保存全局上下文参数的对象)或活动对象(保存函数局部上下文参数的对象)
参考:《JavaScript 高级程序设计(第四版)》10.1 章节 ES6 系列之箭头函数
谈谈对闭包的理解
闭包指的是那些引用了另一个函数作用域中变量的函数(通常是在嵌套函数中实现的)。闭包的产生需满足以下两个条件:
- 在代码中引用了自由变量(其他作用域中的变量)
- 创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回,函数作为参数传递)
参考:《JavaScript 高级程序设计(第四版)》10.14 章节 闭包
this 的值
判断一个运行中函数的this 的值,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this 的绑定对象。
- 由new 调用:绑定到新创建的对象。
- 由call 或者apply(或者bind)调用:绑定到指定的对象。
- 由上下文对象调用:绑定到那个上下文对象。
- 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
参考:《你不知道的JavaScript(上卷)》第2章 从 ECMAScript 规范解读 this this 的绑定规则
宏任务,微任务,事件循环
JavaScript 是通常是运行在浏览器环境下的,浏览器宿主环境中包含5个线程:
- JS引擎:负责执行执行栈的最顶部代码
- GUI线程:负责渲染页面
- 事件监听线程:负责监听各种事件
- 计时线程:负责计时
- 网络线程:负责网络通信
因为 JavaScript 引擎是单线程运行的,当其他线程发起事件和任务请求,该事件和任务请求会以异步的形式加入到事件队列中。
在浏览器中,事件队列分为两种:微任务和宏任务
-
常见微任务:MutationObserver,Promise产生的回调
-
常见宏任务 :macroTask,计时器结束的回调、事件回调、http回调
当执行栈清空时,JS 引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。
事件循环是 JavaScript 用来处理并发的一种模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。
谈谈对 Promise 的理解,模拟 Promise 实现
在Promise 还未提出之前,异步操作都是通过回调函数来执行的,使用回调函数往往会带来回调地狱和较差的可维护性两个缺点,使用 promise 能完美的解决。
对于异步处理,Promises/A+ 规范将异步操作分为两个阶段:unsettled 和 settled,事情总是从 unsettled 逐步发展到 settled
- unsettled: 未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
- settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转
事件存在 3 个状态:
- pending:挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没出来)
- fulfilled(resolved):已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
- rejected:已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误
无论是阶段,还是状态,都是不可逆的,且状态改变如果是 resolved 和 rejected ,则无法再修改状态。
Promise 是一种异步处理的机制,符号Promises/A+ 规范,通过new 操作符来实例化。创建时需要传入执行器(executor,一个函数)函数作为参数。使用 Promise 的代码会执行异步操作,并且不会阻塞进程。Promise 的实例方法包括 Promise.prototype.then Promise.prototype.catch Promise.prototype.finally
,静态方法包括 Promise.resolve Promise.reject Promise.race
返回的都是 Promise 对象可完成链式调用。
参考:Promise/A+ 规范 Promise mdn 45道 Promise 面试题 手写Promise 1 手写 Promise 2