Vue對(duì)象的單層劫持圖文詳細(xì)講解
一,前言
上篇,介紹了 Vue 使用及數(shù)據(jù)初始化的流程
回顧一下,主要涉及以下幾個(gè)核心點(diǎn):
- initMixin 方法: 原型方法 Vue.prototype._init
- vm.$options:使 options 選項(xiàng)在 vm 實(shí)例上被共享
- initState 方法: Vue 初始化時(shí),對(duì)多種數(shù)據(jù)源做集中處理
- initData 方法:data 數(shù)據(jù)的初始化
本篇,繼續(xù)對(duì) data 數(shù)據(jù)進(jìn)行初始化操作,對(duì)象的劫持(對(duì)象屬性的單層劫持)
二,Vue 的響應(yīng)式原理
問(wèn)題:Vue 的響應(yīng)式原理
Vue 的響應(yīng)式原理
核心是通過(guò) Object.defineProperty 為屬性添加 get、set 方法,從而實(shí)現(xiàn)對(duì)數(shù)據(jù)操的劫持…
即下圖中 Data 部分:
三,對(duì)象的劫持
1,initData 中獲取 data
data 在 options 中,而 options 已被 vm 實(shí)例共享
function initData(vm){ let data = vm.$options.data;// 拿到 vue 初始化時(shí),用戶(hù)傳入的data數(shù)據(jù) console.log("進(jìn)入 state.js - initData,數(shù)據(jù)初始化操作", data) }
2,處理 data 的兩種情況
上篇說(shuō)了,data 有可能是函數(shù),也有可能是對(duì)象
因此,后邊邏輯需要對(duì) data 進(jìn)行一次處理
// utils.js export function isFunction(val){ return typeof val == 'function' }
如果 data 是函數(shù),需要執(zhí)行拿到顳部返回的對(duì)象
// 如果 data 是函數(shù),需要執(zhí)行 data 拿到它的返回值 if(isFunction(data)){ data = data(); // 這樣執(zhí)行,this 不是 vm,而是window }
測(cè)試:
let vm = new Vue({ el: '#app', data() { console.log("打印 data函數(shù)執(zhí)行時(shí),this的指向") console.log(this) return { message: 'Hello Vue' } // data 返回一個(gè)對(duì)象 } });
此時(shí),data 函數(shù)執(zhí)行時(shí),this 指向 window
3,處理 this 的指向問(wèn)題
在 Vue 中,data 函數(shù)執(zhí)行時(shí) this 應(yīng)指向當(dāng)前 vm 實(shí)例
所以,在 data 執(zhí)行時(shí)綁定 this 為當(dāng)前 vm 實(shí)例
if(isFunction(data)){ data = data.call(vm);// data 執(zhí)行時(shí),綁定this 為 vm }
測(cè)試:
簡(jiǎn)化代碼:將 data 為對(duì)象或函數(shù)兩種情況的邏輯合并:
// data 可能是函數(shù)或?qū)ο? // 如果 data 是函數(shù),則需要讓 data 函數(shù)執(zhí)行,拿到它返回的對(duì)象 // 如果 data 是對(duì)象,不做處理 data = isFunction(data) ? data.call(vm) : data;
注意:只有根實(shí)例上的 data 可以是對(duì)象,組件必須為函數(shù)
4,核心模塊 observe:對(duì)數(shù)據(jù)進(jìn)行觀測(cè)
data 數(shù)據(jù)的響應(yīng)式原理是:
通過(guò) Object.defineProperty,重寫(xiě) data 上的所有屬性
這就需要遍歷 data 對(duì)象拿到每一個(gè)屬性,再逐一通過(guò) Object.defineProperty 重新定義
- observe 模塊(文件夾)
- observe 方法 (入口)
創(chuàng)建入口文件 observe/index.js
// src/observe/index.js export function observe(value) { console.log(value) }
在 state.js 中引入并使用 observe 方法
調(diào)用 observe 方法觀測(cè)數(shù)據(jù),實(shí)現(xiàn) data 數(shù)據(jù)的響應(yīng)式
import { observe } from "./observe"; import { isFunction } from "./utils"; export function initState(vm) { const opts = vm.$options; if (opts.data) { initData(vm); } } function initData(vm) { let data = vm.$options.data; data = isFunction(data) ? data.call(vm) : data; observe(data); // 使用 observe 實(shí)現(xiàn) data 數(shù)據(jù)的響應(yīng)式 }
經(jīng)過(guò)這次處理后,此時(shí)的 data 一定是一個(gè)對(duì)象
所以,對(duì) data 進(jìn)行檢測(cè),如果不是對(duì)象就直接 return 結(jié)束
// src/utils /** * 判斷是否為對(duì)象:類(lèi)型是object,且不能為 null * @param {*} val * @returns */ export function isObject(val) { return typeof val == 'object' && val !== null }
// src/observe/index.js import { isObject } from "../utils"; export function observe(value) { // 如果 value 不是對(duì)象,就不需要觀測(cè)了,直接 return if(!isObject(value)){ return; } }
5,Observer 類(lèi):對(duì)【對(duì)象】進(jìn)行觀測(cè)
完成的邏輯是這樣的:
// src/observe/index.js export function observe(value) { if(!isObject(value)){ return; } // 觀測(cè) value 對(duì)象,實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式 return new Observer(value); }
Observer 類(lèi):
遍歷對(duì)象屬性,使用 Object.defineProperty 重新定義 data 對(duì)象中的屬性
// src/observe/index.js class Observer { constructor(value){ // 如果value是對(duì)象,遍歷對(duì)象中的屬性,使用 Object.defineProperty 重新定義 this.walk(value); // 循環(huán)對(duì)象屬性 } // 循環(huán) data 對(duì)象,使用 Object.keys 不循環(huán)原型方法 walk(data){ Object.keys(data).forEach(key => { // 使用 Object.defineProperty 重新定義 data 對(duì)象中的屬性 defineReactive(data, key, data[key]); }); } } /** * 給對(duì)象Obj,定義屬性key,值為value * 使用Object.defineProperty重新定義data對(duì)象中的屬性 * 由于Object.defineProperty性能低,所以vue2的性能瓶頸也在這里 * @param {*} obj 需要定義屬性的對(duì)象 * @param {*} key 給對(duì)象定義的屬性名 * @param {*} value 給對(duì)象定義的屬性值 */ function defineReactive(obj, key, value) { Object.defineProperty(obj, key, { get(){ // 閉包 return value; // 問(wèn)題:這里的 value 為什么不用 obj[key]獲??? }, set(newValue) { if (newValue === value) return value = newValue; } }) }
至此,obj 中的所有屬性都通過(guò) defineProperty 重新定義,具有 get、set 方法
問(wèn)題:為什么使用類(lèi)而不使用函數(shù)?
TODO
問(wèn)題:Object.defineProperty 中 get 為什么不用 obj[key] 來(lái)獲???
TODO
6,測(cè)試
在 data 進(jìn)行初始化的 initData 方法中,
通過(guò) observe 方法觀測(cè) data 數(shù)據(jù),實(shí)現(xiàn)對(duì) data 屬性的劫持
function initData(vm) { console.log("進(jìn)入 state.js - initData,數(shù)據(jù)初始化操作") let data = vm.$options.data; data = isFunction(data) ? data.call(vm) : data; // data 數(shù)據(jù)的響應(yīng)式:遍歷對(duì)象拿到所有屬性,再通過(guò)Object.defineProperty 重寫(xiě) data 中的所有屬性 observe(data); // 觀測(cè)數(shù)據(jù) console.log(data) }
打印被觀測(cè)后的 data 對(duì)象,查看執(zhí)行效果:
至此,就實(shí)現(xiàn)了對(duì) data 屬性的劫持,但僅完成了第一層屬性的劫持
7,備注:安裝插件
// 不會(huì)自動(dòng)到observe找index入口文件 import { observe } from "./observe"; // 需要指定index.js import { observe } from "./observe/index"; // 安裝包:@rollup/plugin-node-resolve,實(shí)現(xiàn) node 方式解析文件 // npm i @rollup/plugin-node-resolve
使用插件:修改 rollup 配置文件
import babel from 'rollup-plugin-babel' import resolve from 'rollup-plugin-node-resolve'; // 引入插件 export default { input:'./src/index.js', output:{ file:'dist/vue.js', format:'umd', name:'Vue', sourcemap:true, }, plugins:[ resolve(), // 使用插件 babel({ exclude:'node_modules/**' }) ] }
三,結(jié)尾
本篇主要介紹了 Vue 數(shù)據(jù)初始化流程中,對(duì)象屬性的單層劫持,核心的幾個(gè)點(diǎn):
- data 為函數(shù)和對(duì)象的處理
- data 函數(shù)中 this 的指向
- Observer 類(lèi),對(duì)數(shù)據(jù)進(jìn)行觀測(cè)
- walk 方法,遍歷 data 屬性(后面深層就不能叫 data 了)
- defineReactive 方法:利用 Object.defineProperty 實(shí)現(xiàn)數(shù)據(jù)劫持
下一篇,對(duì)象屬性的深層劫持
到此這篇關(guān)于Vue對(duì)象的單層劫持圖文詳細(xì)講解的文章就介紹到這了,更多相關(guān)Vue對(duì)象單層劫持內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue 解決provide和inject響應(yīng)的問(wèn)題
這篇文章主要介紹了vue 解決provide和inject響應(yīng)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11vue滾動(dòng)軸插件better-scroll使用詳解
這篇文章主要為大家詳細(xì)介紹了vue滾動(dòng)軸插件better-scroll的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10vue webpack build資源相對(duì)路徑的問(wèn)題及解決方法
這篇文章主要介紹了vue webpack build資源相對(duì)路徑的問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Element-ui?DatePicker日期選擇器基礎(chǔ)用法示例
這篇文章主要為大家介紹了Element-ui?DatePicker日期選擇器基礎(chǔ)用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06vue中img或元素背景圖片無(wú)法顯示或路徑錯(cuò)誤的解決
這篇文章主要介紹了vue中img或元素背景圖片無(wú)法顯示或路徑錯(cuò)誤的解決方案,具有很好的參考價(jià)值。希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08Vue項(xiàng)目配置、切換主題顏色詳細(xì)教程(mixin+scss方式,簡(jiǎn)單高效)
這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目配置、切換主題顏色(mixin+scss方式)的相關(guān)資料,根據(jù)預(yù)設(shè)的配色方案,在前端實(shí)現(xiàn)動(dòng)態(tài)切換系統(tǒng)主題顏色,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11vue項(xiàng)目中axios如何捕捉http狀態(tài)碼為401錯(cuò)誤問(wèn)題
這篇文章主要介紹了vue項(xiàng)目中axios如何捕捉http狀態(tài)碼為401錯(cuò)誤問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03Vue項(xiàng)目之安裝引入使用vconsole方式(生產(chǎn)環(huán)境不顯示)
在Vue2開(kāi)發(fā)中,引入vConsole可以為移動(dòng)端提供類(lèi)似瀏覽器F12的調(diào)試工具,支持查看日志、網(wǎng)絡(luò)請(qǐng)求等功能,vConsole是一個(gè)輕量、可拓展的前端調(diào)試面板,與框架無(wú)關(guān),適用于多種前端框架,安裝方法包括npm和CDN兩種,可根據(jù)項(xiàng)目環(huán)境配置是否顯示調(diào)試面板2024-10-10手把手教你如何創(chuàng)建一個(gè)VUE項(xiàng)目(超簡(jiǎn)單)
這篇文章主要給大家介紹了關(guān)于如何創(chuàng)建一個(gè)VUE項(xiàng)目的相關(guān)資料,創(chuàng)建vue項(xiàng)目有很多種方式,這里給大家介紹一種非常簡(jiǎn)單的方法,需要的朋友可以參考下2023-08-08