# fed-e-task-03-02 **Repository Path**: mxydl2009/fed-e-task-03-02 ## Basic Information - **Project Name**: fed-e-task-03-02 - **Description**: Vue.js源码分析作业,有关响应式、模板编译、组件化 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-07-31 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fed-e-task-03-02 ## 介绍 Vue.js源码分析作业,有关响应式、模板编译、组件化 ## 简答题 ### 1. 请简述Vue首次渲染的过程 Vue的首次渲染包括以下过程: - Vue构造函数的初始化 - Vue实例的初始化 - Vue实例的模板编译 - 生成虚拟DOM转换真实DOM挂载到DOM树 #### 1. Vue构造函数的初始化 - Vue构造函数原型成员的初始化:主要包括如下 - initMixin(Vue):给Vue的原型注册`_init()`方法 - stateMixin(Vue):给Vue原型注册成员`$data、$props、$set、$delete、$watch` - eventsMixin(Vue):初始化事件相关方法`$on、$once、$off、$emit` - lifecycleMixin(Vue):初始化生命周期相关的混入方法`_update`(内部调用了`__patch__`方法来渲染或更新DOM)、`$forceUpdate`、 `$destroy` - renderMixin(Vue):给Vue原型注册`$nextTick`、`_render`方法和渲染辅助方法`installRenderHelpers(Vue.prototype)` - 同时,不同平台下的`Vue.prototype.$mount`不同,`Vue`进行了重写,这样`Vue`的实例可以在不同平台下安全调用`$mount`时 - Vue构造函数静态成员的初始化,通过initGlobalAPI函数实现,主要包括如下: - 为Vue挂载一个config属性,指向全局配置信息,config见src/core/config.js - 为Vue挂载了Vue.set、Vue.delete、Vue.nextTick三个方法 - 为Vue挂载了Vue.observable方法 - 为Vue挂载了options属性 - options内定义了components、directives和filters三个属性,分别用来存储全局注册的组件、指令和过滤器 - options_base存储了Vue构造函数,内部使用 - options.components初始化了内置组件keep-alive - 初始化了Vue.use方法initUse(Vue),插件注册src/core/global-api/use.js - 初始化了Vue.mixin方法initMixin(Vue),全局混入src/core/global-api/mixin.js - 初始化了Vue.extend方法initExtend(Vue),返回Vue构造函数的子类src/core/global-api/extend.js - 初始化了Vue.directIve方法、Vue.component方法和Vue.filter方法initAssetRegisters(Vue),全局注册指令、组件和过滤器。src/core/global-api/assets.js - 同时,为不同平台下挂载了Vue.compile静态成员,编译模板 #### 2. Vue实例的初始化 在调用`new Vue()`时,内部调用了`this._init()`来进行实例的初始化: - initLifecycle(vm): vm生命周期相关变量初始化`$children、$parent、$root、$refs` - initEvents(vm): vm 事件监听初始化,父组件绑定在当前组件上的事件 - initRender(vm): vm 的编译render初始化, 包括`$slot、$scopedSlots、_c、$createElement、$attrs、$listeners` - callHook(vm, 'beforeCreate'): 触发beforeCreate钩子函数 - initInjections(vm): 把inject的成员注入到vm上,initInjections(vm)和initProvide(vm)是一对方法,用来实现依赖注入 - initState(vm): 初始化vm 的`_props、methods、_data、computed、watch`成员 - initProvide(vm) // resolve provide after data/props - callHook(vm, 'created'): 触发created钩子函数 最后调用`vm.$mount(vm.$options.el)`,开始编译和挂载过程。 #### 3. Vue实例的模板编译 web平台下挂载了`Vue.compile = compileToFunctions`。模板编译的入口函数为compileToFunctions,\$mount方法内部调用`const { render, staticRenderFns } = compileToFunctions(template, options)`,将模板编译为render函数 #### 4. 虚拟DOM的生成与挂载到真实DOM树 在模板编译为render函数后,调用原始的mount方法(未被重写,与平台无关)开始生成虚拟DOM并挂载到DOM树。 - render函数使用createElement方法创建虚拟DOM - 调用`vm._update()`函数,内部又调用`vm.__patch__()`将虚拟DOM挂载到真实DOM树 ### 2. 请简述Vue响应式原理 - 观察者模式的建立:Vue实例初始化时,会为传入的data对象调用observe方法建立观察者模式 - observe方法会对data对象创建一个Observer对象,称为观察者 - Observer对象在初始化时,会记录当前受观察的data对象,并创建一个依赖收集对象Dep。然后调用walk方法遍历对象成员,使用defineReactive方法将成员转换为getter/setter访问器属性成员,并且也会为对象成员创建Dep对象(为该成员收集依赖)。在defineReactive函数中,还会对data对象的成员进行深度遍历,调用observe建立观察者模式。 - 依赖的收集:在getter函数中,会为当前属性值收集依赖,将watcher实例添加到该成员对应的Dep对象中 - 通知的发送:在setter函数中,在属性值更新的情况下,该属性对应的Dep会调用notify方法来通知watcher,从而驱动watcher去更新视图 - 针对数组对象的补充:Vue并没有对数组对象的索引调用defineReactive来生成getter/setter。从而在数组中使用索引更新数据时,并不会驱动视图的更新。因此,Vue重写了原生数组中会更改原数组的方法,在这些方法更新数组后,数组对应的dep对象调用notify方法来驱动视图的更新。 - 重写的数组方法包括如下:'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ### 3. 请简述虚拟DOM中key的作用和好处 虚拟DOM中使用diff算法来比较两颗树的差异,diff算法核心在于同层次节点比较。 在没有key的情况下,需要依次遍历老节点集合,寻找与新节点相同的节点。 在有key的情况下,会根据老节点集合,建立一个key与索引的映射表。从映射表中根据新节点的key来查找老节点索引是否存在,即省略了遍历老节点集合的步骤,节省了比较时间,提高了效率。 ### 4. 请简述Vue中模板编译的过程 模板编译的过程包括如下 #### 1. 生成模板编译入口函数compileToFunctions 调用`createCompilerCreator(function baseCompile (template: string, options: CompilerOptions) {}` -> 调用`createCompiler(baseOptions)` -> 调用`createCompileToFunctionFn(compile)` -> `compileToFunctions` #### 2. compileToFunctions函数接收template,并返回render函数 - 先调用compile函数,将template编译为字符串形式的代码 - 调用createFunction函数,将字符串形式的代码编译为函数形式 #### 3. compile函数 compile函数内部先将编译的选项对象options与基本选项对象baseOptions合并为finalOptions,然后传入并调用了编译的核心baseCompile函数 #### 4. baseCompile函数 - 模板转换为抽象语法树`ast = parse(template.trim(), options)` - 优化抽象语法树`optimize(ast, options)` - 将抽象语法树转换为字符串形式的JavaScript代码`code = generate(ast, options)` #### 5. createCompileToFunctionFn(compile) 调用createFunction将字符串形式的code转换为函数`new Function(code)` 流程总结:模板字符串 -> AST -> 优化(标记静态节点) -> 生成字符串形式代码 -> new Function(code)匿名函数