最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue實(shí)現(xiàn)簡(jiǎn)易的雙向數(shù)據(jù)綁定

 更新時(shí)間:2020年12月29日 09:31:24   作者:LastStarDust  
這篇文章主要介紹了vue如何實(shí)現(xiàn)簡(jiǎn)易的雙向數(shù)據(jù)綁定,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下

主要是通過(guò)數(shù)據(jù)劫持和發(fā)布訂閱一起實(shí)現(xiàn)的

  • 雙向數(shù)據(jù)綁定 數(shù)據(jù)更新時(shí),可以更新視圖 視圖的數(shù)據(jù)更新是,可以反向更新模型

組成說(shuō)明

  • Observe監(jiān)聽(tīng)器 劫持?jǐn)?shù)據(jù), 感知數(shù)據(jù)變化, 發(fā)出通知給訂閱者, 在get中將訂閱者添加到訂閱器中
  • Dep消息訂閱器 存儲(chǔ)訂閱者, 通知訂閱者調(diào)用更新函數(shù)
  • 訂閱者Wather取出模型值,更新視圖
  • 解析器Compile 解析指令, 更新模板數(shù)據(jù), 初始化視圖, 實(shí)例化一個(gè)訂閱者, 將更新函數(shù)綁定到訂閱者上, 可以在接收通知二次更新視圖, 對(duì)于v-model還需要監(jiān)聽(tīng)input事件,實(shí)現(xiàn)視圖到模型的數(shù)據(jù)流動(dòng)

基本結(jié)構(gòu)

HTML模板

  <div id="app">
    <form>
      <input type="text" v-model="username">
    </form>
    <p v-bind="username"></p>
  </div>
  • 一個(gè)根節(jié)點(diǎn)#app
  • 表單元素,里面包含input, 使用v-model指令綁定數(shù)據(jù)username
  • p元素上使用v-bind綁定數(shù)username

MyVue類(lèi)

簡(jiǎn)單的模擬Vue類(lèi)

將實(shí)例化時(shí)的選項(xiàng)options, 數(shù)據(jù)options.data進(jìn)行保存 此外,通過(guò)options.el獲取dom元素,存儲(chǔ)到$el上

    class MyVue {
      constructor(options) {
        this.$options = options
        this.$el = document.querySelector(this.$options.el)
        this.$data = options.data
      }
    }

實(shí)例化MyVue

實(shí)例化一個(gè)MyVue,傳遞選項(xiàng)進(jìn)去,選項(xiàng)中指定綁定的元素el和數(shù)據(jù)對(duì)象data

    const myVm = new MyVue({
      el: '#app',
      data: {
        username: 'LastStarDust'
      }
    })

Observe監(jiān)聽(tīng)器實(shí)現(xiàn)

劫持?jǐn)?shù)據(jù)是為了修改數(shù)據(jù)的時(shí)候可以感知, 發(fā)出通知, 執(zhí)行更新視圖操作

    class MyVue {
      constructor(options) {
        // ...
        // 監(jiān)視數(shù)據(jù)的屬性
        this.observable(this.$data)
      }
      // 遞歸遍歷數(shù)據(jù)對(duì)象的所有屬性, 進(jìn)行數(shù)據(jù)屬性的劫持 { username: 'LastStarDust' }
      observable(obj) {
        // obj為空或者不是對(duì)象, 不做任何操作
        const isEmpty = !obj || typeof obj !== 'object'
        if(isEmpty) {
          return
        }

        // ['username']
        const keys = Object.keys(obj)
        keys.forEach(key => {
          // 如果屬性值是對(duì)象,遞歸調(diào)用
          let val = obj[key]
          if(typeof val === 'object') {
            this.observable(val)
          }
          // this.defineReactive(this.$data, 'username', 'LastStarDust')
          this.defineReactive(obj, key, val)
        })

        return obj
      }

      // 數(shù)據(jù)劫持,修改屬性的get和set方法
      defineReactive(obj, key, val) {
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            console.log(`取出${key}屬性值: 值為${val}`)
            return val
          },
          set(newVal) {
            // 沒(méi)有發(fā)生變化, 不做更新
            if(newVal === val) {
              return
            }
            console.log(`更新屬性${key}的值為: ${newVal}`)
            val = newVal
          }
        })
      }
    }

Dep消息訂閱器

存儲(chǔ)訂閱者, 收到通知時(shí),取出訂閱者,調(diào)用訂閱者的update方法

    // 定義消息訂閱器
    class Dep {
      // 靜態(tài)屬性 Dep.target,這是一個(gè)全局唯一 的Watcher,因?yàn)樵谕粫r(shí)間只能有一個(gè)全局的 Watcher
      static target = null
      constructor() {
        // 存儲(chǔ)訂閱者
        this.subs = []
      }
      // 添加訂閱者
      add(sub) {
        this.subs.push(sub)
      }
      // 通知
      notify() {
        this.subs.forEach(sub => {
          // 調(diào)用訂閱者的update方法
          sub.update()
        })
      }
    }

將消息訂閱器添加到數(shù)據(jù)劫持過(guò)程中

為每一個(gè)屬性添加訂閱者

      defineReactive(obj, key, val) {
        const dep = new Dep()
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            // 會(huì)在初始化時(shí), 觸發(fā)屬性get()方法,來(lái)到這里Dep.target有值,將其作為訂閱者存儲(chǔ)起來(lái),在觸發(fā)屬性的set()方法時(shí),調(diào)用notify方法
            if(Dep.target) {
              dep.add(Dep.target)
            }
            console.log(`取出${key}屬性值: 值為${val}`)
            return val
          },
          set(newVal) {
            // 沒(méi)有發(fā)生變化, 不做更新
            if(newVal === val) {
              return
            }
            console.log(`更新屬性${key}的值為: ${newVal}`)

            val = newVal
            dep.notify()
          }
        })
      }

訂閱者Wather

從模型中取出數(shù)據(jù)并更新視圖

    // 定義訂閱者類(lèi)
    class Wather {
      constructor(vm, exp, cb) {
        this.vm = vm // vm實(shí)例
        this.exp = exp // 指令對(duì)應(yīng)的字符串值, 如v-model="username", exp相當(dāng)于"username"
        this.cb = cb // 回到函數(shù) 更新視圖時(shí)調(diào)用
        this.value = this.get() // 將自己添加到消息訂閱器Dep中
      }

      get() {
        // 將當(dāng)前訂閱者作為全局唯一的Wather,添加到Dep.target上
        Dep.target = this
        // 獲取數(shù)據(jù),觸發(fā)屬性的getter方法
        const value = this.vm.$data[this.exp]
        // 在執(zhí)行添加到消息訂閱Dep后, 重置Dep.target
        Dep.target = null
        return value
      }

      // 執(zhí)行更新
      update() {
        this.run()
      }

      run() {
        // 從Model模型中取出屬性值
        const newVal = this.vm.$data[this.exp]
        const oldVal = this.value
        if(newVal === oldVal) {
          return false
        }
        // 執(zhí)行回調(diào)函數(shù), 將vm實(shí)例,新值,舊值傳遞過(guò)去
        this.cb.call(this.vm, newVal, oldVal)
      }
    }

解析器Compile

  • 解析模板指令,并替換模板數(shù)據(jù),初始化視圖;
  • 將模板指令對(duì)應(yīng)的節(jié)點(diǎn)綁定對(duì)應(yīng)的更新函數(shù),初始化相應(yīng)的訂閱器;
  • 初始化編譯器, 存儲(chǔ)el對(duì)應(yīng)的dom元素, 存儲(chǔ)vm實(shí)例, 調(diào)用初始化方法
  • 在初始化方法中, 從根節(jié)點(diǎn)開(kāi)始, 取出根節(jié)點(diǎn)的所有子節(jié)點(diǎn), 逐個(gè)對(duì)節(jié)點(diǎn)進(jìn)行解析
  • 解析節(jié)點(diǎn)過(guò)程中
  • 解析指令存在, 取出綁定值, 替換模板數(shù)據(jù), 完成首次視圖的初始化
  • 給指令對(duì)應(yīng)的節(jié)點(diǎn)綁定更新函數(shù), 并實(shí)例化一個(gè)訂閱器Wather
  • 對(duì)于v-model指令, 監(jiān)聽(tīng)'input'事件,實(shí)現(xiàn)視圖更新是,去更新模型的數(shù)據(jù)
    // 定義解析器
    // 解析指令,替換模板數(shù)據(jù),初始視圖
    // 模板的指令綁定更新函數(shù), 數(shù)據(jù)更新時(shí), 更新視圖
    class Compile {
      constructor(el, vm) {
        this.el = el
        this.vm = vm
        this.init(this.el)
      }

      init(el) {
        this.compileEle(el)
      }
      compileEle(ele) {
        const nodes = ele.children
		// 遍歷節(jié)點(diǎn)進(jìn)行解析
        for(const node of nodes) {
		 // 如果有子節(jié)點(diǎn),遞歸調(diào)用
          if(node.children && node.children.length !== 0) {
            this.compileEle(node)
          }

          // 指令時(shí)v-model并且是標(biāo)簽是輸入標(biāo)簽
          const hasVmodel = node.hasAttribute('v-model')
          const isInputTag = ['INPUT', 'TEXTAREA'].indexOf(node.tagName) !== -1
          if(hasVmodel && isInputTag) {
            const exp = node.getAttribute('v-model')
            const val = this.vm.$data[exp]
            const attr = 'value'
            // 初次模型值推到視圖層,初始化視圖
            this.modelToView(node, val, attr)
            // 實(shí)例化一個(gè)訂閱者, 將更新函數(shù)綁定到訂閱者上, 未來(lái)數(shù)據(jù)更新,可以更新視圖
            new Wather(this.vm, exp, (newVal)=> {
              this.modelToView(node, newVal, attr)
            })

            // 監(jiān)聽(tīng)視圖的改變
            node.addEventListener('input', (e) => {
              this.viewToModel(exp, e.target.value)
            })
          }
		 
		 // 指令時(shí)v-bind
          if(node.hasAttribute('v-bind')) {
            const exp = node.getAttribute('v-bind')
            const val = this.vm.$data[exp]
            const attr = 'innerHTML'
            // 初次模型值推到視圖層,初始化視圖
            this.modelToView(node, val, attr)
            // 實(shí)例化一個(gè)訂閱者, 將更新函數(shù)綁定到訂閱者上, 未來(lái)數(shù)據(jù)更新,可以更新視圖
            new Wather(this.vm, exp, (newVal)=> {
              this.modelToView(node, newVal, attr)
            })
          }
        }
      }
      // 將模型值更新到視圖
      modelToView(node, val, attr) {
        node[attr] = val
      }
      // 將視圖值更新到模型上
      viewToModel(exp, val) {
        this.vm.$data[exp] = val
      }
    }

完整代碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <form>
      <input type="text" v-model="username">
    </form>
    <div>
      <span v-bind="username"></span>
    </div>
    <p v-bind="username"></p>
  </div>
  <script>

    class MyVue {
      constructor(options) {
        this.$options = options
        this.$el = document.querySelector(this.$options.el)
        this.$data = options.data

        // 監(jiān)視數(shù)據(jù)的屬性
        this.observable(this.$data)

        // 編譯節(jié)點(diǎn)
        new Compile(this.$el, this)
      }
      // 遞歸遍歷數(shù)據(jù)對(duì)象的所有屬性, 進(jìn)行數(shù)據(jù)屬性的劫持 { username: 'LastStarDust' }
      observable(obj) {
        // obj為空或者不是對(duì)象, 不做任何操作
        const isEmpty = !obj || typeof obj !== 'object'
        if(isEmpty) {
          return
        }

        // ['username']
        const keys = Object.keys(obj)
        keys.forEach(key => {
          // 如果屬性值是對(duì)象,遞歸調(diào)用
          let val = obj[key]
          if(typeof val === 'object') {
            this.observable(val)
          }
          // this.defineReactive(this.$data, 'username', 'LastStarDust')
          this.defineReactive(obj, key, val)
        })

        return obj
      }

      // 數(shù)據(jù)劫持,修改屬性的get和set方法
      defineReactive(obj, key, val) {
        const dep = new Dep()
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            // 會(huì)在初始化時(shí), 觸發(fā)屬性get()方法,來(lái)到這里Dep.target有值,將其作為訂閱者存儲(chǔ)起來(lái),在觸發(fā)屬性的set()方法時(shí),調(diào)用notify方法
            if(Dep.target) {
              dep.add(Dep.target)
            }
            console.log(`取出${key}屬性值: 值為${val}`)
            return val
          },
          set(newVal) {
            // 沒(méi)有發(fā)生變化, 不做更新
            if(newVal === val) {
              return
            }
            console.log(`更新屬性${key}的值為: ${newVal}`)

            val = newVal
            dep.notify()
          }
        })
      }
    }

    // 定義消息訂閱器
    class Dep {
      // 靜態(tài)屬性 Dep.target,這是一個(gè)全局唯一 的Watcher,因?yàn)樵谕粫r(shí)間只能有一個(gè)全局的 Watcher
      static target = null
      constructor() {
        // 存儲(chǔ)訂閱者
        this.subs = []
      }
      // 添加訂閱者
      add(sub) {
        this.subs.push(sub)
      }
      // 通知
      notify() {
        this.subs.forEach(sub => {
          // 調(diào)用訂閱者的update方法
          sub.update()
        })
      }
    }

    // 定義訂閱者類(lèi)
    class Wather {
      constructor(vm, exp, cb) {
        this.vm = vm // vm實(shí)例
        this.exp = exp // 指令對(duì)應(yīng)的字符串值, 如v-model="username", exp相當(dāng)于"username"
        this.cb = cb // 回到函數(shù) 更新視圖時(shí)調(diào)用
        this.value = this.get() // 將自己添加到消息訂閱器Dep中
      }

      get() {
        // 將當(dāng)前訂閱者作為全局唯一的Wather,添加到Dep.target上
        Dep.target = this
        // 獲取數(shù)據(jù),觸發(fā)屬性的getter方法
        const value = this.vm.$data[this.exp]
        // 在執(zhí)行添加到消息訂閱Dep后, 重置Dep.target
        Dep.target = null
        return value
      }

      // 執(zhí)行更新
      update() {
        this.run()
      }

      run() {
        // 從Model模型中取出屬性值
        const newVal = this.vm.$data[this.exp]
        const oldVal = this.value
        if(newVal === oldVal) {
          return false
        }
        // 執(zhí)行回調(diào)函數(shù), 將vm實(shí)例,新值,舊值傳遞過(guò)去
        this.cb.call(this.vm, newVal, oldVal)
      }
    }

    // 定義解析器
    // 解析指令,替換模板數(shù)據(jù),初始視圖
    // 模板的指令綁定更新函數(shù), 數(shù)據(jù)更新時(shí), 更新視圖
    class Compile {
      constructor(el, vm) {
        this.el = el
        this.vm = vm
        this.init(this.el)
      }

      init(el) {
        this.compileEle(el)
      }
      compileEle(ele) {
        const nodes = ele.children
        for(const node of nodes) {
          if(node.children && node.children.length !== 0) {
            // 遞歸調(diào)用, 編譯子節(jié)點(diǎn)
            this.compileEle(node)
          }

          // 指令時(shí)v-model并且是標(biāo)簽是輸入標(biāo)簽
          const hasVmodel = node.hasAttribute('v-model')
          const isInputTag = ['INPUT', 'TEXTAREA'].indexOf(node.tagName) !== -1
          if(hasVmodel && isInputTag) {
            const exp = node.getAttribute('v-model')
            const val = this.vm.$data[exp]
            const attr = 'value'
            // 初次模型值推到視圖層,初始化視圖
            this.modelToView(node, val, attr)
            // 實(shí)例化一個(gè)訂閱者, 將更新函數(shù)綁定到訂閱者上, 未來(lái)數(shù)據(jù)更新,可以更新視圖
            new Wather(this.vm, exp, (newVal)=> {
              this.modelToView(node, newVal, attr)
            })

            // 監(jiān)聽(tīng)視圖的改變
            node.addEventListener('input', (e) => {
              this.viewToModel(exp, e.target.value)
            })
          }

          if(node.hasAttribute('v-bind')) {
            const exp = node.getAttribute('v-bind')
            const val = this.vm.$data[exp]
            const attr = 'innerHTML'
            // 初次模型值推到視圖層,初始化視圖
            this.modelToView(node, val, attr)
            // 實(shí)例化一個(gè)訂閱者, 將更新函數(shù)綁定到訂閱者上, 未來(lái)數(shù)據(jù)更新,可以更新視圖
            new Wather(this.vm, exp, (newVal)=> {
              this.modelToView(node, newVal, attr)
            })
          }
        }
      }
      // 將模型值更新到視圖
      modelToView(node, val, attr) {
        node[attr] = val
      }
      // 將視圖值更新到模型上
      viewToModel(exp, val) {
        this.vm.$data[exp] = val
      }
    }

    const myVm = new MyVue({
      el: '#app',
      data: {
        username: 'LastStarDust'
      }
    })

    // console.log(Dep.target)
  </script>
</body>
</html>

以上就是vue實(shí)現(xiàn)簡(jiǎn)易的雙向數(shù)據(jù)綁定的詳細(xì)內(nèi)容,更多關(guān)于vue 實(shí)現(xiàn)雙向數(shù)據(jù)綁定的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解vue 模擬后臺(tái)數(shù)據(jù)(加載本地json文件)調(diào)試

    詳解vue 模擬后臺(tái)數(shù)據(jù)(加載本地json文件)調(diào)試

    本篇文章主要介紹了詳解vue 模擬后臺(tái)數(shù)據(jù)(加載本地json文件)調(diào)試,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-08-08
  • Vue項(xiàng)目中最新用到的一些實(shí)用小技巧

    Vue項(xiàng)目中最新用到的一些實(shí)用小技巧

    這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目中最新用到的一些實(shí)用小技巧,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • Vue中加載天地圖的離線(xiàn)地圖基本步驟

    Vue中加載天地圖的離線(xiàn)地圖基本步驟

    這篇文章主要給大家介紹了關(guān)于Vue中加載天地圖的離線(xiàn)地圖的基本步驟,Vue天地圖離線(xiàn)地圖是指基于Vue框架開(kāi)發(fā)的應(yīng)用程序,使用天地圖離線(xiàn)地圖服務(wù)提供商提供的地圖數(shù)據(jù),可以在沒(méi)有網(wǎng)絡(luò)的情況下使用地圖功能,需要的朋友可以參考下
    2023-10-10
  • vue3網(wǎng)絡(luò)請(qǐng)求添加loading過(guò)程

    vue3網(wǎng)絡(luò)請(qǐng)求添加loading過(guò)程

    這篇文章主要介紹了vue3網(wǎng)絡(luò)請(qǐng)求添加loading過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • vue2.0/3.0中provide和inject的用法示例

    vue2.0/3.0中provide和inject的用法示例

    provide和inject是成對(duì)出現(xiàn)的,主要用于父組件向子孫組件傳遞數(shù)據(jù),這篇文章主要給大家介紹了關(guān)于vue2.0/3.0中provide和inject用法的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • VUE +Element 實(shí)現(xiàn)多個(gè)字段值拼接功能

    VUE +Element 實(shí)現(xiàn)多個(gè)字段值拼接功能

    這篇文章主要介紹了VUE +Element 實(shí)現(xiàn)多個(gè)字段值拼接,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04
  • 如何用vue封裝axios請(qǐng)求

    如何用vue封裝axios請(qǐng)求

    對(duì)axios進(jìn)行封裝以及將API接口按業(yè)務(wù)模塊統(tǒng)一管理,有助于我們簡(jiǎn)化代碼,方便后期維護(hù)。本文介紹了如何用vue封裝axios請(qǐng)求,感興趣的同學(xué),可以參考下。
    2021-06-06
  • 最新評(píng)論

    班戈县| 临沭县| 吴堡县| 丽江市| 清新县| 五华县| 沾化县| 珠海市| 大余县| 收藏| 璧山县| 调兵山市| 佛山市| 阿勒泰市| 湟中县| 曲阳县| 安乡县| 武夷山市| 云龙县| 汾阳市| 邹平县| 普格县| 赞皇县| 东阳市| 冕宁县| 新晃| 汕头市| 施甸县| 敦煌市| 饶平县| 丰宁| 商水县| 佛学| 陆丰市| 青神县| 伊通| 阿合奇县| 乐昌市| 孟津县| 乌什县| 榆中县|