性能优化
1.webpack打包文件体积过大?(最终打包为一个js文件)
2.如何优化webpack构建的性能
3.移动端的性能优化
4.Vue的SPA 如何优化加载速度
5.移动端300ms延迟
6.页面的重构
所有的知识点都有详细的解答,我整理成了280页PDF《前端校招面试真题精编解析》。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
-
关于
**
乘方表达式? -
关于
==
的几个错觉? -
从输入 URL 到看到网页,经历了什么?
-
HTTP1.1/HTTP2/HTTPS 区别?
-
['1', '2', '3'].map(parseInt)
输出什么? -
Set/Map/WeakSet/WeakMap 区别?
-
描述下 es5/es6 继承的区别?
-
简述下浏览器的渲染过程?
-
回流和重绘?
-
如何优化重绘重排?
-
介绍下 js 的模块化?
-
ES6 转换为 ES5 中间经历了什么?
题目详解
1. 为什么有的编程规范要求用 void 0
代替 undefined
?
❝
JavaScript
的代码undefined
是一个变量,而并非是一个关键字,这是JavaScript
语言公认的设计失误之一,所以,我们为了避免无意中被篡改,建议使用void 0
来获取undefined
值。
❞
2. Undefined
和 Null
的区别
-
Undefined
:Undefined
类型表示未定义,它的类型只有一个值,就是undefined
;JavaScript
的代码undefined
是一个变量,而并非是一个关键字,这是JavaScript
语言公认的设计失误之一; -
Null
:Null
类型也只有一个值,就是null
,它的语义表示控制,与undefined
不同,null
是JavaScript
中的关键字,所以在任何代码中,你都可以放心的用null
关键字来获取null
值;
❝
Undefined
跟Null
有一定的表意差别,Null
表示的是:“定义了但是为空”。所以,在实际编程时,我们一般不会把变量赋值为undefined
,这样可以保证所有值为undefined
的变量,都是从未赋值的自然状态。
❞
3. JavaScript
中的 String
是否有最大长度?
- 答案当然是有的,
String
有最大长度是2^53 - 1
;
❝
这个所谓最大长度,并不完全是你理解中的字符数。因为 String 的意义并非“字符串”,而是字符串的 UTF16 编码,我们字符串的操作 charAt、charCodeAt、length 等方法针对的都是 UTF16 编码。所以,字符串的最大长度,实际上是受字符串的编码长度影响的。
❞
❝
现行的字符集国际标准,字符是以 Unicode 的方式表示的,每一个 Unicode 的码点表示一个字符,理论上,Unicode 的范围是无限的。UTF 是 Unicode 的编码方式,规定了码点在计算机中的表示方法,常见的有 UTF16 和 UTF8。Unicode 的码点通常用 U+??? 来表示,其中 ??? 是十六进制的码点值。0-65536(U+0000 - U+FFFF)的码点被称为基本字符区域(BMP)。
❞
❝
JavaScript 字符串把每个 UTF16 单元当作一个字符来处理,所以处理非 BMP(超出 U+0000 - U+FFFF 范围)的字符时,你应该格外小心。JavaScript 这个设计继承自 Java,最新标准中是这样解释的,这样设计是为了“性能和尽可能实现起来简单”。因为现实中很少用到 BMP 之外的字符。
❞
4. Number 的长度?
- Number 类型有
18437736874454810627
(即2^64-2^53+3
) 个值
5. 为什么 JavaScript 中 x/0 不会报错?
- JavaScript 为了表达几个额外的语言场景(比如不让除以 0 出错,而引入了无穷大的概念),规定了几个例外情况:
-
NaN
,占用了9007199254740990
,这原本是符合IEEE
规则的数字; -
Infinity
,无穷大; -
-Infinity
,负无穷大。
❝
另外,值得注意的是,JavaScript 中有
+0
和 -0,在加法类运算中它们没有区别,但是除法的场合则需要特别留意区分,“忘记检测除以 -0,而得到负无穷大”的情况经常会导致错误,而区分 +0 和 -0 的方式,正是检测 1/x 是 Infinity 还是 -Infinity。
❞
6. 为什么 0.1 + 0.2
不等于 0.3
?
console.log(0.1 + 0.2 === 0.3); // false
❝
根据双精度浮点数的定义,Number 类型中有效的整数范围是 -0x1fffffffffffff 至 0x1fffffffffffff,所以 Number 无法精确表示此范围外的整数。同样根据浮点数的定义,非整数的 Number 类型无法用 (= 也不行) 来比较:这是浮点运算的特点,也是很多同学疑惑的来源,浮点数运算的精度问题导致等式左右的结果并不是严格相等,而是相差了个微小的值。
❞
- 所以实际上,这里错误的不是结论,而是比较的方法,正确的比较方法是使用 JavaScript 提供的最小精度值:
// 检查等式左右两边差的绝对值是否小于最小精度,才是正确的比较浮点数的方法。
console.log(Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
7. 为什么给对象添加的方法能用在基本类型上?
❝
JavaScript 中的几个基本类型,都在对象类型中有一个“亲戚”。它们是:Number;String;Boolean;Symbol。
- 我们必须认识到 3 与 new Number(3) 是完全不同的值,它们一个是 Number 类型, 一个是对象类型。
- Number、String 和 Boolean,三个构造器是两用的,当跟 new 搭配时,它们产生对象,当直接调用时,它们表示强制类型转换。
- Symbol 函数比较特殊,直接用 new 调用它会抛出错误,但它仍然是 Symbol 对象的构造器。
❞
console.log(“abc”.charAt(0)); //a
Symbol.prototype.hello = () => console.log(“hello”);
var a = Symbol(“a”);
console.log(typeof a); //symbol,a并非对象
a.hello(); // hello,有效
- 所以这里总结答案就是
.
运算符提供了装箱操作
,它会根据基础类型构造一个临时对象,使得我们能在基础类型上调用对应对象的方法。
8. 什么是装箱转换?
- 每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。
❝
装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。
❞
9. 为什么我们常用 Object.prototype.toString
来获取对象类型?
-
每一类装箱对象皆有私有的 Class 属性,这些属性可以用
Object.prototype.toString
获取; -
在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此
Object.prototype.toString
是可以准确识别对象对应的基本类型的方法,它比instanceof
更加准确。
var symbolObject = Object(Symbol(“a”));
console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]
❝
但需要注意的是,
call
本身会产生装箱操作,所以需要配合typeof
来区分基本类型还是对象类型。
❞
10. 什么是拆箱转换?
- 在
JavaScript
标准中,规定了ToPrimitive
函数,它是对象类型到基本类型的转换(即,拆箱转换)
❝
对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。
❞
- 拆箱转换会尝试调用
valueOf
和toString
来获得拆箱后的基本类型。如果valueOf
和toString
都不存在,或者没有返回基本类型,则会产生类型错误 TypeError。
var o = {
valueOf: () => {
console.log(“valueOf”);
return {};
},
toString: () => {
console.log(“toString”);
return {};
},
};
// Number 的转换 先 valueOf 再 toString
o * 2; // TypeError
// String 的转换则是 先 toString 再 valueOf
String(o); // TypeError
// 在 ES6 之后,还允许对象通过显式指定 @@toPrimitive Symbol 来覆盖原有的行为。
o[Symbol.toPrimitive] = () => {
console.log(“toPrimitive”);
return “hello”;
};
// toPrimitive
console.log(o + “”); // hello
11. try
里面放 return
,finally
还会执行吗?
finally
不管try
/catch
如何处理最终都会执行,并且如果finally
中加入return
/throw
则会覆盖try
/catch
的return
或者throw
;
function foo() {
try {
return 0;
} catch (err) {
} finally {
// 这里会先执行
console.log(“a”);
}
}
console.log(foo());
// a
// 0
function foo() {
try {
i++;
return 0;
} catch (err) {
throw err;
} finally {
// 最终以 finally 为主
return 1;
}
}
console.log(foo()); // 1
12. 为什么 12.toString
会报错?
- 这时候
12.
会被当作省略了小数点后面部分的数字,而单独看成一个整体,所以我们要想让点单独成为一个token
,就要加入空格,或者 .,这样写:12 .toString()
/12..toString()
// 数字直接量
.01
12.01
❝
Token 词
- IdentifierName 标识符名称,典型案例是我们使用的变量名,注意这里关键字也包含在内了。
- Punctuator 符号,我们使用的运算符和大括号等符号。
- NumericLiteral 数字直接量,就是我们写的数字。
- StringLiteral 字符串直接量,就是我们用单引号或者双引号引起来的直接量。
- Template 字符串模板,用反引号` 括起来的直接量。
❞
13. 关于 **
乘方表达式?
**
运算是右结合的,因此2 ** 2 ** 3
运算规则如下:
2 ** (2 ** 3); // 256
-2
这样的一元运算表达式,是不可以放入乘方表达式的,如果需要表达类似的逻辑,必须加括号。
(++i) ** 30;
2 ** 30; //正确
// -2 ** 30 //报错
(-2) ** 30;
14. 关于 ==
的几个错觉?
- 即使字符串与布尔值比较,也都要转换为数字
false == “0”; // true
true == “true”; // false
- 对象如果转换成了 primitive 类型跟等号另一边类型恰好相同,则不需要转换成数字。
[] == “” // true
[] == 0 // true
[] == false // true
❝
所以
==
比较坑,尽量还是使用===
,或者仅在确定字符串与数值比较时去使用==
❞
15. 从输入 URL 到看到网页,经历了什么?
-
浏览器首先使用 HTTP 协议或者 HTTPS 协议,向服务端请求页面;
-
把请求回来的 HTML 代码经过解析,构建成 DOM 树;
-
计算 DOM 树上的 CSS 属性;
-
最后根据 CSS 属性对元素逐个进行渲染,得到内存中的位图;
-
一个可选的步骤是对位图进行合成,这会极大地增加后续绘制的速度;
-
合成之后,再绘制到界面上。
16. HTTP1.1/HTTP2/HTTPS 区别?
- HTTP2 相比于 HTTP1.1
❝
总的来说 HTTP2 大幅度的提升了 web 性能。在与 HTTP/1.1 完全语义兼容的基础上,进一步减少了网络延迟,在性能上大幅提升。
❞
❝
在二进制分帧层中, HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码 ,其中 HTTP1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面。
HTTP/2 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。
在过去, HTTP 性能优化的关键并不在于高带宽,而是低延迟。TCP 连接会随着时间进行自我「调谐」,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐则被称为 TCP 慢启动。由于这种原因,让原本就具有突发性和短时性的 HTTP 连接变的十分低效。
HTTP/2 通过让所有数据流共用同一个连接,可以更有效地使用 TCP 连接,让高带宽也能真正的服务于 HTTP 的性能提升。
❞
❝
HTTP/1.1 并不支持 HTTP 首部压缩,为此 SPDY 和 HTTP/2 应运而生, SPDY 使用的是通用的 算法,而 HTTP/2 则使用了专门为首部压缩而设计的 HPACK 算法。
❞
-
- 单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大
-
由于 TCP 连接的减少而使网络拥塞状况得以改善,同时慢启动时间的减少,使拥塞和丢包恢复速度更快
-
首部压缩
-
服务端推送:能够在客户端发送第一个请求到服务端时,提前把一部分内容推送给客户端,放入缓存当中,这可以避免客户端请求顺序带来的并行度不高,从而导致的性能问题。
-
TCP 连接复用,则使用同一个 TCP 连接来传输多个 HTTP 请求,避免了 TCP 连接建立时的三次握手开销,和初建 TCP 连接时传输窗口小的问题。
-
二进制分帧
-
HTTPS 有两个作用,一是确定请求的目标服务端身份,二是保证传输的数据不会被网络中间节点窃听或者篡改。
-
❝
HTTPS 是使用加密通道来传输 HTTP 的内容。但是 HTTPS 首先与服务端建立一条 TLS 加密通道。TLS 构建于 TCP 协议之上,它实际上是对传输的内容做一次加密,所以从传输内容上看,HTTPS 跟 HTTP 没有任何区别。
❞
17. ['1', '2', '3'].map(parseInt)
输出什么?
- 乍一看可能会说出
[1, 2, 3]
,其实不然,答案是[1, NaN, NaN]
[“1”, “2”, “3”].map(parseInt);
// parseInt(string, radix) 接收2个参数,(要处理的值, 解析时的基数)
// 基数是一个介于2和36之间的整数
// 平铺一下
[“1”, “2”, “3”].map((item, idx) => parseInt(item, idx));
parseInt(“1”, 0); // 1 radix为 0时该值无效,且第一个参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1
parseInt(“2”, 1); // NaN 当radix小于2并且大于36或第一个参数的第一个非空格字符不能转换为数字。
parseInt(“3”, 2); // NaN 基数为2(2进制)表示的数中,最大值小于3,所以无法解析,返回NaN
18. Set/Map/WeakSet/WeakMap 区别?
- Set
❝
- 成员不能重复
- 只有健值,没有健名,有点类似数组。
- 可以遍历,方法有 add, delete, has
❞
- WeakSet
❝
- 只能储存对象引用,不能存放值,而 Set 对象都可以
- 成员都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的应用,随时可以消失。可以用来保存 DOM 节点,不容易造成内存泄漏
- 不能遍历,方法有 add, delete, has
❞
- Map
❝
- 本质上是健值对的集合,类似集合
- 可以遍历,方法很多,方便跟各种数据格式进行转换
❞
- WeakMap
❝
- 直接受对象作为健名(null 除外),不接受其他类型的值作为健名
- 健名所指向的对象,不计入垃圾回收机制
- 不能遍历,方法同 get, set, has, delete
❞
19. 描述下 es5/es6 继承的区别?
class
的声明没有被变量提升,类似let/const
的声明,不能前置使用;
var t = new Test(); // Test is not defined
class Test {}
class
声明内部会启用严格模式
class Test {
constructor() {
empty = “test”; // empty is not defined
}
}
class
的所有方法(包括静态方法和实例方法)都是不可枚举的
最后
如果你已经下定决心要转行做编程行业,在最开始的时候就要对自己的学习有一个基本的规划,还要对这个行业的技术需求有一个基本的了解。有一个已就业为目的的学习目标,然后为之努力,坚持到底。如果你有幸看到这篇文章,希望对你有所帮助,祝你转行成功。