ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript データ バインディングは、単純な MVVM library_javascript スキルを実装します。
推奨読書:
MVVM は Web フロントエンドの非常に人気のある開発モデルです。MVVM を使用すると、コードは DOM 操作ではなくビジネス ロジックの処理に重点を置くことができます。現在、有名な MVVM フレームワークとしては、vue、avalon、react などが挙げられます。それぞれのフレームワークには独自のメリットがありますが、実装の考え方はほぼ同じで、データ バインディング + ビューの更新です。好奇心といじくり回しの意欲から、この方向に沿って、合計 2,000 行を超えるコードを含む最も単純な MVVM ライブラリ (mvvm.js) も作成しました。命令の名前と使用法は vue に似ています。ここで実装原則と私のコード構成のアイデアについて話しましょう。
アイデアの整理
MVVM は概念的にはビューとデータ ロジックを完全に分離するパターンであり、ViewModel はパターン全体の中心です。 ViewModel を実装するには、データ モデル (Model) をビュー (View) に関連付ける必要があります。実装のアイデア全体は次の 5 つのポイントに簡単に要約できます。
要素の各ノードの命令をスキャンして抽出するコンパイラーを実装します。要素の命令を解析するパーサーを実装し、ノードの解析などのリフレッシュ関数 (ビューのリフレッシュを特に担当するモジュールが途中で必要になる場合があります) を通じて dom への命令の意図を更新します。 p v-show=" isShow">94b3e26ee717c64999d7867364b1b4a3 まずモデル内の isShow の値を取得し、次に isShow に従って node.style.display を変更して要素の表示と非表示を制御します。
パーサーの各命令のリフレッシュ関数を対応するモデルのフィールドに接続するウォッチャーを実装します。
オブジェクトのすべてのフィールドの値の変更を監視するオブザーバーを実装すると、変更が発生すると、最新の値を取得して通知コールバックをトリガーできます。
オブザーバーを使用してウォッチャーでモデルのモニタリングを確立します。モデルの値が変更されると、ウォッチャーは新しい値を取得した後、ステップ 2 で関連付けられたリフレッシュ関数を呼び出してデータを認識します。変更時にビューを更新する目的。効果例
最初に、他の MVVM フレームワークのインスタンス化に似た最後の使用例を簡単に見てみましょう。
<div id="mobile-list"> <h1 v-text="title"></h1> <ul> <li v-for="item in brands"> <b v-text="item.name"></b> <span v-show="showRank">Rank: {{item.rank}}</span> </li> </ul> </div> var element = document.querySelector('#mobile-list'); var vm = new MVVM(element, { 'title' : 'Mobile List', 'showRank': true, 'brands' : [ {'name': 'Apple', 'rank': 1}, {'name': 'Galaxy', 'rank': 2}, {'name': 'OPPO', 'rank': 3} ] }); vm.set('title', 'Top 3 Mobile Rank List'); // => <h1>Top 3 Mobile Rank List</h1>
MVVM を 5 つのモジュールに分割して実装しました: コンパイル モジュール Compiler、解析モジュール Parser、ビュー更新モジュール Updater、データ サブスクリプション モジュール Watcher、およびデータ リスニング モジュール Observer。このプロセスは次のように簡単に説明できます。コンパイラーは命令をコンパイルした後、パーサーに命令情報を渡して解析します。パーサーは初期値を更新し、オブザーバーはデータの変更を監視します。その後、それらを Watcher にフィードバックし、Updater は、ビューを更新するための対応する更新関数を見つけます。
上記のプロセスを図に示します:以下では、これら 5 つのモジュールの実装の基本原則を紹介します (コードの重要な部分のみが掲載されています。完全な実装を読むには私の Github にアクセスしてください)
1. コンパイラモジュール Compiler
コンパイラの主な役割は、要素の各ノードの命令をスキャンして抽出することです。コンパイルと解析のプロセスはノード ツリー全体を複数回実行するため、コンパイル効率を向上させるために、要素は最初に MVVM コンストラクター内のドキュメント フラグメントの形式でコピー フラグメントに変換されます。コンパイル オブジェクトはこのドキュメント フラグメントであり、すべてのノードがコンパイルされた後、ドキュメントのフラグメントが元の実際のノードに追加されて戻されます。
vm.complieElement は、要素のすべてのノードのスキャンと命令抽出を実装します。
vm.complieElement = function(fragment, root) { var node, childNodes = fragment.childNodes; // 扫描子节点 for (var i = 0; i < childNodes.length; i++) { node = childNodes[i]; if (this.hasDirective(node)) { this.$unCompileNodes.push(node); } // 递归扫描子节点的子节点 if (node.childNodes.length) { this.complieElement(node, false); } } // 扫描完成,编译所有含有指令的节点 if (root) { this.compileAllNodes(); } }
2. 命令解析モジュール Parser
コンパイラ Compiler が各ノードの命令を抽出すると、パーサーで解析することができます。各命令には異なる解析方法があります。すべての命令の解析方法は 2 つのことだけを実行する必要があります。1 つはデータ値をビュー (初期状態) に更新すること、もう 1 つは更新関数を変更監視にサブスクライブすることです。モデル。ここでは、v-text の解析を例として、命令の一般的な解析方法を説明します。
3. 数据订阅模块 Watcher
上个例子,Watcher 提供了一个 watch 方法来对数据变化进行订阅,一个参数是模型字段 model 另一个是回调函数,回调函数是要通过 Observer 来触发的,参数传入新值 last 和 旧值 old , Watcher 拿到新值后就可以找到 model 对应的回调(刷新函数)进行更新视图了。model 和 刷新函数是一对多的关系,即一个 model 可以有任意多个处理它的回调函数(刷新函数),比如: v-text="title" 和 v-html="title" 两个指令共用一个数据模型字段。
添加数据订阅 watcher.watch 实现方式为:
watcher.watch = function(field, callback, context) { var callbacks = this.$watchCallbacks; if (!Object.hasOwnProperty.call(this.$model, field)) { console.warn('The field: ' + field + ' does not exist in model!'); return; } // 建立缓存回调函数的数组 if (!callbacks[field]) { callbacks[field] = []; } // 缓存回调函数 callbacks[field].push([callback, context]); }
当数据模型的 field 字段发生改变时,Watcher 就会触发缓存数组中订阅了 field 的所有回调。
4. 数据监听模块 Observer
Observer 是整个 mvvm 实现的核心基础,看过有一篇文章说 O.o (Object.observe) 将会引爆数据绑定革命,给前端带来巨大影响力,不过很可惜,ES7 草案已经将 O.o 给废弃了!目前也没有浏览器支持!所幸的是还有 Object.defineProperty 通过拦截对象属性的存取描述符(get 和 set) 可以模拟一个简单的 Observer :
// 拦截 object 的 prop 属性的 get 和 set 方法 Object.defineProperty(object, prop, { get: function() { return this.getValue(object, prop); }, set: function(newValue) { var oldValue = this.getValue(object, prop); if (newValue !== oldValue) { this.setValue(object, newValue, prop); // 触发变化回调 this.triggerChange(prop, newValue, oldValue); } } });
然后还有个问题就是数组操作 ( push, shift 等) 该如何监测?所有的 MVVM 框架都是通过重写该数组的原型来实现的:
observer.rewriteArrayMethods = function(array) { var self = this; var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methods = 'push|pop|shift|unshift|splice|sort|reverse'.split('|'); methods.forEach(function(method) { Object.defineProperty(arrayMethods, method, function() { var i = arguments.length; var original = arrayProto[method]; var args = new Array(i); while (i--) { args[i] = arguments[i]; } var result = original.apply(this, args); // 触发回调 self.triggerChange(this, method); return result; }); }); array.__proto__ = arrayMethods; }
这个实现方式是从 vue 中参考来的,觉得用的很妙,不过数组的 length 属性是不能够被监听到的,所以在 MVVM 中应避免操作 array.length
5. 视图刷新模块 Updater
Updater 在五个模块中是最简单的,只需要负责每个指令对应的刷新函数即可。其他四个模块经过一系列的折腾,把最后的成果交给到 Updater 进行视图或者事件的更新,比如 v-text 的刷新函数为:
updater.updateNodeTextContent = function(node, text) { node.textContent = text; }
v-bind:style 的刷新函数:
updater.updateNodeStyle = function(node, propperty, value) { node.style[propperty] = value; }
双向数据绑定的实现
表单元素的双向数据绑定是 MVVM 的一个最大特点之一:
其实这个神奇的功能实现原理也很简单,要做的只有两件事:一是数据变化的时候更新表单值,二是反过来表单值变化的时候更新数据,这样数据的值就和表单的值绑在了一起。
数据变化更新表单值利用前面说的 Watcher 模块很容易就可以做到:
watcher.watch(model, function(last, old) { input.value = last; });'
表单变化更新数据只需要实时监听表单的值得变化事件并更新数据模型对应字段即可:
var model = this.$model; input.addEventListenr('change', function() { model[field] = this.value; });‘
其他表单 radio, checkbox 和 select 都是一样的原理。
以上,整个流程以及每个模块的基本实现思路都讲完了,第一次在社区发文章,语言表达能力不太好,如有说的不对写的不好的地方,希望大家能够批评指正!
结语
折腾这个简单的 mvvm.js 是因为原来自己的框架项目中用的是 vue.js 但是只是用到了它的指令系统,一大堆功能只用到四分之一左右,就想着只是实现 data-binding 和 view-refresh 就够了,结果没找这样的 javascript 库,所以我自己就造了这么一个轮子。
虽说功能和稳定性远不如 vue 等流行 MVVM 框架,代码实现可能也比较粗糙,但是通过造这个轮子还是增长了很多知识的 ~ 进步在于折腾嘛!
目前我的 mvvm.js 只是实现了最本的功能,以后我会继续完善、健壮它,如有兴趣欢迎一起探讨和改进~