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
- 首先我們暴露一個 initMixin方法,用於向vue原型上創建一個_init 方法,這樣在 index.js 中就可以直接調用了
- _init 方法 主要是用來進行初始化操作的
- 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
- 用於初始化狀態
- 工作原理:
- 通過 vm.$options 我們可以拿到vue的數據來源,屬性,方法,計算屬性,watch等…
- 於是我們可以用逐一判斷我們所傳入的屬性是否存在,然後運行相對應的方法
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) 數據初始化
- 工作原理:
- 獲取vm的data屬性
- 判斷data是否爲函數,如果是,就直接觸發,並把this指向實例,如果不是,就直接返回data就好了。(這也就解釋了爲什麼我們平時可以直接用this.xxx獲取到data的值)
- 進行數據劫持,也就是 MVVM 模式(數據變化驅動視圖變化)
- 進行響應式操作
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目錄(所有響應式操作都寫這)
- 在src下創建observer目錄,並創建index.js
- 向外暴露 observe() 方法
observe() (響應式原理)
- 說到響應式,無非就是通過 Object.defineProperty() 給屬性添加get方法和set方法
- 我們傳入data數據後,給所有的數據都使用 Object.defineProperty() 重新定義get方法和set方法
- 它是 es5 的方法,不能兼容 IE8 及以下,所以 Vue2 無法兼容 IE8 版本
- 工作原理:
- 首先判斷 data 是不是對象或爲空
- 返回 Observer 實例,該方法用於觀測數據(數據劫持)
Observer類
- 如果數據爲對象,我們還要遞歸該對象對裏面的數據進行響應式處理(正因如此,我們編寫數據的時候儘量不要寫太多層)
- 利用 walk(value) 遍歷得到每一個值
- 取到值之後給每一個值定義響應式數據 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;
}
})
}
寫到這裏,我們就成功對元素進行響應式處理了(數組劫持在下一篇)