js で特定の変数の変更を監視する必要があります。変数が変更されると、一部のイベントがトリガーされます。より良い解決策はありますか?
一般的な MVVM JS ライブラリ/フレームワークの共通機能はデータ バインディングです。これは、関連する計算を自動的に実行し、データ変更後に応答して DOM 表示を変更します。 したがって、この質問は、MVVM ライブラリ/フレームワークのデータ バインディングを実装する方法として理解することもできます。
一般的なデータ バインディングの実装には、ダーティ値の検出、ES5 ベースのゲッターとセッター、ES の放棄された Object.observe、ES6 で追加されたプロキシが含まれます。
Angular はダーティ値の検出を使用します。その原理は、新しい値と古い値を比較し、値が実際に変更されたときに DOM を変更することです。そのため、Angular には $digest があります。 では、なぜ ng-click などの組み込み命令がトリガーされた後に自動的に変更されるのでしょうか? 原理も非常にシンプルで、ng-click などの組み込み命令の最後に $digest が追加されます。
ダーティ値検出の簡単な実装:
<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title>two-way binding</title> </head> <body onload="init()"> <button ng-click="inc"> Increase </button> <button ng-click="reset"> Reset </button> <span style="color:red" ng-bind="counter"></span> <span style="color:blue" ng-bind="counter"></span> <span style="color:green" ng-bind="counter"></span> <script type="text/javascript"> /* 数据模型区开始 */ var counter = 0; function inc() { counter++; } function reset() { counter = 0; } /* 数据模型区结束 */ /* 绑定关系区开始 */ function init() { bind(); } function bind() { var list = document.querySelectorAll("[ng-click]"); for (var i=0; i<list.length; i++) { list[i].onclick = (function(index) { return function() { window[list[index].getAttribute("ng-click")](); apply(); }; })(i); } } function apply() { var list = document.querySelectorAll("[ng-bind='counter']"); for (var i=0; i<list.length; i++) { if (list[i].innerHTML != counter) { list[i].innerHTML = counter; } } } /* 绑定关系区结束 */ </script> </body></html>
この欠点は、データを変更した後に DOM を自動的に変更できないことです。apply() をトリガーする方法を見つける必要があるため、ng- のみを使用できます。 click パッケージ化。ng-click には実際のクリック イベント監視が含まれており、DOM を更新するかどうかを決定するダーティ値検出が追加されています。
もう 1 つの欠点は、注意しないと、ダーティ値の検出ごとに大量のデータが検出され、検出には大量のデータが必要ないため、パフォーマンスに影響しやすいことです。
Angular と同様のダーティ値検出の実装方法については、原理を理解した後でも、最適化の方法など、やるべきことがまだたくさんあります。興味がある場合は、Migong Shu がかつて推奨した「Build Your Own Angular.js」を読むことができます。最初の章「スコープ」では、Angular のスコープとダーティ値検出の実装方法について説明しています。 ちなみに、上記の例も出稼ぎおじさんのブログを少し改変したものです。原文は最後にリンクを貼ってあるので読むことをお勧めします。
新しい Object.defineProperty が ES5 に追加され、オブジェクトの新しいプロパティを直接定義したり、既存のプロパティを変更してオブジェクトを返すことができます。
Object.defineProperty(obj, prop, descriptor)
受け入れる 3 番目のパラメータは get と set で、それぞれが getter メソッドと setter メソッドに対応します。
var a = { zhihu:0 };Object.defineProperty(a, 'zhihu', { get: function() { console.log('get:' + zhihu); return zhihu; }, set: function(value) { zhihu = value; console.log('set:' + zhihu); } }); a.zhihu = 2; // set:2console.log(a.zhihu); // get:2 // 2
ES5 に基づく getter と setter は、要件をほぼ完全に満たしていると言えます。なぜほぼと言っていいのでしょうか?
まず第一に、IE の IE8 以前のバージョンは使用できません。また、この機能にはポリフィルがなく、サポートされていないプラットフォームでは実装できません。これが、ES5 のゲッターとセッターに基づく Vue.js が IE8 以前をサポートしない理由です。 IE のバージョン。 おそらく誰かが avalon について言及するでしょう。Avalon は vbscript という黒魔術を使用して、IE の下位バージョンで同様の機能を実現します。
さらに、別の問題は、配列の長さの変更、items[0] = {} などのインデックスによる要素の直接設定、配列のプッシュなどの突然変異メソッドではセッターをトリガーできないことです。 この問題を解決したい場合は、Vue の方法を参照してください。Vue の observer/array.js では、Vue は配列のプロトタイプ メソッドを直接変更します。配列の変更メソッドの実行後にトリガーされます。
しかし、これでも配列の長さを変更したり、インデックスを直接使用して items[0] = {} などの要素を設定したりする問題は解決できません。この問題を解決したい場合は、Vue のアプローチを参照することができます。前者の問題の場合は、古い配列を新しい配列に直接置き換えることができます。後者の場合は、配列の $set メソッドを拡張し、変更を実行した後にビューの更新をトリガーできます。
非推奨の Object.observe
const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)/** * Intercept mutating methods and emit events */;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] .forEach(function (method) { // cache original method var original = arrayProto[method] def(arrayMethods, method, function mutator () { // avoid leaking arguments: // http://jsperf.com/closure-with-arguments var i = arguments.length var args = new Array(i) while (i--) { args[i] = arguments[i] } var result = original.apply(this, args) var ob = this.__ob__ var inserted switch (method) { case 'push': inserted = args break case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) });
これは廃止された機能であるため、Chrome はかつてサポートしていましたが、サポートも廃止されました。ここでは詳しく説明しません。興味がある場合は、検索してください。これは有望な機能です (Object.observe() によってもたらされるデータ バインディングの変更)。 もちろん、Polymer/observe-js の代替手段がいくつかあります。
によってもたらされる
は、その名前が示すように、HTTP のプロキシに似ています。
// 一个数据模型var user = { id: 0, name: 'Brendan Eich', title: 'Mr.'};// 创建用户的greetingfunction updateGreeting() { user.greeting = 'Hello, ' + user.title + ' ' + user.name + '!'; } updateGreeting();Object.observe(user, function(changes) { changes.forEach(function(change) { // 当name或title属性改变时, 更新greeting if (change.name === 'name' || change.name === 'title') { updateGreeting(); } }); });
target はターゲット オブジェクトであり、配列、関数、またはその他の任意のタイプのオブジェクトにすることができます。プロキシオブジェクト。ハンドラーはプロセッサ オブジェクトであり、生成されたプロキシ オブジェクトのさまざまな動作を制御する一連のプロキシ メソッドが含まれています。
例:
var p = new Proxy(target, handler);
もちろん、プロキシの機能はこれをはるかに超えており、プロキシ転送などを実装することもできます。
ただし、現在ブラウザでこの機能をサポートしているのは Firefox 18 だけであり、babel はこの機能をサポートしていないと公式に述べていることに注意してください:
let a = new Proxy({}, { set: function(obj, prop, value) { obj[prop] = value; if (prop === 'zhihu') { console.log("set " + prop + ": " + obj[prop]); } return true; } }); a.zhihu = 100;
現在、それを実装できる babel プラグインがありますが、実装はより複雑だと述べています。 Node の場合は、最新バージョンにアップグレードすると使用できるようになります。上記の例のテスト環境は Node v6.4.0 です。
関連する推奨事項:
以上がJS変数の変更を監視する方法の例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。