# 自我介绍

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

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

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

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

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

# 基础HTML/CSS

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

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

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

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

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

# float布局

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

<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

# 定位

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

# flex

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

# Table

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

# grid

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 优缺点

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

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

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

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

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

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

# absolute + 负margin

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# absolute + calc

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# absolute + margin auto

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# absolute + transform

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# table

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# flex

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# grid

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 优缺点

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

  • 模板字符串

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

# 任意值转Boolean

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

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

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

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

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

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

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

# 如何创建对象?

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

  • 构造函数

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

# new 操作符做了什么?

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

# 如何实现继承?

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

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

  3. 组合方式

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

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

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

# apply和call有什么区别?

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

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

  1. apply

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

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

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

区别,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 严格模式

"use strict";
1

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

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

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

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

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

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

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

    "use strict";
    var x;
    delete x; // 语法错误
    
    var o = Object.create(null, {'x': {
      value: 1,
      configurable: true
    }});
    delete o.x; // 删除成功
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 严格模式下对于只读属性赋值将会报错;非严格模式静默失败

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

# 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的优点

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

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

ES6 之前 NaN 不等于 NaN

没有命名空间

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

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

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

TS的特点

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

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

类型系统

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

# 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的双向绑定是如何实现的?

 
解法1 首先双向绑定指的是 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双向绑定的冰山一角。 这段是关键,上面的实现思路其实非常浅,而且还留了非常多的可发问的余地,这段先谦虚,说出自己表述没考虑的地方,表现不足,避免面试官往深的问,同时再隐含的表述自己看过源码。总之很关键


解法2 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有什么区别?

# 管理能力

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

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

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

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

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

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

# 你离职的原因是什么

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

Last Updated: 2020-4-27 22:56:04