# 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))
1
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)) 
    
1
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)
1
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)
1
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);

1
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()

1
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. 实现一个数据的深度拷贝,必问原生如何实现

  1. 简单粗暴, 但函数、正则、Symbol都没有被正确的复制
  JSON.parse(JSON.stringify(obj));
1
  1. 考虑对象相互引用以及 Symbol 拷贝的情况
[木易杨](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/)
[深度拷贝](https://juejin.im/post/5abb55ee6fb9a028e33b7e0a)

1
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
   
// 这道题相当经典了 花点时间自己去看下吧
1
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

1
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"
 
1
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]))
1
2
3
4
5
6

# 11. javascript 12345678 改为 12,345,678, 也就是千位分隔

  1. 写不出来才可以这样写哈 要不面试官打你啊哈哈
let num = 12345678
console.log(num.toLocaleString())

1
2
3
  1. 求模
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))

1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 正则
// 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))
1
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

1
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

1
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)

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))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 16. Object.defineProperty()有什么缺点 有什么方法

  1. 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。
  2. 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。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);
1
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.  打印什么 为什么
1
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. 打印什么 为什么
1

var y = 1; if (function f(){}) { y += typeof f; } console.log(y); // 1undefined

## 20. 打印什么 为什么
1

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. 说一下防抖和节流 什么场景下用
#### 防抖
1
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); } }


#### 节流
1
2

概念: 规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效

应用场景: 滚动刷新如 touchmove, mousemove, scroll。

生活实例:(实例忘记参考谁的了,看到我添加连接)

  1. 我们知道目前的一种说法是当 1 秒内连续播放 24 张以上的图片时,在人眼的视觉中就会形成一个连贯的动画,所以在电影的播放(以前是,现在不知道)中基本是以每秒 24 张的速度播放的,为什么不 100 张或更多是因为 24 张就可以满足人类视觉需求的时候,100 张就会显得很浪费资源
  2. 假设电梯一次只能载一人的话,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. 打印什么 为什么
1
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 输出什么

1
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 级的台阶总共有多少种跳法。

1
2
3

跳到 n 阶假设有 f(n)种方法。

往前倒退,如果青蛙最后一次是跳了 2 阶,那么之前有 f(n-2)种跳法; 如果最后一次跳了 1 阶,那么之前有 f(n-1)种跳法。

所以:f(n) = f(n-1) + f(n-2)。就是斐波那契数列

参考 
## 28. 打印什么
1
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. 打印什么 为什么

1
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 打印什么

1
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
1
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 的区别
1
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);
1
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;
};
1
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;
}
1
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;
};
1
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)
1
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);
1
2
3
4
5
6
7
8
9
10
11

这里的难点在于 p1.sex 没有被修改成 woman,因为在 new 创建一个对象时,原型对象会被挂载到新对象上。修改 Person 的原型,并不能修改到 p1 的原型。

Last Updated: 8/31/2019, 5:41:02 PM