成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

用原生 JS 實(shí)現(xiàn) MVVM 框架2——單向綁定

Zoom / 1359人閱讀

摘要:上一篇寫了實(shí)現(xiàn)框架的一些基本概念本篇用代碼來實(shí)現(xiàn)一個完整的框架思考假設(shè)有如下代碼,里面的會和試圖中的一一映射,修改的值,會直接引起試圖中對應(yīng)數(shù)據(jù)的變化如何實(shí)現(xiàn)上述呢回想下這篇講的觀察者模式和數(shù)據(jù)監(jiān)聽主題是什么觀察者是什么觀察者何時(shí)訂閱主題主

上一篇寫了實(shí)現(xiàn) MVVM 框架的一些基本概念

本篇用代碼來實(shí)現(xiàn)一個完整的 MVVM 框架

思考

假設(shè)有如下代碼,data里面的name會和試圖中的{{name}}——一一映射,修改data的值,會直接引起試圖中對應(yīng)數(shù)據(jù)的變化


{{name}}

如何實(shí)現(xiàn)上述 MVVM 呢?

回想下這篇講的觀察者模式和數(shù)據(jù)監(jiān)聽:

主題(subject)是什么?

觀察者(observer)是什么?

觀察者何時(shí)訂閱主題?

主題何時(shí)通知更新?

簡單回答下:
上面例子中,主題應(yīng)該是dataname屬性,觀察者是試圖里的{{name}},當(dāng)一開始執(zhí)行 MVVM 初始化(根據(jù)el解析模板發(fā)現(xiàn){{name}})的時(shí)候訂閱主題,當(dāng)data.name發(fā)生改變的時(shí)候,通知觀察者更新內(nèi)容,我們可以在一開始監(jiān)控data.name,當(dāng)用戶修改data.name的時(shí)候調(diào)用主題的subject.ontify。

單向綁定

有如下 HTML

{{name}}"is age is {{age}}

從上面 HTML 中我們看出,操作的節(jié)點(diǎn)是div#app,需要的數(shù)據(jù)是nameage,所以實(shí)例化 MVVM 可以需要傳遞兩個參數(shù)elementdata

let vm = MVVM({
    element:"#app",
    data:{
        name:"zhangsan",
        age:20
    }
})
setInterval(function(){
    vm.data.age++
},2000)

我們 MVVM 的構(gòu)造函數(shù)應(yīng)該怎么寫呢?我們只需要做兩件事情:

我們需要觀察這些數(shù)據(jù),當(dāng)以后這些數(shù)據(jù)變動時(shí),會做一些事情去調(diào)用

需要解析這個模板,把模板中的一些符號替換成對應(yīng)的數(shù)據(jù)

初始化是必須做的,將實(shí)例化的數(shù)據(jù)存在自身上面,后面要用,這里就不敘述了。

class MVVM{
    constructor(options){
        init(options)
        observe(this.data)
        this.compile()
    }
    init(options){
        this.element = document.querySelector(options.element)
        this.data = options.data
    }
}

先看compile這個方法,它就是在編譯頁面中的節(jié)點(diǎn),如果節(jié)點(diǎn)里還有孩子,需要再去遍歷這些孩子,如果遍歷到文本,就進(jìn)行下一步文本替換。

compile(){    //雖然這里可以直接對節(jié)點(diǎn)進(jìn)行遍歷,但最好還是分開來比較好點(diǎn)
    this.traverse(this.el)
}
traverse(node){        //對節(jié)點(diǎn)進(jìn)行遍歷,如果遇到元素節(jié)點(diǎn),用遞歸繼續(xù)遍歷直到遍歷到都是文本為止,進(jìn)行下一步頁面渲染
    node.childNodes.forEach(childNode=>{
        if(childNode.nodeType === 1){
            this.traverse(childNode)
        }else if(childNode.nodeType === 3){
            this.renderText(childNode)
        }
    })
}
renderText(textNode){    //到這一步,已經(jīng)獲取到頁面中的文本了,用正則去匹配
    let reg = /{{([^}]*)}}/g    //正則或者可以寫稱/{{(.+?)}}/g
    let match
    while(match = reg.exec(textNode.textContent)){    //將匹配到的內(nèi)容賦值給match,match是一個數(shù)組
        let raw = match[0]    
        let key = match[1].trim() 
        textNode.textContent = textNode.textContent.replace(raw,this.data[key])    //頁面渲染
        new Observer(this,key,function(val,oldVal){
                textNode.textContent = textNode.textContent.replace(oldVal,val)
            })    //創(chuàng)建一個觀察者
    }
}

假設(shè)用戶去修改數(shù)據(jù)時(shí),那數(shù)據(jù)該如何進(jìn)行實(shí)時(shí)的變動呢?

這里就引入了觀察者和主題的概念,我們在解析的過程中創(chuàng)建一個個觀察者,這個觀察者就觀察這個屬性,解析到下個屬性在創(chuàng)建一個觀察者,并觀察這個屬性。

觀察這個屬性就是訂閱這個主題,我們在this.compile()解析完后創(chuàng)建一個觀察者,它有個方法,如果這個屬性變動,我就會修改頁面。

function observe(data){
    if(!data || typeof data !== "object")return
    for(let key in data){
        let val = data[key]
        let subject = new Subject()    //創(chuàng)建主題
        if(typeof val === "object"){
            observe(val)
        }
        Object.defineProperty(data,key,{
            configurable:true,
            enumerable:true,
            get(){
                return val
            },
            set(newVal){
                val = newVal
                subject.notify()
            }
        })
    }
}

問題是創(chuàng)建了觀察者后什么時(shí)候去觀察這個主題?

在創(chuàng)建后立刻觀察這個主題,可是主題在哪?觀察者有了,就是剛剛new的時(shí)候。主題是在observe遍歷屬性時(shí)創(chuàng)建的。主題存在在observe局部變量中,外面是訪問不到的,那觀察者怎樣訂閱這個主題呢?

思考到這里發(fā)現(xiàn)行不通了,就需要換種思路了。

當(dāng)創(chuàng)建觀察者時(shí),會調(diào)用getValue(),它做什么事情呢,把我設(shè)置為場上權(quán)限最高的觀察者,因?yàn)轫撁嬷杏泻芏嘤^察者,此時(shí)this.key,就是我要訂閱的主題,當(dāng)我調(diào)用this.vm.data[this.key]就等于調(diào)用了observeget方法,因?yàn)閯倓偽乙呀?jīng)把觀察者設(shè)置為場上權(quán)限最高者,此時(shí)currentObserver是存在的,這時(shí)觀察者就開始訂閱主題,訂閱的之后在把權(quán)限去掉

let currentObserver = null
class Observer{
    constructor(vm,key,cb){
        this.subjects = {}
        this.vm = vm
        this.key = key
        this.cb = cb
        this.value = this.getValue()
    }
    getValue(){
        currentObserver = this
        let value = this.vm.data[this.key]
        currentObserver = null
        return value
    }
}

通過currentObserver去訂閱主題,因?yàn)樵趧?chuàng)建觀察者時(shí)調(diào)用了getValue方法,把currentObserver設(shè)置為Observer,通過它去訂閱主題

get:function(){
    if(currentObserver){
        currentObserver.subscribeTo(subject)
    }
}

主題的構(gòu)造函數(shù)

let id = 0
class Subject{
    constructor(){
        this.id = id++
        this.observers = []
    }
    addObserver(observer){
        this.observers.push(observer)
    }
    notify(){
        this.observers.forEach(observer=>{
            observer.update()
        })
    }
}

添加觀察者

subscribeTo(subject){
    if(!this.subjects[subject.id]){
        subject.addObserver(this)
        this.subjects[subject.id] = subject
    }
}

更新頁面數(shù)據(jù),舊值通過自身屬性獲取,新值通過getValue方法獲取

update(){
    let oldVal = this.value
    let value = this.getValue()
    if(value !== oldVal){
        this.value = value
        this.cb.call(this.vm,value,oldVal)
    }
}

最后貼上完整的單向綁定的代碼

function observe(data){
    if(!data || typeof data !== "object")return
    for(let key in data){
        let val = data[key]
        let subject = new Subject()
        if(typeof val === "object"){
            observe(val)
        }
        Object.defineProperty(data,key,{
            configurable:true,
            enumerable:true,
            get(){
                if(currentObserver){
                    currentObserver.subscribeTo(subject)
                }
                return val
            },
            set(newVal){
                val = newVal
                subject.notify()
            }
        })
    }
}
let id = 0
class Subject{
    constructor(){
        this.id = id++
        this.observers = []
    }
    addObserver(observer){
        this.observers.push(observer)
    }
    notify(){
        this.observers.forEach(observer=>{
            observer.update()
        })
    }
}
let currentObserver = null
class Observer{
    constructor(vm,key,cb){
        this.subjects = {}
        this.vm = vm
        this.key = key
        this.cb = cb
        this.value = this.getValue()
    }
    update(){
        let oldVal = this.value
        let value = this.getValue()
        if(value !== oldVal){
            this.value = value
            this.cb.call(this.vm,value,oldVal)
        }
    }
    subscribeTo(subject){
        if(!this.subjects[subject.id]){
            subject.addObserver(this)
            this.subjects[subject.id] = subject
        }
    }
    getValue(){
        currentObserver = this
        let value = this.vm.data[this.key]
        currentObserver = null
        return value
    }
}
class mvvm{
    constructor(options){
        this.init(options)
        observe(this.data)
        this.compile()
    }
    init(options){
        this.el = document.querySelector(options.el)
        this.data = options.data
    }
    compile(){
        this.traverse(this.el)
    }
    traverse(node){
        node.childNodes.forEach(childNode=>{
            if(childNode.nodeType === 1){
                this.traverse(childNode)
            }else if(childNode.nodeType === 3){
                this.renderText(childNode)
            }
        })
    }
    renderText(textNode){
        let reg = /{{([^}]*)}}/g
        let match
        while(match = reg.exec(textNode.textContent)){
            let raw = match[0]
            let key = match[1].trim()
            textNode.textContent = textNode.textContent.replace(raw,this.data[key])
            new Observer(this,key,function(val,oldVal){
                textNode.textContent = textNode.textContent.replace(oldVal,val)
            })
        }
    }
}
let vm = new mvvm({
    el:"#app",
    data:{
        name:"uccs",
        age:20
    }
})
setInterval(function(){
    vm.data.age++
},2000)

本篇詳細(xì)講述了 MVVM 單項(xiàng)綁定的原理,下一篇講述雙向綁定

用原生 JS 實(shí)現(xiàn) MVVM 框架MVVM 框架系列:
用原生 JS 實(shí)現(xiàn) MVVM 框架1——觀察者模式和數(shù)據(jù)監(jiān)控

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97279.html

相關(guān)文章

  • 【教學(xué)向】150行代碼教你實(shí)現(xiàn)一個低配版的MVVM庫(1)- 原理篇

    摘要:模塊則負(fù)責(zé)維護(hù),以及各個模塊間的調(diào)度思考題了解了的實(shí)現(xiàn)機(jī)制,你能否自己動手也試著用百來行代碼實(shí)現(xiàn)一個庫呢好了本教程第一部分設(shè)計(jì)篇就寫到這里,具體請移步下一篇教學(xué)向行代碼教你實(shí)現(xiàn)一個低配版的庫代碼篇我會用給出一版實(shí)現(xiàn)。 適讀人群 本文適合對MVVM有一定了解(如有主流框架ng,vue等使用經(jīng)驗(yàn)配合本文服用則效果更佳),雖然會用這類框架,但是對框架底層核心實(shí)現(xiàn)又不太清楚,或者能說出個所以然...

    selfimpr 評論0 收藏0
  • 使 Proxy 實(shí)現(xiàn)簡單的 MVVM 模型

    摘要:綁定實(shí)現(xiàn)的歷史綁定的基礎(chǔ)是事件。但臟檢查機(jī)制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機(jī)制的嘗試,在中引入。掙扎了一段時(shí)間后谷歌團(tuán)隊(duì)宣布收回的提議,并在中完全刪除了實(shí)現(xiàn)。自然全軍覆沒其他各大瀏覽器實(shí)現(xiàn)的時(shí)間也較晚。 綁定實(shí)現(xiàn)的歷史 綁定的基礎(chǔ)是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發(fā) MVVM 框架的首要問題。主流框架的處理有一下三...

    BetaRabbit 評論0 收藏0
  • 使 Proxy 實(shí)現(xiàn)簡單的 MVVM 模型

    摘要:綁定實(shí)現(xiàn)的歷史綁定的基礎(chǔ)是事件。但臟檢查機(jī)制隨之帶來的就是性能問題。是谷歌對于簡化雙向綁定機(jī)制的嘗試,在中引入。掙扎了一段時(shí)間后谷歌團(tuán)隊(duì)宣布收回的提議,并在中完全刪除了實(shí)現(xiàn)。自然全軍覆沒其他各大瀏覽器實(shí)現(xiàn)的時(shí)間也較晚。 綁定實(shí)現(xiàn)的歷史 綁定的基礎(chǔ)是 propertyChange 事件。如何得知 viewModel 成員值的改變一直是開發(fā) MVVM 框架的首要問題。主流框架的處理有一下三...

    MarvinZhang 評論0 收藏0
  • React 可視化開發(fā)工具 Shadow Widget 非正經(jīng)入門(之四:flux、mvc、mvvm

    摘要:是分發(fā)器,是數(shù)據(jù)與邏輯處理器,會在注冊針對各個命令字的響應(yīng)回調(diào)函數(shù)。當(dāng)按如下方式觸發(fā)回調(diào)時(shí),回調(diào)函數(shù)具備事件的特性。 本系列博文從 Shadow Widget 作者的視角,解釋該框架的設(shè)計(jì)要點(diǎn)。本篇解釋 Shadow Widget 在 MVC、MVVM、Flux 框架之間如何做選擇。 showImg(https://segmentfault.com/img/bVOODj?w=380&h...

    msup 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<