1. 程式人生 > >【Vue】原始碼分析--雙向資料繫結的實現

【Vue】原始碼分析--雙向資料繫結的實現

總結

Vue的雙向資料繫結主要通過Object.defineProperty來實現,先為所有的屬性加上get/set的監控,這樣當屬性值改變時就會觸發對應的set方法,然後再在set方法中通過觀察者來更新檢視,同時在get方法中進行依賴收集。

極簡版的實現

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"
>
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>極簡雙向資料繫結</title> </head> <body> <input type="text" id="message" /> <div id="msg"></div> <script src="app.js"></script> </body> </html>
  • app.js
var
obj = {} Object.defineProperty(obj, "data", { get: function () { console.log("get") }, set: function (newValue) { document.getElementById("message").value = newValue document.getElementById("msg").innerText = newValue } }) document.getElementById("message").addEventListener('keyup'
, function () { obj.data = event.target.value })
  • 分析
1)通過Object.defineProperty的方法為屬性加上get/set的監控
(2)通過EventListener監聽屬性的改變,不斷觸發屬性的set方法,從而實現資料的雙向繫結

稍微複雜版的實現

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>雙向資料繫結</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="message"> 
        {{ message }}
    </div>

    <script src="dist/bundle.js"></script>
</body>
</html>
  • main.js
import myVue from './myVue'

new myVue({
    el: '#app',
    data: {
        message: "hello myVue"
    }
})
  • myVue.js
import Observer from './Observer'
import Compiler from './Compiler'

class myVue {
    constructor(options) {
        // 獲取物件傳入的資料
        this.$options = options
        this.$el = this.$options.el
        this._data = this.$options.data

        // 將傳入的所有屬性新增get/set的屬性監聽
        // 屬性值發生改變會觸發set方法
        Object.keys(this._data).forEach(key=>{
            this.add_watch(key)
        })

        // 將所有屬性加入訂閱釋出者模式的管理中
        new Observer(this._data)

        // 編譯渲染頁面
        new Compiler(this.$el, this)
    }
    add_watch(key) {
        var self = this
        Object.defineProperty(this, key, {
            get() {
                return self._data[key]
            },
            set(value) {
                self._data[key] = value
            }
        })
    }
}

export default myVue
  • Observer.js
import Dep from './Dep'

class Observer {
    constructor(data) {
        // 獲取所有屬性資料
        this.data = data

        // 為所有屬性資料新增get/sete的屬性監聽
        Object.keys(this.data).forEach(key=>{
            this._bind(data, key, data[key])
        })
    }
    _bind(data, key, val) {
        var myDep = new Dep()
        Object.defineProperty(data, key, {
            get() {
                // 如果是為訂閱的物件,則添訂閱
                if(Dep.target) myDep.listen(Dep.target)
                return val
            },
            set(newValue) {
                if (newValue === val) return 
                val = newValue
                // 如果數值改變,則釋出更新
                myDep.notify()    
            }
        })
    }
}

export default Observer
  • Watcher.js
import Dep from './Dep'

class Watcher {
    constructor(node, name, vm) {
        this.node = node
        this.name = name
        this.vm = vm

        Dep.target = this
        this.update()
        Dep.target = null
    }
    update() {
        this.node.nodeValue = this.vm[this.name]
    }
}

export default Watcher
  • Dep.js

class Dep {
    constructor() {
        this.list = []
    }
    listen(subs) {
        this.list.push(subs)
    }
    notify() {
        for(var i=0; i<this.list.length; i++){
            this.list[i].update()
        }
    }
}
Dep.prototype.target = null

export default Dep
  • Compiler.js
import Watcher from './Watcher'

const REG = /\{\{(.*)\}\}/

class Compiler {
    constructor(el, vm) {
        this.el = document.querySelector(el)
        this.vm = vm

        // 建立文件片段,編譯完成後,掛載到el元素上
        this.frag = this._createFragment()
        this.el.appendChild(this.frag)
    }
    _createFragment() {
        var frag = document.createDocumentFragment()
        var child
        while (child = this.el.firstChild) {
            this._compile(child)
            frag.appendChild(child)
        }
        return frag
    }
    _compile(node) {
        // 如果傳入的是節點node
        if(node.nodeType === 1) {
            var attr = node.attributes
            var self = this
            if(attr.hasOwnProperty('v-model')){
                var name = attr['v-model'].nodeValue
                node.addEventListener('input', function(e) {
                    self.vm[name] = e.target.value
                })
                node.value = this.vm[name]
            }
        }

        // 如果傳入的是元素elemet
        if (node.nodeType === 3) {
            if(REG.test(node.nodeValue)) {
                var name = RegExp.$1
                name = name.trim()
                new Watcher(node, name, this.vm)
            }
        }
    }
}

export default Compiler

相關推薦

Vue原始碼分析--雙向資料實現

總結 Vue的雙向資料繫結主要通過Object.defineProperty來實現,先為所有的屬性加上get/set的監控,這樣當屬性值改變時就會觸發對應的set方法,然後再在set方法中通過觀

Vue.js學習筆記5:雙向資料,計算屬性

雙向資料繫結 雙向資料繫結往往會用到input、select、textarea等表單標籤上,因為總是涉及一個數據資料的地方和輸出資料的地方。 當資料發生變化的時候,檢視也就發生變化,當檢視發生變化的時候,資料也會跟著同步變化。 資料雙向繫結,一定是對於UI控制元件來說的,

Vue原始碼分析--vdom與html的相互轉換

簡析 vdom是由js物件節點組成的一個樹狀結構,通過diff演算法對比js物件節點來更新,最後對映到原生的dom中 一個簡單的dom結構 <div id="container">

vue.js v-model雙向資料vue.js form表單資料

vue.js v-model雙向資料繫結, vue.js form表單資料繫結   ================================ ©Copyright 蕃薯耀 2018年11月29日 http://fanshuyao.iteye.com/   &l

vue指令v-model(雙向資料)自動收集資料

前言:表單提交資料在網站頁面中是十分常見的,而這個表單資料的獲取在原生寫法甚至於JQ都是比較麻煩的(首先需要獲取DOM,然後獲取值)。 但是,在vue的專案環境下,表單資料的收集又該怎麼辦呢?(這種自己寫input元素的方法在實際專案中是不常用的哈,因為一般我們都會用一個UI庫,方便而快捷!

vue.js和angular雙向資料實現原理

一、vue雙向資料繫結 1、原理 資料劫持: vue.js 是採用資料劫持結合釋出者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在資料變動時釋出訊息給訂閱者,觸發相應的監聽回撥。 2、實現步驟 要實現mv

Vue 雙向資料實現

  <!DOCTYPE html> <html> <head> <title>myVue</title> <style> #app{ text-align: center;

Vue渲染原理及其雙向資料詳解

雙向資料繫結原理 1. 新建vue例項 var data = { text:'hello world!' }; var vm = new Vue({

Vue.js雙向資料實現

js中物件屬性型別有資料屬性和訪問器屬性,這裡實現簡單的雙向資料繫結是利用了物件的訪問器屬性中包含的get和set 修改屬性的預設特性使用Object.defineProperty()方法 addEventListener(event,function,useCapture

vue的思中雙向資料的原理

我們在面試中經常會被問道什麼是mvc 什麼是 mvvm  還有雙向資料繫結的原理:MVC:對專案的整體把控,M代表的是資料庫中的資料,V代表的是前端的檢視層,C用於處理M和V之間進行互動的業務邏輯(業務

雙向資料實現之Object.defineProperty

vue.js利用的是es5的 defineproperty 特性實現的雙向資料繫結,瞭解一下基本原理。 舉例 var person= {}; Object.defineProperty(person, "name", { v

reactreact實現類似vue雙向資料

import React from 'react' import ReactDOM from 'react-dom' class Comment extends React.Component { constructor() { sup

深入vue原始碼,瞭解vue雙向資料原理

大家都知道vue是一種MVVM開發模式,資料驅動檢視的前端框架,並且內部已經實現了雙向資料繫結,那麼雙向資料繫結是怎麼實現的呢? 先手動擼一個最最最簡單的雙向資料繫結 1 <div> 2 <input type="text" name="" id="te

Android原始碼分析 - LRUCache快取實現原理

一、Android中的快取策略 一般來說,快取策略主要包含快取的新增、獲取和刪除這三類操作。如何新增和獲取快取這個比較好理解,那麼為什麼還要刪除快取呢?這是因為不管是記憶體快取還是硬碟快取,它們的快取大小都是有限的。當快取滿了之後,再想其新增快取,這個時候就需要刪除一些舊的快取

Android原始碼分析 - View事件分發機制

事件分發物件 (1)所有 Touch 事件都被封裝成了 MotionEvent 物件,包括 Touch 的位置、時間、歷史記錄以及第幾個手指(多指觸控)等。 (2)事件型別分為 ACTION_DOWN, ACTION_UP,ACTION_MOVE,ACTION_POINTER_D

Android原始碼分析 - Activity啟動流程

啟動Activity的方式 Activity有2種啟動的方式,一種是在Launcher介面點選應用的圖示、另一種是在應用中通過Intent進行跳轉。我們主要介紹與後者相關的啟動流程。 Intent intent = new Intent(this, TestActivity

面試題:你能寫一個Vue雙向資料嗎?

在目前的前端面試中,vue的雙向資料繫結已經成為了一個非常容易考到的點,即使不能當場寫出來,至少也要能說出原理。本篇文章中我將會仿照vue寫一個雙向資料繫結的例項,名字就叫myVue吧。結合註釋,希望能讓大家有所收穫。 1、原理 Vue的雙向資料繫結的原理相信大家也都十分了解了,主要是通過 Obje

vue實現雙向資料之原理及實現vue雙向原理及實現

轉自:canfoo#! vue的雙向繫結原理及實現 前言 先上個成果圖來吸引各位: 程式碼:                          &nb

Vue 框架-03-鍵盤事件、健值修飾符、雙向資料

Vue 框架-03-鍵盤時間及健值修飾符 一、鍵盤事件,當按鍵盤時,在控制檯輸出提示 html 原始碼: <!DOCTYPE html> <html> <head> <meta charset="utf-8" />

Vue學習(3)————————ClassStyle,雙向資料,dom節點

標籤內繫結屬性(此功能看來可以動態繫結標籤屬性) <template> <div id="app"> <div v-bind:title="title"> 滑鼠走一走 </div> </div> </temp