国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

vue中MVVM原理及其實現(xiàn)

Awbeci / 679人閱讀

摘要:實現(xiàn)訂閱中心和之間通信的橋梁是訂閱中心,其主要職責(zé)是在自身實例化時往屬性訂閱器里面添加自己,與建立連接自身必須有一個方法,與建立連接當(dāng)屬性變化時,中通知,然后能調(diào)用自身的方法,并觸發(fā)中綁定的回調(diào),實現(xiàn)更新。

一. 什么是mvvm

MVVM是Model-View-ViewModel的簡寫。它本質(zhì)上就是MVC 的改進版。MVVM 就是將其中的View 的狀態(tài)和行為抽象化,讓我們將視圖 UI 和業(yè)務(wù)邏輯分開。

要實現(xiàn)一個mvvm的庫,我們首先要理解清楚其實現(xiàn)的整體思路。先看看下圖的流程:

1.實現(xiàn)compile,進行模板的編譯,包括編譯元素(指令)、編譯文本等,達到初始化視圖的目的,并且還需要綁定好更新函數(shù);
2.實現(xiàn)Observe,監(jiān)聽所有的數(shù)據(jù),并對變化數(shù)據(jù)發(fā)布通知;
3.實現(xiàn)watcher,作為一個中樞,接收到observe發(fā)來的通知,并執(zhí)行compile中相應(yīng)的更新方法。
4.結(jié)合上述方法,向外暴露mvvm方法。

二. 實現(xiàn)方法

首先編輯一個html文件,如下:




  
  MVVM原理及其實現(xiàn)


{{message}}
1.實現(xiàn)一個mvvm類(入口)

新建一個mvvm.js,將參數(shù)通過options傳入mvvm中,并取出el和data綁定到mvvm的私有變量$el和$data中。

// mvvm.js
class MVVM {
  constructor(options) {
    this.$el = options.el
    this.$data = options.data
  }
}
2.實現(xiàn)compile(編譯模板)

新建一個compile.js文件,在mvvm.js中調(diào)用compile。compile.js接收mvvm中傳過來的el和vm實例。

// mvvm.js
class MVVM {
  constructor(options) {
    this.$el = options.el
    this.$data = options.data
    // 如果有要編譯的模板 =>編譯
    if(this.$el) {
      // 將文本+元素模板進行編譯
      new Compile(this.$el, this)
    }
  }
}

(1)初始化傳值

// compile.js
export default class Compile {
  constructor(el, vm) {
    // 判斷是否是元素節(jié)點,是=》取該元素 否=》取文本
    this.el = this.isElementNode(el) ? el:document.querySelector(el)
    this.vm = vm
  },
  // 判斷是否是元素節(jié)點
  isElementNode(node) {
    return node.nodeType === 1
  }
}

(2)先把真實DOM移入到內(nèi)存中 fragment,因為fragment在內(nèi)存中,操作比較快

// compile.js
class Compile {
  constructor(el, vm) {
    // 判斷是否是元素節(jié)點,是=》取該元素 否=》取文本
    this.el = this.isElementNode(el) ? el:document.querySelector(el)
    this.vm = vm
    // 如果這個元素能獲取到 我們才開始編譯
    if(this.el) {
      // 1. 先把真實DOM移入到內(nèi)存中 fragment
      let fragment = this.node2fragment(this.el)
    }
  },
  // 判斷是否是元素節(jié)點
  isElementNode(node) {
    return node.nodeType === 1
  }
  // 將el中的內(nèi)容全部放到內(nèi)存中
  node2fragment(el) { 
    let fragment = document.createDocumentFragment()
    let firstChild
    // 遍歷取出firstChild,直到firstChild為空
    while (firstChild = el.firstChild) {
      fragment.appendChild(firstChild)
    }
    return fragment // 內(nèi)存中的節(jié)點
  }
}

(3)編譯 =》 在fragment中提取想要的元素節(jié)點 v-model 和文本節(jié)點

// compile.js
class Compile {
  constructor(el, vm) {
    // 判斷是否是元素節(jié)點,是=》取該元素 否=》取文本
    this.el = this.isElementNode(el) ? el:document.querySelector(el)
    this.vm = vm
    // 如果這個元素能獲取到 我們才開始編譯
    if(this.el) {
      // 1. 先把真實DOM移入到內(nèi)存中 fragment
      let fragment = this.node2fragment(this.el)
      // 2. 編譯 =》 在fragment中提取想要的元素節(jié)點 v-model 和文本節(jié)點
      this.compile(fragment)
      // 3. 把編譯好的fragment在放回到頁面中
      this.el.appendChild(fragment)
    }
  }
  // 判斷是否是元素節(jié)點
  isElementNode(node) {
    return node.nodeType === 1
  }
  // 是不是指令
  isDirective(name) {
    return name.includes("v-")
  }
  // 將el中的內(nèi)容全部放到內(nèi)存中
  node2fragment(el) {
    let fragment = document.createDocumentFragment()
    let firstChild
    // 遍歷取出firstChild,直到firstChild為空
    while (firstChild = el.firstChild) {
      fragment.appendChild(firstChild)
    }
    return fragment // 內(nèi)存中的節(jié)點
  }
  //編譯 =》 提取想要的元素節(jié)點 v-model 和文本節(jié)點
  compile(fragment) {
    // 需要遞歸
    let childNodes = fragment.childNodes
    Array.from(childNodes).forEach(node => {
      // 是元素節(jié)點 直接調(diào)用文本編譯方法 還需要深入遞歸檢查
      if(this.isElementNode(node)) {
        this.compileElement(node)
        // 遞歸深入查找子節(jié)點
        this.compile(node)
      // 是文本節(jié)點 直接調(diào)用文本編譯方法
      } else {
        this.compileText(node)
      }
    })
  }
  // 編譯元素方法
  compileElement(node) {
    let attrs = node.attributes
    Array.from(attrs).forEach(attr => {
      let attrName = attr.name
      // 判斷屬性名是否包含 v-指令
      if(this.isDirective(attrName)) {
        // 取到v-指令屬性中的值(這個就是對應(yīng)data中的key)
        let expr = attr.value
        // 獲取指令類型
        let [,type] = attrName.split("-")
        // node vm.$data expr
        compileUtil[type](node, this.vm, expr)
      }
    })
  }
  // 這里需要編譯文本
  compileText(node) {
    //取文本節(jié)點中的文本
    let expr = node.textContent
    let reg = /{{([^}]+)}}/g
    if(reg.test(expr)) {
      // node this.vm.$data text
      compileUtil["text"](node, this.vm, expr)
    }
  }
}
// 解析不同指令或者文本編譯集合
const compileUtil = {
  text(node, vm, expr) { // 文本
    let updater = this.updater["textUpdate"]
    updater && updater(node, getTextValue(vm, expr))
  },
  model(node, vm, expr){ // 輸入框
    let updater = this.updater["modelUpdate"]
    updater && updater(node, getValue(vm, expr))
  },
  // 更新函數(shù)
  updater: {
    // 文本賦值
    textUpdate(node, value) {
      node.textContent = value
    },
    // 輸入框value賦值
    modelUpdate(node, value) {
      node.value = value
    }
  }
}
// 輔助工具函數(shù)
// 綁定key上對應(yīng)的值,從vm.$data中取到
const getValue = (vm, expr) => {
  expr = expr.split(".") // [message, a, b, c]
  return expr.reduce((prev, next) => {
    return prev[next]
  }, vm.$data)
}
// 獲取文本編譯后的對應(yīng)的數(shù)據(jù)
const getTextValue = (vm, expr) => {
  return expr.replace(/{{([^}]+)}}/g, (...arguments) => {
    return getValue(vm, arguments[1])
  })
}

(3) 將編譯后的fragment放回到dom中

  let fragment = this.node2fragment(this.el)
  this.compile(fragment)
  // 3. 把編譯好的fragment在放回到頁面中
  this.el.appendChild(fragment)

進行到這一步,頁面上初始化應(yīng)該渲染完成了。如下圖:

3.實現(xiàn)observe(數(shù)據(jù)監(jiān)聽/劫持)

不同于發(fā)布者-訂閱者模式和臟值檢測,vue采用的observe + sub/pub 實現(xiàn)數(shù)據(jù)的劫持,通過js原生的方法Object.defineProperty()來劫持各個屬性的setter,getter,在屬性對應(yīng)數(shù)據(jù)改變時,發(fā)布消息給訂閱者,然后觸發(fā)相應(yīng)的監(jiān)聽回調(diào)。
主要內(nèi)容:observe的數(shù)據(jù)對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter和getter。

// observe.js
class Observe {
  constructor(data) {
    this.observe(data)
  }
  // 把data數(shù)據(jù)原有的屬性改成 get 和 set方法的形式
  observe(data) {
    if(!data || typeof data!== "object") {
      return
    }
    console.log(data)
    // 將數(shù)據(jù)一一劫持
    // 先獲取到data的key和value
    Object.keys(data).forEach((key) => {
      // 數(shù)據(jù)劫持
      this.defineReactive(data, key, data[key])
      this.observe(data[key]) // 深度遞歸劫持,保證子屬性的值也會被劫持
    })
  }
  // 定義響應(yīng)式
  defineReactive(obj, key, value) {
    let _this = this
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() { // 當(dāng)取值時調(diào)用
        return value
      },
      set(newValue) { //當(dāng)data屬性中設(shè)置新值得時候 更改獲取的新值
        if(newValue !== value) {
          _this.observe(newValue) // 如果是對象繼續(xù)劫持
          console.log("監(jiān)聽到值變化了,舊值:", value, " --> 新值:", newValue);
          value = newValue
        }
      }
    })
  }
}

完成observe.js后,修改mvvm.js文件,將屬性傳入observe中

// mvvm.js
class MVVM {
  constructor(options) {
    console.log(options)
    this.$el = options.el
    this.$data = options.data
    // 如果有要編譯的模板 =》編譯
    if(this.$el) {
      // 數(shù)據(jù)劫持 就是把對象的所有屬性改成 get 和 set方法
      new Observe(this.$data)
      // 將文本+元素模板進行編譯
      new Compile(this.$el, this)
    }
  }
}

可以在控制臺查看到以下信息,說明劫持屬性成功。

實現(xiàn)數(shù)據(jù)劫持后,接下來的任務(wù)怎么通知訂閱者了,我們需要在監(jiān)聽數(shù)據(jù)時實現(xiàn)一個消息訂閱器,具體的方法是:定義一個數(shù)組,用來存放訂閱者,數(shù)據(jù)變動通知(notify)訂閱者,再調(diào)用訂閱者的update方法。
在observe.js添加Dep類:

//observe.js

// ...
    let _this = this
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() { // 當(dāng)取值時調(diào)用
        return value
      },
      set(newValue) { //當(dāng)data屬性中設(shè)置新值得時候 更改獲取的新值
        if(newValue !== value) {
          _this.observe(newValue) // 如果是對象繼續(xù)劫持
          console.log("監(jiān)聽到值變化了,舊值:", value, " --> 新值:", newValue);
          value = newValue
          dep.notify() //通知所有人 數(shù)據(jù)更新了
        }
      }
    })
// ...
// 消息訂閱器Dep()
class Dep {
  constructor() {
    // 訂閱的數(shù)組
    this.subs = []
  }
  addSub(watcher) {
    // push到訂閱數(shù)組
    this.subs.push(watcher)
  }
  notify() {
    // 通知訂閱者,并執(zhí)行訂閱者的update回調(diào)
    this.subs.forEach(watcher => watcher.update())
  }
}

實現(xiàn)了消息訂閱器,并且能夠執(zhí)行訂閱者的回調(diào),那么訂閱者怎么獲取,并push到訂閱器數(shù)組中呢?這個要和watcher結(jié)合。

4.實現(xiàn)watcher(訂閱中心)

Observer和Compile之間通信的橋梁是Watcher訂閱中心,其主要職責(zé)是:
1、在自身實例化時往屬性訂閱器(Dep)里面添加自己,與Observer建立連接;
2、自身必須有一個update()方法,與Compile建立連接;
3、當(dāng)屬性變化時,Observer中dep.notice()通知,然后能調(diào)用自身(Watcher)的update()方法,并觸發(fā)Compile中綁定的回調(diào),實現(xiàn)更新。

// watcher.js
// 訂閱中心(觀察者): 給需要變化的那個元素 增加一個觀察者, 當(dāng)數(shù)據(jù)變化后,執(zhí)行對應(yīng)的方法
class Watcher {
  constructor(vm, expr, cb) {
    this.vm = vm
    this.expr = expr
    this.cb = cb
    // 先獲取一下老值
    this.value = this.get()
  }
  getValue(vm, expr) { // 獲取實例上對應(yīng)的數(shù)據(jù)
    expr = expr.split(".") // [message, a, b, c]
    return expr.reduce((prev, next) => {
      return prev[next]
    }, vm.$data)
  }
  get() { // 獲取文本編譯后的對應(yīng)的數(shù)據(jù)
    // 獲取當(dāng)前訂閱者
    Dep.target = this
    // 觸發(fā)getter,當(dāng)前訂閱者添加訂閱器中 在 劫持數(shù)據(jù)時,將訂閱者放到訂閱者數(shù)組
    let value = this.getValue(this.vm, this.expr)
    // 重置訂閱者
    Dep.target = null
    return value
  }
  // 對外暴露的方法
  update() {
    let newValue = this.getValue(this.vm, this.expr)
    let oldValue = this.value
    // 更新的值 與 以前的值 進行比對, 如果發(fā)生變化就更新方法
    if(newValue !== oldValue) {
      this.cb(newValue)
    }
  }
}

// observe.js 
// ... 省略
Object.defineProperty(data, key, {
    get: function() {
        // 在取值時將訂閱者push入訂閱者數(shù)組
        Dep.target && dep.addDep(Dep.target);
        return val;
    }
    // ... 省略
});
// ... 省略

上面步驟搭建了watcher與observe之間的連接,還需要搭建watcher與之間的連接。
我們需要在compile中解析不同指令或者文本編譯集合的時候綁定watcher.

// compile.js
// ...省略
 model(node, vm, expr){ // 輸入框
    let updater = this.updater["modelUpdate"]
    // 這里加一個監(jiān)控 數(shù)據(jù)變化了 應(yīng)該調(diào)用這個watcher的callback
    new Watcher(vm, expr, (newValue) => {
      // 當(dāng)值變化后 會調(diào)用cb ,將新值傳遞過來
      updater && updater(node, this.getValue(vm, expr))
    })
    node.addEventListener("input", (e) => {
      let newValue = e.target.value
      this.setVal(vm, expr, newValue)
    })
    updater && updater(node, this.getValue(vm, expr))
 },
// ...省略

此時,在瀏覽器控制臺執(zhí)行下圖操作,手動改變 message 屬性的值,發(fā)現(xiàn)輸入框的值也隨之變化,v-model 綁定完成。

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/102408.html

相關(guān)文章

  • MVVM框架理解及其原理實現(xiàn)

    摘要:小白一枚,一直使用的是,想要多了解一些其它的框架,正好最近越來越火熱,上的數(shù)已經(jīng)超過了。框架理解說起這個模型,就不得不說框架。函數(shù)表示創(chuàng)建一個文本節(jié)點,函數(shù)表示創(chuàng)建一個數(shù)組。 小白一枚,一直使用的是React,想要多了解一些其它的框架,正好最近Vue越來越火熱,Github上的Star數(shù)已經(jīng)超過了React。而其背后蘊含的MVVM框架思想也一直跟React的組件化開發(fā)思想并駕齊驅(qū),在這...

    DevWiki 評論0 收藏0
  • 前端MVVM模式及其Vue和React的體現(xiàn)

    摘要:在模式中一般把層算在層中,只有在理想的雙向綁定模式下,才會完全的消失。層將通過特定的展示出來,并在控件上綁定視圖交互事件,一般由框架自動生成在瀏覽器中。三大框架的異同三大框架都是數(shù)據(jù)驅(qū)動型的框架及是雙向數(shù)據(jù)綁定是單向數(shù)據(jù)綁定。 MVVM相關(guān)概念 1) MVVM典型特點是有四個概念:Model、View、ViewModel、綁定器。MVVM可以是單向綁定也可以是雙向綁定甚至是不綁...

    沈建明 評論0 收藏0
  • 前端面試題總結(jié)

    摘要:工作中總結(jié)的一些比較重要的前端技能,覺得在面試中比較合適提問,即能查看出面試者的技術(shù)功底,又能考察其知識體系的廣度。異步編程的考察,其關(guān)鍵字的使用,與的關(guān)系,同時可以深入考察總共有幾種異步編程的方式。 工作中總結(jié)的一些比較重要的前端技能,覺得在面試中比較合適提問,即能查看出面試者的技術(shù)功底,又能考察其知識體系的廣度。適用于應(yīng)屆生和工作年限兩年下的同學(xué),掌握下面的知識基本滿足工作需求了。...

    wuyangnju 評論0 收藏0
  • 前端面試題總結(jié)

    摘要:工作中總結(jié)的一些比較重要的前端技能,覺得在面試中比較合適提問,即能查看出面試者的技術(shù)功底,又能考察其知識體系的廣度。異步編程的考察,其關(guān)鍵字的使用,與的關(guān)系,同時可以深入考察總共有幾種異步編程的方式。 工作中總結(jié)的一些比較重要的前端技能,覺得在面試中比較合適提問,即能查看出面試者的技術(shù)功底,又能考察其知識體系的廣度。適用于應(yīng)屆生和工作年限兩年下的同學(xué),掌握下面的知識基本滿足工作需求了。...

    yangrd 評論0 收藏0

發(fā)表評論

0條評論

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