ホームページ  >  記事  >  ウェブフロントエンド  >  Vue データの双方向バインディングの原理を理解する方法

Vue データの双方向バインディングの原理を理解する方法

清浅
清浅オリジナル
2019-04-25 12:00:0589245ブラウズ

Vue データの双方向バインディングの原理は、「パブリッシャー/サブスクライバー」モデルと組み合わせたデータ ハイジャックによって実現されます。まず、データが監視され、監視されているプロパティが変更されたときにサブスクライバーに通知されます。更新するかどうか。更新された場合は、対応する更新関数が実行されてビューが更新されます。

Vue データの双方向バインディングの原理を理解する方法

Vue の双方向データ バインディング原則は、パブリッシャー/サブスクライバー モデルと組み合わせたデータ ハイジャックによって実現されます。まず、データが監視され、次にいつ監視されるかがわかります。監視対象のプロパティが変更されると、サブスクライバーは更新するかどうかを通知されます。更新された場合は、対応する更新関数が実行されてビューが更新されます。

Vue データの双方向バインディングの原理を理解する方法

[推奨コース: Vue チュートリアル]

MVC モード

以前の MVC モードは一方向バインディング、つまりモデルでした。 JavaScript コードでモデルを更新すると、ビューは自動的に

Vue データの双方向バインディングの原理を理解する方法

MVVM モード

MVVM を更新します。モードはモデル – ビュー – ViewModel モードです。 View での変更が ViewModel に自動的に反映され、その逆も同様であることがわかります。双方向バインディングの理解は、ユーザーがビューを更新すると、モデルのデータが自動的に更新されるということであり、この状況が双方向バインディングです。より具体的には、一方向バインディングに基づいて、inputやtextareなどの入力要素に変更(入力)イベントを追加(変更イベントがトリガーされ、Viewの状態が更新される)して動的に変更します。モデルを変更します。

Vue データの双方向バインディングの原理を理解する方法

双方向バインディングの原則

Vue データの双方向バインディングは、パブリッシャー/サブスクライバー モデルと組み合わせたデータ ハイジャックによって実現されます。実装

データの双方向バインディングを実装するには、最初にデータをハイジャックして監視する必要があることはすでにわかっているため、すべてのプロパティを監視するためにリスナー オブザーバーをセットアップする必要があります。属性が変更された場合は、更新する必要があるかどうかをサブスクライバー Watcher に通知する必要があります。多くのサブスクライバーが存在するため、これらのサブスクライバーを特別に収集し、オブザーバーとウォッチャーの間でそれらを均一に管理するメッセージ サブスクライバー Dep が必要です。次に、各ノード要素をスキャンして解析し、関連する命令 (v-model、v-on など) をサブスクライバ Watcher に初期化し、テンプレート データを置換するか、対応する関数をバインドするための命令パーサー コンパイルも必要です。サブスクライバ Watcher は、対応する属性の変更を受け取ると、対応する更新関数を実行してビューを更新します。

したがって、次に、データの双方向バインディングを実現するために次の 3 つの手順を実行します:

(1) リスナー オブザーバーを実装して、すべてのプロパティをハイジャックして監視します。変更がある場合は、サブスクライバーに通知します。 。

(2) サブスクライバ ウォッチャーを実装します。各ウォッチャーは更新関数にバインドされています。ウォッチャーは属性変更の通知を受け取り、対応する関数を実行してビューを更新できます。

(3) 各ノードの関連命令 (v-model、v-on などの命令) をスキャンおよび解析できるパーサー Compile を実装します。ノードに v-model、v-on などがある場合、パーサー Compile は、このタイプのノードのテンプレート データをビューに表示できるように初期化してから、対応するサブスクライバー (ウォッチャー) を初期化します。

Vue データの双方向バインディングの原理を理解する方法

オブザーバーの実装

オブザーバーはデータ リスナーであり、その中心となる実装メソッドは Object.defineProperty() です。すべてのプロパティを監視したい場合は、再帰によってすべてのプロパティ値を走査し、Object.defineProperty() で処理できます。
次のコードはオブザーバーを実装します。

function Observer(data) {    this.data = data;    this.walk(data);
}

Observer.prototype = {    walk: function(data) {        
var self = this;        //这里是通过对一个对象进行遍历,对这个对象的所有属性都进行监听
 Object.keys(data).forEach(function(key) {
       self.defineReactive(data, key, data[key]);
        });
    },    defineReactive: function(data, key, val) {        
    var dep = new Dep();      // 递归遍历所有子属性
        var childObj = observe(val);        
        Object.defineProperty(data, key, {            
        enumerable: true,            
        configurable: true,            
        get: function getter () {                
        if (Dep.target) {                  
        // 在这里添加一个订阅者
                  console.log(Dep.target)
                    dep.addSub(Dep.target);
                }                return val;
            },           
            // setter,如果对一个对象属性值改变,就会触发setter中的dep.notify(),
            通知watcher(订阅者)数据变更,执行对应订阅者的更新函数,来更新视图。
            set: function setter (newVal) {                
            if (newVal === val) {                    
            return;
                }
                val = newVal;              
                // 新的值是object的话,进行监听
                childObj = observe(newVal);
                dep.notify();
            }
        });
    }
};function observe(value, vm) {    if (!value || typeof value !== 'object') {        
return;
    }    return new Observer(value);
};// 消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数
function Dep () {    
this.subs = [];
}
Dep.prototype = {  /**
   * [订阅器添加订阅者]
   * @param  {[Watcher]} sub [订阅者]
   */
    addSub: function(sub) {        
    this.subs.push(sub);
    },  // 通知订阅者数据变更
    notify: function() {        
    this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};
Dep.target = null;

Observer で他の人のソース コードを見たときに、Dep.target がどこから来たのか理解できなかったことが 1 つありました。同じ問題を抱えている人もいると思います。私への質問です。 Watcher について書くと、Dep.target が Watcher から来ていることがわかります。

ウォッチャーの実装

ウォッチャーはサブスクライバーです。オブザーバーによって送信された更新メッセージを処理し、ウォッチャーにバインドされた更新関数を実行するために使用されます。

次のコードは Watcher を実装します

function Watcher(vm, exp, cb) {    
this.cb = cb;    
this.vm = vm;    
this.exp = exp;    
this.value = this.get();  // 将自己添加到订阅器的操作}

Watcher.prototype = {    update: function() {        
this.run();
    },    run: function() {        
    var value = this.vm.data[this.exp];        
    var oldVal = this.value;        
    if (value !== oldVal) {            
    this.value = value;            
    this.cb.call(this.vm, value, oldVal);
        }
    },    get: function() {
        Dep.target = this;  // 缓存自己
        var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
        Dep.target = null;  // 释放自己
        return value;
    }
};

コードを勉強する過程で、最も複雑なことはこれらの関数のパラメータを理解することだと思います。後でこれらのパラメータを出力した後、機能 これらの機能も分かりやすいです。 vm は後で記述する SelfValue オブジェクトで、Vue における new Vue のオブジェクトに相当します。 exp は、ノードノードの v-model または v-on: click およびその他の命令の属性値です。

上記のコードからわかるように、Watcher の getter 関数では、Dep.target がそれ自体 (Watcher オブジェクト) を指します。したがって、ゲッター関数では、

var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数。
这里获取vm.data[this.exp] 时,会调用Observer中Object.defineProperty中的get函数
get: function getter () {                
if (Dep.target) {                  
// 在这里添加一个订阅者                  
console.log(Dep.target)                    
dep.addSub(Dep.target);                
}                
return val;            
},

によってウォッチャーがサブスクライバーに追加され、上記の Dep.target がどこから来たのかという問題が解決されます。

コンパイルの実装

Compile主要的作用是把new SelfVue 绑定的dom节点,(也就是el标签绑定的id)遍历该节点的所有子节点,找出其中所有的v-指令和" {{}} ".
(1)如果子节点含有v-指令,即是元素节点,则对这个元素添加监听事件。(如果是v-on,则node.addEventListener('click'),如果是v-model,则node.addEventListener('input'))。接着初始化模板元素,创建一个Watcher绑定这个元素节点。

(2)如果子节点是文本节点,即" {{ data }} ",则用正则表达式取出" {{ data }} "中的data,然后var initText = this.vm[exp],用initText去替代其中的data。

实现一个MVVM

可以说MVVM是Observer,Compile以及Watcher的“boss”了,他需要安排给Observer,Compile以及Watche做的事情如下

(1)Observer实现对MVVM自身model数据劫持,监听数据的属性变更,并在变动时进行notify
(2)Compile实现指令解析,初始化视图,并订阅数据变化,绑定好更新函数
(3)Watcher一方面接收Observer通过dep传递过来的数据变化,一方面通知Compile进行view update。
最后,把这个MVVM抽象出来,就是vue中Vue的构造函数了,可以构造出一个vue实例。

最后写一个html测试一下我们的功能

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>self-vue</title></head><style>
    #app {        
    text-align: center;
    }</style><body>
    <div id="app">
        <h2>{{title}}</h2>
        <input v-model="name">
        <h1>{{name}}</h1>
        <button v-on:click="clickMe">click me!</button>
    </div></body><script src="js/observer.js"></script>
    <script src="js/watcher.js"></script>
    <script src="js/compile.js"></script>
    <script src="js/mvvm.js"></script>
    <script type="text/javascript">
     var app = new SelfVue({        
     el: &#39;#app&#39;,        
     data: {            
     title: &#39;hello world&#39;,            
     name: &#39;canfoo&#39;
        },        
        methods: {            
        clickMe: function () {                
        this.title = &#39;hello world&#39;;
            }
        },        
        mounted: function () {            
        window.setTimeout(() => {                
        this.title = &#39;你好&#39;;
            }, 1000);
        }
    });</script></html>

先执行mvvm中的new SelfVue(...),在mvvm.js中,

observe(this.data);
new Compile(options.el, this);

先初始化一个监听器Observer,用于监听该对象data属性的值。
然后初始化一个解析器Compile,绑定这个节点,并解析其中的v-," {{}} "指令,(每一个指令对应一个Watcher)并初始化模板数据以及初始化相应的订阅者,并把订阅者添加到订阅器中(Dep)。这样就实现双向绑定了。
如果v-model绑定的元素,

<input v-model="name">

即输入框的值发生变化,就会触发Compile中的

node.addEventListener(&#39;input&#39;, function(e) {            
var newValue = e.target.value;            
if (val === newValue) {                
return;
            }            
            self.vm[exp] = newValue;
            val = newValue;
        });

self.vm[exp] = newValue;这个语句会触发mvvm中SelfValue的setter,以及触发Observer对该对象name属性的监听,即Observer中的Object.defineProperty()中的setter。setter中有通知订阅者的函数dep.notify,Watcher收到通知后就会执行绑定的更新函数。
最后的最后就是效果图啦:

Vue データの双方向バインディングの原理を理解する方法

以上がVue データの双方向バインディングの原理を理解する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。