1. 1. 自我介绍
  2. 2. 基础HTML/CSS
    1. 2.1. Html语义化标签有哪些?语音化有什么意义?
    2. 2.2. 三栏布局,水平排列,左右固宽,中间自适应都有哪些方案?优缺点是什么?
      1. 2.2.1. float布局
      2. 2.2.2. 定位
      3. 2.2.3. flex
      4. 2.2.4. Table
      5. 2.2.5. grid
      6. 2.2.6. 优缺点
    3. 2.3. 元素水平垂直居中实现方案有哪些?
      1. 2.3.1. absolute + 负margin
      2. 2.3.2. absolute + calc
      3. 2.3.3. absolute + margin auto
      4. 2.3.4. absolute + transform
      5. 2.3.5. table
      6. 2.3.6. flex
      7. 2.3.7. grid
      8. 2.3.8. 优缺点
    4. 2.4. 如何做响应式?移动端如何处理?
  3. 3. JS/ES6+
    1. 3.1. JS中的数据类型都有哪些?原始类型和引用类型的区别是什么?
    2. 3.2. 如何实现对引用类型的深拷贝?
    3. 3.3. 在JS中如何判断一个变量的数据类型?
    4. 3.4. 如何进行类型转换?
      1. 3.4.1. String => Number
      2. 3.4.2. Number => String
      3. 3.4.3. 任意值转Boolean
    5. 3.5. 你怎么理解函数节流防抖?
    6. 3.6. 什么是闭包?闭包的意义是什么?
    7. 3.7. 如何创建对象?
    8. 3.8. new 操作符做了什么?
    9. 3.9. 如何实现继承?
    10. 3.10. apply和call有什么区别?
    11. 3.11. 如何修改this指向?是否了解bind方法?bind和apply与call的区别是什么?
    12. 3.12. 对于新版ES标准了解多少?使用过那些?
    13. 3.13. 严格模式与非严格模式
      1. 3.13.1. 严格模式与非严格模式的区别
    14. 3.14. const/var/let有什么区别?为什么会存在变量提升?
    15. 3.15. 箭头函数与function函数有什么区别?
    16. 3.16. Map和Object的区别?Set和Array的区别?Map与WeakMap的区别?Set和WeakSet的区别?
    17. 3.17. 如何对Array、Object、Map、Set进行遍历?
    18. 3.18. 是否了解Promise、async/await、generator?解决了什么问题?
    19. 3.19. common.js 和 ES Module 的区别
    20. 3.20. 是否了解Event Loop?
    21. 3.21. 一段JS代码是如何被执行的?
  4. 4. TS
    1. 4.1. 为什么要用TS
  5. 5. Vue/React/小程序
    1. 5.1. 什么是MVVM?
    2. 5.2. new Vue之后都发生了什么?
    3. 5.3. Vue的组件间通信都有哪些方法?
    4. 5.4. 怎么修改Vuex中的数据?怎么触发Action?
    5. 5.5. Action和Mutation的区别是什么?为什么这么设计?
    6. 5.6. 直接修改数组元素,Vue能监测到变化吗?为什么监测不到?监测不到如何处理?
    7. 5.7. Vue的双向绑定是如何实现的?
    8. 5.8. 你刚才说了 Object.defineProperty() 只能劫持对象,那 Vue 中对于数组是怎么实现劫持的。
    9. 5.9. Vue3中对于数据劫持由 Object.defineProperty() 改成了 proxy,它俩有什么区别
    10. 5.10. 是否了解Vue的mixin?有什么作用?
    11. 5.11. 是否了解Vue的插件机制?如何写一个Vue插件?
    12. 5.12. Vue-router有几种模式?有什么区别?是怎么实现的?
    13. 5.13. router-view有什么作用?
    14. 5.14. 页面间如何传参?都有哪些传参方式?
    15. 5.15. 为什么组件中的data需要是函数?
    16. 5.16. 能聊聊 Vuex吗?
    17. 5.17. Virtual Dom 的优势在哪里?
    18. 5.18. 虚拟 Dom 实现原理
    19. 5.19. 小程序、Vue、React的共同点和区别?
    20. 5.20. 如何在Vue和小程序中实现登录校验?
    21. 5.21. 小程序的组件间通信都有哪些方式?
    22. 5.22. 小程序中父组件如何影响子组件的样式?
    23. 5.23. 如何在小程序中监听数据变化?
    24. 5.24. 如何在小程序中进行多组件间数据共享?
    25. 5.25. 小程序的性能比Web性能会好一点,为什么?
  6. 6. HTTP
    1. 6.1. 在浏览器输入一个Url到用户看到页面,中间都发生了什么?
    2. 6.2. DNS是如何进行解析的?
    3. 6.3. 客户端是如何与服务器创建连接的?
    4. 6.4. HTTP请求方式有哪些?GET和POST有什么区别?什么情况下会进行OPTIONS请求?
    5. 6.5. 常见的HTTP状态码有哪些?
    6. 6.6. 什么是持久连接?什么是管线化?
    7. 6.7. 浏览器拿到文件后是如何进行解析渲染的?
    8. 6.8. 为什么会出现跨域?
    9. 6.9. 都有哪些方法可以解决跨域?是否了解它们的原理?
    10. 6.10. 是否了解HTTP/2?如何开启?
  7. 7. 性能优化
    1. 7.1. 如何检测分析前端性能?
    2. 7.2. 前端性能优化的方法有哪些?
  8. 8. 前端工程化
    1. 8.1. 如何理解前端工程化?
    2. 8.2. 如何进行webpack性能优化?
    3. 8.3. 你做过那些工程化的实践?
    4. 8.4. 是否了解git-flow流程?
    5. 8.5. 你对devOps是怎么理解的
  9. 9. 代码质量/Web安全
    1. 9.1. 团队协作如何规范代码风格?
    2. 9.2. 如何控制代码质量?
    3. 9.3. 常见的Web安全问题有哪些?
    4. 9.4. 如何进行防范?
  10. 10. Node
    1. 10.1. 如何理解Koa的洋葱圈模型?
    2. 10.2. 如何编写Koa的中间件?
    3. 10.3. Egg和Koa有什么区别?
  11. 11. 管理能力
    1. 11.1. 对于一个大型项目你是考虑设计的
    2. 11.2. 你在项目开发中遇到过那些问题,你是如何解决的
    3. 11.3. 你是如何带团队的?你们团队的工作流程是怎样的?有没有遇到过哪些问题?你是如何处理的?
    4. 11.4. 工作过程中是否会与其他部门打交道,你们是如何配合的?
  12. 12. 你离职的原因是什么

自我介绍

自我介绍是面试的开场白,是非常非常非常重要的一环,如果你的自我介绍只有个人信息(姓名,年龄,毕业学校,几年工作经验)的话,那么面试官是从你的自我介绍中提取不出来任何有效信息的,当你自我介绍结束,面试官也不知道他该问你什么,而且自我介绍时间也非常短,你自我介绍完了,你简历的第一页,面试官可能也就刚把基本信息看完。

当面试官不知道问你什么的时候,那么他就只能问他熟悉擅长的的,然后整场面试的主动权就全都掌握在面试官手中的了,如果他擅长的,你恰好不了解,那整场面试下来,就是你感觉面试官是傻逼,面试官感觉这人水平这么差,这都不了解。

所以自我介绍的也是一个给面试官递话的过程,把你擅长的东西夹到自我介绍里,当自我介绍结束,恰好你的自我介绍中有个技术点,那么面试官也能从这个技术点进行切入,然后面试就开始了。这样的话,面试的主动权就算是掌握在你手中了。

对于一些有三四面的大公司,他们每一面的考察点基本都不一样,所以自我介绍最好准备四套,每套的侧重点分别突出你的基础能力,业务开发能力,项目管理和团队管理能力,交流沟通能力。

不要给面试官留思考的时间,在回答一个问题的时候,就在这个回答里留下可供面试官发问的点,那么下一个问题很有可能就是从这个点进行切入的。

基础HTML/CSS

这部分可以跳过,如果你是有工作经验的人,面试问这部分的可能性非常低,建议从 JS/ES6+ 开始看起

Html语义化标签有哪些?语音化有什么意义?

标签:header aside footer nav article section audio video address time progress

意义:有利于 SEO ,增强代码的可读性,标准化(与 <div class="header"></div> 相比,从语法层面确认了标准),方便其他设备解析(盲人阅读器)

三栏布局,水平排列,左右固宽,中间自适应都有哪些方案?优缺点是什么?

float布局

元素左右中顺序,左右浮动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<style>
.left,.right {
width: 150px;
}
.left,.right,.center {
height: 150px;
}
.left {
float: left;
background-color: aqua;
}
.center {
background-color: yellowgreen;
}
.right {
float: right;
background-color: orange;
}
</style>
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="center"></div>
</div>

定位

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
<style>
.left,.right {
width: 150px;
}
.left,.right,.center {
position: absolute;
height: 150px;
}
.left {
left: 0;
background-color: aqua;
}
.center {
left: 150px;
right: 150px;
background-color: yellowgreen;
}
.right {
right: 0;
background-color: orange;
}
</style>
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="center"></div>
</div>

flex

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
<style>
.container {
display: flex;
}
.left,.right {
width: 150px;
}
.left,.right,.center {
height: 150px;
}
.left {
background-color: aqua;
}
.center {
flex: 1;
background-color: yellowgreen;
}
.right {
background-color: orange;
}
</style>
<div class="container">
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</div>

Table

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
<style>
.container {
width: 100%;
display: table;
}
.left,.right {
width: 150px;
}
.left,.right,.center {
display: table-cell;
height: 150px;
}
.left {
background-color: aqua;
}
.center {
background-color: yellowgreen;
}
.right {
background-color: orange;
}
</style>
<div class="container">
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</div>

grid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
.container {
width: 100%;
display: grid;
grid-template-columns: 150px auto 150px;
grid-template-rows: 150px;
}
.left {
background-color: aqua;
}
.center {
background-color: yellowgreen;
}
.right {
background-color: orange;
}
</style>
<div class="container">
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</div>

优缺点

float: 简单,兼容性比较好,但要注意清除浮动,否则会造成高度塌陷

定位: 简单,兼容性好,但定位脱离了文档流,最好给父元素添加 position: relative;

table: 兼容性好,但一个元素高度更改,其它子元素会同样被修改

flex: 简单,便于理解,但兼容性不支持IE8以下

grid: 简单,代码量少,IE支持部分属性,Chrome从57开始支持

元素水平垂直居中实现方案有哪些?

absolute + 负margin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style>
.container {
position: relative;
width: 600px;
height: 400px;
background-color: orange;
}
.center {
width: 100px;
height: 100px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -50px;
background-color: yellowgreen;
}
</style>
<div class="container">
<div class="center"></div>
</div>

absolute + calc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<style>
.container {
position: relative;
width: 600px;
height: 400px;
background-color: orange;
}
.center {
width: 100px;
height: 100px;
position: absolute;
top: calc(50% - 50px);
left: calc(50% - 50px);
background-color: yellowgreen;
}
</style>
<div class="container">
<div class="center"></div>
</div>

absolute + margin auto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
.container {
position: relative;
width: 600px;
height: 400px;
background-color: orange;
}
.center {
width: 100px;
height: 100px;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
background-color: yellowgreen;
}
</style>
<div class="container">
<div class="center"></div>
</div>

absolute + transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<style>
.container {
position: relative;
width: 600px;
height: 400px;
background-color: orange;
}
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: yellowgreen;
}
</style>
<div class="container">
<div class="center">子元素内容</div>
</div>

table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style>
.container {
width: 600px;
height: 400px;
background-color: orange;
display: table-cell;
vertical-align: middle;
text-align: center;
}
.center {
display: inline-block;
background-color: yellowgreen;
}
</style>
<div class="container">
<div class="center">子元素内容</div>
</div>

flex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<style>
.container {
width: 600px;
height: 400px;
background-color: orange;
display: flex;
justify-content: center;
align-items: center;
}
.center {
background-color: yellowgreen;
}
</style>
<div class="container">
<div class="center">子元素内容</div>
</div>

grid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<style>
.container {
width: 600px;
height: 400px;
background-color: orange;
display: flex;
justify-content: center;
align-items: center;
}
.center {
background-color: yellowgreen;
}
</style>
<div class="container">
<div class="center">子元素内容</div>
</div>

优缺点

absolute+负margin/absolute+calc:这俩原理是相同的,前者兼容性更好,后者兼容性依赖calc函数,缺点是需要知道子元素的宽高

absolute+margin auto:兼容性好,但需要知道子元素宽高

absolute+transform:兼容性依赖transform,有点是不需要知道子元素宽高

table:兼容性好,无需知道子元素宽高

flex/grid:代码简介,无需知道子元素宽高,但兼容性后者更差点

如何做响应式?移动端如何处理?

JS/ES6+

JS中的数据类型都有哪些?原始类型和引用类型的区别是什么?

数据类型分两类,原始类型和引用类型;原始类型有:String、Number、Boolean、undefined、null、Bigint、Symbol;引用类型有 Array、Object、Function。

值和内存地址的区别;原始类型在栈中存着,我们拿到的是值,引用类型在堆中存着,在栈中保存着引用类型的内存地址,我们拿到的实质是内存地址。

如何实现对引用类型的深拷贝?

JSON.parse(JSON.stringify(obj));

Window.messageChannel();

遍历加递归

在JS中如何判断一个变量的数据类型?

typeof obj.constructor.name instanceof Object.prototype.toString.call()

typeof 对于 null 返回 Object

obj.constructor.name 对于构造函数生成对象的返回返回构造函数名

instanceof 对于原型上的所有祖先原型也会返回true

Object.prototype.toString.call() 是目前最完善的类型检测方法 [object Function]

如何进行类型转换?

String => Number

  • Number方,不允许传入非数字字符 Number('109') => 109
  • parseInt方法,可以传入非数字字符,遇到非数字字符会停下,将已解析的返回 parseInt('109px') => 109
  • +一元符 +'18' => 18

Number => String

  • 模板字符串

    1
    2
    const n = 109;
    const b = `${a}`;
  • toString发方法 18.toString() => '18'

任意值转Boolean

  • Boolean()方法 Boolean(null) => false

你怎么理解函数节流防抖?

节流:在某段时间被频繁触发的事件,以一个固定的时间长度,间隔的去执行一个函数

防抖:在某段时间被频繁触发的事件,只在最后一次触发时执行

使用场景:窗口调整、页面滚动,实时拖拽适合使用节流;搜索联系和输入框正则验证适合防抖

什么是闭包?闭包的意义是什么?

  1. 简单来说闭包就是一个函数的返回值是一个函数
  2. 闭包的最大的优点即 避免了定义过多的全局变量造成变量冲突
  3. 函数节流和函数防抖就是闭包的典型应用场景;在插件开发中闭包的使用率也非常高

如何创建对象?

  • 字面量 const obj1 = {} const obj2 = new Object()

  • 构造函数

    1
    2
    3
    4
    const M = function() {
    this.name = 'm';
    }
    const obj = new M();
  • Object.create() const obj = Object.create({})

new 操作符做了什么?

  1. 创建了一个空对象 obj
  2. 将 obj 的 __proto__ 指向构造函数的 prototype ,将构造函数的 this 指向替换成 obj
  3. 如果构造函数最后 return 了一个对象,那么这个对象就会取代最后 new 出来的结果

如何实现继承?

  1. apply 和 call;缺点只能继承父类构造函数上的方法,不能继承父类原型链上的方法

  2. 使用原型链,子的原型链等于父的实例;缺点:子类的原型链被覆盖,子类生成多个实例多个实例的原型链共享,其中一个对原型链修改,其他实例也会被修改

  3. 组合方式

    1
    2
    3
    4
    5
    6
    7
    8
    function Parent1() {
    this.name = 'parent1'
    }
    function Child1() {
    Parent1.call(this);
    this.type = 'child'
    }
    Child1.prototype = new Parent1();
  4. 对3优化

    1
    2
    3
    4
    5
    6
    7
    8
    function Parent1() {
    this.name = 'parent1'
    }
    function Child1() {
    Parent1.call(this);
    this.type = 'child'
    }
    Child1.prototype = Parent1.prototype;
  5. 对4优化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Parent1() {
    this.name = 'parent1'
    }
    function Child1() {
    Parent1.call(this);
    this.type = 'child'
    }
    Child1.prototype = Object.create(Parent1.prototype);
    Child1.prototype.constructor = Child1;
  6. ES6中通过 extends

apply和call有什么区别?

入参方式不一样,apply第二个参数是数组,call 使用剩余参数

如何修改this指向?是否了解bind方法?bind和apply与call的区别是什么?

  1. apply

    1
    2
    3
    4
    5
    const obj = {name: 'obj'}
    const fn = function() {
    console.log(this.name)
    }
    fn.apply(obj)
  2. call

    1
    2
    3
    4
    5
    const obj = {name: 'obj'}
    const fn = function() {
    console.log(this.name)
    }
    fn.call(obj)
  3. bind

    1
    2
    3
    4
    5
    const obj = {name: 'obj'}
    const fn = function() {
    console.log(this.name)
    }
    fn.bind(obj)()

区别,apply和call都接收第二个参数,第二个参数形式不同,bind会创建一个新函数返回

对于新版ES标准了解多少?使用过那些?

ES标准从2015之后,每年会发布一版,常说的ES6就是ES2015年的标准,2020已经是ES11了,常说了async/await其实是ES2017定义的也就是ES8加的,并不是ES7加入的。

const、let、箭头函数、模板字符串、解构赋值、Promise、async/await、Map、Set、扩展运算符、数组的find、includes、flat、对象的values、keys、entries

严格模式与非严格模式

开启 JS 严格模式

1
"use strict";

如果 "use strict" 写在脚本文件第一行,则整个脚本文件都以严格模式运行;

如果 "use strict" 写在函数定义的第一行,则整个函数以严格模式运行;

如果 "use strict" 写在其他位置,则就是一行普通的字符串;

严格模式与非严格模式的区别

  • 严格模式的全局变量必须显示声明;非严格模式不需要

    1
    2
    3
    a = 1;
    // 严格模式下代码会报错,a未被声明
    // 非严格模式下,a会挂载到全局,为全局变量
  • 严格模式下 this 指向 undefined ;非严格模式下 this 指向 window

    1
    2
    3
    4
    5
    function fn() {
    "use strict";
    return this; // 严格模式this是undefined;非严格模式是 window
    }
    fn();
  • 严格模式下禁止删除变量,对于 Object ,只有设置了 configurable=true 的属性才可以删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "use strict";
    var x;
    delete x; // 语法错误

    var o = Object.create(null, {'x': {
    value: 1,
    configurable: true
    }});
    delete o.x; // 删除成功
  • 严格模式下对于只读属性赋值将会报错;非严格模式静默失败

    1
    2
    3
    4
    "use strict";
    var o = {};
    Object.defineProperty(o, "v", { value: 1, writable: false });
    o.v = 2; // 报错

const/var/let有什么区别?为什么会存在变量提升?

实质上三者都是定义变量的,var定义的变量存在变量提升、可以重复声明一个变量,const和let有块级作用域,没有变量提升的问题,重复声明一个变量会报错,const定义的原始类型的数据不允许被修改,定义的引用类型数据,内存地址不可以被更改,这也是为什么定义的对象和数组可以添加属性或者元素。

和JS的执行机制有关,当一段JS代码被执行,JS引擎会将这段代码中的变量提到当前作用域的顶部,并给这些变量分配一个内存地址并给个初始值undefined

箭头函数与function函数有什么区别?

  1. 箭头函数的this指向是在定义时它所在的对象,而不是使用时所处的对象,箭头函数的this使用call与apply都无法改变
  2. 普通函数的剩余参数是arguments,而箭头函数是arg
  3. 箭头函数不能做构造函数用,不能 new

Map和Object的区别?Set和Array的区别?Map与WeakMap的区别?Set和WeakSet的区别?

如何对Array、Object、Map、Set进行遍历?

  1. for…in.. 遍历对象数组
  2. for…of…遍历数组,Map、Set
  3. forEach遍历数组,Map、Set
  4. map遍历数组

是否了解Promise、async/await、generator?解决了什么问题?

common.js 和 ES Module 的区别

CommonJs 是一种模块化规范,最初被应用与 NodeJs,成为 Nodejs 的模块化规范。运行在浏览器端的 JS 由于也缺少类似的规范,在 ES6 出来前,前端也实现了一套类型的模块化规范(AMD、UMD…)。自 ES6 起,引入了一套新的 ES6 Module 规范,在语言标准层面实现了模块功能,右往成为浏览器和服务器通用的模块解决方案。但目前浏览器对 ES6 Module 兼容还不太友好,所以平时在 webpack 中使用的 export 和 import ,会经过 Babel 转换为 CommonJs 规范。

  • CommonJs 模块输出的是一个值得拷贝,ES6 模块输出的是一个值得引用。
  • CommonJs 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJs 是单个值的导出,ES6 Module 可以导出多个。
  • CommonJs 是动态语法 可以写在判断中,ES6 Module 是静态语法 只能写在顶层。
  • CommonJs 得 this 指向当前模块,ES6 Module 得 this 是 undefined。

是否了解Event Loop?

  1. 在JS的执行过程中同步任务优先执行,同步任务执行之后才会去执行异步任务

  2. JS是单线程的,从上往下的执行过程中会遇到同步和异步的任务,例如 console.log 就是一个同步任务,element.onClick = function(){} 就是一个异步任务。当遇到同步的代码后,会将任务添加到运行栈中,异步任务会添加到任务队列中。当运行栈中的同步任务全部执行完成之后,才会去遍历任务队列查找是否存在可以执行异步任务,如果存在的话,会将这个异步任务推入运行栈进行执行。当浏览器执行到 setTiomeoutsetInterval 代码时,并不会立刻将 setTiomeoutsetInterval 添加到任务队列,此时 setTiomeoutsetInterval 会交给浏览器的 timer 模块,当时间到了之后会将 setTiomeoutsetInterval 的内容推入任务队列,在 Event Loop 的过程中被执行。

  3. 那些语句会被添加到任务队列 setTimeoutsetInterval ,DOM 事件,ES6 的 Promise。

一段JS代码是如何被执行的?

JS 的执行分为创建阶段和执行阶段。

创建阶段,在代码执行前,JS 引擎会先创建一个执行栈,然后在创建一个全局的执行上下文,并 push 进执行栈,在这个过程中 JS 引擎会为这段代码中所有变量分配内存并给一个初始值 undefined,这也就是我们常说的 变量提升

创建完成后 JS 引擎就会进入执行阶段,这个过程 JS 引擎会逐行的执行代码,为之前分配好内存的变量逐个赋值(真实值)。

如果这段代码中存在 function 的声明和调用,JS 引擎会创建一个函数执行上下文,并 push 进执行栈,它的创建和执行过程与全局执行上文一样。但如果这个函数中存在对其他函数的调用时,JS 引擎会在父函数的执行过程中,将子函数的执行上下文 push 进执行栈,这也是为什么子函数可以访问父函数内所声明的变量。(相当于在父函数中定义了子函数,父函数执行过程中会将子函数上下文 push 进执行栈)

对于闭包,父函数 return 了一个子函数,子函数执行的时候,父函数已经 return 了(子函数执行的时候父函数早就执行完了)。这种场景,JS 引擎会将父函数的上下文从执行栈中移除,父函数上下文被移除的同时,JS 引擎会为尚未执行或还在执行过程中的子函数上下文创建一个闭包,这个闭包内保存了父函数内声明的变量及其赋值,子函数仍然能够在其上下文中访问并使用这个变量。当子函数执行结束,JS 引擎才会将子函数的上下文及闭包一并从执行栈移除。

高并发,例如 Promise.all ,当异步任务被触发,会将异步任务 push 进执行栈,执行过程中对于需要异步执行的代码,会移出执行栈 push 进任务队列,继续执行下一个异步任务以此循环,当执行栈中已经没有需要被执行的代码了,JS 引擎回去遍历任务队列,等之前的请求回来了,立刻将任务队列中的回调 push 进执行栈执行。

TS

为什么要用TS

这是一个开放性问题,有可能会出现“TS的优点是什么” “有了JS为什么还要有TS” 等各种类似问题,回答的核心思路都可以按照 先说JS存在的问题,再说TS的优点

 

JavaScript 是一门动态 弱类型 解释型的脚本语言,动态带来了很多便利,我们可以在代码运行中随意修改变量类型以达到预期目的。但同时,当一个庞大复杂的项目出现在你面前,面对无比复杂的逻辑,很难通过代码看出某个变量是什么类型,这个变量要做什么,随意修改很容易出现bug。

混乱的类型系统,同样是字符串,字面量定义和包装类型定义会得倒不同的类型

ES6 之前 NaN 不等于 NaN

没有命名空间

  • 提升开发效率:虽然短期看需要多写一些类型定义代码,但 TS 在 VSCode、WebStorm 等 IDE 下可以做到智能提升,只能感知 Bug,这在团队协作的项目中可以提升整体的开发效率。

  • 提高项目可维护性:长期迭代维护的项目开发和维护的成员会很多,这个周期内,团队成员也会一茬一茬的更换,成员水平会有差异,随着项目越大,时间越长,代码的可维护性会逐渐降低,有了强类型和静态检查,以及智能IDE,可以降低应用的腐化速度,提升可维护性。

  • 提高代码质量:我们现在的项目一部分 bug ,都是由于调用方和被调用方的数据格式不匹配引起的,由于 TS 有编译期的静态类型检查,可以让我们的bug尽可能消灭在编译期,加上 IDE 的智能纠错,编码时就能提前感知 bug 的存在,是我们的代码在线上运行时质量更加稳定可控。

静态检查 —> 低级错误、非空判断、类型推断

面向对象编程增强 —> 访问权限控制(私有属性)、接口、泛型

类型系统

模块系统增强,支持命名空间

Vue/React/小程序

什么是MVVM?

MVVM 是 Model-View-ViewModel 的简写,即模型-视图-视图模型。MVVM最早由微软的工程师提出来的,它借鉴了MVC的思想;在前端项目中,Model用纯 JavaScript 对象表示,View负责页面视图,两者做到了最大限度的分离。把 Model 和 View 关联起来的就是 ViewModel。ViewModel 负责把 Model 的数据同步到 View 显示出来,同时还负责把View的修改同步回Model。

【模型】到【视图】的转化,实现方式是数据绑定;【视图】到【模型】的转化,实现方式是 DOM 事件监听。两个方向都实现了的,我们称之为双向绑定。Vue 双向绑定的核心是数据劫持,Angular 双向绑定的核心是脏检查,React 是单向数据流,实现了【模型】影响【视图】。

new Vue之后都发生了什么?

Vue的组件间通信都有哪些方法?

  1. props/emit — 父组件通过属性向子组件传入,子组件通过 props 接收父组件的传入;子组件通过 emit 向外触发事件向外抛出数据,父组件通过事件监听实现数据接收,props 还可以传递父组件的方法,子组件通过调用父组件的方法将数据传出去 — 原理:应该是发布订阅
  2. $attrs/emit — 与前一个方式基本相同,父组件通过属性向子组件传入,子组件不通过 props 接收,此时父组件传递的数据,子组件可以通过 this.$attrs 来获取
  3. $refs — 父组件通过给子组件设置 ref ,获取子组件的实例,通过调用子组件实例上的方法实现数据传输
  4. $parent/$children — 子组件通过 $parent 获取父组件的实例,通过操作父组件来实现数据传出(比如调用父组件的方法);父组件通过 $children 获取子组件实例,通过操作实例实现数据传入,需要注意的是 $children 获取的是当前父组件下所有子组件的实例集合
  5. eventBus — 通过 on 监听另一个组件发布的事件,另一个组件通过 emit 发布事件,来实现数据传递 — 原理:发布订阅
  6. Vuex — Vue官方的状态管理仓库,核心原理是 单例模式 + 发布订阅模式,单例模式的作用是使所有组件可以共享同一个状态库,发布订阅的作用是 使组件间通过发布事件的方式实现数据流动。项目中很常用,但一两句话暂无法描述清楚使用过程
  7. $root — 这个方法是间接的实现数据传递,$root 是 Vue 跟组件的实例,一个组件将自身的方法挂载到跟组件实例上,这个方法的作用是取当前组件的某些数据;另一组件通过调用跟实例上的这个方法,来获取数据。
  8. 自行写一个发布订阅模式实现数据传递

怎么修改Vuex中的数据?怎么触发Action?

Action和Mutation的区别是什么?为什么这么设计?

直接修改数组元素,Vue能监测到变化吗?为什么监测不到?监测不到如何处理?

Vue的双向绑定是如何实现的?

 

首先双向绑定指的是 Model《==》View 的双向影响,其中 View 到 Model 的改变是通过 DOM 事件监听实现的,Model 到 View 的改变是通过数据绑定实现的,只有两个方向都实现了,才称之为双向绑定。 (先聊概念 )

在 Vue 中 View 到 Model 的改变也是通过 DOM 事件监听实现的,但数据绑定是通过数据劫持来实现的,在 Vue2.x 中,数据劫持核心使用的是 Object.defineProperty() ,Vue3 改成了Proxy。像大多数人描述双向绑定都谈到了 Object.defineProperty() ,但其实他只是双向绑定中数据绑定的一部分,很少有人会说到 DOM 监听。 再突出自己与其他人的不同

DOM 事件监听和数据劫持是怎么起作用的呢,首先 Vue2.x 的 API 都是 options API,当我们 new Vue 生成 Vue 实例的时候,是需要给 Vue 构造函数传入参数的,这个参数里有一个 el 和 data 属性,当参数传入后 Vue 会先做模板解析。

模板解析会先取 el ,获取到根元素,再获取根元素的子元素,如果子元素还有子元素,会进行递归解析,如果子元素不再有子元素,Vue 会进行正则匹配,将插值表达式的数据替换成 data 中的数据,同时这个时候是有这个元素的实例的,会给这个子元素设置事件监听,这个事件监听是用 发布订阅模式实现的,同时这个监听分为两方面,一个是监听这个元素所依赖的数据被改变时发布的事件,监听到之后,通过元素实例修改元素内容;另一个是监听这个元素的 input、change这些事件,当这些事件触发时,Vue 会向外发布事件,通知这个数据被改了。这是模板解析过程。

模板解析后会对data进行解析劫持,首先对 data 进行遍历,如果 data 的子属性还是个 对象,会对其进行递归遍历,如果不是对象,就会将这个对象(这个对象不一定是 data,有可能是data的属性对象)和 key,传给 Object.defineProperty() ,让其去代理这个对象的这个属性,同时给 Object.defineProperty() 传入第三个参数,第三个参数是一个对象,这个对象中要去设置个 get 和 set 方法,当用户获取这个对象的这个属性或者修改时,就会触发 get 和 set 方法,我们需要在 set 方法中去发布事件和监听事件,当这个属性被修改时,我们去发布事件,通知模板解析中元素实例设置数据监听方法,好让页面更新,同时监听元素 input、change 后发布的事件,好使的页面改变的时候,数据同步会 model。

但这只是一个核心的实现思路,在 Vue 的源码中,Vue 是做了相当多的工作的,像对于 Vue 指令的解析,插值表达式中的函数调用,v-bind的解析,以及指令等可能不在最内层元素上的解析,还有像对于数组的劫持,因为 Object.defineProperty() 只能劫持对象,我这块表述的其实只能算是Vue双向绑定的冰山一角。 这段是关键,上面的实现思路其实非常浅,而且还留了非常多的可发问的余地,这段先谦虚,说出自己表述没考虑的地方,表现不足,避免面试官往深的问,同时再隐含的表述自己看过源码。总之很关键


Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

  • 实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
  • 实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
  • 实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
  • 实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

img

你刚才说了 Object.defineProperty() 只能劫持对象,那 Vue 中对于数组是怎么实现劫持的。

我了解到的 Vue 对数组的 push、pop、shift 等会影响数组的方法,进行了重写,当是用 push 等方法时,会调用 Vue 重写的方法,Vue 在这些方法内做了拦截。但如果直接通过索引去修改数组元素,Vue 也拦截不到,如果想要通过索引修改数组元素,好像需要调用 $next 或者 $set 方法,项目中很少使用索引去修改数组,这块记得不是很清楚。

Vue3中对于数据劫持由 Object.defineProperty() 改成了 proxy,它俩有什么区别

  1. Object.defineProperty() 是 ES5 新加的 API,在IE8及以下的浏览器并不支持;proxy 是 ES6 新增的对象,所有版本的IE浏览器都不支持至今
  2. Object.defineProperty() 只能对Object进行代理,且代理的是对象的属性,且只能代理通过字面量设置了代理的对象属性,所以在 Vue2.x 中,对于在 data 中,没有定义的属性,无法进行响应式;proxy 可以代理 Object, Array, Symbol, String 等多种数据格式,对于对象的属性无需显示设置,会代理对象上所有的属性,哪怕是后来添加的。如果对象的属性还是对象,对于深层次的代理,仍需递归设置。

是否了解Vue的mixin?有什么作用?

是否了解Vue的插件机制?如何写一个Vue插件?

首先对于 Vue 的插件我们是通过 Vue.use 方法来进行使用的,那么我们就需要对 Vue.use 有所了解,Vue.use 需要一个参数,这个参数可以是对象也可以是个函数,如果是对象,那么这个对象必须拥有一个 install 方法,Vue.use 会调用这个 install 方法,然后将 Vue 传进去。

开发插件就是将我们所要做的封装起来,或者封装成函数,或者封装成对象,最后这个封装起来的东西挂到 Vue 上,这样在 Vue 就可以使用插件。

挂到 Vue 的方法有多种,可以通过 prototype 挂到 Vue 原型上,也可以通过全局混入的方式挂到之后每个被创建的组件上。像 Vuex 就是通过混入的方式加到所有组件上的。

Vue-router有几种模式?有什么区别?是怎么实现的?

Vue-router 是做前端路由的,它有三种模式,hash 和 history 以及 abstract 模式。

hash 模式没太多可说的,核心原理就是 # 后面的内容修改,不会造成页面刷新,然后监听 hashChange 事件,监听到对于事件后获取路由,做对应渲染。

abstract 模式支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。

关键是 history 模式,hash 模式用 # 毕竟很丑,history 就看起来好多了,但正常来说这种形式的 url 改变,必然会造成浏览器重新去服务器加载请求,也就是说页面会闪,但我们在应用过程中实际并没有发生闪的情况。核心就是 history.pushState() 和 history.replaceState() API,push 会在历史记录顶层在添加一条记录,replace 会替换当前的历史记录,这两个API的共同特点是当调用他们修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会立即发送请求。同时通过这俩API去做url修改,会触发 popstate ,通过监听这个事件,去做对应的视图渲染。

但还有点是:如果直接在浏览器输入地址,浏览器会向服务器发出请求,但服务器根本没有这个地址的文件,所以会报404,这就需要我们对 nginx 做对应配置,如果 url 不存在,就还是返回这个单页面。

router-view 就相当于一个容器,当路由匹配到之后,会将对应的组件填充到 router-view 中

router-link 就是封了个组件,当被点击的时候调用 router 的 API 做跳转

router-view有什么作用?

router-view 就相当于一个容器,当路由匹配到之后,会将对应的组件填充到 router-view 中

页面间如何传参?都有哪些传参方式?

为什么组件中的data需要是函数?

能聊聊 Vuex吗?

首先对于 Vuex 我们需要了解他解决的问题是什么,无论是在 Vuex 中,还是 React 中,我们都需要状态管理。比如一个组件需要使用另一个组件或多个组件的状态,或者一个组件需要改变另一个组件或多个组件的状态,这个时候我们就需要把这些状态独立出来,去共享,使所有的组件都可以使用。这些状态很多时候在代码里是通过数据的改变来实现的,所以也叫数据仓库。

所以我们需要做的就是对这些状态进行合理的管理,在软件开发里,有些通用的思想,比如隔离变化,约定优于配置等,隔离变化就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我们几个人约定,view 文件夹里只能放视图,不能放过滤器,过滤器必须放到 filter 文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文件了,我们要找所有的视图,直接从 view 文件夹里找就行。

根据这些思想,对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测

一个很简单的方法就是将这些状态存到一个外部变量中,或者全局变量中,总之这个变量需要让所有的组件都可以访问到。但这样有一个问题,就是所有组件都可以在自己的地方去对这个数据进行改变,没有集中管理,比较分散;另一个问题是,数据改变后,不会留下变更过的记录,调试不方便,开发也不友好。

所以我们就需要做一些约定,在 Vuex 中,我们约定 state 中存放共享的数据,数据如果要被改变,必须先通过 dispatch 派发改变需求到 action ,让 action 通过 commit 提交到 mutation,由 mutation 去做最后的修改,这样每一个数据的修改,都是通过我们约定好的流程进行的,每一步的改变都有迹可循。

但是到这里有一个很关键的点我们没有说,当 state 中的数据被修改的时候,我们的组件状态也要改变,对于这个我们可以依旧采用数据劫持的思想,在做次拦截,让组件去监听改变的事件。在 Vuex 中,这块依赖了 Vue 中的数据劫持,state 中的数据通过 $store 混入了组件的 state,由 Vue 去做劫持了,所以也使得 Vuex 只能在 Vue 中使用。

Virtual Dom 的优势在哪里?

DOM 引擎、JS 引擎相互独立,但又工作在同一线程(主线程)JS 代码调用 DOM API 后,必须挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后在转换可能有的返回值,最后激活 JS 引擎并继续执行,若有 DOM API 调用,且浏览器厂商不做”批量处理“的优化,引擎间切换的单位代价将迅速累积,若其中有强制重绘的 DOM API 调用,重排重绘会引起更大的性能消耗。

  • Virtual Dom 不会立刻进行重排与重绘的操作。
  • Virtual Dom 进行频繁更改,然后一次性比较并修改真实 DOM 中需要更改的部分,最后在真实 DOM 进行重排重绘,减少过多的 DOM 操作。
  • Virtual Dom 有效降低大面积真实 DOM 的重绘重排,因为最终与真实 DOM 比较差异,可以只渲染局部。

虚拟 Dom 实现原理

这个题的重要性仅次于 Vue 双向绑定原理,建议掌握

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

优点:

  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

  • 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

小程序、Vue、React的共同点和区别?

如何在Vue和小程序中实现登录校验?

小程序的组件间通信都有哪些方式?

小程序中父组件如何影响子组件的样式?

如何在小程序中监听数据变化?

如何在小程序中进行多组件间数据共享?

小程序的性能比Web性能会好一点,为什么?

影Web性能的因素:

  1. 用户在访问网页的时候,在浏览器开始显示之前都会有一个的白屏过程,在移动端,受限于设备性能和网络速度,白屏会更加明显。

  2. 网页开发渲染线程和脚本线程是互斥的,所以长时间的脚本运行可能会导致页面失去响应。

微信小程序做了什么?

  1. Web 资源离线存储:通过使用微信离线存储,Web 开发者可借助微信提供的资源存储能力,直接从微信本地加载 Web 资源而不需要再从服务端拉取,从而减少网页加载时间,为微信用户提供更优质的网页浏览体验。每个公众号下所有 Web App 累计最多可缓存 5M 的资源。

  2. 双线程,在小程序中渲染线程和脚本线程分别运行在不同的线程中。小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。

HTTP

在浏览器输入一个Url到用户看到页面,中间都发生了什么?

DNS是如何进行解析的?

客户端是如何与服务器创建连接的?

HTTP请求方式有哪些?GET和POST有什么区别?什么情况下会进行OPTIONS请求?

常见的HTTP状态码有哪些?

什么是持久连接?什么是管线化?

浏览器拿到文件后是如何进行解析渲染的?

为什么会出现跨域?

都有哪些方法可以解决跨域?是否了解它们的原理?

是否了解HTTP/2?如何开启?

性能优化

如何检测分析前端性能?

前端性能优化的方法有哪些?

  1. CDN
  2. 资源打包压缩:代码压缩、雪碧图
  3. 非核心代码异步加载,给 script 标签添加 defer 和 async 属性
  4. 使用缓存
  5. DNS预解析
  6. Gzip

性能优化请看这篇文章:如何明显的提高前端性能?

前端工程化

如何理解前端工程化?

如何进行webpack性能优化?

两个方面,提高打包速度,减小打包体积

打包速度:

  1. 第三方依赖通过 CDN 的方式挂载,不要通过 npm install

  2. 使用路径别名,避免过多的使用相对路径(路径别名相当于绝对路径,如果使用相对路径,webpack 打包必须根据相对路径一层一层去查找文件)

  3. 导入的模块文件添加后缀,不带的后缀的,通过webpack配置文件类型查找的优先级,常用的放前面,比如 .vue .js .css 配置在前面

  4. 配置不需要被解析的模块,例如 node_modules 就不需要被打包编译

  5. 借助 webpack.DllPlugin 插件进行预编译

  6. 使用插件开启多线程打包和开启 babel 的缓存

减小体积:

  1. CDN
  2. 代码压缩
  3. 静态资源放 OSS,小图标可以使用iconfont

详细内容请看这篇文章:webpack性能优化

你做过那些工程化的实践?

是否了解git-flow流程?

git-flow 是一套git管理流程,他的形式有多种,比较经典的模型是:

  1. Master分支,这个分支负责发布生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改;这个分支的每一次 commit 都应该打 tag
  2. Develop 分支,这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支
  3. Feature 分支,这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
  4. Release分支,当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支
  5. Hotfix分支,当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release

你对devOps是怎么理解的

DevOps 一词的来自于 Development 和 Operations 的组合,突出重视软件开发人员和运维人员的沟通合作,通过自动化流程来使得软件构建、测试、发布更加快捷、频繁和可靠。DevOps 其实包含了三个部分:开发、测试和运维。换句话 DevOps 希望做到的是软件产品交付过程中IT工具链的打通,使得各个团队减少时间损耗,更加高效地协同工作。

它由一系列的工具组成,像项目管理工具,我们用过明道、蝉道,jria,目前用的是teambition,代码管理用的是公司自己搭的 gitlab,使用 Docker 进行容器管理,以及使用 jenkins 进行自动化部署,当然这条链上的工具很多,但更多的是由运维在打交道。

缺少自动化测试,想办法编

img

代码质量/Web安全

团队协作如何规范代码风格?

如何控制代码质量?

常见的Web安全问题有哪些?

如何进行防范?

Node

如何理解Koa的洋葱圈模型?

如何编写Koa的中间件?

Egg和Koa有什么区别?

管理能力

对于一个大型项目你是考虑设计的

你在项目开发中遇到过那些问题,你是如何解决的

你是如何带团队的?你们团队的工作流程是怎样的?有没有遇到过哪些问题?你是如何处理的?

遇到问题,解决问题,考察的是交流沟通能力

工作过程中是否会与其他部门打交道,你们是如何配合的?

思路:各部门职责 —-> 工作流程 —-> 出现问题 —-> 如何沟通 —-> 结果

你离职的原因是什么

不要说上家公司不好的话,可以说遇到瓶颈,缺少挑战,不想躺在舒适圈,想要进行自我提升等