摘要:套數(shù)據(jù),實現(xiàn)界面先把計算屬性這個注釋掉,后面進(jìn)行實現(xiàn)計算屬性然后在函數(shù)中增加一個編譯函數(shù),號表示是添加的函數(shù)添加一個編譯函數(shù)上面我們添加了一個的構(gòu)造函數(shù)。
Proxy、Reflect的簡單概述
Proxy 可以理解成,在目標(biāo)對象之前架設(shè)一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機(jī)制,可以對外界的訪問進(jìn)行過濾和改寫。Proxy 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。
出自阮一峰老師的ECMAScript 6 入門,詳細(xì)點擊http://es6.ruanyifeng.com/#docs/proxy
例如:
var obj = new Proxy({}, { get: function (target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); } });
上面代碼對一個空對象架設(shè)了一層攔截,重定義了屬性的讀?。╣et)和設(shè)置(set)行為。這里暫時先不解釋具體的語法,只看運(yùn)行結(jié)果。對設(shè)置了攔截行為的對象obj,去讀寫它的屬性,就會得到下面的結(jié)果。
obj.count = 1 // setting count! ++obj.count // getting count! // setting count! // 2
var proxy = new Proxy(target, handler);
這里有兩個參數(shù),target參數(shù)表示所要攔截的目標(biāo)對象,handler參數(shù)也是一個對象,用來定制攔截行為。
注意,要使得Proxy起作用,必須針對Proxy實例(上例是proxy對象)進(jìn)行操作,而不是針對目標(biāo)對象(上例是空對象)進(jìn)行操作。
Reflect對象與Proxy對象一樣,也是 ES6 為了操作對象而提供的新 API。
Reflect對象的方法與Proxy對象的方法一一對應(yīng),只要是Proxy對象的方法,就能在Reflect對象上找到對應(yīng)的方法。這就讓Proxy對象可以方便地調(diào)用對應(yīng)的Reflect方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說,不管Proxy怎么修改默認(rèn)行為,你總可以在Reflect上獲取默認(rèn)行為。
同樣也放上阮一峰老師的鏈接http://es6.ruanyifeng.com/#docs/reflect
初始化結(jié)構(gòu)看到這里,我就當(dāng)大家有比較明白Proxy(代理)是做什么用的,然后下面我們看下要做最終的圖騙。
看到上面的圖片,首先我們新建一個index.html,然后里面的代碼是這樣子滴。很簡單
簡單版mvvm 開發(fā)語言:{{language}}
組成部分:
- {{makeUp.one}}
- {{makeUp.two}}
- {{makeUp.three}}
描述:
{{describe}}
計算屬性:{{sum}}
看到上面的代碼,大概跟vue長得差不多,下面去實現(xiàn)Mvvm這個構(gòu)造函數(shù)
實現(xiàn)Mvvm這個構(gòu)造函數(shù)首先聲明一個Mvvm函數(shù),options當(dāng)作參數(shù)傳進(jìn)來,options就是上面代碼的配置,里面有el、data、computed~~
function Mvvm(options = {}) { // 把options 賦值給this.$options this.$options = options // 把options.data賦值給this._data let data = this._data = this.$options.data let vm = initVm.call(this) return this._vm }
上面Mvvm函數(shù)很簡單,就是把參數(shù)options 賦值給this.$options、把options.data賦值給this._data、然后調(diào)用初始化initVm函數(shù),并用call改變this的指向,方便initVm函操作。然后返回一個this._vm,這個是在initVm函數(shù)生成的。
下面繼續(xù)寫initVm函數(shù),
function initVm () { this._vm = new Proxy(this, { // 攔截get get: (target, key, receiver) => { return this[key] || this._data[key] || this._computed[key] }, // 攔截set set: (target, key, value) => { return Reflect.set(this._data, key, value) } }) return this._vm }
這個init函數(shù)用到Proxy攔截了,this對象,生產(chǎn)Proxy實例的然后賦值給this._vm,最后返回this._vm,
上面我們說了,要使得Proxy起作用,必須針對Proxy實例。
在代理里面,攔截了get和set,get函數(shù)里面,返回this對象的對應(yīng)的key的值,沒有就去this._data對象里面取對應(yīng)的key,再沒有去this._computed對象里面去對應(yīng)的key值。set函數(shù)就是直接返回修改this._data對應(yīng)key。
做好這些各種攔截工作。我們就可以直接從實力上訪問到我們相對應(yīng)的值了。(mvvm使我們第一塊代碼生成的實例)
mvvm.b // 2 mvvm.a // 1 mvvm.language // "Javascript"
如上圖看控制臺??梢栽O(shè)置值,可以獲取值,但是這不是響應(yīng)式的。
打開控制臺看一下
可以詳細(xì)的看到。只有_vm這個是proxy,我們需要的是,_data下面所有數(shù)據(jù)都是有攔截代理的;下面我們就去實現(xiàn)它。
實現(xiàn)所有數(shù)據(jù)代理攔截我們首先在Mvvm里面加一個initObserve,如下
function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) + initObserve.call(this, data) // 初始化data的Observe return this._vm }
initObserve這個函數(shù)主要是把,this._data都加上代理。如下
function initObserve(data) { this._data = observe(data) // 把所有observe都賦值到 this._data } // 分開這個主要是為了下面遞歸調(diào)用 function observe(data) { if (!data || typeof data !== "object") return data // 如果不是對象直接返回值 return new Observe(data) // 對象調(diào)用Observe }
下面主要實現(xiàn)Observe類
// Observe類 class Observe { constructor(data) { this.dep = new Dep() // 訂閱類,后面會介紹 for (let key in data) { data[key] = observe(data[key]) // 遞歸調(diào)用子對象 } return this.proxy(data) } proxy(data) { let dep = this.dep return new Proxy(data, { get: (target, key, receiver) => { return Reflect.get(target, key, receiver) }, set: (target, key, value) => { const result = Reflect.set(target, key, observe(value)) // 對于新添加的對象也要進(jìn)行添加observe return result } }) } }
這樣子,通過我們層層遞歸添加proxy,把我們的_data對象都添加一遍,再看一下控制臺
很不錯,_data也有proxy了,很王祖藍(lán)式的完美。
看到我們的html的界面,都是沒有數(shù)據(jù)的,上面我們把數(shù)據(jù)都準(zhǔn)備好了,下面我們就開始把數(shù)據(jù)結(jié)合到html的界面上。
套數(shù)據(jù),實現(xiàn)hmtl界面先把計算屬性這個html注釋掉,后面進(jìn)行實現(xiàn)
然后在Mvvm函數(shù)中增加一個編譯函數(shù),?號表示是添加的函數(shù)
function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) + new Compile(this.$options.el, vm) // 添加一個編譯函數(shù) return this._vm }
上面我們添加了一個Compile的構(gòu)造函數(shù)。把配置的el作為參數(shù)傳機(jī)進(jìn)來,把生成proxy的實例vm也傳進(jìn)去,這樣子我們就可以拿到vm下面的數(shù)據(jù),下面我們就去實現(xiàn)它。順序讀注釋就可以了,很好理解
// 編譯類 class Compile { constructor (el, vm) { this.vm = vm // 把傳進(jìn)來的vm 存起來,因為這個vm.a = 1 沒毛病 let element = document.querySelector(el) // 拿到 app 節(jié)點 let fragment = document.createDocumentFragment() // 創(chuàng)建fragment代碼片段 fragment.append(element) // 把a(bǔ)pp節(jié)點 添加到 創(chuàng)建fragment代碼片段中 this.replace(fragment) // 套數(shù)據(jù)函數(shù) document.body.appendChild(fragment) // 最后添加到body中 } replace(frag) { let vm = this.vm // 拿到之前存起來的vm // 循環(huán)frag.childNodes Array.from(frag.childNodes).forEach(node => { let txt = node.textContent // 拿到文本 例如:"開發(fā)語言:{{language}}" let reg = /{{(.*?)}}/g // 定義匹配正則 if (node.nodeType === 3 && reg.test(txt)) { replaceTxt() function replaceTxt() { // 如果匹配到的話,就替換文本 node.textContent = txt.replace(reg, (matched, placeholder) => { return placeholder.split(".").reduce((obj, key) => { return obj[key] // 例如:去vm.makeUp.one對象拿到值 }, vm) }) } } // 如果還有字節(jié)點,并且長度不為0 if (node.childNodes && node.childNodes.length) { // 直接遞歸匹配替換 this.replace(node) } }) } }
上面的編譯函數(shù),總之就是一句話,千方百計的把{{xxx}}的占位符通過正則替換成真實的數(shù)據(jù)。
然后刷新瀏覽器,鐺鐺檔鐺鐺檔,就出現(xiàn)我們要的數(shù)據(jù)了。
很好很好,但是我們現(xiàn)在的數(shù)據(jù)并不是改變了 就發(fā)生變化了。還需要訂閱發(fā)布和watcher來配合,才能做好改變數(shù)據(jù)就發(fā)生變化了。下面我們先實現(xiàn)訂閱發(fā)布。
實現(xiàn)訂閱發(fā)布訂閱發(fā)布其實是一種常見的程序設(shè)計模式,簡單直白來說就是:
把函數(shù)push到一個數(shù)組里面,然后循環(huán)數(shù)據(jù)調(diào)用函數(shù)。
例如:舉個很直白的例子
let arr = [] let a = () => {console.log("a")} arr.push(a) // 訂閱a函數(shù) arr.push(a) // 又訂閱a函數(shù) arr.push(a) // 雙訂閱a函數(shù) arr.forEach(fn => fn()) // 發(fā)布所有 // 此時會打印三個a
很簡單吧。下面我們?nèi)崿F(xiàn)我們的代碼
// 訂閱類 class Dep { constructor() { this.subs = [] // 定義數(shù)組 } // 訂閱函數(shù) addSub(sub) { this.subs.push(sub) } // 發(fā)布函數(shù) notify() { this.subs.filter(item => typeof item !== "string").forEach(sub => sub.update()) } }
訂閱發(fā)布是寫好了,但是在什么時候訂閱,什么時候發(fā)布??這時候,我們是在數(shù)據(jù)獲取的時候訂閱watcher,然后在數(shù)據(jù)設(shè)置的時候發(fā)布watcher,在上面的Observe類里面里面,看?號的代碼。 .
... //省略代碼 ... proxy(data) { let dep = this.dep return new Proxy(data, { // 攔截get get: (target, prop, receiver) => { + if (Dep.target) { // 如果之前是push過的,就不用重復(fù)push了 if (!dep.subs.includes(Dep.exp)) { dep.addSub(Dep.exp) // 把Dep.exp。push到sub數(shù)組里面,訂閱 dep.addSub(Dep.target) // 把Dep.target。push到sub數(shù)組里面,訂閱 } + } return Reflect.get(target, prop, receiver) }, // 攔截set set: (target, prop, value) => { const result = Reflect.set(target, prop, observe(value)) + dep.notify() // 發(fā)布 return result } }) }
上面代碼說到,watcher是什么鬼?然后發(fā)布里面的sub.update()又是什么鬼??
帶著一堆疑問我們來到了watcher
實現(xiàn)watcher看詳細(xì)注釋
// Watcher類 class Watcher { constructor (vm, exp, fn) { this.fn = fn // 傳進(jìn)來的fn this.vm = vm // 傳進(jìn)來的vm this.exp = exp // 傳進(jìn)來的匹配到exp 例如:"language","makeUp.one" Dep.exp = exp // 給Dep類掛載一個exp Dep.target = this // 給Dep類掛載一個watcher對象,跟新的時候就用到了 let arr = exp.split(".") let val = vm arr.forEach(key => { val = val[key] // 獲取值,這時候會粗發(fā)vm.proxy的get()函數(shù),get()里面就添加addSub訂閱函數(shù) }) Dep.target = null // 添加了訂閱之后,把Dep.target清空 } update() { // 設(shè)置值會觸發(fā)vm.proxy.set函數(shù),然后調(diào)用發(fā)布的notify, // 最后調(diào)用update,update里面繼續(xù)調(diào)用this.fn(val) let exp = this.exp let arr = exp.split(".") let val = this.vm arr.forEach(key => { val = val[key] }) this.fn(val) } }
Watcher類就是我們要訂閱的watcher,里面有回調(diào)函數(shù)fn,有update函數(shù)調(diào)用fn,
我們都弄好了。但是在哪里添加watcher呢??如下代碼
在Compile里面
... ... function replaceTxt() { node.textContent = txt.replace(reg, (matched, placeholder) => { + new Watcher(vm, placeholder, replaceTxt); // 監(jiān)聽變化,進(jìn)行匹配替換內(nèi)容 return placeholder.split(".").reduce((val, key) => { return val[key] }, vm) }) }
添加好有所的東西了,我們看一下控制臺。修改發(fā)現(xiàn)果然起作用了。
然后我們回顧一下所有的流程,然后看見古老(我也是別的地方弄來的)的一張圖。
幫助理解嘛
響應(yīng)式的數(shù)據(jù)我們都已經(jīng)完成了,下面我們完成一下雙向綁定。
實現(xiàn)雙向綁定看到我們html里面有個,v-module綁定了一個language,然后在Compile類里面的replace函數(shù),我們加上
replace(frag) { let vm = this.vm Array.from(frag.childNodes).forEach(node => { let txt = node.textContent let reg = /{{(.*?)}}/g // 判斷nodeType + if (node.nodeType === 1) { const nodeAttr = node.attributes // 屬性集合 Array.from(nodeAttr).forEach(item => { let name = item.name // 屬性名 let exp = item.value // 屬性值 // 如果屬性有 v- if (name.includes("v-")){ node.value = vm[exp] node.addEventListener("input", e => { // 相當(dāng)于給this.language賦了一個新值 // 而值的改變會調(diào)用set,set中又會調(diào)用notify,notify中調(diào)用watcher的update方法實現(xiàn)了更新操作 vm[exp] = e.target.value }) } }); + } ... ... } }
上面的方法就是,讓我們的input節(jié)點綁定一個input事件,然后當(dāng)input事件觸發(fā)的時候,改變我們的值,而值的改變會調(diào)用set,set中又會調(diào)用notify,notify中調(diào)用watcher的update方法實現(xiàn)了更新操作。
然后我們看一下,界面
雙向數(shù)據(jù)綁定我們基本完成了,別忘了,我們上面還有個注釋掉的計算屬性。
計算屬性先把
計算屬性:{{sum}}
注釋去掉,以為上面一開始initVm函數(shù)里面,我們加了這個代碼return this[key] || this._data[key] || this._computed[key],到這里大家都明白了,只需要把this._computed也加一個watcher就好了。function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) initObserve.call(this, data) + initComputed.call(this) // 添加計算函數(shù),改變this指向 new Compile(this.$options.el, vm) return this._vm } function initComputed() { let vm = this let computed = this.$options.computed // 拿到配置的computed vm._computed = {} if (!computed) return // 沒有計算直接返回 Object.keys(computed).forEach(key => { // 相當(dāng)于把sum里的this指向到this._vm,然后就可以拿到this.a、this、b this._computed[key] = computed[key].call(this._vm) // 添加新的Watcher new Watcher(this._vm, key, val => { // 每次設(shè)置的時候都會計算 this._computed[key] = computed[key].call(this._vm) }) }) }
上面的initComputed 就是添加一個watcher,大致流程:
this._vm改變 ---> vm.set() ---> notify() -->update()-->更新界面
最后看看圖片
一切似乎沒什么毛病~~~~
添加mounted鉤子添加mounted也很簡單
// 寫法和Vue一樣 let mvvm = new Mvvm({ el: "#app", data: { ... ... }, computed: { ... ... }, mounted() { console.log("i am mounted", this.a) } })
在new Mvvm里面添加mounted,
然后到function Mvvm里面加上
function Mvvm(options = {}) { this.$options = options let data = this._data = this.$options.data let vm = initVm.call(this) initObserve.call(this, data) initComputed.call(this) new Compile(this.$options.el, vm) + mounted.call(this._vm) // 加上mounted,改變指向 return this._vm } // 運(yùn)行mounted + function mounted() { let mounted = this.$options.mounted mounted && mounted.call(this) + }
執(zhí)行之后會打印出
i am mounted 1
完結(jié)~~~~撒花
ps:編譯里面的,參考到這個大神的操作。@chenhongdong,謝謝大佬
最后附上,源代碼地址,直接下載運(yùn)行就可以啦。
源碼地址:https://github.com/naihe138/proxy-mvvm
預(yù)覽地址:http://gitblog.naice.me/proxy-mvvm/index.html
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/95880.html
摘要:前言在說架構(gòu)之前,先說說框架吧。在架構(gòu)中就是這個轉(zhuǎn)接頭。當(dāng)一個新框架誕生后,關(guān)注點從學(xué)習(xí)這個框架,慢慢變成了這個框架是如何設(shè)計的,解決什么樣的問題。前幾年使用過各種框架,小到,大到。 前言 在說 MVC 架構(gòu)之前,先說說PHP框架吧。很多很多學(xué)完P(guān)HP語言的人,面對的就是PHP各種各樣的框架。什么TP啊、Yii啊、CI啊,還有很流行的laravel啊等等。 他們的大部分都會說自己是基于...
摘要:綁定實現(xiàn)的歷史綁定的基礎(chǔ)是事件。但臟檢查機(jī)制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機(jī)制的嘗試,在中引入。掙扎了一段時間后谷歌團(tuán)隊宣布收回的提議,并在中完全刪除了實現(xiàn)。自然全軍覆沒其他各大瀏覽器實現(xiàn)的時間也較晚。 綁定實現(xiàn)的歷史 綁定的基礎(chǔ)是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發(fā) MVVM 框架的首要問題。主流框架的處理有一下三...
摘要:綁定實現(xiàn)的歷史綁定的基礎(chǔ)是事件。但臟檢查機(jī)制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機(jī)制的嘗試,在中引入。掙扎了一段時間后谷歌團(tuán)隊宣布收回的提議,并在中完全刪除了實現(xiàn)。自然全軍覆沒其他各大瀏覽器實現(xiàn)的時間也較晚。 綁定實現(xiàn)的歷史 綁定的基礎(chǔ)是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發(fā) MVVM 框架的首要問題。主流框架的處理有一下三...
摘要:在中,格式是,所以需要把格式統(tǒng)一為注冊表的標(biāo)準(zhǔn)。注冊表的二進(jìn)制值及關(guān)鍵信息如下開關(guān)長度地址是否跳過本地代理地址通過在中導(dǎo)入文件的方式執(zhí)行并立即生效。本代碼可以根據(jù)需要自動設(shè)置代理。 聲明下:不同于網(wǎng)絡(luò)上千百篇方法,下文是經(jīng)過各種嚴(yán)格測試都通過的,同時也是一個實驗的過程,排除了各種不靠譜的方法。有需要的可以評論來討論,想要源碼和相關(guān)參考文獻(xiàn)或筆記的,也可以找我。 思路及啟發(fā) 先說一下我這...
閱讀 3118·2021-11-23 09:51
閱讀 1983·2021-09-09 09:32
閱讀 1094·2019-08-30 15:53
閱讀 2965·2019-08-30 11:19
閱讀 2475·2019-08-29 14:15
閱讀 1442·2019-08-29 13:52
閱讀 560·2019-08-29 12:46
閱讀 2827·2019-08-26 12:18