ES 6は、新しい特性Proxyを用いて実現されるデーターバインディング機能の例を示す。


この例は、ES 6が新しい特性Proxyを使用して実装されるデータバインディング機能を説明する。皆さんに参考にしてあげます。具体的には以下の通りです。
プロジェクト住所:https://github.com/jrainlau/mog
オンライン体験:https://codepen.io/jrainlau/pen/YpyBBY
開発者として、多くの「データーバインディング」のピットを踏んだことがあります。以前はjQueryなどのツールで手動でこれらの機能を完成しましたが、データ量が非常に大きい時には、これらの手動の仕事がとてもつらいです。VueJSを使うまで、これらの苦痛は終わります。VueJSの一つのセールスポイントは、「データーバインディング」です。利用者はデータがどうやってdomに結合されているかに関心を持たなくてもいいです。データに関心を持つだけでいいです。VueJSはすでに自動的にこれらの仕事を完成してくれました。
これは本当に不思議です。私はしょうがないほどVueJSを好きになりました。そしてそれを自分のプロジェクトに使いました。使うにつれて、もっと知りたいのです。その深い原理は何ですか?
VueJSはどのようにデータバインディングを行いますか?
公式文書を読むことによって、次のような話を見ました。
一般的なJavascriptオブジェクトをVueに伝えてそのdataオプションとして、Vueはその属性を遍歴して、Object.definePropertyでそれらをgetter/setterに変えます。
キーワードはObject.definPropertyで、MDNドキュメントの中でこう言います。Object.defineProperty()方法では、オブジェクトの属性を直接定義したり、オブジェクトの中に既に存在する属性を変更したりして、このオブジェクトに戻ります。
例を書いてテストしてみましょう。
まず、アイアンマンオブジェクトを作成し、彼に属性を与えます。

let ironman = {
 name: 'Tony Stark',
 sex: 'male',
 age: '35'
}
Object.defineProperty()方法を使用して彼の属性を修正し、コンソールで修正されたコンテンツを出力します。

Object.defineProperty(ironman, 'age', {
 set (val) {
  console.log(`Set age to ${val}`)
  return val
 }
})

ironman.age = '48'
// --> Set age to 48
完璧に見えます。console.log('Set age to ${val}')element.innerHTML = valに変更すると、データバインディングが完了したということですか?
アイアンマンの属性を改めましょう。

let ironman = {
 name: 'Tony Stark',
 sex: 'male',
 age: '35',
 hobbies: ['girl', 'money', 'game']
}
彼はプレイボーイです。今は彼にいくつかの「趣味」を追加したいです。そしてコンソールで対応する出力を見たいです。

Object.defineProperty(ironman.hobbies, 'push', {
 value () {
  console.log(`Push ${arguments[0]} to ${this}`)
  this[this.length] = arguments[0]
 }
})

ironman.hobbies.push('wine')
console.log(ironman.hobbies)

// --> Push wine to girl,money,game
// --> [ 'girl', 'money', 'game', 'wine' ]
その前に、私はget()方法を使ってオブジェクトの属性変化を追跡しますが、1つの配列に対しては、この方法は使えません。代わりにvalue()方法を使っています。これも霊だが、最良の方法ではない。これらの追跡対象や配列属性の変化を簡単にする方法がありますか?
ECMA 2015では、Proxyはいい選択です。Proxyとは?MDNドキュメントでは、そう言います。
Proxyは、ターゲットの前に「ブロック」を設置し、外部からの訪問は、まずこの層を通過しなければならないということで、外部へのアクセスをフィルタリングしたり書き換えたりする仕組みを提供していると理解できます。ProxyはECMA 2015の新しい特性で、非常に強いですが、それについてはあまり議論しません。今度は私達と一緒にProxyの例を作りましょう。

let ironmanProxy = new Proxy(ironman, {
 set (target, property, value) {
  target[property] = value
  console.log('change....')
  return true
 }
})

ironmanProxy.age = '48'
console.log(ironman.age)

// --> change....
// --> 48
予想にかなう。配列については?

let ironmanProxy = new Proxy(ironman.hobbies, {
 set (target, property, value) {
  target[property] = value
  console.log('change....')
  return true
 }
})

ironmanProxy.push('wine')
console.log(ironman.hobbies)

// --> change...
// --> change...
// --> [ 'girl', 'money', 'game', 'wine' ]
まだ予想通り!しかし、なぜ2回change...を出力しましたか?push()方法をトリガするたびに、この配列のlength属性とbodyコンテンツが修正されたので、2つの変化が生じる。
リアルタイムデータリンク
最も核心的な問題を解決しました。他の問題も考えられます。
想像してください。テンプレートとデータオブジェクトがあります。

<!-- html template -->
<p>Hello, my name is {{name}}, I enjoy eatting {{hobbies.food}}</p>

<!-- javascript -->
let ironman = {
 name: 'Tony Stark',
 sex: 'male',
 age: '35',
 hobbies: {
  food: 'banana',
  drink: 'wine'
 }
}
前のコードによって、オブジェクトの属性変化を追跡する場合、この属性は最初のパラメータとしてProxyに導入されるべきであることを知っています。新しいProxyのインスタンスを返す関数を作成しましょう。

function $setData (dataObj, fn) {
  let self = this
  let once = false
  let $d = new Proxy(dataObj, {
   set (target, property, value) {
    if (!once) {
     target[property] = value
     once = true
     /* Do something here */
    }
    return true
   }
  })
  fn($d)
 }
これは以下の方法で使用できます。

$setData(dataObj, ($d) => {
 /* 
  * dataObj.someProps = something
  */
})

//   

$setData(dataObj.arrayProps, ($d) => {
 /* 
  * dataObj.push(something)
  */
})
それ以外に、Tony Starkを置き換えるために、テンプレートのデータオブジェクトへのマッピングを実現しなければならない。

function replaceFun(str, data) {
  let self = this
  return str.replace(/{{([^{}]*)}}/g, (a, b) => {
   return data[b]
  })
 }

replaceFun('My name is {{name}}', { name: 'xxx' })
// --> My name is xxx
この関数は{{name}}のような単層属性オブジェクトに対して良好に動作するが、{ name: 'xx', age: 18 }のような多層属性オブジェクトには無力である。例えば、テンプレートキーが{ hobbies: { food: 'apple', drink: 'milk' } }であれば、{{hobbies.food}}関数はreplaceFun()に戻るべきである。
この問題を解決するために、もう一つの関数があります。

function getObjProp (obj, propsName) {
  let propsArr = propsName.split('.')
  function rec(o, pName) {
   if (!o[pName] instanceof Array && o[pName] instanceof Object) {
    return rec(o[pName], propsArr.shift())
   }
   return o[pName]
  }
  return rec(obj, propsArr.shift())
 }

getObjProp({ data: { hobbies: { food: 'apple', drink: 'milk' } } }, 'hobbies.food')
// --> return { food: 'apple', drink: 'milk' }
最終的なdata['hobbies']['food']関数は次のようになるべきです。

function replaceFun(str, data) {
  let self = this
  return str.replace(/{{([^{}]*)}}/g, (a, b) => {
   let r = self._getObjProp(data, b);
   console.log(a, b, r)
   if (typeof r === 'string' || typeof r === 'number') {
    return r
   } else {
    return self._getObjProp(r, b.split('.')[1])
   }
  })
 }
データバインディングの例を「Mog」といいます。
なぜかというと、「Mog」です。

class Mog {
 constructor (options) {
  this.$data = options.data
  this.$el = options.el
  this.$tpl = options.template
  this._render(this.$tpl, this.$data)
 }

 $setData (dataObj, fn) {
  let self = this
  let once = false
  let $d = new Proxy(dataObj, {
   set (target, property, value) {
    if (!once) {
     target[property] = value
     once = true
     self._render(self.$tpl, self.$data)
    }
    return true
   }
  })
  fn($d)
 }

 _render (tplString, data) {
  document.querySelector(this.$el).innerHTML = this._replaceFun(tplString, data)
 }

 _replaceFun(str, data) {
  let self = this
  return str.replace(/{{([^{}]*)}}/g, (a, b) => {
   let r = self._getObjProp(data, b);
   console.log(a, b, r)
   if (typeof r === 'string' || typeof r === 'number') {
    return r
   } else {
    return self._getObjProp(r, b.split('.')[1])
   }
  })
 }

 _getObjProp (obj, propsName) {
  let propsArr = propsName.split('.')
  function rec(o, pName) {
   if (!o[pName] instanceof Array && o[pName] instanceof Object) {
    return rec(o[pName], propsArr.shift())
   }
   return o[pName]
  }
  return rec(obj, propsArr.shift())
 }

}
使用:

<!-- html -->

  <div id="app">
   <p>
    Hello everyone, my name is <span>{{name}}</span>, I am a mini <span>{{lang}}</span> framework for just <span>{{work}}</span>. I can bind data from <span>{{supports.0}}</span>, <span>{{supports.1}}</span> and <span>{{supports.2}}</span>. What's more, I was created by <span>{{info.author}}</span>, and was written in <span>{{info.jsVersion}}</span>. My motto is "<span>{{motto}}</span>".
   </p>
  </div>
  <div id="input-wrapper">
   Motto: <input type="text" id="set-motto" autofocus>
  </div>

<!-- javascript -->

let template = document.querySelector('#app').innerHTML

let mog = new Mog({
 template: template,
 el: '#app',
 data: {
  name: 'mog',
  lang: 'javascript',
  work: 'data binding',
  supports: ['String', 'Array', 'Object'],
  info: {
   author: 'Jrain',
   jsVersion: 'Ecma2015'
  },
  motto: 'Every dog has his day'
 }
})

document.querySelector('#set-motto').oninput = (e) => {
 mog.$setData(mog.$data, ($d) => {
  $d.motto = e.target.value
 })
}
ここですでオンライン体験ができます。
後記replaceFun()はデータバインディングの実験的性質を学習するための項目であり、コードはまだ優雅さが足りず、機能も十分ではない。でも、このおもちゃはたくさん勉強させてくれました。もし興味があれば、ここですまでプロジェクトをforkして、いくつかのアイデアを入れてください。
読んでくれてありがとうございます
興味のある友達はオンラインHTML/CSS/JavaScriptコードを使ってツールを実行できます。http://tools.jb51.net/code/HtmlJsRun上記コードの運行効果をテストします。
もっと多くのJavaScriptに関する内容に興味がある読者は、当駅のテーマを見ることができます。「javascript対象向け入門教程」、「JavaScriptエラーとデバッグテクニックのまとめ」、「JavaScriptデータ構造とアルゴリズム技術のまとめ」、「JavaScriptはアルゴリズムと技術の総括を遍歴します。」および「JavaScript数学演算の使い方のまとめ
本論文で述べたように、JavaScriptプログラムの設計に役に立ちます。