/**
* author:Echonessy
* des:
* date:2020.07.24
* target: Vue
* 1.Vue
* 把data中的成員注入到Vue實例中若债,并且把data中的成員轉成getter/setter
* 2.Observer
* 能夠對數(shù)據(jù)對象的所有屬性進行監(jiān)聽火焰,如果變動可拿到最新值并通知Dep(發(fā)布者-目標)
* 3.Watcher
* 定義觀察者,定義update()函數(shù)纱耻,當數(shù)據(jù)發(fā)生變動,更新視圖
* 4.Dep
* 添加觀察者险耀,當數(shù)據(jù)發(fā)生變化的時候弄喘,通知所有的觀察者,執(zhí)行觀察者的update()函數(shù)
* 5.Compiler
* 負責編譯模板甩牺,解析指令/差值表達式蘑志,負責頁面的首次渲染,當數(shù)據(jù)變化后更新視圖
* */
/**
* 1.Vue
* 把data中的成員注入到Vue實例中贬派,并且把data中的成員轉成getter/setter
* 功能:
* 1.負責接受初始化的參數(shù)(選項)
* 2.負責吧data中的屬性注入到Vue實例急但,轉換成getter/setter
* 3.負責調(diào)用Observer監(jiān)聽data中所有屬性的變化
* 4.負責調(diào)用compiler解析指令/差值表達式
* 結構:
* +$options :記錄所有參數(shù)配置
* +$el :記錄綁定的DOM Element
* +$data :記錄響應式數(shù)據(jù)
* ---------------------
* -_proxyData() 私有成員,把data中的屬性搞乏,轉換成getter/setter注入到Vue實例中
* */
class Vue {
constructor(options) {
// 1.通過屬性保存選項的數(shù)據(jù)
this.$options = options || Object.create(null);
// data 必須是一個函數(shù)波桩,為了防止與內(nèi)部變量沖突
if(typeof options.data !== 'function'){
throw ('data must be a function')
return
}
this.$data = options.data() || Object.create(null);
this.$el = typeof options.el === 'string' ? document.querySelector(options.el):options.el;
// 2.把data中的成員轉換成getter/setter注入到Vue實例中
this._proxyData(this.$data);
// 3.調(diào)用Observer對象,監(jiān)聽數(shù)據(jù)的變化
new Observer(this.$data)
// 4.調(diào)用Compiler對象请敦,解析指令和差值表達式
new Compiler(this)
}
// 私有成員镐躲,把data中的屬性,轉換成getter/setter注入到Vue實例中
_proxyData(data){
// 1.遍歷data中的所有屬性侍筛,
if(!data || typeof data !== 'object') return;
Object.keys(data).forEach(key =>{
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
get() {
return data[key]
},
set(nv) {
if(data[key] == nv) return;
data[key] = nv;
}
})
})
}
}
/**
* 2.Observer 核心
* 數(shù)據(jù)響應式處理
* 功能:
* 1.負責編譯模板萤皂,解析指令/差值表達式,
* 2.負責頁面的首次渲染
* 3.當數(shù)據(jù)變化后更新視圖
* 結構:
* +go(data)
* 負責遍歷對象屬性匣椰,對象攔截裆熙,只針對對象數(shù)據(jù)進行響應式處理,
* +proxyData(data)
* 數(shù)據(jù)代理
* 負責通過Object.defineProperty進行對象劫持,通過遞歸進行深度對象監(jiān)聽禽笑,
* 針對新賦值屬性值入录,如果是對象,同樣進行數(shù)據(jù)攔截
* */
//監(jiān)聽data
class Observer {
constructor(data) {
this.go(data)
}
go(data){
if(typeof data !== 'object'){
return
}
Object.keys(data).forEach(key =>{
this.proxyData(data,key,data[key])
})
}
proxyData(data,key,value){
this.go(value);
let that = this;
//收集依賴佳镜,發(fā)送通知
let dep = new Dep();
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get() {
// console.log('getter -> ' + value)
Dep.target && dep.addSub(Dep.target)
return value;
},
set(nv) {
if(value == nv) return;
console.log('數(shù)據(jù)變化'+value+'-->'+nv+'僚稿,發(fā)送通知')
value = nv;
that.go(nv);
dep.notify(key,nv)
// 數(shù)據(jù)變化,發(fā)送通知
}
})
}
}
/**
* 3.Compiler 核心
* 編譯邀杏、更新視圖
* 功能:
* 1.負責編譯模板贫奠,解析指令和差值表達式
* 2.負責頁面的首次加載
* 3.當數(shù)據(jù)變化時唬血,更新視圖
*
* 結構:
* +el
* Vue構造函數(shù)的options.el ,DOM對象
* +vm
* Vue實例
* ----------------------------------------------
* +compile(el)
* 用于遍歷DOM對象所有節(jié)點唤崭,如果是文本節(jié)點拷恨,解析差值表達式。如果是元素節(jié)點谢肾,解析指令腕侄。
* +compileText(node)
* 解析差值表達式
* +compileElement(node)
* 解析元素指令
* +isDirective(node)
* 判斷是否是指令
* +isTextNode(node)
* 判斷是否是文本節(jié)點
* +isElementNode(node)
* 判斷是否是元素節(jié)點
* +update(node,key,attrName)
* 更新視圖,執(zhí)行指令芦疏,根據(jù) attrName+Update 執(zhí)行對應方法
* +textUpdate(node,key,attrName)
* 更新文本冕杠,執(zhí)行指令v-text
* +modelUpdate(node,key,attrName)
* 更新表單value,執(zhí)行指令v-model
*
* nodeType:12種節(jié)點類型
* 1 Element 代表元素
* 2 Attr 代表屬性
* 3 Text 代表元素或屬性中的文本內(nèi)容。
* 4 CDATASection 代表文檔中的 CDATA 部分(不會由解析器解析的文本)酸茴。
* 5 EntityReference 代表實體引用分预。
* 6 Entity 代表實體。
* 7 ProcessingInstruction 代表處理指令薪捍。
* 8 Comment 代表注釋笼痹。
* 9 Document 代表整個文檔(DOM 樹的根節(jié)點)。
* 10 DocumentType 向為文檔定義的實體提供接口
* 11 DocumentFragment 代表輕量級的 Document 對象酪穿,能夠容納文檔的某個部分
* 12 Notation 代表 DTD 中聲明的符號凳干。
* */
class Compiler {
constructor(vm) {
this.vm = vm;
this.el = vm.$el;
this.compile(this.el)
}
//編譯模板,處理文本節(jié)點和元素節(jié)點
compile(el){
let childNodes = el.childNodes; // 所有節(jié)點被济,屬于偽數(shù)組需要通過Array.from()轉換成真實數(shù)組
Array.from(childNodes).forEach(node =>{
if(this.isTextNode(node)){
// 處理文本節(jié)點
this.compileText(node)
} else if(this.isElementNode(node)){
// 處理元素節(jié)點
this.compileElement(node)
}
// 判斷node節(jié)點救赐,是否有子節(jié)點,如果有只磷,遞歸深度遍歷
if(node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
//編譯元素節(jié)點经磅,處理指令
compileElement(node){
// v-text v-html
// 1.遍歷所有的屬性節(jié)點
// 2.判斷是否是指令
Array.from(node.attributes).forEach(attr=>{
let attrName = attr.name;
if(this.isDirective(attrName)){
attrName = attrName.substr(2);
let key = attr.value;
// 如果當前元素含有指令,則需要首次渲染指令對應的內(nèi)容
this.update(node,key,attrName)
}
})
}
update(node,key,attrName){
let updateFn = this[attrName+'Update'];
updateFn && updateFn.call(this,node,this.vm[key],key);
}
// 處理v-for 指令
forUpdate(node,value,key){
let reg = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
let list = this.vm[key.match(reg)[2]];
list.forEach(item =>{
// console.log(item)
})
// console.log(list)
}
// 處理v-text 指令
textUpdate(node,value,key){
node.textContent = value;
new Watcher(this.vm,key,(k,nv) =>{
console.log('創(chuàng)建Watcher 喳瓣,當數(shù)據(jù)改變更新視圖' + nv)
node.textContent = nv;
})
}
//編譯文本節(jié)點馋贤,處理差值表達式
compileText(node){
// {{name}}
// . 匹配除換行符 \n 之外的任何單字符。要匹配 . 畏陕,請使用 \.
// \ 將下一個字符標記為或特殊字符、或原義字符仿滔、或向后引用惠毁、或八進制轉義符。例如崎页, 'n' 匹配字符 'n'鞠绰。'\n' 匹配換行符。序列 '\\' 匹配 "\"飒焦,而 '\(' 則匹配 "("蜈膨。
// ? 匹配前面的子表達式零次或一次屿笼,或指明一個非貪婪限定符。要匹配 ? 字符翁巍,請使用 \?驴一。
// + 匹配前面的子表達式一次或多次。要匹配 + 字符灶壶,請使用 \+肝断。
let reg = /\{\{(.+?)\}\}/; // 匹配單個的{{key1}}
let value = node.textContent;
if(reg.test(value)){
let key = RegExp.$1.trim();
node.textContent = value.replace(reg,this.vm[key]);
new Watcher(this.vm,key,(k,nv) =>{
console.log('創(chuàng)建Watcher ,當數(shù)據(jù)改變更新視圖' + nv)
node.textContent = this.vm[key];
})
}
}
// 處理v-model 指令
modelUpdate(node,value,key){
node.value = value;
new Watcher(this.vm,key,(k,nv) =>{
console.log('創(chuàng)建Watcher 驰凛,當數(shù)據(jù)改變更新視圖' + nv)
node.value = nv;
})
//設置雙向綁定事件
node.addEventListener('input',e => this.vm[key] = node.value)
}
// 判斷元素是否是指令
isDirective(attrName){
//判斷屬性是否是v-開頭
return attrName.startsWith('v-');
}
//判斷是否是文本節(jié)點
isTextNode(node){
return node.nodeType === 3;
}
//判斷是否是元素節(jié)點
isElementNode(node){
return node.nodeType === 1;
}
}
/**
* 4.Dep 核心 dependence
* 目標(發(fā)布者)
* 功能:
* 1.收集依賴胸懈,添加觀察者
* 2.通知所有觀察者
*
* 結構:
* +subs 數(shù)組:存儲所有的觀察者
* ---------------------------------
* +addSub():添加觀察者
* +notify():當事件發(fā)生時,調(diào)用所有的觀察者的update()方法
* */
class Dep {
constructor() {
// 記錄所有的(觀察者/訂閱者)
this.subs = new Array(0);
}
addSub(sub){
// 每一個觀察者都必須包含一個update方法
if(sub && sub.update) this.subs.push(sub);
}
notify(key,nv){
this.subs.forEach(sub =>sub.update(key,nv))
}
}
/**
* 4.Watcher 核心
* 觀察者 ->update():當事件發(fā)生時恰响,具體要做的事情
* 功能:
* 1.當數(shù)據(jù)變化觸發(fā)依賴趣钱,dep通知所有的Watcher實例更新視圖
* 2.自身實例化的時候往dep對象中添加自己
*
* 結構:
* +vm Vue 實例
* +key data中的屬性名稱
* +cb 回調(diào)函數(shù) 負責更新視圖
* +oldValue 記錄數(shù)據(jù)變化之前的值
* ------------------------------------
* +update() 當數(shù)據(jù)發(fā)生變化的時候,更新視圖
* */
// 訂閱者-觀察者
class Watcher {
constructor(vm,key,cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
// 把Watcher對象記錄到Dep類的靜態(tài)屬性target
// 觸發(fā)get方法胚宦,在get中會調(diào)用addSub
Dep.target = this;
// 當獲取vm[key]的時候會執(zhí)行getter
this.oldValue = vm[key];
// 當Watcher 添加到subs之后羔挡,我們要對Dep進行靜態(tài)屬性的重置
Dep.target = null;
}
update(key,nv){
if(nv == this.oldValue) return;
this.cb(key,nv)
this.oldValue = nv;
}
}
Vue2.0雙向綁定核心實現(xiàn)
最后編輯于 :
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
- 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昨悼,“玉大人蝗锥,你說我怎么就攤上這事÷蚀ィ” “怎么了终议?”我有些...
- 文/不壞的土叔 我叫張陵,是天一觀的道長葱蝗。 經(jīng)常有香客問我穴张,道長,這世上最難降的妖魔是什么两曼? 我笑而不...
- 正文 為了忘掉前任皂甘,我火速辦了婚禮,結果婚禮上悼凑,老公的妹妹穿的比我還像新娘偿枕。我一直安慰自己璧瞬,他們只是感情好,可當我...
- 文/花漫 我一把揭開白布渐夸。 她就那樣靜靜地躺著嗤锉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捺萌。 梳的紋絲不亂的頭發(fā)上档冬,一...
- 文/蒼蘭香墨 我猛地睜開眼伞梯,長吁一口氣:“原來是場噩夢啊……” “哼玫氢!你這毒婦竟也來了?” 一聲冷哼從身側響起谜诫,我...
- 正文 年R本政府宣布,位于F島的核電站多搀,受9級特大地震影響歧蕉,放射性物質發(fā)生泄漏。R本人自食惡果不足惜康铭,卻給世界環(huán)境...
- 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赌髓。 院中可真熱鬧从藤,春花似錦催跪、人聲如沸。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悯搔,卻和暖如春骑丸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妒貌。 一陣腳步聲響...