Vue本質上是一個暴露在全局的名爲Vue的函數,在使用的時候經過new這個Vue函數來建立一個Vue實例,而且會傳入一個配置對象。
Vue函數內須要作的事情就是根據傳入的配置對象進行初始化。如:數組
// src/index.js function Vue(options) { this._init(options); }
這裏經過this調用了_init()方法,這個this就是建立的Vue實例對象,可是目前Vue實例上並無這個_init()方法,因此咱們須要給Vue的prototype上添加一個_init()方法。爲了方便模塊管理,咱們須要專門建一個單獨的init.js用於作初始化的工做。init.js中須要暴露一個initMix()方法,該方法接收Vue以便在Vue的prototype上添加原型方法,如:app
// src/init.js export function initMixin(Vue) { Vue.prototype._init = function(options) { // 這裏進行Vue的初始化工做 } }
// src/index.js import {initMixin} from "./init"; function Vue(options) { this._init(options); } initMixin(Vue); // 傳入Vue以便在其prototype上添加_init()方法
此時_init()方法就能拿到用戶傳入的options配置對象,而後開始進行初始化工做:
① 將options對象掛載到Vue實例的\$options屬性上;
② 初始化狀態數據;
③ 判斷用戶有沒有傳el屬性,若是傳了則主動進行調用\$mount()方法進行掛載,若是沒有傳,那麼須要用戶本身調用\$mount()方法進行掛載。函數
// src/init.js export 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) { // 這裏進行掛載操做 } }
接下來就是進行狀態的初始化,即實現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屬性的初始化是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實例上,如:this
// src/utils/index.js export 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對象。prototype
接着就是對整個data數據進行觀測了,進行數據觀測的時候,這個數據必須是對象或者數組,不然不進行觀測,若是這個對象中某個key的屬性值也未對象,那麼也須要對其進行觀測,因此這裏會存在一個遞歸操做,這也是影響Vue性能的重要緣由。數據觀測也是一個獨立複雜的過程,須要對其單獨管理,如:代理
// src/observer/index.js import {isObject} from "../utils/index"; export function observe(data) { if (!isObject(data)) { // 僅觀察對象和數組 return; } // 若是要觀測的數據是一個對象或者數組,那麼給其建立一個Observer對象 return new Observer(data); }
// src/utils/index.js export function isObject(data) { return data && typeof data === "object"; }
接下來Vue會給符合對象或者數組的data進行觀測,給其建立一個Observer對象,觀測的時候,對象和數組的處理會有所不一樣,對於對象而言,遍歷對象中的每一個屬性並將其定義成響應式便可;對於數組而言,因爲數組可能存在很是多項,爲了不性能影響,不是將數組的全部索引定義成響應式的,而是對數組中屬於對象或者數組的元素進行觀測。code
// src/observer/index.js class 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會給數組新增元素,咱們還須要知道新增的是什麼數據,須要對這些新增的數據進行觀測。server
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; } });