摘要:在前端頁面中,把用純對象表示,負責顯示,兩者做到了最大化的分離把和關聯起來的就是。了解了思想后,自己用原生實現一個框架。注意數據描述符和存儲描述符不能同時存在,否則會報錯報錯數據攔截使用來實現數據攔截,從而實現數據監聽。
在前端頁面中,把 Model 用純 JS 對象表示,View 負責顯示,兩者做到了最大化的分離
把 Model 和 View 關聯起來的就是 ViewModel。ViewModel 負責把 Model 的數據同步到 View 中顯示出來,還負責把 View 的修改同步回 Model。
MVVM 的設計思想:關注 Model 的變化,讓 MVVM 框架去自動更新 DOM 的狀態,從而把開發者從操作 DOM 的繁瑣步驟中解脫出來。
了解了 MVVM 思想后,自己用原生 JS 實現一個 MVVM 框架。
實現 MVVM 框架前先來看幾個基本用法:
Object.defineProperty普通聲明對象,定義和修改屬性
let obj = {} obj.name = "zhangsan" obj.age = 20
用ObjectdefineProperty聲明對象
語法:
Object.defineProperty(obj,prop,descriptor)
obj:要處理的目標對象
prop:要定義或修改的屬性的名稱
descriptor:將被定義或修改的屬性描述符
let obj = {} Object.defineProperty(obj,"age",{ value = 14, })
咋一看有點畫蛇添足,這不很雞肋嘛
別急,往下看
描述符descriptor有兩種形式:數據描述符和存儲描述符,他們兩個共有屬性:
configurable,是否可刪除,默認為false,定義后無法修改
enumerable,是否可遍歷,默認為false,定以后無法修改
共有屬性configurable設置為false時,其內部屬性無法用delete刪除;如要刪除,需要把configurable設置為true。
let obj = {} Object.defineProperty(obj,"age",{ configurable:false, value:20, }) delete obj.age //false
enumerable設置為false時,其內部屬性無法遍歷;如需遍歷,要把enumerable設置為true
let obj = {name:"zhangsan"} Object.defineProperty(obj,"age",{ enumerable:false, value:20, }) for(let key in obj){ console.log(key) //name }數據描述符
value:該屬性對應的值,默認為undefined。
writable:當且緊當為true時,value才能被賦值運算符改變。默認為false。
let obj = {} Object.defineProperty(obj,"age",{ value:10, writable:false }) obj.age = 11 obj.age //10
writable和configurable的區別是前者是value能否被修改,后者是value能否被刪除。
存儲描述符get():一個給屬性提供getter的方法,默認為undefined。
set():一個給屬性提供setter的方法,默認為undefined。
let obj = {} let age Object.defineProperty(obj,"age",{ get:function(){ return age }, set:function(newVal){ age = newVal } }) obj.age = 20 obj.age //20
當我調用obj.age時,其實是在向obj對象要age這個屬性,它會干嘛呢?它會調用obj.get()方法,它會找到全局變量age,得到undefined。
當我設置obj.age = 20時,它會調用obj.set()方法,將全局變量age設置為20。
此時在調用obj.age,得到20。
注意:數據描述符和存儲描述符不能同時存在,否則會報錯
let obj = {} let age Object.defineProperty(obj,"age",{ value:10, //報錯 get:function(){ return age }, set:function(newVal){ age = newVal } })數據攔截
使用Object.defineProperty來實現數據攔截,從而實現數據監聽。
首先有一個對象
let data = { name:"zhangsan", friends:[1,2,3,4] }
下面寫一個函數,實現對data對象的監聽,就可以在內部做一些事情
observe(data)
換句話說,就是data內部的屬性都被我們監控的,當調用屬性時,就可以在上面做些手腳,使得返回的值變掉;當設置屬性時,不給他設置。
當然這樣做很無聊,只是想說明,我們可以在內部做手腳,實現我們想要的結果。
那observe這個函數應該怎么寫呢?
function observe(data){ if(!data || typeof data !== "object")return //如果 data 不是對象,什么也不做,直接跳出,也就是說只對 對象 操作 for(let key in data){ //遍歷這個對象 let val = data[key] //得到這個對象的每一個`value` if(typeof val === "object"){ //如果這個 value 依然是對象,用遞歸的方式繼續調用,直到得到基本值的`value` observe(val) } Object.defineProperty(data,key,{ //定義對象 configurable:true, //可刪除,原本的對象就能刪除 enumerable:true, //可遍歷,原本的對象就能遍歷 get:function(){ console.log("這是假的") //調用屬性時,會調用 get 方法,所以調用屬性可以在 get 內部做手腳 //return val //這里注釋掉了,實際調用屬性就是把值 return 出去 }, set:function(newVal){ console.log("我不給你設置。。。") //設置屬性時,會調用 set 方法,所以設置屬性可以在 set 內部做手腳 //val = newVal //這里注釋掉了,實際設置屬性就是這樣寫的。 } }) } }
注意兩點:
我們在聲明let val = data[key]時,不能用var,因為這里需要對每個屬性進行監控,用let每次遍歷都會創建一個新的val,在進行賦值;如果用var,只有第一次才是聲明,后面都是對一次聲明val進行賦值,遍歷結束后,得到的是最后一個屬性,顯然這不是我們需要的。
get方法里,return就是前面聲明的val,這里不能用data[key],會報錯。因為調用data.name,就是調用get方法時,得到的結果是data.name,又繼續調用get方法,就隨變成死循環,所以這里需要用一個變量來存儲data[key],并將這個變量返回出去。
觀察者模式一個典型的觀察者模式應用場景——微信公眾號
不同的用戶(我們把它叫做觀察者:Observer)都可以訂閱同一個公眾號(我們把它叫做主體:Subject)
當訂閱的公眾號更新時(主體),用戶都能收到通知(觀察者)
用代碼怎么實現呢?先看邏輯:
Subject 是構造函數,new Subject()創建一個主題對象,它維護訂閱該主題的一個觀察者數組數組(舉例來說:Subject 是騰訊推出的公眾號,new Subject() 是一個某個機構的公眾號——新世相,它要維護訂閱這個公眾號的用戶群體)
主題上有一些方法,如添加觀察者addObserver、刪除觀察者removeObserver、通知觀察者更新notify(舉例來說:新世相將用戶分為兩組,一組是忠粉就是 addObserver,一組是黑名單就是:removeObserver,它在忠粉組可以添加用戶,可以在黑名單里拉黑一些杠精,如果有福利發放,它就會統治忠粉里的用戶:notify)
Observer 是構造函數,new Observer() 創建一個觀察者對象,該對象有一個update方法(舉例來說:Observer 是忠粉用戶群體,new Observer() 是某個具體的用戶——小王,他必須要打開流量才能收到新世相的福利推送:updata)
當調用notify時實際上調用全部觀察者observer自身的update方法(舉例來說:當新世相推送福利時,它會自動幫忠粉組的用戶打開流量,這比較極端,只是用來舉例)
ES5 寫法:function Subject(){ this.observers = [] } Subject.prototype.addObserver = function(observer){ this.observers.push(observer) } Subject.prototype.removeObserver = function(observer){ let index = this.observers.indexOf(observer) if(index > -1){ this.observers.splice(index,1) } } Subject.prototype.notify = function(){ this.observers.forEach(observer=>{ observer.update() }) } function Observer(name){ this.name = name this.update = function(){ console.log(name + " update...") } } let subject = new Subject() //創建主題 let observer1 = new Observer("xiaowang") //創建觀察者1 subject.addObserver(observer1) //主題添加觀察者1 let observer2 = new Observer("xiaozhang") //創建觀察者2 subject.addObserver(observer2) //主題添加觀察者2 subject.notify() //主題通知觀察者 /**** 輸出 *****/ xiaowang update... xiaozhang update...ES6 寫法:
class Subject{ constructor(){ this.observers = [] } addObserver(observer){ this.observers.push(observer) } removeObserver(observer){ let index = this.observers.indexOf(observer) if(index > -1){ this.observers.splice(index,1) } } notify(){ this.observers.forEach(observer=>{ observer.update() }) } } class Observer{ constructor(name){ this.name = name this.update = function(){ console.log(name + " update...") } } } let subject = new Subject() //創建主題 let observer1 = new Observer("xiaowang") //創建觀察者1 subject.addObserver(observer1) //主題添加觀察者1 let observer2 = new Observer("xiaozhang") //創建觀察者2 subject.addObserver(observer2) //主題添加觀察者2 subject.notify() //主題通知觀察者 /**** 輸出 *****/ xiaowang update... xiaozhang update...
ES5 和 ES6 寫法效果一樣,ES5 的寫法更好理解,ES6 只是個語法糖
主題添加觀察者的方法subject.addObserver(observer)很繁瑣,直接給觀察者下方權限,給他們增加添加進忠粉組的權限
class Observer{ constructor() { this.update = function() { console.log(name + " update...") } } subscribeTo(subject) { //只要用戶訂閱了主題就會自動添加進忠粉組 subject.addObserver(this) //這里的 this 是 Observer 的實例 } } let subject = new Subject() let observer = new Observer("lisi") observer.subscribeTo(subject) //觀察者自己訂閱忠粉分組 subject.notify() /****** 輸出 *******/ lisi update...
MVVM 框架的內部基本原理就是上面這些,下一篇用代碼寫一遍完整的 MVVM 框架。
用原生 JS 實現 MVVM 框架MVVM 框架系列:
用原生 JS 實現 MVVM 框架2——單向綁定
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97271.html
摘要:上一篇寫了實現框架的一些基本概念本篇用代碼來實現一個完整的框架思考假設有如下代碼,里面的會和試圖中的一一映射,修改的值,會直接引起試圖中對應數據的變化如何實現上述呢回想下這篇講的觀察者模式和數據監聽主題是什么觀察者是什么觀察者何時訂閱主題主 上一篇寫了實現 MVVM 框架的一些基本概念 本篇用代碼來實現一個完整的 MVVM 框架 思考 假設有如下代碼,data里面的name會和試圖中的...
摘要:,的事件回調函數中調用的操作方法。以為例調用關系模式實際就是將中的改名為,調用過程基本一致,最大的改良是間的雙向綁定。和間,有一個對象,可以操作修改,使用。 參考:MVC,MVP 和 MVVM 的圖示 - 阮一峰http://www.ruanyifeng.com/blo...Web開發的MVVM模式http://www.cnblogs.com/dxy198...界面之下:還原真實的MV...
摘要:,的事件回調函數中調用的操作方法。以為例調用關系模式實際就是將中的改名為,調用過程基本一致,最大的改良是間的雙向綁定。和間,有一個對象,可以操作修改,使用。 參考:MVC,MVP 和 MVVM 的圖示 - 阮一峰http://www.ruanyifeng.com/blo...Web開發的MVVM模式http://www.cnblogs.com/dxy198...界面之下:還原真實的MV...
摘要:,而且每種框架雙向數據綁定的實現方式都不太一致,比如內部使用的是臟檢查,而內部實現方式的本質是設置屬性訪問器。在中也有類似的概念,不過不叫魔術方法,而是叫做訪問器。 緣起前幾天在看一些流行的迷你mvvm框架(比如avalon.js、 vue.js 這種較輕的框架,而非Angularjs、Emberjs這種較重的框架)的實現。現代流行的mvvm框架一般都會將數據雙向綁定(two-ways...
閱讀 3197·2021-09-06 15:02
閱讀 2243·2019-08-30 15:48
閱讀 3439·2019-08-29 11:08
閱讀 3281·2019-08-26 13:55
閱讀 2440·2019-08-26 13:35
閱讀 3162·2019-08-26 12:11
閱讀 2598·2019-08-26 11:48
閱讀 881·2019-08-26 11:42