# part3-module2 **Repository Path**: ren_wei_1/part3-module2 ## Basic Information - **Project Name**: part3-module2 - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-09-13 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # part3-module2 #### 1、请简述 Vue 首次渲染的过程。 ###### 1. Vue初始化过程 1. 首先取出Vue的`$mount`,对`$mount`进行重写,给`$mount`增加新的功能 ```javascript // src/platforms/web/entry-runtime-with-compiler // 带有compiler的版本 // 取出vue中的$mount函数 用于后面调用 const mount = Vue.prototype.$mount // 重写Vue原型上的$mount函数 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) .... } ``` 2. 然后在这个函数内部,我们会判断里面是否传入了`render`选项,如果没有传入`render`选项,就把模板`template`取出来,把`template`编译成为`render`函数,接下来并调用`mount`来进行渲染 ```javascript const options = this.$options // resolve template/el and convert to render function // 判断传入的是否有render选项 if (!options.render) { let template = options.template ...... if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } // 把template通过compileToFunctions 编译成为reder函数 const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } // 调用前面取出来的$mount函数 return mount.call(this, el, hydrating) ``` ###### 2. 初始化静态成员 1. 在`src/core/index.js`中调用了`initGlobalAPI(Vue)`注册了全局的api,在这个方法里面里面主要做了这些事情 - 在`Vue`上面初始化了`config`属性 - 初始化了`set` `delete` `nextTick` 等静态方法 - 注册了各种函数 ```javascript // src/core/global-api/index.js export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } // 初始化config属性 Object.defineProperty(Vue, 'config', configDef) // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. // 注册一些util函数 Vue.util = { warn, extend, mergeOptions, defineReactive } // 注册一些静态方法 Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 2.6 explicit observable API // 让数据变为响应式 Vue.observable = (obj: T): T => { observe(obj) return obj } Vue.options = Object.create(null) // 在Vue.options上面增加三个空对象 ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue extend(Vue.options.components, builtInComponents) // 注册Vue.use() initUse(Vue) // 注册mixin initMixin(Vue) // 注册Vue.extend initExtend(Vue) // 注册Vue.directive(), Vue.component(), Vue.filter initAssetRegisters(Vue) } ``` ###### 3. 初始化实例成员 1. 首先我们定位到`src/core/instance/index.js` ```javascript function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } // 注册vue的_init方法 初始化vue initMixin(Vue) // 注册vue中的各种属性和方法 stateMixin(Vue) // 注册vue中的事件 eventsMixin(Vue) // 注册vue中的生命周期函数 lifecycleMixin(Vue) // 注册vue中的render和nextTick renderMixin(Vue) export default Vue ``` 2. 接下来,我们先看一下`initMixin`中执行了什么 ```javascript // expose real self vm._self = vm // 初始化了生命周期相关的变量 initLifecycle(vm) // vue事件监听的初始化 并把父组件的事件绑定到当前组件上 initEvents(vm) // vue上面render的初始化 initRender(vm) // 调用beforeCreate的所有hooks callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props // 初始化Vue中的state 如props,methods,data等 initState(vm) // 初始化provide initProvide(vm) // resolve provide after data/props // 调用created的所有hooks callHook(vm, 'created') // 调用$mount来挂载整个页面 if (vm.$options.el) { vm.$mount(vm.$options.el) } ``` 3. 由于调用了`$mount`函数,下面进入`src/platform/web/runtime/index.js`,从这里面,我们可以看到`$mount`函数 ```javascript // public mount method // 注册了$mount函数 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } ``` 4. `$mount`函数主要是执行了`mountComponent`,所以我们进入这个函数看看 ```javascript export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { // 如果没有render就创建一个空的node并赋值给render vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ .... } } // 调用beforeMount的hooks callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { ... } else { updateComponent = () => { // vm._render() 是生成虚拟dom // vm._update() 是把虚拟dom渲染到真实dom上 vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true // 调用挂载完成的hooks callHook(vm, 'mounted') } return vm } ``` ###### 4. Vue首次渲染 - 在渲染之前,首先对Vue进行初始化 - 初始化的时候会调用`new Vue()`,由于在Vue的构造函数中会调用`this._init()`,所以就调用了`_init`函数 - 在`_init`函数中,我们也初始化了一些内容,并调用了一些hooks,最后,我们返回了`$mount`函数 - 接下来,我们进入`src/platform/web/runtime/index.js`查看`$mount`函数,然后我们发现`$mount`函数内部调用了`mountComponent`,于是我们进入`core/instance/lifecycle`查看该函数,在这个函数内部,我们会先判断当前是否有`render`,没有的话就会创建一个空的`Vnode`,然后会调用`beforeMount`的hooks来执行 - 接下来是定义了`updateComponent`函数,里面主要是`vm._update(vm._render(), hydrating)`,其中`vm._render`主要是用来生成虚拟dom,`vm_update`是把虚拟dom渲染到真实dom上 - 下面就是创建了`watcher`对象,并传递了`updateComponent`进去,同时也在里面申明了`beforeUpdate`hoos的调用时机 - 最后就是调用`mounted`的hooks并返回vue对象 #### 2、请简述 Vue 响应式原理。 - 在Vue初始化的时候调用了`initState`函数,并且在这个函数里面初始化数据的调用了`observe(data, true /* asRootData */)`来让数据变成了响应式的 - 所以下面,我们就要看observer类 - 所以我们先进入`src/core/observer/index.js`,首先,我们看构造函数 ```javascript constructor (value: any) { // 传入的数据 this.value = value // 依赖对象 this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } // 遍历数组 给数组中的每一个元素都调用observer来生成响应式的数据 this.observeArray(value) } else { // 遍历value中的所有属性,使之都变为响应式的数据 里面调用了 this.walk(value) } } ``` - 下面,我们看一下`this.walk`函数,在这个函数里面,我们遍历了object的所有key并调用了`defineReactive`来生成响应式的数据 ```javascript walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } ``` - 下面就看看在`defineReactive`中执行了什么,核心代码就是下面这一部分 ```javascript Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val // 如果存在Dep.target if (Dep.target) { // 添加进依赖 dep.depend() // 如果有子对象 if (childOb) { // 给子对象也添加依赖 childOb.dep.depend() // 判断是否是数组 if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { // 设置值 setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) // 当值改变的时候对所有的依赖进行通知 dep.notify() } }) ``` - 接下来,我们看一下Deps这个类,这个类其实很简单,主要就是用来存储依赖,同时提供一个notify方法,来让数据在改变的时候调用进行通知 ```javascript constructor () { this.id = uid++ // 依赖数组 this.subs = [] } // 添加到依赖数组中 addSub (sub: Watcher) { this.subs.push(sub) } // 从依赖数组中删除 removeSub (sub: Watcher) { remove(this.subs, sub) } // 添加依赖 depend () { if (Dep.target) { Dep.target.addDep(this) } } // 调用来进行通知 notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } ``` - 下面,我们到watcher类中看一下,在这个类里面,我们主要是要提供一个update方法来进行更新,同时在watcher的构造函数中,我们要调用一个get方法来触发更新一下dep #### 3、请简述虚拟 DOM 中 Key 的作用和好处。 - 可以提升diff算法的性能(可以对比出当前的节点是不是同一个,key值不同的肯定不是同一个节点) - 如果不设置key,可能会导致列表出现一些bug #### 4、请简述 Vue 中模板编译的过程。 - 调用`$mount`函数,判断当前是否传入了render,如果没有的话,判断当前的template是否为空,如果为空的话,获取el节点的outerHtml来做为template - 调用`compileToFunctions`来把template转换成为render - 在`compileToFunctions`中调用了`baseCompile`来进行转换 - 调用`parse`函数来编译转换为ast抽象语法树 - 调用`optimize`来对静态节点进行标记,主要是用作渲染优化 - 调用`generate`来把ast抽象语法树转换为render函数 - 调用`createFunction`来把render函数转换成为一个可以执行的函数 - 把render挂载到options上面 - 执行mount函数