一、Vue的初始化
Vue实质上是一个裸露在全局的名为Vue的函数,在应用的时候通过new这个Vue函数来创立一个Vue实例,并且会传入一个配置对象。
Vue函数内须要做的事件就是依据传入的配置对象进行初始化。如:
// src/index.jsfunction Vue(options) { this._init(options);}
这里通过this调用了_init()办法,这个this就是创立的Vue实例对象,然而目前Vue实例上并没有这个_init()办法,所以咱们须要给Vue的prototype上增加一个_init()办法。为了不便模块治理,咱们须要专门建一个独自的init.js用于做初始化的工作。init.js中须要裸露一个initMix()办法,该办法接管Vue以便在Vue的prototype上增加原型办法,如:
// src/init.jsexport function initMixin(Vue) { Vue.prototype._init = function(options) { // 这里进行Vue的初始化工作 }}
// src/index.jsimport {initMixin} from "./init";function Vue(options) { this._init(options);}initMixin(Vue); // 传入Vue以便在其prototype上增加_init()办法
此时_init()办法就能拿到用户传入的options配置对象,而后开始进行初始化工作:
① 将options对象挂载到Vue实例的\$options属性上;
② 初始化状态数据;
③ 判断用户有没有传el属性,如果传了则被动进行调用\$mount()办法进行挂载,如果没有传,那么须要用户本人调用\$mount()办法进行挂载。
// src/init.jsexport function initMixin(Vue) { Vue.prototype._init = function(options) { const vm = this; vm.$options = options; // 将options挂载到Vue实例的$options属性上 // beforeCreate 这里执行Vue的beforeCreate生命周期 initState(vm); // 进行状态的初始化 // created 这里执行Vue的created生命周期 if (options.el) { // 如果配置了el对象,那么就要进行mount vm.$mount(options.el); // 被动调用$mount()进行挂载 } } Vue.prototype.$mount = function(el) { // 这里进行挂载操作 }}
二、Vue状态数据的初始化
接下来就是进行状态的初始化,即实现initState()办法,状态的初始化是一个独立简单的过程,咱们须要将其独自放到一个state.js中进行,次要就是依据options中配置的属性进行特定的初始化操作,如:
export function initState(vm) { const options = vm.$options; if (options.data) { // 如果配置了data属性 initData(vm); } if (options.computed) { // 如果配置了计算属性 initComputed(vm); } if (options.watch) { // 如果配置了用户的watch initWatch(vm); }}function initData(vm) { // 这里进行data属性的初始化}function initComputed(vm) { // 这里进行computed计算属性的初始化}function initWatch(vm) { // 这里进行用户watch的初始化}
三、将data数据变成响应式的
data属性的初始化是Vue响应式零碎的外围,即对data对象中的每一个属性进行观测监控。用户传入的data可能是一个对象也可能是一个返回对象的函数。所以须要对data的类型进行判断,如果是函数,那么传入Vue实例并执行这个函数拿到返回的对象作为用于观测的data。同时为了不便Vue实例操作data中的数据,还须要将data中的属性一一定义到Vue实例上,如:
// src/state.js 实现initData()办法import {proxy} from "./utils/index";import {observe} from "./observer/index";function initData(vm) { let data = vm.$options.data; // 可能是一个函数 // 给Vue实例增加一个_data属性和$data属性保留用户的data data = vm._data = vm.$data = typeof data === "function" ? data.call(vm) : data; for (let key in data) { // 遍历data的所有属性 proxy(vm, "_data", key); // 将data中的属性代理到Vue实例上,不便操作data } observe(data); // 对数据进行察看}
下面用到了一个proxy工具办法,用于将data中的属性代理到Vue实例上,其外部次要就是通过Object.defineProperty()办法,将data中的属性代理到Vue实例上,如:
// src/utils/index.jsexport function proxy(vm, source, key) { Object.defineProperty(vm, key, { get() { return vm[source][key]; }, set(newVal) { vm[source][key] = newVal; } });}
这里的vm[source]就是vm._data对象也就是用户传入的data,这样当用户通过Vue实例去操作数据的时候,实际上操作的就是用户传入的data对象。
接着就是对整个data数据进行观测了,进行数据观测的时候,这个数据必须是对象或者数组,否则不进行观测,如果这个对象中某个key的属性值也未对象,那么也须要对其进行观测,所以这里会存在一个递归操作,这也是影响Vue性能的重要起因。数据观测也是一个独立简单的过程,须要对其独自治理,如:
// src/observer/index.jsimport {isObject} from "../utils/index";export function observe(data) { if (!isObject(data)) { // 仅察看对象和数组 return; } // 如果要观测的数据是一个对象或者数组,那么给其创立一个Observer对象 return new Observer(data);}
// src/utils/index.jsexport function isObject(data) { return data && typeof data === "object";}
接下来Vue会给合乎对象或者数组的data进行观测,给其创立一个Observer对象,观测的时候,对象和数组的解决会有所不同,对于对象而言,遍历对象中的每个属性并将其定义成响应式即可;对于数组而言,因为数组可能存在十分多项,为了防止性能影响,不是将数组的所有索引定义成响应式的,而是对数组中属于对象或者数组的元素进行观测。
// src/observer/index.jsclass Observer { constructor(data) { this.data = data; def(data, "__ob__", this); // 给每个被察看的对象增加一个__ob__属性,如果是数组,那么这个数组也会有一个__ob__属性 if (Array.isArray(data)) { // 对数组进行观测 data.__proto__ = arrayMethods; // 重写数组办法 this.observeArray(data); // 遍历数组中的每一项值进行观测 } else { this.walk(data); // 对对象进行观测 } } walk(data) { for (let key in data) { // 遍历对象中的所有key,并定义成响应式的数据 defineReactive(data, key, data[key]); } } observeArray(arr) { arr && arr.forEach((item) => { observe(item); // 对数组中的每一项进行观测 } }}function defineReactive(data, key, value) { let ob = observe(value); // 对传入的对象的属性值进行递归观测 Object.defineProperty(data, key, { get() { return value; }, set(newVal) { if (newVal === value) { return; } observe(newVal); // 如果用户批改了值,那么也要对用户传入的新值观测一下,因为可能传入的是一个对象或者数组,对新值批改的时候能力检测到 value = newVal; } })}
对数组的观测,次要就是要从新那些会扭转原数组的办法,如: push、pop、shift、unshift、sort、reverse、splice,以便数组发生变化后可能给观察者发送告诉,并且push、unshift、splice会给数组新增元素,咱们还须要晓得新增的是什么数据,须要对这些新增的数据进行观测。
const arrayProto = Array.prototype; // 获取数组的原型对象export const arrayMethods = Object.create(arrayProto); // 依据数组的原型对象创立一个新的原型对象,防止办法有限循环执行const methods = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse"];methods.forEach((method) => { arrayMethods[method] = function(...args) { const result = arrayProto[method].apply(this, args); // 执行数组的上本来的办法 const ob = this.__ob__; let inserted; // 用于记录用户给数组插入的新元素 switch(method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2);// 对应splice第三个参数才是用户要插入的数据 break; default: console.log("拦挡的办法不存在"); } if (inserted) { // 数组办法内,惟一能拿到的就是数组这个数据,所以咱们须要给察看的数组对象增加一个key,值为Observer对象,能力拿到Observer对象上的办法 ob.observeArray(inserted); // 对插入的新元素进行观测 } return result; }});