概述
最近在學(xué)習(xí)如何實現(xiàn)一個簡版的Vue(MVVM)的課程,為了理解課程內(nèi)容,將相關(guān)的學(xué)習(xí)內(nèi)容進行梳理記錄,便于以后查閱。
前置知識
MVVM
MVVM(Model-View-ViewModel)就是在MVC的基礎(chǔ)上把業(yè)務(wù)處理的邏輯分離到ViewModel層中。
MVVM分別指的是
-
M:
Model層,表示請求的原始數(shù)據(jù) -
V
View層,負責(zé)視圖的展示,由ViewModel層控制 -
VM
ViewModel層,負責(zé)業(yè)務(wù)處理和數(shù)據(jù)轉(zhuǎn)化
MVVM的原理
MVVM的作用就是API請求完數(shù)據(jù)(從數(shù)據(jù)庫中查詢數(shù)據(jù)),接著將請求的數(shù)據(jù)解析成Model,然后在ViewModel層中將Model層中的數(shù)據(jù)轉(zhuǎn)化成能夠直接在視圖層中使用的數(shù)據(jù),最后將轉(zhuǎn)化的數(shù)據(jù)交付給View層,最終將數(shù)據(jù)渲染到頁面,呈現(xiàn)給用戶查看;
MVVM vs MVC

Vue是什么?
Vue.js是一個漸進式的JavaScript庫。
Vue的設(shè)計思想
核心思想:
- 數(shù)據(jù)驅(qū)動
- 組件系統(tǒng)
數(shù)據(jù)驅(qū)動
Vue.js是一個MVVM框架
MVVM框架的三要素

數(shù)據(jù)響應(yīng)式原理
數(shù)據(jù)響應(yīng)式即是數(shù)據(jù)的變化能夠在視圖(頁面)中體現(xiàn),即數(shù)據(jù)(變量)的變化會引起頁面中所有放置了該數(shù)據(jù)的地方發(fā)生更新;
官網(wǎng)說明:
當(dāng)你把一個普通的 JavaScript 對象傳入 Vue 實例作為
data選項,Vue 將遍歷此對象所有的 property,并使用Object.defineProperty把這些 property 全部轉(zhuǎn)為 getter/setter。Object.defineProperty是 ES5 中一個無法 shim 的特性,這也就是 Vue 不支持 IE8 以及更低版本瀏覽器的原因。
這些 getter/setter 對用戶來說是不可見的,但是在內(nèi)部它們讓 Vue 能夠追蹤依賴,在 property 被訪問和修改時通知變更。
即是Vue2.x中通過Object.defineProperty方法將我們傳入Vue實例的data選項逐一轉(zhuǎn)換為具有getter和setter方法的可以動態(tài)修改屬性值的屬性。
使用Object.defineProperty給對象添加屬性的原因:
可以實時的監(jiān)聽到數(shù)據(jù)的變化,然后更新視圖
栗子
- 給目標對象添加單個屬性
<!--
* @Author: xl
* @Date: 2021-04-23 15:42:26
* @LastEditTime: 2021-04-23 16:13:28
* @LastEditors: Please set LastEditors
* @Description: 使用Object.defineProperty給對象添加屬性并監(jiān)聽數(shù)據(jù)變化然后更新DOM
* @FilePath: \vue-study-demo\kVue\index.html
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用Object.defineProperty給對象添加屬性并監(jiān)聽數(shù)據(jù)變化然后更新DOM</title>
</head>
<body>
<div id="app">
<h1>使用Object.defineProperty給對象添加屬性并監(jiān)聽數(shù)據(jù)變化然后更新DOM:</h1>
<p>現(xiàn)在是幾點:<strong id="strong" style="color:red;"></strong></p>
</div>
<script>
let strong = document.querySelector('#strong');
let obj = {}
// 給目標對象定義屬性(單個屬性),并在監(jiān)聽到數(shù)據(jù)變化的時候更新相應(yīng)的DOM
function defineProperty(target,key,value) {
Object.defineProperty(target,key,{
get() {
return value;
},
set(newVal) {
console.log('newVal :>> ', newVal);
if (newVal !== value) {
value = newVal;
update()
}
}
})
}
defineProperty(obj,'time',new Date().toLocaleTimeString())
console.log('obj.time :>> ', obj.time);
// 更新DOM
function update(val) {
strong.innerHTML = obj.time;
}
setInterval(() => {
obj.time = new Date().toLocaleTimeString();
},1000)
</script>
</body>
</html>
- 給目標對象添加多個屬性
- 對象嵌套
- 新值是對象
<!--
* @Author: xl
* @Date: 2021-04-23 15:42:26
* @LastEditTime: 2021-04-23 16:31:34
* @LastEditors: Please set LastEditors
* @Description: 使用Object.defineProperty給對象添加屬性
* @FilePath: \vue-study-demo\kVue\index.html
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> 使用Object.defineProperty給對象添加屬性</title>
</head>
<body>
<div id="app">
<h1>處理添加對屬性和屬性值嵌套對象的問題</h1>
</div>
<script>
let strong = document.querySelector('#strong');
let obj = {
name:'處理添加對屬性和屬性值嵌套對象的問題',
time: new Date().toLocaleTimeString(),
nest: {
name:'嵌套'
}
}
// 給目標對象定義屬性(單屬性)
function defineProperty(target,key,value) {
// 遞歸:存在屬性值嵌套對象的
observe(value);
Object.defineProperty(target,key,{
get() {
return value;
},
set(newVal) {
if (newVal !== value) {
// 新值也是對象,則需要對其進行響應(yīng)式處理 obj.a = {c:a}
observe(newVal)
value = newVal;
}
}
})
}
// 添加多屬性
function observe(target) {
// 判斷是否是是對象
if (typeof target === 'object' && target !== null) {
return target;
}
let keys = Object.keys(target);
if (keys.length) {
// 遍歷
keys.map(key => {
defineProperty(target,key,target[key])
});
}
}
// 修改
observe(obj)
obj.name = '修改obj的name屬性'
console.log('obj.name :>> ', obj.name);
obj.nest.name = '修改nest.name屬性值'
console.log('obj :>> ', obj);
obj.nest.a = {
name:'第二層嵌套'
}
console.log('obj :>> ', obj);
obj.nest.a.name = '修改第二層嵌套name'
console.log('obj :>> ', obj);
</script>
</body>
</html>
Vue中的數(shù)據(jù)響應(yīng)式
Vue的基本使用
<!--
* @Author:xl
* @Date: 2021-04-23 16:44:46
* @LastEditTime: 2021-04-23 16:49:07
* @LastEditors: Please set LastEditors
* @Description: Vue的基本使用
* @FilePath: \vue-study-demo\kVue\index2.html
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>時間:{{time}}</h2>
</div>
<script src="./vue.js"></script>
<script>
let vm = new Vue({
el:'#app',
data: {
time: null
},
mounted() {
setInterval(() => {
this.time = new Date().toLocaleTimeString();
},1000)
}
})
</script>
</body>
</html>
原理分析
-
new Vue()?先執(zhí)?初始化,對data執(zhí)?響應(yīng)化處理,這個過程發(fā)?在Observer中. - 同時對模板執(zhí)?編譯,找到其中動態(tài)綁定的數(shù)據(jù),從
data中獲取并初始化視圖,這個過程發(fā)?在
Compile中. - 同時定義?個更新函數(shù)和
Watcher,將來對應(yīng)數(shù)據(jù)變化時Watcher會調(diào)?更新函數(shù). - 由于
data的某個key在?個視圖中可能出現(xiàn)多次,所以每個key都需要?個管家Dep來管理多個
Watcher - 將來
data中數(shù)據(jù)?旦發(fā)?變化,會?先找到對應(yīng)的Dep,通知所有Watcher執(zhí)?更新函數(shù)
涉及的類
1.CVue
- 框架的構(gòu)造函數(shù)
2.Observer - 執(zhí)行數(shù)據(jù)的響應(yīng)式處理(分辨數(shù)據(jù)是對象還是數(shù)據(jù))
Compile
- 編譯模板,初始化視圖,收集依賴(更新函數(shù),
watcher創(chuàng)建)
Watcher
- 執(zhí)行更新函數(shù)(更新
DOM)
Dep
- 管理多個
Watcher,批量更新
CVue類及數(shù)據(jù)響應(yīng)式的實現(xiàn)
1.響應(yīng)式處理函數(shù)的實現(xiàn)
/*
* @Author: xl
* @Date: 2021-04-25 10:10:43
* @LastEditTime: 2021-04-25 10:21:21
* @LastEditors: Please set LastEditors
* @Description: 數(shù)據(jù)響應(yīng)式(數(shù)據(jù)劫持)函數(shù)
* @FilePath: \vue-study-demo\CVue\reactive.js
*/
/**
* @description 給指定對象添加屬性
* @param {Object} target 需要添加屬性的目標對象
* @param {String} key 要添加的屬性名
* @param {String} val 要添加的屬性對應(yīng)的值
* @return {Null} 沒有返回值
*/
function defineReactive(target, key, val){
// 如果對象內(nèi)嵌套對象,則需要遞歸處理
observe(target)
Object.defineProperty(target,key,{
get() {
return val;
},
set(newVal) {
if (newVal !== val) {
// 如果屬性值是對象
observe(newVal)
val = newVal;
}
}
})
}
/**
* @description 給指定的目標對象批量添加屬性
* @param {Object} target 要添加屬性的目標對象
*/
function observe(target) {
// 判斷參數(shù)是否是對象,不是對象直接返回
if (typeof target !== 'object' && target == null) {
return target;
}
let keys = Object.keys(target);
if (keys.length) {
keys.map(key => {
defineReactive(target,key,target[key])
})
}
}
-
Observer類,對數(shù)組進行數(shù)據(jù)劫持
/*
* @Author: xl
* @Date: 2021-04-25 10:34:50
* @LastEditTime: 2021-04-25 10:39:38
* @LastEditors: Please set LastEditors
* @Description: 數(shù)據(jù)劫持 -- 處理數(shù)組
* @FilePath: \vue-study-demo\CVue\Observe.js
*/
class Observer {
constructor(value){
this.value = value;
if (Array.isArray(value)) {
this.walk(value)
}
}
//對象的響應(yīng)式處理
walk(target) {
Object.keys(target).map(key => {
defineReactive(target, key, target[key])
})
}
}
-
CVue類的實現(xiàn)--以及初始化時對data選項進行響應(yīng)式處理
// 定義一個CVue類
class CVue {
constructor(options){
// 緩存options選項
this.$options = options;
// 緩存data選項
this.$data = options.data;
// 數(shù)據(jù)劫持 -- 初始化(new CVue)的時候?qū)?data數(shù)據(jù)進行響應(yīng)式處理
observe(this.$data)
}
}
- 數(shù)據(jù)代理
為了直接能訪問數(shù)據(jù),即是不通過this.$data.xxx的方式訪問屬性,而是通過this.xxx的方式訪問屬性,將this.$data中的數(shù)據(jù)全部加入到CVue的實例上,實現(xiàn)如下
/*
* @Author:xl
* @Date: 2021-04-25 10:45:36
* @LastEditTime: 2021-04-25 10:50:53
* @LastEditors: Please set LastEditors
* @Description: 數(shù)據(jù)代理--將data選項中的數(shù)據(jù)全部加入到CVue的實例上
* @FilePath: \vue-study-demo\CVue\proxy.js
*/
/**
* @description 數(shù)據(jù)代理--將data選項中的數(shù)據(jù)全部加入到CVue的實例上
*
* @param {Object} vm CVue的實例對象
*/
function proxy(vm) {
// vm存在
if (Object.keys(vm) && Object.keys(vm).length) {
let {$data} = vm;
let keys = Object.keys($data);
if (keys.length) {
keys.map(key => {
// 將數(shù)據(jù)加入vm上也要保證數(shù)據(jù)是響應(yīng)式的
Object.defineProperty(vm,key,{
get() {
return $data[key]
},
set(newVal) {
if (newVal != $data[key]) {
$data[key] = newVal;
}
}
})
})
}
}
}
在CVue中調(diào)用該函數(shù)
// 定義一個CVue類
class CVue {
constructor(options){
// 緩存options選項
this.$options = options;
// 緩存data選項
this.$data = options.data;
// 數(shù)據(jù)劫持 -- 初始化(new CVue)的時候?qū)?data數(shù)據(jù)進行響應(yīng)式處理
observe(this.$data);
// 數(shù)據(jù)代理:把data代理到CVue的實例上
proxy(this);
}
}
模板編譯處理 - Compile
編譯模板中vue模板特殊語法,初始化視圖、更新視圖

- 創(chuàng)建Complie類
/**
* @description 獲取DOM元素
* @param {String} selector 選擇器
* @return {Object} 返回獲取的DOM元素
*/
function getEl(selector) {
return document.querySelector(selector) ? document.querySelector(selector) : null;
}
class Compile {
constructor(el,vm) {
this.vm = vm;
// 根據(jù)el獲取DOM元素
this.$el = getEl(el)
}
}
在CVue中初始化
// 定義一個CVue類
class CVue {
constructor(options){
// 緩存options選項
this.$options = options;
// 緩存data選項
this.$data = options.data;
// 數(shù)據(jù)劫持 -- 初始化(new CVue)的時候?qū)?data數(shù)據(jù)進行響應(yīng)式處理
observe(this.$data);
// 數(shù)據(jù)代理:把data代理到CVue的實例上
proxy(this);
// 初始化編譯模板,更新視圖
if (options.el) {
new Compile(options.el,this)
}
}
}
- 初始化視圖
獲取DOM以及其所以的子節(jié)點,根據(jù)節(jié)點類型進行相關(guān)的編譯
- 獲取
el對應(yīng)的DOM元素,然后獲取該DOM元素的所有子節(jié)點 - 遍歷獲取到的子節(jié)點,根據(jù)節(jié)點的類型執(zhí)行相應(yīng)的編譯函數(shù)
- 如果是文本節(jié)點,并且值是插值表達式
{{xxx}},則將該文本節(jié)點的textContent設(shè)置為對應(yīng)的xxx數(shù)據(jù)的具體值
4.如果是元素節(jié)點,則獲取該節(jié)點的所有屬性,找出對應(yīng)的指令節(jié)點,針對不同的指令執(zhí)行不同的編譯函數(shù) - 如果是
c-text指令,則對應(yīng)節(jié)點的textContent屬性值,取當(dāng)前綁定數(shù)據(jù)對應(yīng)的值;
6.如果是c-html指令,則對應(yīng)節(jié)點的innerHTML屬性值,取當(dāng)前綁定數(shù)據(jù)對應(yīng)的值;
/*
* @Author: xl
* @Date: 2021-04-25 10:56:49
* @LastEditTime: 2021-04-28 16:12:42
* @LastEditors: Please set LastEditors
* @Description: 模板編譯
* @FilePath: \vue-study-demo\CVue\Compile.js
*/
/**
* @description 獲取DOM元素
* @param {String} selector 選擇器
* @return {Object} 返回獲取的DOM元素
*/
function getEl(selector) {
return document.querySelector(selector) ? document.querySelector(selector) : null;
}
class Compile {
constructor(el, vm) {
this.$vm = vm;
// 根據(jù)el獲取DOM元素
this.$el = getEl(el);
if (this.$el) {
this.compile(this.$el);
}
}
// 編譯函數(shù)
compile(el) {
// 獲取所有的子節(jié)點
let nodes = el.childNodes || [];
// 遍歷子節(jié)點,獲取節(jié)點類型
Array.from(nodes).map(node => {
let {
childNodes
} = node;
// 元素節(jié)點
if (this.isElement(node)) {
this.compileEle(node)
}
// 文本節(jié)點并且是插值表達式的形式
if (this.isInterpolation(node)) {
this.compileText(node)
}
// 存在子節(jié)點,則進行遞歸處理
if (childNodes && childNodes.length) {
this.compile(node)
}
})
}
// 是否是插值表達式: 形如{{xxx}}
isInterpolation(node) {
return /\{\{(.*)\}\}/.test(node.textContent);
}
// 文本節(jié)點
isText(node) {
return node.nodeType == 3;
}
// 是否是元素節(jié)點
isElement(node) {
return node.nodeType == 1;
}
// 是否是指令
isDirective(attr) {
return attr.indexOf("c-") == 0 && attr.indexOf("c-bind:") == -1;
}
// 是否是綁定的事件: c-bind:xxx | @xxx
isBind(attr) {
return attr.indexOf("c-bind:") == 0 || attr.indexOf('@') == 0;
}
// 編譯元素節(jié)點
compileEle(node) {
let that = this;
// 獲取節(jié)點的所有屬性
let attrs = node.attributes;
// 遍歷所有的屬性節(jié)點
Array.from(attrs).map(attr => {
let {
name,
value
} = attr;
// 判斷當(dāng)前屬性是否是指令:c-xxx : c-text c-html,c-model
if (this.isDirective(name)) {
// 截取指令的名稱
let directName = name.substring(2);
// 執(zhí)行對應(yīng)的編譯函數(shù)
this[directName] && this[directName](node, value)
} else if (this.isBind(name)) { // 處理綁定事件
this.event(node, name, value)
}
})
}
// 處理on:xxx | @xxx
event(node, name, eventName) {
let that = this;
let eventType = null
// c-bind:xxx
if (name.indexOf('c-bind:') == 0) {
eventType = name.split(':')[1];
} else { // @xxx
eventType = name.substring(1);
}
node.addEventListener(eventType, () => {
that.$vm.$methods[eventName] && that.$vm.$methods[eventName].call(that.$vm);
})
}
update(node, exp, dir) {
if (!node) {
return;
}
const fn = this[dir + 'Updater'];
fn && fn(node, this.$vm[exp]);
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val)
})
}
textUpdater(node, val) {
node.textContent = val;
}
htmlUpdater(node, val) {
node.innerHTML = val
}
// c-text
text(node, exp) {
//node.textContent = this.$vm[exp];
this.update(node, exp, 'text')
}
// c-html
html(node, exp) {
// node.innerHTML = this.$vm[exp];
this.update(node, exp, 'html')
}
// c-model
model(node, exp) {
let that = this;
node.addEventListener('input', function (e) {
that.$vm[exp] = e.target.value;
})
}
// 編譯文本節(jié)點
compileText(node) {
// node.textContent = this.$vm[RegExp.$1];
// 調(diào)?update函數(shù)執(zhí)插值?本賦值
this.update(node, RegExp.$1, 'text')
}
}
依賴收集
- 依賴
視圖中會用到data選項中的某個Key,即是視圖中的數(shù)據(jù)展示需要依賴于data選項中的某個屬性key; - 依賴收集
同一個key可能出現(xiàn)在視圖中的多個位置,每次都需要收集出來??個Watcher來維護它們,即是視圖中同一個key出現(xiàn)多少次就需要多少個Watcher和key建立聯(lián)系
多個Watcher需要?個Dep來管理,需要更新時由Dep統(tǒng)?通知
依賴管理 - 實現(xiàn)思路
-
defineReactive時為每?個key創(chuàng)建?個Dep實例; - 初始化視圖時讀取某個
key,例如name1,創(chuàng)建?個watcher1; - 觸發(fā)
name1的getter?法時,便將watcher1添加到name1對應(yīng)的Dep中; - 當(dāng)
name1更新,setter觸發(fā)時,便可通過對應(yīng)Dep通知其管理所有Watcher更新
- 實現(xiàn)類
-
Watcher類
監(jiān)聽器:負責(zé)更新視圖
Watcher類的聲明
/*
* @Author:xl
* @Date: 2021-04-27 10:30:47
* @LastEditTime: 2021-04-27 11:05:20
* @LastEditors: Please set LastEditors
* @Description: 監(jiān)聽器:根據(jù)監(jiān)聽的Key的值變化執(zhí)行對應(yīng)的更新函數(shù)去更新視圖
* @FilePath: \vue-study-demo\CVue\Watcher.js
*/
class Watcher {
// vm: CVue的實例對象 key: Watcher監(jiān)聽的key updateFn: 更新函數(shù)
constructor(vm,key,updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
}
// 更新函數(shù)
update() {
this.updateFn.call(this.vm,this.vm[this.key])
}
}
編寫更新函數(shù),創(chuàng)建Watcher
/*
* @Author: xl
* @Date: 2021-04-25 10:56:49
* @LastEditTime: 2021-04-28 16:12:42
* @LastEditors: Please set LastEditors
* @Description: 模板編譯
* @FilePath: \vue-study-demo\CVue\Compile.js
*/
/**
* @description 獲取DOM元素
* @param {String} selector 選擇器
* @return {Object} 返回獲取的DOM元素
*/
function getEl(selector) {
return document.querySelector(selector) ? document.querySelector(selector) : null;
}
class Compile {
constructor(el, vm) {
this.$vm = vm;
// 根據(jù)el獲取DOM元素
this.$el = getEl(el);
if (this.$el) {
this.compile(this.$el);
}
}
// 編譯函數(shù)
compile(el) {
// 獲取所有的子節(jié)點
let nodes = el.childNodes || [];
// 遍歷子節(jié)點,獲取節(jié)點類型
Array.from(nodes).map(node => {
let {
childNodes
} = node;
// 元素節(jié)點
if (this.isElement(node)) {
this.compileEle(node)
}
// 文本節(jié)點并且是插值表達式的形式
if (this.isInterpolation(node)) {
this.compileText(node)
}
// 存在子節(jié)點,則進行遞歸處理
if (childNodes && childNodes.length) {
this.compile(node)
}
})
}
// 是否是插值表達式: 形如{{xxx}}
isInterpolation(node) {
return /\{\{(.*)\}\}/.test(node.textContent);
}
// 文本節(jié)點
isText(node) {
return node.nodeType == 3;
}
// 是否是元素節(jié)點
isElement(node) {
return node.nodeType == 1;
}
// 是否是指令
isDirective(attr) {
return attr.indexOf("c-") == 0 && attr.indexOf("c-bind:") == -1;
}
// 是否是綁定的事件: c-bind:xxx | @xxx
isBind(attr) {
return attr.indexOf("c-bind:") == 0 || attr.indexOf('@') == 0;
}
// 編譯元素節(jié)點
compileEle(node) {
let that = this;
// 獲取節(jié)點的所有屬性
let attrs = node.attributes;
// 遍歷所有的屬性節(jié)點
Array.from(attrs).map(attr => {
let {
name,
value
} = attr;
// 判斷當(dāng)前屬性是否是指令:c-xxx : c-text c-html,c-model
if (this.isDirective(name)) {
// 截取指令的名稱
let directName = name.substring(2);
// 執(zhí)行對應(yīng)的編譯函數(shù)
this[directName] && this[directName](node, value)
} else if (this.isBind(name)) { // 處理綁定事件
this.event(node, name, value)
}
})
}
// 處理on:xxx | @xxx
event(node, name, eventName) {
let that = this;
let eventType = null
// c-bind:xxx
if (name.indexOf('c-bind:') == 0) {
eventType = name.split(':')[1];
} else { // @xxx
eventType = name.substring(1);
}
node.addEventListener(eventType, () => {
that.$vm.$methods[eventName] && that.$vm.$methods[eventName].call(that.$vm);
})
}
update(node, exp, dir) {
if (!node) {
return;
}
const fn = this[dir + 'Updater'];
fn && fn(node, this.$vm[exp]);
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val)
})
}
textUpdater(node, val) {
node.textContent = val;
}
htmlUpdater(node, val) {
node.innerHTML = val
}
// c-text
text(node, exp) {
//node.textContent = this.$vm[exp];
this.update(node, exp, 'text')
}
// c-html
html(node, exp) {
// node.innerHTML = this.$vm[exp];
this.update(node, exp, 'html')
}
// c-model
model(node, exp) {
let that = this;
node.addEventListener('input', function (e) {
that.$vm[exp] = e.target.value;
})
}
// 編譯文本節(jié)點
compileText(node) {
// node.textContent = this.$vm[RegExp.$1];
// 調(diào)?update函數(shù)執(zhí)插值?本賦值
this.update(node, RegExp.$1, 'text')
}
}
-
Dep類
用于收集同一個key的Watcher,并監(jiān)聽key值的編譯,一旦key的值有變化則通知其下管理的Watcher進行更新視圖
/*
* @Author: xl
* @Date: 2021-04-27 10:30:28
* @LastEditTime: 2021-04-27 11:17:13
* @LastEditors: Please set LastEditors
* @Description: Dep: 負責(zé)收集同一個key對應(yīng)的watcher,監(jiān)聽key的值變化,通知其管理的Watcher執(zhí)行各自的更新函數(shù)
* @FilePath: \vue-study-demo\CVue\Dep.js
*/
class Dep {
constructor() {
this.deps = [];
}
// 保存watcher
addDep(dep) {
this.deps.push(dep)
}
// 通知watcher進行更新
notify() {
this.deps.map(dep => dep.update())
}
}
創(chuàng)建watcher時觸發(fā)getter
class Watcher {
// vm: CVue的實例對象 key: Watcher監(jiān)聽的key updateFn: 更新函數(shù)
constructor(vm,key,updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
// 把watcher保存在Dep的靜態(tài)屬性target上
Dep.target = this;
// 創(chuàng)建watcher時觸發(fā)getter -- 即是手動獲取一次key
this.vm[this.key];
Dep.target = null;
}
// 更新函數(shù)
update() {
this.updateFn.call(this.vm,this.vm[this.key])
}
}
依賴收集,創(chuàng)建Dep實例
function defineReactive(target, key, val){
// 如果對象內(nèi)嵌套對象,則需要遞歸處理
observe(val)
// 創(chuàng)建Dep實例
const dep = new Dep()
Object.defineProperty(target,key,{
get() {
// 保存key的Watcher
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal !== val) {
// 如果屬性值是對象
observe(newVal)
val = newVal;
// 通知watcher更新
dep.notify();
}
}
})
}

