• 对自己做过的事要有足够的了解和思考,特别是简历上提到的技术关键词,务必做到了然于心,做到熟悉源码的程度

  • 对于常用的api不能仅限于使用层面上,分析源码是必须的

  • 算法目标还是刷到100道,手写代码这块每天逼自己多写多练

  • 心态方面吧,及时调整和沟通,多跟大佬们交流、学习

  • vue-router跟vuex怎么混入到vue实例中的

  • vue-directive指令的声明周期

  • requestAnimationFrame是怎么保证在宏任务里的执行顺序

  • 304跟200的区别(304从缓存系统中返回数据,速度更快,如果没有名字协商缓存,需要从源服务器返回,响应慢)

  • ts中的高级用法源码是如何实现的,比如Picker

    1
    2
    3
    type Picker<T, K extends keyof T> = {
    [P in K]: T[P]
    }

    需要刻意练习,加深理解

四场面试做个总结以及需要补充的点

  • axios封装和分析源码(精简)

  • 内存管理GC,新生代和老生代

  • 异步编程方案,横向比较Promise/async/Generator

  • 常用的loader和plugin源码分析

  • vue-router和vuex源码实现(精简)

  • 项目部署相关

JS 内存机制:栈(基本类型、引用类型地址)与堆(引用类型数据)

JavaScript 中的内存空间主要分为三种类型:

  • 代码空间:主要用来存放可执行代码,与内存回收关系不大
  • 栈空间:调用栈的存储空间就是栈空间,调用栈,存储执行上下的
  • 堆空间: 存储执行上下文之外的数据类型

JavaScript 中的变量类型有 8 种,可分为两种:基本类型、引用类型

1
2
3
4
5
6
7
number
string
undefined
null
symbol
bigInt
boolean

引用类型主要是 object

基本类型是保存在栈内存中的简单数据段,而引用类型保存在堆内存

栈空间

基本类型在内存中占有固定大小的空间,所以它们的值保存在栈空间,我们通过 按值访问
一般栈空间不会很大

堆空间

值大小不固定,指针大小(内存地址)是固定的
当查询引用类型的变量时, 先从栈中读取内存地址,然后再通过内存地址找到堆中的值。对于这种,我们把它叫做 按引用访问
一般堆内存空间很大,能存放大量数据,所以内存分配和回收都需要一定的时间

基本类型(栈空间)与引用类型(堆空间)的存储方式决定了:基本类型赋值是值赋值,而引用类型赋值是地址赋值

栈内存垃圾回收

栈内存中的垃圾回收其实就是销毁执行栈中的执行上下文,我们都知道执行栈中存放的就是函数执行过程中的执行上下文,栈顶就是我们正在执行函数的执行上下文。
当我们的函数执行完毕后,执行栈中对应的执行上下文会被销毁,这也就是栈垃圾回收的过程。
为了了解这一过程,我们举个简单的例子:

主线程上会存在 ESP 指针 ESP 是执行栈中用来记录当前执行状态的指针

综上所述,JS 引擎是通过 ESP 指针的下移操作来完成栈内存中的垃圾回收的

堆内存垃圾回收

从上面的介绍我们知道,堆内存中主要存放的是复杂数据类型(引用类型)和闭包内部函数引用的基本类型的数据。那么堆内存又是怎么进行垃圾回收的,下面我们来看一下。
V8 中把堆分成新生代与老生代两个区域:

新生代:用来存放生存周期较短的小对象,一般只支持1~8M的容量
老生代:用来存放生存周期较长的对象或大对象
V8 对这两块使用了不同的回收器:
新生代使用副垃圾回收器
老生代使用主垃圾回收器

其实无论哪种垃圾回收器,都采用了同样的流程(三步走):

  • 标记: 标记堆空间中的活动对象(正在使用)与非活动对象(可回收)
  • 垃圾清理: 回收非活动对象所占用的内存空间
  • 内存整理: 当进行频繁的垃圾回收时,内存中可能存在大量不连续的内存碎片,当需要分配一个需要占用较大连续内存空间的对象时,可能存在内存不足的现象,所以,这时就需要整理这些内存碎片。

新生代回收也就是副垃圾回收器

它采用 Scavenge 算法及对象晋升策略来进行垃圾回收

所谓 Scavenge 算法,即把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域,如下图所示:

新加入的对象都加入对象区域,当对象区满的时候,就执行一次垃圾回收,执行流程如下:

标记:首先要对区域内的对象进行标记(活动对象、非活动对象)
垃圾清理:然后进行垃圾清理:将对象区的活动对象复制到空闲区域,并进行有序的排列,当复制完成后,对象区域与空闲区域进行翻转,空闲区域晋升为对象区域,对象区域为空闲区域
新生代区域很小的,一般1~8M的容量,所以它很容易满,所以,JavaScript 引擎采用对象晋升策略来处理,即只要对象经过两次垃圾回收之后依然继续存活,就会被晋升到老生代区域中。

老生代回收策略(主垃圾回收器)

老生代区域里除了存在从新生代晋升来的存活时间久的对象,当遇到大对象时,大对象也会直接分配到老生代。
所以主垃圾回收器主要保存存活久的或占用空间大的对象,此时采用 Scavenge 算法就不合适了。V8 中主垃圾回收器主要采用
标记-清除法 进行垃圾回收。

主要流程如下:
标记:遍历调用栈,看老生代区域堆中的对象是否被引用,被引用的对象标记为活动对象,没有被引用的对象(待清理)标记为垃圾数据。
垃圾清理:将所有垃圾数据清理掉
内存整理:标记-整理策略,将活动对象整理到一起

  • 增量标记

V8 浏览器会自动执行垃圾回收,但由于 JavaScript 也是运行在主线程上的,一旦执行垃圾回收,就要打断 JavaScript 的运行,可能会或多或少的造成页面的卡顿,影响用户体验,所以 V8 决定采用增量标记算法回收:
即把垃圾回收拆成一个个小任务,穿插在 JavaScript 中执行。

vue-router跟vuex怎么混入到vue实例中的

Vuex的双向绑定通过调用 new Vue实现,然后通过 Vue.mixin 注入到Vue组件的生命周期中,再通过劫持state.get将数据放入组件中

Vuex仅仅是Vue的一个插件。Vuex只能使用在vue上,因为其高度依赖于Vue的双向绑定和插件系统。
Vuex的注入代码比较简单,调用了一下applyMixin方法,现在的版本其实就是调用了Vue.mixin,在所有组件的 beforeCreate生命周期注入了设置 this.$store这样一个对象

划重点:1行代码 Vue.mixin

划重点:重点就是一行代码resetStoreVM(this, state)

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
// src/store.js
constructor (options = {}) {
const {
plugins = [],
strict = false
} = options
// store internal state
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
const store = this
const { dispatch, commit } = this
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
resetStoreVM(this, state)
// apply plugins
plugins.forEach(plugin => plugin(this))
}

new Vue。通过 Vue自己的响应式系统注入

resetStoreVM是什么呢

1
2
3
4
5
6
7
8
function resetStoreVM (store, state, hot) {
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
}

当你再Vue中通过 this 如果调用 store的数据呢?

1
2
3
4
5
6
7
8
9
10
11
12
// 当获取state时,返回以双向绑定的$$sate
var prototypeAccessors$1 = { state: { configurable: true } };
prototypeAccessors$1.state.get = function () {
return this._vm._data.$$state
};
prototypeAccessors$1.state.set = function (v) {
if ((process.env.NODE_ENV !== 'production')) {
assert(false, "use store.replaceState() to explicit replace store state.");
}
};
// 将state定义在原型中
Object.defineProperties( Store.prototype, prototypeAccessors$1 );

最终是通过this._vm._data.$$state来获取的

  • vuex
    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
    function applyMixin (Vue) {
    var version = Number(Vue.version.split('.')[0]);
    if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit });
    } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    var _init = Vue.prototype._init;
    Vue.prototype._init = function (options) {
    if ( options === void 0 ) options = {};
    options.init = options.init
    ? [vuexInit].concat(options.init)
    : vuexInit;
    _init.call(this, options);
    };
    }
    /**
    * Vuex init hook, injected into each instances init hooks list.
    */
    function vuexInit () {
    var options = this.$options;
    // store injection
    if (options.store) {
    this.$store = typeof options.store === 'function'
    ? options.store()
    : options.store;
    } else if (options.parent && options.parent.$store) {
    // Vue 实例才会具有 $store 属性,组件是没有的
    console.log('mixin', options)
    this.$store = options.parent.$store;
    }
    }
    }
  • vue-router
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    let _Vue = null;
    export default class VueRouter {
    static install(Vue) {
    // 1. 判断当前插件是否已安装
    if (VueRouter.install.installed) {
    return;
    }
    VueRouter.install.installed = true;
    // 2. 把 Vue 构造函数存储到全局变量中
    _Vue = Vue;
    // 3. 把创建 Vue 实例时传入的 router 对象注入到所有 Vue 实例上
    // 混入
    _Vue.mixin({
    beforeCreate() {
    if (this.$options.router) {
    _Vue.prototype.$router = this.$options.router;
    }
    },
    });
    }
    }

参考文档