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