摘要:方法實(shí)現(xiàn)將所有屬性?huà)燧d在觀察對(duì)象,將每一項(xiàng)做一個(gè)數(shù)據(jù)劫持就是將中每一項(xiàng)用定義新屬性并返回這個(gè)對(duì)象。當(dāng)和發(fā)生變化時(shí),自動(dòng)會(huì)觸發(fā)視圖更新,獲取得到的也就是最新值。
MVVM及Vue實(shí)現(xiàn)原理
Github源碼地址:https://github.com/wyj2443573...
mvvm 雙向數(shù)據(jù)綁定1. Object.defineProperty
數(shù)據(jù)影響視圖,視圖影響數(shù)據(jù)
angular 臟值檢測(cè) vue數(shù)據(jù)劫持+發(fā)布訂閱模式
vue 不兼容低版本 用的是Object.defineProperty
下面涉及涵蓋的知識(shí)點(diǎn)
因?yàn)関ue底層是基于Object.defineProperty實(shí)現(xiàn)的,所以對(duì)于這方面不懂的自己先學(xué)習(xí)。
Object.defineProperty() 方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性, 并返回這個(gè)對(duì)象。
語(yǔ)法Object.defineProperty(obj, prop, descriptor)
基本用法
var o = {}; o.a = 1; // 等同于 : Object.defineProperty(o, "a", { value : 1, writable : true, configurable : true, enumerable : true }); // 另一方面, Object.defineProperty(o, "a", { value : 1 }); // 等同于 : Object.defineProperty(o, "a", { value : 1, writable : false, configurable : false, enumerable : false });
let o={} Object.defineProperty(o,"a",{ get(){ //獲取o.a的值時(shí),會(huì)調(diào)用get方法 return "hello"; }, set(value){ //o.a="s" 賦值的時(shí)候觸發(fā)set方法 console.log(value) } }) o.a="s"http://"s" o.a //"hello"2.數(shù)據(jù)劫持Observe
vue基本格式
//html{{a}}
模仿vue的格式
vue 中有$options : 存在屬性 data、el、components 等等
_data : Vue實(shí)例參數(shù)中data對(duì)象
接下來(lái)構(gòu)建基本頁(yè)面
index.html
Title {{a}}
mvvm.js
function Vue(options={}){ this.$options=options; //將所有屬性?huà)燧d在$options; //this._data let data=this._data=this.$options.data; //觀察data對(duì)象,將每一項(xiàng)做一個(gè)數(shù)據(jù)劫持;就是將data中每一項(xiàng)用Object.defineProperty定義新屬性并返回這個(gè)對(duì)象。 observe(data); } function Observe(data) { //這里寫(xiě)的是主要邏輯 for(let key in data){ //把data屬性通過(guò)object.defineProperty的方式 定義屬性 let val=data[key]; Object.defineProperty(data,key,{ enumerable:true, //可以枚舉 get(){ return val; //僅僅是將以前的 a:1的方式 轉(zhuǎn)換換位defineProperty的方式 }, set(newValue){ //更改值的時(shí)候觸發(fā) if(newValue===val){ //如果設(shè)置的值跟之前的值一樣則什么也不做 return; } val=newValue; //將新值賦給val,那么get獲取val的時(shí)候,獲取的就是newValue; } }) } } //觀察對(duì)象給對(duì)象增加ObjectDefinedProperty function observe(data){ return new Observe(data); }
此時(shí)在控制臺(tái)打印 vue,發(fā)現(xiàn)已經(jīng)在vue._data中映射了正確的數(shù)據(jù)。
接下來(lái)操作: vue._data.a=30
如果此時(shí)data中a是一個(gè)復(fù)雜的對(duì)象類(lèi)型,如下
{{a.a}}
則此時(shí)打印輸出vue._data,只有第一層添加上了defineProperty,第二層的a無(wú)法劫持
那么我們要遞歸 深層添加defineProperty 另外遞歸的時(shí)候注意添加退出條件,當(dāng)value不是對(duì)象的時(shí)候退出。
代碼添加如下
function Observe(data) { //這里寫(xiě)的是主要邏輯 for(let key in data){ //把data屬性通過(guò)object.defineProperty的方式 定義屬性 let val=data[key]; observe(val); //遞歸 劫持 Object.defineProperty(data,key,{ enumerable:true, //可以枚舉 get(){ return val; //僅僅是將以前的 a:1的方式 裝換位defineProperty的方式 }, set(newValue){ //更改值的時(shí)候觸發(fā) if(newValue===val){ //如果設(shè)置的值跟之前的值一樣則什么也不做 return; } val=newValue; //將新值賦給val,那么get獲取val的時(shí)候,獲取的就是newValue; } }) } } function observe(data){ if(typeof data!="object"){ //如果非對(duì)象,則退出遍歷遞歸 return; } return new Observe(data); }
此時(shí)內(nèi)層的a也同樣得到劫持
如果我們給a設(shè)置新值的時(shí)候vue._data.a={b:3} ,會(huì)發(fā)現(xiàn)內(nèi)層b并沒(méi)有被數(shù)據(jù)劫持。那么在賦新值的時(shí)候,也應(yīng)該通過(guò)defineProperty去定義。
在set中用defineProperty定義新屬性
set(newValue){ //更改值的時(shí)候觸發(fā) if(newValue===val){ //如果設(shè)置的值跟之前的值一樣則什么也不做 return; } val=newValue; //將新值賦給val,那么get獲取val的時(shí)候,獲取的就是newValue; observe(newValue) }
分析到這里,我們已經(jīng)能夠?qū)崿F(xiàn)了深度的數(shù)據(jù)觀察
3.數(shù)據(jù)代理上面的代碼如果想要訪問(wèn)a的屬性需要通過(guò) vue._data.a 這樣的寫(xiě)法獲得,這種寫(xiě)法過(guò)于繁瑣。我們接下來(lái)改善一下:用vue.a的方式直接訪問(wèn)到a(用vue 代理 vue._data )
-- 1.首先用this代理this._data; 讓數(shù)據(jù)中的每一項(xiàng)都用defineProperty代理。
function Vue(options={}){ this.$options=options; //將所有屬性?huà)燧d在$options; //this._data let data=this._data=this.$options.data; //觀察data對(duì)象,將每一項(xiàng)做一個(gè)數(shù)據(jù)劫持;就是將data中每一項(xiàng)用Object.defineProperty定義新屬性并返回這個(gè)對(duì)象。 observe(data); for(let key in data){ Object.defineProperty(this,key,{ //this 代理this._data; enumerable:true, get(){ return this._data[key]; //相當(dāng)于 this.a={a:1} }, set(newVal){ } }) } }
到這一步我們已經(jīng)實(shí)現(xiàn)了數(shù)據(jù)代理的初級(jí)版,vue.a 可以直接獲取值而非vue._data.a
-- 2.get方法很容易理解,這里較為重點(diǎn)的是在set中設(shè)置。首先思考:如果直接設(shè)置this.a={name:1} ,this.a 與this._data.a 它們的值同步改變嗎?
set(newVal){ this[key]=newVal; //?可以這樣做嗎?我們來(lái)實(shí)踐下 }
很顯然兩者是不能夠同步改變的。
方法實(shí)現(xiàn)
function Vue(options={}){ this.$options=options; //將所有屬性?huà)燧d在$options; //this._data let data=this._data=this.$options.data; //觀察data對(duì)象,將每一項(xiàng)做一個(gè)數(shù)據(jù)劫持;就是將data中每一項(xiàng)用Object.defineProperty定義新屬性并返回這個(gè)對(duì)象。 observe(data); //this 代理this._data; for(let key in data){ Object.defineProperty(this,key,{ enumerable:true, get(){ return this._data[key]; //相當(dāng)于 this.a={a:1} }, set(newVal){ //如果直接更改this[key]="XXX",那么this._data[key]的值是不會(huì)被同步改變的。 // 我們可以通過(guò)給this._data[key]=value賦值,從而調(diào)取Observe方法中的set,賦予this._data[key]新值。 // get(){return this._data[key]},獲取到的值即是調(diào)取Observe方法中g(shù)et方法return的值 // 也就是根源上的改變是this._data[key];這樣不管是this._data[key]還是this[key]隨便哪一個(gè)被賦予新值,兩者都是同步變化的 this._data[key]=newVal; } }) } }
下面來(lái)分析一下思路
1.我們可以在set中設(shè)置this._data[key]=newValue,如果此時(shí)vue.a={name:1}它調(diào)取是Observe方法中的set,賦予this._data[key]新值。
設(shè)置值的時(shí)候相當(dāng)于走的是這一步
set(newValue){ //更改值的時(shí)候觸發(fā) if(newValue===val){ //如果設(shè)置的值跟之前的值一樣則什么也不做 return; } val=newValue; //將新值賦給val,那么get獲取val的時(shí)候,獲取的就是newValue; observe(newValue) }
2.如果此時(shí)我們獲取vue.a 的值,即通過(guò)get方法獲取return this._data[key],得到的就是最新值
這里解釋說(shuō)明一下
4.編譯模板Compile學(xué)到這里我們應(yīng)該了解到:
vue特點(diǎn)不能新增不存在的屬性 因?yàn)椴淮嬖诘膶傩詻](méi)有g(shù)et 和 set,它就不會(huì)監(jiān)控?cái)?shù)據(jù)的變化
深度響應(yīng): 因?yàn)槊看钨x予一個(gè)新對(duì)象時(shí)會(huì)給這個(gè)新對(duì)象增加數(shù)據(jù)劫持。
這一步我們要做的目的是將目標(biāo)元素內(nèi){{xx}} 花括號(hào)中的xx替換成對(duì)應(yīng)的值。
第一步、代碼實(shí)現(xiàn)如下
function Vue(options={}){ /*代碼承接上面*/ new Compile(options.el,this) //實(shí)例化Compile } function Compile(el){ //el表示替換哪個(gè)元素范圍內(nèi)的模板 let replacePart=document.querySelector(el); let fragment=document.createDocumentFragment(); while(child = replacePart.firstChild){ //將app中的內(nèi)容移至內(nèi)存中 fragment.appendChild(child); } replace() //我們?cè)诖艘龅氖峭ㄟ^(guò)replace方法,將代碼片段中的{{a.a}}的a.a替換為data中對(duì)應(yīng)的值。 replacePart.appendChild(fragment); } function replace(){ }
第二步、replace方法先找到所有要替換的地方,代碼如下:
{{A}}{{a.a}}
{{b}}
{{c}}
{hphlfvt}
function Compile(el,vm){ //el代表替換的范圍 let replacePart=document.querySelector(el); let fragment=document.createDocumentFragment(); while(child = replacePart.firstChild){ //將app中的內(nèi)容移至內(nèi)存中 fragment.appendChild(child); } replace(fragment) //我們?cè)诖艘龅氖峭ㄟ^(guò)replace方法,將代碼片段中的{{a.a}}的a.a替換為data中對(duì)應(yīng)的值。 replacePart.appendChild(fragment); function replace(fragment){ Array.from(fragment.childNodes).forEach(function(node){ let text=node.textContent; let reg=/{{(.*)}}/; if(node.nodeType===3&& reg.test(text)){ //nodeType:3 文本節(jié)點(diǎn) console.log(RegExp.$1); // A、 a.a 、b 等等 } if(node.childNodes){ replace(node) //如果當(dāng)前node存在子節(jié)點(diǎn),遞歸找到所有需要替換的地方 } }) } }
這一步我們能夠找到所有要替換的目標(biāo)了
第三步、replace方法中 用對(duì)應(yīng)值替換掉需要替換掉的地方,代碼如下:
function replace(fragment){ Array.from(fragment.childNodes).forEach(function(node){ let text=node.textContent; let reg=/{{(.*)}}/; if(node.nodeType===3&®.test(text)){ //nodeType:3 文本節(jié)點(diǎn) console.log(RegExp.$1); let arr=RegExp.$1.split(".") // [A] [a,a] [b] ... let val=vm; //val:{a:{a:1}} arr.forEach(function(k){ val=val[k] //舉例 第一次循環(huán) val=val.a val賦值后val:{a:1} ;第二次循環(huán) val=val.a val賦值后為1 }) node.textContent=text.replace(reg,val) } if(node.childNodes){ replace(node) //如果當(dāng)前node存在子節(jié)點(diǎn),遞歸替換 } }) }
替換結(jié)果如下
5.發(fā)布訂閱模式不明白發(fā)布訂閱模式的朋友先去學(xué)習(xí)
參考鏈接:https://segmentfault.com/a/11...
代碼一:
//發(fā)布訂閱模式 先訂閱 再發(fā)布 // 訂閱就是往事件池里面扔函數(shù) 發(fā)布的就是事件池中的函數(shù)依次執(zhí)行 //我們假設(shè)subs中每個(gè)方法中都有update屬性, function Dep(){ this.subs=[] } Dep.prototype.addSub=function(sub){ //訂閱 this.subs.push(sub) } Dep.prototype.notify=function(){ this.subs.forEach(sub=>{ sub.update(); }) } function Watcher(fn){ //watch是一個(gè)類(lèi) 通過(guò)這個(gè)類(lèi)創(chuàng)建的實(shí)例都擁有update方法 this.fn=fn } Watcher.prototype.update=function(){ this.fn(); } let watcher=new Watcher(function(){console.log(1)}); //監(jiān)聽(tīng)函數(shù) let dep=new Dep(); dep.addSub(watcher); //將watcher放在數(shù)組中 dep.addSub(watcher); dep.addSub(watcher); dep.notify() // 數(shù)組關(guān)系
代碼二(然后我們將代碼二的這個(gè)發(fā)布訂閱的模板放到我們的mvvm.js最下面)
function Dep(){ this.subs=[] } Dep.prototype.addSub=function(sub){ //訂閱 this.subs.push(sub) } Dep.prototype.notify=function(){ this.subs.forEach(sub=>{ sub.update(); }) } function Watcher(fn){ this.fn=fn } Watcher.prototype.update=function(){ this.fn(); }6.連接視圖與數(shù)據(jù)
那么我們接下來(lái)的目的是:當(dāng)數(shù)據(jù)變化的時(shí)候,我們需要重新刷新視圖,將頁(yè)面中的{{a.a}}雙括號(hào)中的a.a也能夠被實(shí)時(shí)的替換掉。這就用到了上面介紹的發(fā)布訂閱模式。方法:我們得先將node.textContent=text.replace(reg,val)訂閱一下,當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候,執(zhí)行node.textContent=text.replace(reg,val)此操作。
我們先寫(xiě)要訂閱的事件
這里思考當(dāng)數(shù)據(jù)變化的時(shí)候會(huì)產(chǎn)生新的值,我們需要用newValue替換原有的值。要想取到新的值,我們需要用到當(dāng)前實(shí)例vm與正則的捕獲到的RegExp.$1,從而獲取類(lèi)似this.a.a的最新值。
new Watcher(vm,RegExp.$1,function(newValue){ //訂閱的事件 函數(shù)里需要接受新的值 node.textContent=text.replace(reg,newValue); });
此時(shí)Watcher類(lèi)也應(yīng)該改動(dòng)下,不懂沒(méi)關(guān)系,可以順著看下面的解析。
function Watcher(vm,exp,fn){ this.fn=fn; this.vm=vm; this.exp=exp; //我們要將fn添加到訂閱中 Dep.target=this; console.log(Dep.target) let val=vm; let arr=exp.split("."); arr.forEach(function(k){ val=val[k]; }) Dep.target=null; }
Dep.target為
下面我們來(lái)分析代碼
這個(gè)邏輯相對(duì)復(fù)雜,不明白的話(huà)多看幾遍
上述代碼中,我們可以看到執(zhí)行了這一步
let val=vm; let arr=exp.split("."); arr.forEach(function(k){ val=val[k]; //獲取this.a.a的時(shí)候就會(huì)觸發(fā)get方法 })
這一步遍歷 arr 取val[k]的時(shí)候相當(dāng)于獲取this.a,會(huì)觸發(fā)了默認(rèn)的get方法。也就是觸發(fā)這里的get方法
在get方法中我們需要訂閱事件:
上述代碼中,取值的時(shí)候會(huì)調(diào)取get方法,Dep.target值是存在的,此時(shí)將Dep.target放到我們的事件池中。
當(dāng)我們set的時(shí)候,觸發(fā)事件池中的事件
此時(shí)update的方法我們得更改下,賦予新值
Watcher.prototype.update=function(){ let val=this.vm; let arr=this.exp.split("."); arr.forEach(function(k){ //this.a.a val=val[k]; }) this.fn(val); //newValue }
此時(shí)我們就能得到我們想要的結(jié)果了,當(dāng)數(shù)據(jù)發(fā)生改變時(shí),視圖也會(huì)更新。
7.雙向數(shù)據(jù)綁定的實(shí)現(xiàn)思路: 先找到v-model這個(gè)屬性,取對(duì)應(yīng)的屬性值s,然后用vue.s 替換input中value值
在Compile方法中寫(xiě)邏輯
打印結(jié)果如下
此時(shí)已經(jīng)能夠?qū)?duì)應(yīng)的s值賦值給輸入框
接下來(lái)我們需要做的是,每次更改輸入框中的值的時(shí)候,對(duì)應(yīng)的s也會(huì)跟著改變
操作結(jié)果如下
8.實(shí)現(xiàn)computed官網(wǎng)上有兩種computed的基礎(chǔ)用法
用法一
var vm = new Vue({ el: "#example", data: { message: "Hello" }, computed: { // 一個(gè) computed 屬性的 getter 函數(shù) reversedMessage: function () { // `this` 指向 vm 實(shí)例 return this.message.split("").reverse().join("") } } })
用法二
computed: { fullName: { // getter 函數(shù) get: function () { return this.firstName + " " + this.lastName }, // setter 函數(shù) set: function (newValue) { var names = newValue.split(" ") this.firstName = names[0] this.lastName = names[names.length - 1] } } }
思路: computed實(shí)現(xiàn)相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,只是把數(shù)據(jù)掛在vm上
1.html頁(yè)面如下
s的值:{{s}}
computed的值:{{hello}}
{{A}}{{a.a}}
{{b}}
{{c}}
{d7vx5xl}
2.將hello屬性?huà)燧d在當(dāng)前實(shí)例上,先初始化computed
initComputed 函數(shù)實(shí)現(xiàn)
function initComputed(){ let vm=this; let computed=this.$options.computed; //Object.keys [name:"tom",age:2]=>[name,age] Object.keys(computed).forEach(function(key){ Object.defineProperty(vm,key,{ //computed[key] get:typeof computed[key]==="function"? computed[key]:computed[key].get, set(){ } }) }) }
分析以上代碼,我們能得知this.hello的變化 只依賴(lài)于this.s 和 this.c 。當(dāng)s和c發(fā)生變化時(shí),自動(dòng)會(huì)觸發(fā)視圖更新,獲取this.hello得到的也就是最新值。
++++++++++++到此已經(jīng)完成了分享,如有相關(guān)的問(wèn)題和建議還望不吝提出++++++++++++
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/103195.html
摘要:先來(lái)看一張系統(tǒng)前后端架構(gòu)模型圖。一種接口的約定本文用于定義一種統(tǒng)一的接口設(shè)計(jì)方案,希望具有參考價(jià)值。,和都是常見(jiàn)的軟件架構(gòu)設(shè)計(jì)模式,它通過(guò)分離關(guān)注點(diǎn)來(lái)改進(jìn)代碼的組織方式。 如何無(wú)痛降低 if else 面條代碼復(fù)雜度 相信不少同學(xué)在維護(hù)老項(xiàng)目時(shí),都遇到過(guò)在深深的 if else 之間糾纏的業(yè)務(wù)邏輯。面對(duì)這樣的一團(tuán)亂麻,簡(jiǎn)單粗暴地繼續(xù)增量修改常常只會(huì)讓復(fù)雜度越來(lái)越高,可讀性越來(lái)越差,有沒(méi)...
摘要:插件開(kāi)發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實(shí)現(xiàn)文件分片斷點(diǎn)續(xù)傳。 Vue.js 插件開(kāi)發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強(qiáng)大而且簡(jiǎn)單的方式。插....
摘要:模塊化是隨著前端技術(shù)的發(fā)展,前端代碼爆炸式增長(zhǎng)后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調(diào)也不等同于異步。將會(huì)討論安全的類(lèi)型檢測(cè)惰性載入函數(shù)凍結(jié)對(duì)象定時(shí)器等話(huà)題。 Vue.js 前后端同構(gòu)方案之準(zhǔn)備篇——代碼優(yōu)化 目前 Vue.js 的火爆不亞于當(dāng)初的 React,本人對(duì)寫(xiě)代碼有潔癖,代碼也是藝術(shù)。此篇是準(zhǔn)備篇,工欲善其事,必先利其器。我們先在代...
摘要:最近,筆者就在為組里的框架去做一套基本的工具。通過(guò)這邊文章,筆者希望大家都能簡(jiǎn)單的去實(shí)現(xiàn)一個(gè)屬于自己的腳手架工具。我們?cè)谙滦略鑫募@個(gè)文件導(dǎo)出一個(gè)的類(lèi)。結(jié)語(yǔ)到此,一個(gè)簡(jiǎn)單的就制作完成了,大家可以參考等優(yōu)秀的適當(dāng)?shù)臄U(kuò)展自己的工具。 你有沒(méi)有遇到過(guò)在沒(méi)有vue-cli、create-react-app這樣子的腳手架的時(shí)候一個(gè)文件一個(gè)文件的去拷貝老項(xiàng)目的配置文件。最近,筆者就在為組里的框架...
閱讀 3648·2021-11-25 09:43
閱讀 647·2021-09-22 15:59
閱讀 1751·2021-09-06 15:00
閱讀 1776·2021-09-02 09:54
閱讀 695·2019-08-30 15:56
閱讀 1186·2019-08-29 17:14
閱讀 1846·2019-08-29 13:15
閱讀 887·2019-08-28 18:28