クラスコンストラクター:
constructor(obj){ let instance; if (obj) { instance = Binder.createProto(obj, this); } else { instance = this; } Object.defineProperty(instance, '_binder', { configurable: true, value: {} }); Object.defineProperties(instance._binder, { 'properties': { configurable: true, value: {} }, 'bindings':{ configurable: true, value: {} }, 'watchers':{ configurable: true, value: {} }, 'emitter':{ configurable: true, writable: true, value: {} } }); return instance; } — , , . . `createProto` . — — - `_bunder`, , , . «emitter» , . , (`writable: false`).
静的クラスメソッド:
static get timeout(){ return Binder._timeout || 0; } static set timeout(ms){ Object.defineProperty(this, '_timeout', { configurable: true, value: ms }); } static delay(ms = Binder.timeout){ return new Promise((res, rej) => { if(ms > 0){ setTimeout(res, ms); } else { res(); } }); } static get queue(){ return Promise.resolve(); } -— `_timeout` . , , ES6 -. - queue , . `delay` , "" . .
static createProto(obj, instance){ let className = obj.constructor.name; if(!this.prototypes){ Object.defineProperty(this, 'prototypes', { configurable: true, value: new Map() }); } if(!this.prototypes.has(className)){ let descriptor = { 'constructor': { configurable: true, value: obj.constructor } }; Object.getOwnPropertyNames(instance.__proto__).forEach( ( prop ) => { if(prop !== 'constructor'){ descriptor[prop] = { configurable: true, value: instance[prop] }; } } ); this.prototypes.set( className, Object.create(obj.__proto__, descriptor) ); } obj.__proto__ = this.prototypes.get(className); return obj; } — . . , - `Binder.prototypes`
static transform(obj, prop){ let descriptor, nativeSet; let newGet = function(){ return this._binder.properties[prop];}; let newSet = function(value){ let queues = [Binder.queue, Binder.queue]; if(this._binder.properties[prop] === value){ return; } Object.defineProperty(this._binder.properties, prop, { configurable: true, value: value }); if(this._binder.bindings[prop]){ this._binder.bindings[prop].forEach(( [prop, ms], boundObj ) => { if(boundObj === this._binder.emitter) { this._binder.emitter = null; return; } if(boundObj[prop] === value) return; queues[0] = queues[0] .then(() => Binder.delay(ms) ) .then(() => { boundObj._binder.emitter = obj; boundObj[prop] = value; }); }); queues[0] = queues[0].catch(err => console.log(err) ); } if( this._binder.watchers[prop] ){ this._binder.watchers[prop].forEach( ( [cb, ms] ) => { queues[1] = queues[1] .then(() => Binder.delay(ms) ) .then(() => { cb(value); }); }); } if( this._binder.watchers['*'] ){ this._binder.watchers['*'].forEach( ( [cb, ms] ) => { queues[1] = queues[1] .then(() => Binder.delay(ms) ) .then(() => { cb(value); }); }); } queues[1] = queues[1].catch(err => console.log(err)); }; if(obj.constructor.name.indexOf('HTML') === -1){ descriptor = { configurable: true, enumerable: true, get: newGet, set: newSet }; } else { if('value' in obj) { descriptor = Object.getOwnPropertyDescriptor( obj.constructor.prototype, 'value' ); obj.addEventListener('keydown', function(evob){ if(evob.key.length === 1){ newSet.call(this, this.value + evob.key); } else { Binder.queue.then(() => { newSet.call(this, this.value); }); } }); } else { descriptor = Object.getOwnPropertyDescriptor( Node.prototype, 'textContent' ); } nativeSet = descriptor.set; descriptor.set = function(value){ nativeSet.call(this, value); newSet.call(this, value); }; } Object.defineProperty(obj._binder.properties, prop, { configurable: true, value: obj[prop] }); Object.defineProperty(obj, prop, descriptor); return obj; } - `transform` . JS , `obj._binder.properties`, /. DOM , /. - . - . - - . `obj._binder.emitter` . . . - . - . - DOM - DOM . "" `input`, `textarea` `value` `textContent`. "" / `value` (. `. 2`). , `input` `HTMLInputElementPrototype`. `textContent` / `Node.prototype`(. `. 3`). / `Object.getOwnPropertyDescriptor`. "" . - , . - `newSet` `newGet`, , .
図2-「value」プロパティの継承
図3-`div`要素の例を使用した` textContent`プロパティの継承
わかりやすくするために、DOM要素の変換を説明する別の画像を示します。例としてdiv要素を使用します( 図4 )
図4-DOM要素の変換の図。
非同期キューについて説明します。 最初は、特定のプロパティのすべてのバインディングに対して1つの実行キューを作成することを想定していましたが、不快な効果が生じました。図を参照してください。 5 。 つまり 最初のバインディングは、キュー全体が完了するのを待ってから、値を再度更新します。 個別のキューの場合、最初のバインディングが特定の間隔で更新され、後続のすべてが前のバインディングの間隔の合計を通じて更新されることは確かです。
図5一般的な実行キューと個別の実行キューの比較。
バインダークラスのインスタンスメソッド:
_bind(ownProp, obj, objProp, ms){ if(!this._binder.bindings[ownProp]) { this._binder.bindings[ownProp] = new Map(); Binder.transform(this, ownProp); } if(this._binder.bindings[ownProp].has(obj)){ return !!console.log('Binding for this object is already set'); } this._binder.bindings[ownProp].set(obj, [objProp, ms]); if( !obj._binder.bindings[objProp] || !obj._binder.bindings[objProp].has(this)) { obj._bind(objProp, this, ownProp, ms); } return this; } _unbind(ownProp, obj, objProp){ try{ this._binder.bindings[ownProp].delete(obj); obj._binder.bindings[objProp].delete(this); return this; } catch(e) { return !!console.log(e); } }; _watch(prop = '*', cb, ms){ var cbHash = Binder.hash(cb.toString().replace(/\s/g,'')); if(!this._binder.watchers[prop]) { this._binder.watchers[prop] = new Map(); if(prop === '*'){ Object.keys(this).forEach( item => { Binder.transform(this, item); }); } else { Binder.transform(this, prop); } } if(this._binder.watchers[prop].has(cbHash)) { return !!console.log('Watchers is already set'); } this._binder.watchers[prop].set(cbHash, [cb, ms]); return cbHash; }; _unwatch(prop = '*', cbHash = 0){ try{ this._binder.watchers[prop].delete(cbHash); return this; } catch(e){ return !!console.log(e); } }; - - /. , , , . () . . `. 6` - - /. ( - "*"). . .
図6ネコバインダーに会う