# JS 面试题
- 本文是自己在面试时遇到的,面的都是十几k的职位
- 所有的答案都是我在翻阅资料,思考验证之后写出的,但能力有限,本人的答案未必是最优的,如果您有更好的答案,欢迎给我留言
- 本文答案也借用优秀答案,已经添加链接,如有侵权,请联系我删除
- 如果有错误或者不严谨的地方,请务必给予指正,以免误导他人
- 如果喜欢或者有所启发,欢迎点赞,对我也是一种鼓励
# 1. 以 let str = 'abc' 实现reverse
let str = 'abc'
function rev(str) {
let arr = str.split('')
let tem = []
for (let i = arr.length-1; i >= 0; i--) {
tem.push(arr[i])
}
return tem.join('')
}
rev(str)
console.log(rev(str))
2
3
4
5
6
7
8
9
10
11
# 2. 针对 var arr = [3,4,5,22,1,3]; 写一个排序函数,对数组进行由小到大排序 (注意不要使用js内置的排序相关函数)
// 冒泡排序: 随便拿出一个数与后者比较,如果前者比后者大,那么两者交换位置,
let arr = [5,2,3,1,4]
function bubbleSort(arr) {
let arrLen = arr.length
for (let i = 0; i < arrLen; i++){
for (let j = i; j < arrLen; j++){
let tempi = arr[i];
let tempj = arr[j];
if (tempi > tempj) {
arr[i] = tempj;
arr[j] = tempi;
}
}
}
return arr
}
console.log(bubbleSort(arr))
/* 快速排序 :
* 1. 以一个数为基准(基准值可以任意选择,选择中间的值容易理解
* 2. 按照顺序,将每个元素与"基准"进行比较,形成两个子集,一个"小于3",另一个"大于等于3"。
* 3. 对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止
*/
let arr = [5, 2, 3, 1, 4]
function quickSort(arr) {
// 如果数组为1那就没必要比较了
if (arr.length <= 1) { return arr }
// 取中间索引值
let pivotIndex = Math.floor(arr.length / 2)
// 获取中间值
let pivot = arr.splice(pivotIndex, 1)[0]
let left = []
let right = []
for (let i = 0; i < arr.length; i ++) {
if(arr[i] < pivot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
// 使用递归不断重复这个过程
return quickSort(left).concat([pivot], quickSort(right))
}
console.log(quickSort(arr))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 3. 数组去重至少2种
// 1. from && set
let arr = [1, 1, 4, 50, 50, 6, 2, 2]
let res = Array.from(new Set(arr))
console.log(res)
// 2. filter && indexOf
let arr = [2, 2, 33, 44, 33]
let res = arr.filter((item, index, arr) => {
return arr.indexOf(item) == index
})
console.log(res)
// 3. forEach && includes
let arr = [2, 2, 33, 44, 33]
let res = []
arr.forEach((item) => {
if (!res.includes(item)) {
res.push(item)
}
})
console.log(res)
// 4. filter && Map
let arr = [2, 2, 33, 44, 33]
const tem = new Map();
let res = arr.filter((item) => !tem.has(item) && tem.set(item, 1))
console.log(res)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 3. 去掉 var str = “ abc beijing ”;中的所有空格
let str = ' adb ddd '
let res = str.replace(/\s+/g, "")
console.log(res)
2
3
# 4. 判断字符串 var str = ‘abdcddsseeed’ 出现最多的字符,并统计出现次数
let str = 'asdasddsfdsfadsfdghdadsdfdgdasd';
function getMax(str) {
let obj = {};
for (let i in str) {
if (obj[str[i]]) {
obj[str[i]]++
} else {
obj[str[i]] = 1
}
}
let num = 0
let number = 0
for (var i in obj) {
//如果当前项大于下一项
if (obj[i] > num) {
//就让当前值更改为出现最多次数的值
num = obj[i]
number = i
}
}
console.log('最多的值是' +number+ '出现次数为' + num);
}
getMax(str);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 5. js 实现继承
// 构造函数继承
function Super(){
this.name = ["张三","李四"]
}
function Sub(){
Super.call(this)
}
let a = new Sub();
a.name.push("王五")
// “张三","李四","王五"
console.log(a.name);
let b = new Sub()
//// “张三","李四"
console.log(b.name)
// Class 继承
class Fathter {
constructor(name) {
this.name = name
}
hello() {
console.log('Hello, ' + this.name)
}
}
class Son extends Fathter {
constructor(name) {
// 记得用super调用父类的构造方法
super(name)
console.log(name)
}
}
let res = new Son('张三')
res.hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 6. 实现一个数据的深度拷贝,必问原生如何实现
- 简单粗暴, 但函数、正则、Symbol都没有被正确的复制
JSON.parse(JSON.stringify(obj));
- 考虑对象相互引用以及 Symbol 拷贝的情况
[木易杨](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/)
[深度拷贝](https://juejin.im/post/5abb55ee6fb9a028e33b7e0a)
2
3
# 7. 各打印什么? 为什么?
function Foo(){
getName = function () {
console.log(1);
}
return this;
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function(){
console.log(3);
};
var getName = function(){
console.log(4);
};
function getName(){
console.log(5)
};
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3 相当于 var f = new Foo() f.getName()
new new Foo().getName(); // 3
// 这道题相当经典了 花点时间自己去看下吧
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 8. 输出什么? 为什么
function fun() {
this.a = 0
this.b = function() {
console.log(this)
console.log(this.a)
}
}
fun.prototype = {
b: function() {
this.a = 20
console.log(this.a)
},
c: function() {
this.a = 30
console.log(this.a)
}
}
var my_fun = new fun();
//私有方法 this=>my_fun
my_fun.b();
console.log(my_fun.a);
//公有方法 this=>my_fun this.a = 30(私有属性a修改为30)
my_fun.c();
console.log(my_fun.a);
var my_fun2 = new fun();
console.log(my_fun2.a);
//this=>my_fun2.__proto__ 当前实例·通过原型链在共有属性增加的了一a:30
my_fun2.__proto__.c();
console.log(my_fun2.a);
console.log(my_fun2.__proto__.a);
// 0,0,30,30,0,30,0,30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 9. 输出什么 为什么
function setName(obj){
obj.name = "Tom"
obj = new Object()
obj.name = 'Mike'
}
var person = new Object()
setName(person)
console.log(person.name) // "Tom"
2
3
4
5
6
7
8
9
10
# 10. 怎么用min 或者是max 查找数组里的最大值或者最小值
// ES5 的写法
let arr = [ 2,4,5,1]
console.log(Math.min.apply(null, arr))
// ES6 的写法
console.log(Math.max(...[14, 3, 77, 30]))
2
3
4
5
6
# 11. javascript 12345678 改为 12,345,678, 也就是千位分隔
- 写不出来才可以这样写哈 要不面试官打你啊哈哈
let num = 12345678
console.log(num.toLocaleString())
2
3
- 求模
let num = 12345678
function toThousands(num) {
var result = '', counter = 0;
num = (num || 0).toString();
for (var i = num.length - 1; i >= 0; i--) {
counter++;
result = num.charAt(i) + result;
if (!(counter % 3) && i != 0) { result = ',' + result; }
}
return result;
}
console.log(toThousands(num))
2
3
4
5
6
7
8
9
10
11
12
13
- 正则
// 3-1
let num = 12345678
function toThousands(num) {
return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}
console.log(toThousands(num))
// 3-2
let num = 12345678
function toCurrency(num){
var parts = num.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
}
console.log(toCurrency(num))
2
3
4
5
6
7
8
9
10
11
12
13
14
# 12. 闭包问题
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
2
3
4
5
6
7
8
9
10
11
12
13
# 13. 输出结果, 为什么
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()()); // the window
2
3
4
5
6
7
8
9
10
11
# 14. 输出结果, 为什么
var length = 1
function fn() {
console.log(this.length)
}
var obj = {
length: 3,
me:function(fn){
fn() // 1
arguments[0]() // 2
}
}
obj.me(fn, 1)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 15. let str = '你好,abc' 字符串长度
let str = '你好,abc'
function getLength(str){
let length = 0
let reg = /[\u4e00-\u9fa5]/
for (let i = 0; i < str.length; i++) {
if (reg.test(str.charAt(i))) {
length +=2
} else {
length ++
}
}
return length
}
console.log(getLength(str))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 16. Object.defineProperty()有什么缺点 有什么方法
- 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
- 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
利用 Proxy
参考 -- 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索
# 17. prototype 和 proto 区别是什么
任何对象都有一个
__proto__
属性。任何方法都有一个
prototype
属性。
__proto__
指向 new 出来的构造函数的原型prototype
。
prototype
是一个对象,当函数被定义的时候默认被创建的,它的作用很像 java 中的静态属性/方法。其中的对象可以给所有实例使用。
需要注意的是:
prototype
也是一个对象 ,所以其中也有一个__proto__
属性,指向对象的原型 Object.protytype
。
Object
本身是构造函数,继承了 Function.prototype。Object.__proto__ === Function.prototype
Function
本身就是函数,继承了 Function.prototype。Function.__proto__ === Function.prototype
举一个例子:
function A() {}
var a = new A();
console.log(a.__proto__ === A.prototype);
console.log(A.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);
console.log(a.__proto__.__proto__.__proto__ === null);
2
3
4
5
6
7
8
9
10
11
任何一个对象都有一个__proto__
属性,指向构造函数的原型 prototype,而 prototype 也是一个对象,也有一个__proto__
属性,这样一环接一环,就形成了一个链,到最后 Object.protytype 截止。
- 函数对象有
__proto__
和 prototype 属性。 - 非函数对象只有
__proto__
属性。 - prototype 中有
__proto__
属性。且是 Object 构造函数创建的。 - 函数对象
__proto__
指向它的创建者及 Function 构造函数。 - Function 构造函数
__proto__
指向它自己。 - Object 对象的 prototype 中的
__proto__
是 null。
[参考刘小夕解答](https://juejin.im/post/5cab0c45f265da2513734390)
## 18. 打印什么 为什么
2
var a = {n:1};
var b = a; // 持有a,以回查
a.x = a = {n:2};
console.log(a.x);// --> undefined
console.log(b.x);// --> {n:2}
## 19. 打印什么 为什么
var y = 1; if (function f(){}) { y += typeof f; } console.log(y); // 1undefined
## 20. 打印什么 为什么
var a = 0 function b(){ console.log(a) // fun a a = 10 console.log(a) // 10 return; function a(){} } b() console.log(a) // 0
## 21. 说一下防抖和节流 什么场景下用
#### 防抖
2
概念: 1. 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。 2. 多次触发事件后,事件处理函数只执行一次,并且是在触发操作结束时执行。
应用场景: 1. 文本输入的验证(连续输入文字后发送 ajax 请求进行验证,验证一次就好) 2. 给按钮加函数防抖防止表单多次提交。
生活实例: 如果有人进电梯(触发事件),那电梯将在10秒钟后出发(执行事件监听器),这时如果又有人进电梯了(在10秒内再次触发该事件),我们又得等10秒再出发(重新计时)。
代码
function debounce(func, interval = 100){ let timer; return (...args) => { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { func.apply(func, args); }, interval); } }
#### 节流
2
概念: 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效
应用场景: 滚动刷新如 touchmove, mousemove, scroll。
生活实例:(实例忘记参考谁的了,看到我添加连接)
- 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源
- 假设电梯一次只能载一人的话,10 个人要上楼的话电梯就得走 10 次,是一种浪费资源的行为;而实际生活正显然不是这样的,当电梯里有人准备上楼的时候如果外面又有人按电梯的话,电梯会再次打开直到满载位置,从电梯的角度来说,这时一种节约资源的行为(相对于一次只能载一个人)。。
代码
function throttle(fn, interval = 100) { let canRun = true; // 通过闭包保存一个标记 return (...args) => { if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return canRun = false; // 立即设置为false setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中 fn.apply(fn, args) // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉 canRun = true; }, interval); }; }
## 22. 监测数组类型有几种方式 有什么区别
+ instanceof
+ instanceof不一定能保证检测的结果一定正确,因为只要是在原型链上的都会返回true ,arr instanceof Object 也返回true
+ Array.isArray()
+ 兼容性问题
+ Object.prototype.toString.call()
+ 较好方案
## 23. 观察者模式和发布订阅模式的写一下 说一下也行 区别是什么
[木易杨-观察者模式和订阅-发布模式的区别,适用场景](https://www.muyiy.cn/question/design/23.html)
[发布订阅模式,在工作中它的能量超乎你的想象
](https://juejin.im/post/5b125ad3e51d450688133f22)
[不好意思,观察者模式跟发布订阅模式就是不一样
](https://juejin.im/post/5af05d406fb9a07a9e4d2799)
## 24. 打印什么 为什么
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo(a) { var a; return a; } function bar(a) { var a = 'bye'; return a; } [foo('hello'), bar('hello')] // hello bye
// 在两个函数里, a作为参数其实已经声明了, 所以 var a; var a = 'bye' 其实就是 a; a ='bye'
## 25. 说一下原型链
原型链解决的主要是继承问题。
每个对象拥有一个原型对象,通过 proto (读音: dunder proto) 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(==Object.proptotype.__proto__ 指向的是null==)。这种关系被称为原型链 (prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法。
构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(==p.__proto__ === Parent.prototype==)
[参考刘小夕解答](https://juejin.im/post/5cab0c45f265da2513734390)
##26 输出什么
2
3
4
5
6
7
8
9
10
11
var name = "World!"; (function() { if(typeof name=== 'undefined'){ var name='Jack'; console.log('Goodbye'+name);
} else {
console.log('hello'+name);
}
})()
// Goodbye Jack
## 27. 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
2
3
跳到 n 阶假设有 f(n)种方法。
往前倒退,如果青蛙最后一次是跳了 2 阶,那么之前有 f(n-2)种跳法; 如果最后一次跳了 1 阶,那么之前有 f(n-1)种跳法。
所以:f(n) = f(n-1) + f(n-2)。就是斐波那契数列
参考
## 28. 打印什么
2
class Chameleon { static colorChange(newColor) { this.newColor = newColor return this.newColor }
constructor({ newColor = 'green' } = {}) { this.newColor = newColor } }
const freddie = new Chameleon({ newColor: 'purple' }) freddie.colorChange('orange')
// 会报错 colorChange是一个静态方法。静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon),并且不能传递给实例。因为 freddie 是一个实例,静态方法不能被实例使用,因此抛出了 TypeError 错误。
[解答参考](https://github.com/lydiahallie/javascript-questions/blob/master/zh-CN/README-zh_CN.md)
## 29. 打印什么 为什么
2
3
4
5
function* generator(i) { yield i; yield i * 2; }
const gen = generator(10);
console.log(gen.next().value); // 10 console.log(gen.next().value); // 20
一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到yield关键字的时候,会生成yield后面的值。注意,生成器在这种情况下不 返回 (return )值,而是 生成 (yield)值。
首先,我们用10作为参数i来初始化生成器函数。然后使用next()方法一步步执行生成器。第一次执行生成器的时候,i的值为10,遇到第一个yield关键字,它要生成i的值。此时,生成器“暂停”,生成了10。
然后,我们再执行next()方法。生成器会从刚才暂停的地方继续,这个时候i还是10。于是我们走到了第二个yield关键字处,这时候需要生成的值是i*2,i为10,那么此时生成的值便是20。所以这道题的最终结果是10,20。
[参考MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*)
[解答参考](https://github.com/lydiahallie/javascript-questions/blob/master/zh-CN/README-zh_CN.md)
## 30 打印什么
2
3
4
5
6
const num = parseInt("7*6", 10); console.log(num) // 7
parseInt检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。
## 31. 继承优缺点
[参考JS原型链与继承别再被问倒了](https://juejin.im/post/58f94c9bb123db411953691b)
## 32. class 实现队列 链表
[艾伦-前端数据结构与算法系列](https://www.cnblogs.com/aaronjs/p/3623646.html)
## 33. array.sort如何实现的
[参考githubarray源码-](https://github.com/v8/v8/blob/6.7.241/src/js/array.js#L668)
## 34. 实现object.create
2
3
4
5
6
7
8
9
10
function create(proto) { function F() {}; F.prototype = proto; F.prototype.constructor = F; return new F(); }
[关于JS中一些重要的api实现, 巩固你的原生JS功底
](https://juejin.im/post/5d635566e51d4561e224a360)
## 35. 说一下Reflect
[只能贴下MDN了](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect)
## 36. 说一下generator
[zhangxiang958-理解 ES6 generator ](https://github.com/zhangxiang958/zhangxiang958.github.io/issues/32)
[阮一峰](http://es6.ruanyifeng.com/#docs/generator)
## 37. 说一下proxy
[Proxy及其优势](https://juejin.im/post/5d46a94e6fb9a06b24430027)
[快来围观一下JavaScript的Proxy](https://juejin.im/post/5b09234d6fb9a07acf569905)
[Proxy 的巧用](https://juejin.im/post/5d2e657ae51d4510b71da69d)
## 38. offsetWidth,clientWidth,scrollWidth 的区别
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
clientWidth:元素的 width + padding
offsetWidth:元素的 width + padding + border
scrollWidth:
- 内部元素小于外部元素,scrollWidth = width + padding
- 内部元素大于外部元素,scrollWidth = width + padding + 溢出部分尺寸
## 39. arguments 是数组吗
arguments 是数组吗?怎么实现用它调用数组方法?类数组和数组的区别是什么?arguments 有 length 属性吗?
1、arguments 不是数组,但有 length 属性。无法使用数组方法。
2、可以转换成数组,因为它有 Symbol(Symbol.iterator) 方法。
```js
[...arguments];
Array.prototype.slice.call(arguments);
Array.from(arguments);
2
3
4
5
6
7
8
9
10
11
12
13
3、类数组是一个对象
# 40. 手写一个 bind 函数
- 绑定 this
- bind 后可以被 new 函数调用
- 容错处理
Function.prototype.bind2 = function(ctx, ...rest) {
if (typeof this !== 'function') {
throw new Error('只能由 function 调用');
}
const func = this;
var result = function(...params) {
return func.apply(
// 如果是 new 对象出的,this绑定的应该绑定为构造函数
this instanceof result ? this : ctx,
rest.concat(params)
);
};
var fNOP = function() {};
fNOP.prototype = func.prototype;
result.prototype = new fNOP();
return result;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 41. 微任务和宏任务
微任务包括 process.nextTick ,promise ,MutationObserver,其中 process.nextTick 为 Node 独有。
宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。
# 42. 用过 NodeJS 的 EventEmitter 模块吗,它是怎么实现功能的,步骤是什么
类似于 观察者模式
# 43. JS new 过程中发生了什么
1,创建一个以这个函数为原型的空对象.
2,将函数的 prototype
赋值给对象的 __proto__
属性。
3,将对象作为函数的 this
传进去。如果有 return 出来东西是对象的话就直接返回该对象,如果不是就返回创建的这个对象。
function newFunc(father, ...rest) {
var result = {};
result.__proto = father.prototype;
var result2 = father.apply(result, rest);
if (
(typeof result2 === 'object' || typeof result2 === 'function') &&
result2 !== null
) {
return result2;
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
# 44. setTimeout 和 setInterval 方法有执行顺序吗
setTimeout 等待 xx 毫秒后,把方法推入异步队列。
setInterval 每隔 xx 毫秒,把方法推入异步队列。
setInterval 有个了例外:当间隙时间较小、方法内部执行非常耗时的时候,会导致间隔不连续。
例如: 每隔 100ms 执行 fn,如果 fn 执行时间是 150ms,那么第二次执行,会在 150ms 处执行(异步事件会等待同步事件执行完后才执行)。
可以使用 setTimeout 自调用,在确定方法执行完后,再推入异步队列。
# 45. 手写一个 call 函数
Function.prototype.myCall = function(context, ...rest) {
// context是需要绑定的作用域
var context = context || window;
context.fn = this;
var result = context.fn(rest);
delete context.fn;
return result;
};
2
3
4
5
6
7
8
# 46. 分析这一段代码的返回结果
var a = 0
var b = async () => {
a = a + await 10
console.log('b中的a:', a);
}
b()
a++
console.log('外部a:', a)
2
3
4
5
6
7
8
- 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
- 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
- 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 promise 的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。
# 47. JS 代码在 V8 中的优化
js 代码如果要运行起来,必须要一个编译器来编译(V8),以下是 V8 中编译代码的过程:
- 将 JS 代码编译成抽象语法树(AST)。
- Ignition 将 AST 转化为 ByteCode。
- TuboFan 将 ByteCode 转化为 MachineCode(可选)。
我们都知道,机器码运行时的效率是最快的,所以如果能让更多的 js 代码转换成机器码,就能大幅度提高 js 运行效率。但受 js 动态类型的影响,V8 并不敢直接编译成机器语言,而是编译成 ByteCode。只有是纯静态的变量,才可以直接编译成机器代码,所以应该多编写静态类型的变量。
- 可以压缩合并 js 代码,减少嵌套函数,减少编译时间。
- 变量定义时是什么类型,就永远当做这个类型来使用,不要将对象修改为另一个类型(使用 typescript)。
- 函数在定义时,需要传递的参数类型也不要改变(使用 typescript)。
# 49. 写出以下函数打印结果
function Person(){}
var p1 = new Person();
Person.prototype.sex = 'man';
Person.prototype = {sex: 'woman'};
var p2 = new Person();
console.log(p1.sex);
console.log(p2.sex);
2
3
4
5
6
7
8
9
10
11
这里的难点在于 p1.sex 没有被修改成 woman,因为在 new 创建一个对象时,原型对象会被挂载到新对象上。修改 Person 的原型,并不能修改到 p1 的原型。
CSS 面试题 →