日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

用原生 JS 实现 MVVM 框架2——单向绑定

發布時間:2024/1/17 javascript 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用原生 JS 实现 MVVM 框架2——单向绑定 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上一篇寫了實現 MVVM 框架的一些基本概念

本篇用代碼來實現一個完整的 MVVM 框架

思考

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

<body> <div id='app'>{{name}}</div> <script> function MVVM(){//todo... } var vm = new MVVM({el:'#app',data:{name:'zhangsan'} }) </script> </body>

如何實現上述 MVVM 呢?

回想下這篇講的觀察者模式和數據監聽:

  • 主題(subject)是什么?
  • 觀察者(observer)是什么?
  • 觀察者何時訂閱主題?
  • 主題何時通知更新?
  • 簡單回答下:
    上面例子中,主題應該是data的name屬性,觀察者是試圖里的{{name}},當一開始執行 MVVM 初始化(根據el解析模板發現{{name}})的時候訂閱主題,當data.name發生改變的時候,通知觀察者更新內容,我們可以在一開始監控data.name,當用戶修改data.name的時候調用主題的subject.ontify。

    單向綁定

    有如下 HTML

    <div id="app"><h1>{{name}}'is age is {{age}}</h1> </div>

    從上面 HTML 中我們看出,操作的節點是div#app,需要的數據是name和age,所以實例化 MVVM 可以需要傳遞兩個參數element和data

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

    我們 MVVM 的構造函數應該怎么寫呢?我們只需要做兩件事情:

  • 我們需要觀察這些數據,當以后這些數據變動時,會做一些事情去調用
  • 需要解析這個模板,把模板中的一些符號替換成對應的數據
  • 初始化是必須做的,將實例化的數據存在自身上面,后面要用,這里就不敘述了。

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

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

    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 //正則或者可以寫稱/{{(.+?)}}/glet matchwhile(match = reg.exec(textNode.textContent)){ //將匹配到的內容賦值給match,match是一個數組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)}) //創建一個觀察者} }

    假設用戶去修改數據時,那數據該如何進行實時的變動呢?

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

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

    function observe(data){if(!data || typeof data !== 'object')returnfor(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(){return val},set(newVal){val = newValsubject.notify()}})} }

    問題是創建了觀察者后什么時候去觀察這個主題?

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

    思考到這里發現行不通了,就需要換種思路了。

    當創建觀察者時,會調用getValue(),它做什么事情呢,把我設置為場上權限最高的觀察者,因為頁面中有很多觀察者,此時this.key,就是我要訂閱的主題,當我調用this.vm.data[this.key]就等于調用了observe的get方法,因為剛剛我已經把觀察者設置為場上權限最高者,此時currentObserver是存在的,這時觀察者就開始訂閱主題,訂閱的之后在把權限去掉

    let currentObserver = null class Observer{constructor(vm,key,cb){this.subjects = {}this.vm = vmthis.key = keythis.cb = cbthis.value = this.getValue()}getValue(){currentObserver = thislet value = this.vm.data[this.key]currentObserver = nullreturn value} }

    通過currentObserver去訂閱主題,因為在創建觀察者時調用了getValue方法,把currentObserver設置為Observer,通過它去訂閱主題

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

    主題的構造函數

    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} }

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

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

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

    function observe(data){if(!data || typeof data !== 'object')returnfor(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 = newValsubject.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 = vmthis.key = keythis.cb = cbthis.value = this.getValue()}update(){let oldVal = this.valuelet value = this.getValue()if(value !== oldVal){this.value = valuethis.cb.call(this.vm,value,oldVal)}}subscribeTo(subject){if(!this.subjects[subject.id]){subject.addObserver(this)this.subjects[subject.id] = subject}}getValue(){currentObserver = thislet value = this.vm.data[this.key]currentObserver = nullreturn 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 = /{{([^}]*)}}/glet matchwhile(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)

    本篇詳細講述了 MVVM 單項綁定的原理,下一篇講述雙向綁定

    用原生 JS 實現 MVVM 框架MVVM 框架系列:
    用原生 JS 實現 MVVM 框架1——觀察者模式和數據監控

    總結

    以上是生活随笔為你收集整理的用原生 JS 实现 MVVM 框架2——单向绑定的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。