vue源碼系列02_數據監控與劫持

Vue實例

在src目錄下創建一個index.js
在這裏插入圖片描述
該模塊主要放的就是vue實例(核心代碼放置的位置)

// vue 核心代碼 只是Vue的一個聲明
function Vue(options){
	
}
export default Vue

當我們需要進行初始化,渲染,監控等流程的時候,再向其中導入對應的方法。由此可見,該模塊是用於整合其他方法

初始化操作

因爲我們在頁面剛加載的時候,需要對頁面等操作進行渲染,如 data,computed,watch等進行初始化,所以我們需要vue實例創建的時候,運行初始化操作

init 函數

在vue中,初始化操作被放在了vue的原型上,編寫初始化代碼我們放到init.js中,然後再引入到index.js中,如
index.js

// vue 核心代碼 只是Vue的一個聲明
import {initMixin} from './init'
function Vue(options){
    // 進行vue初始化操作
    this._init(options); //這個init方法在 initMixin 中

}
// 通過引入文件的方式給 Vue原型添加方法
initMixin(Vue);
export default Vue

init.js

  1. 首先我們暴露一個 initMixin方法,用於向vue原型上創建一個_init 方法,這樣在 index.js 中就可以直接調用了
  2. _init 方法 主要是用來進行初始化操作的
  3. initState() 用於描述 初始化流程,該方法被封裝在 state.js 中 。注意:在初始化一開始,vue通過 const vm = this; vm.$options = options; 將 this 付給了vm,並將屬性傳給了 vm.$options,於是當我們給 initState 傳入 vm 時,就已經把Vue實例傳給了 initState
import {initState} from './state'
// 在原型上添加一個 init 方法
export function initMixin(Vue){ //向外暴露該方法,方便實例化操作
    // 初始化流程
    Vue.prototype._init = function(options){
        // 數據的 劫持
        const vm = this; // vue 中使用 this.$options 指代的就是用戶傳遞的屬性
        vm.$options = options;
        initState(vm); // 分隔代碼
        // 如果用戶傳入了 el數據,需要頁面渲染
        // 如果用戶傳入了 el, 就要實現掛載流程

        if(vm.$options.el){
            vm.$mount(vm.$options.el)
        }
    }
    function query(el){
        if(el==='String'){
            return document.querySelector(el);
        }
        return;
    }

    Vue.prototype.$mount = function(el){
        const vm = this;
        const options = vm.$options;
        el = vm.$el = query(el)
        // 默認先查找有沒有 render 方法,沒有render會採用template template也沒有就用el中的內容

        let updateComponent = ()=>{
            console.log("更新和渲染的實現")
        }

        // new Watcher(vm,updateComponent)

        if(!options.render){
            //對模板進行編譯
            let template = options.template; //取出模板
            if(!template && el){
                template = el.outerHTML;
            }

            
            const render = compileToFunction(template); //把template掛載
            options.render = render;
            // 我們將 template 轉化爲render方法,需要使用 虛擬DOM 
        }
    }
}

state.js

  1. 用於初始化狀態
  2. 工作原理:
    1. 通過 vm.$options 我們可以拿到vue的數據來源,屬性,方法,計算屬性,watch等…
    2. 於是我們可以用逐一判斷我們所傳入的屬性是否存在,然後運行相對應的方法
import { observe } from './observer/index.js'
export function initState(vm) {
    const opts = vm.$options;
    // vue的數據來源 屬性 方法 數據 計算屬性 watch
    if (opts.props) {
        initProps(vm);
    }
    if (opts.methods) {
        initMethod(vm);
    }
    if (opts.data) {
        initData(vm);
    }
    if (opts.computed) {
        initComputed(vm);
    }
    if (opts.watch) {
        initWatch(vm);
    }
}

function initProps() { }
function initMethod() { }
function initData(vm) { } // 該方法用於初始化數據
function initComputed() { }
function initWatch() { }

initData(vm) 數據初始化

  • 工作原理:
  1. 獲取vm的data屬性
  2. 判斷data是否爲函數,如果是,就直接觸發,並把this指向實例,如果不是,就直接返回data就好了。(這也就解釋了爲什麼我們平時可以直接用this.xxx獲取到data的值)
  3. 進行數據劫持,也就是 MVVM 模式(數據變化驅動視圖變化)
  4. 進行響應式操作 observe(data)
function initData(vm) {
    // 數據初始化工作
    let data = vm.$options.data; //用戶傳遞的data
    data = vm._data = typeof data === 'function' ? data.call(vm) : data; //如果data是一個函數,就直接執行,並把this指向vue實例,否則就獲取data對象
    // 對象劫持,用戶改變了數據 我希望可以得到通知 => 刷新頁面
    // MVVM 模式 數據變化驅動視圖變化
    // Object.defineProperty() 給屬性添加get方法和set方法
    observe(data); // 響應式原理
}

observer目錄(所有響應式操作都寫這)

  1. 在src下創建observer目錄,並創建index.js
  2. 向外暴露 observe() 方法

observe() (響應式原理)

  1. 說到響應式,無非就是通過 Object.defineProperty() 給屬性添加get方法和set方法
  2. 我們傳入data數據後,給所有的數據都使用 Object.defineProperty() 重新定義get方法和set方法
  3. 它是 es5 的方法,不能兼容 IE8 及以下,所以 Vue2 無法兼容 IE8 版本
  4. 工作原理:
    1. 首先判斷 data 是不是對象或爲空
    2. 返回 Observer 實例,該方法用於觀測數據(數據劫持)

Observer類

  1. 如果數據爲對象,我們還要遞歸該對象對裏面的數據進行響應式處理(正因如此,我們編寫數據的時候儘量不要寫太多層)
  2. 利用 walk(value) 遍歷得到每一個值
  3. 取到值之後給每一個值定義響應式數據 defineReactive()

defineReactive

function defineReactive(data, key, value) {
    observe(value) // 遞歸,對data中的對象進行響應式操作,遞歸實現深度檢測
    Object.defineProperty(data, key, {
        get() { // 獲取值的時候做一些操作
            console.log("獲取數據")
            return value;
        },
        set(newValue) { // 也可以做一些操作
            console.log("更新數據")
            if (newValue == value) return;
            observe(newValue); //繼續劫持用戶設置的值,因爲用戶設置的值有可能是一個對象
            value = newValue;
        }
    })
}

寫到這裏,我們就成功對元素進行響應式處理了(數組劫持在下一篇)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章