Ben*_*aum 234 html javascript data-binding dom
请将此问题视为严格教育.我仍然有兴趣听到新的答案和想法来实现这一点
我如何使用JavaScript实现双向数据绑定?
通过数据绑定到DOM,我的意思是,例如,拥有一个a带有属性的JavaScript对象b.然后有一个<input>DOM元素(例如),当DOM元素改变时,a改变,反之亦然(也就是说,我的意思是双向数据绑定).
以下是AngularJS的示意图:

所以基本上我有类似的JavaScript:
var a = {b:3};
Run Code Online (Sandbox Code Playgroud)
然后输入(或其他形式)元素,如:
<input type='text' value=''>
Run Code Online (Sandbox Code Playgroud)
我希望输入的值是a.b值(例如),当输入文本发生变化时,我也想a.b改变.当a.bJavaScript发生变化时,输入会发生变化.
在纯JavaScript中完成此操作的基本技术有哪些?
具体来说,我想要一个很好的答案来参考:
我是Mustache的忠实粉丝,所以我尝试用它来模板化.但是,当我尝试执行数据绑定本身时遇到了问题,因为Mustache将HTML作为字符串处理,因此在得到结果之后,我没有引用viewmodel中对象的位置.我能想到的唯一解决方法是使用属性修改HTML字符串(或创建DOM树)本身.我不介意使用不同的模板引擎.
基本上,我有一种强烈的感觉,我正在使手头的问题变得复杂,并且有一个简单的解决方案.
注意:请不要提供使用外部库的答案,尤其是那些包含数千行代码的库.我已经使用了(并且喜欢!)AngularJS和KnockoutJS.我真的不想要"使用框架x"形式的答案.最理想的是,我希望未来的读者不知道如何使用许多框架来掌握如何自己实现双向数据绑定.我不希望得到一个完整的答案,而是一个能够理解这个想法的答案.
小智 100
- 如何绑定对象的工作?
- 如何听取表格中的变化可能有效?
我想还有其他技术,但最终我有一个对象,它持有对相关DOM元素的引用,并提供一个接口来协调对自己的数据及其相关元素的更新.
在.addEventListener()此提供了一个非常漂亮的界面.您可以为它提供一个实现eventListener接口的对象,它将使用该对象作为this值调用其处理程序.
这使您可以自动访问元素及其相关数据.
原型继承是实现这一目标的好方法,当然不是必需的.首先,您将创建一个接收元素和一些初始数据的构造函数.
function MyCtor(element, data) {
this.data = data;
this.element = element;
element.value = data;
element.addEventListener("change", this, false);
}
Run Code Online (Sandbox Code Playgroud)
所以这里的构造函数将元素和数据存储在新对象的属性中.它还将change事件绑定到给定的事件element.有趣的是它传递新对象而不是函数作为第二个参数.但仅此一点是行不通的.
eventListener接口要使其工作,您的对象需要实现该eventListener接口.完成此任务所需要的只是为对象提供一种handleEvent()方法.
这就是继承的来源.
MyCtor.prototype.handleEvent = function(event) {
switch (event.type) {
case "change": this.change(this.element.value);
}
};
MyCtor.prototype.change = function(value) {
this.data = value;
this.element.value = value;
};
Run Code Online (Sandbox Code Playgroud)
有许多不同的方式可以构建它,但是对于协调更新的示例,我决定使change()方法只接受一个值,并handleEvent传递该值而不是事件对象.这样就change()可以在没有事件的情况下调用它.
所以现在,当change事件发生时,它将更新元素和.data属性.当您调用.change()JavaScript程序时也会发生同样的情况.
现在,您只需创建新对象,然后让它执行更新.JS代码中的更新将出现在输入中,并且JS代码可以看到输入上的更改事件.
var obj = new MyCtor(document.getElementById("foo"), "20");
// simulate some JS based changes.
var i = 0;
setInterval(function() {
obj.change(parseInt(obj.element.value) + ++i);
}, 3000);
Run Code Online (Sandbox Code Playgroud)
演示: http : //jsfiddle.net/RkTMD/
Ben*_*aum 34
所以,我决定把自己的解决方案扔进锅里.这是一个工作小提琴.请注意,这仅适用于非常现代的浏览器.
这种实现非常现代 - 它需要一个(非常)现代的浏览器和用户两种新技术:
MutationObserver小号,以检测在DOM的改变(事件侦听器被用作孔)Object.observe检测对象的变化并通知dom.危险,因为这个答案已经写好了,所以ECMAScript TC已经讨论并决定反对,考虑使用polyfill.domAttribute:objAttribute映射 - 例如bind='textContent:name'这是dataBind函数,注意它只有20行代码,可能更短:
function dataBind(domElement, obj) {
var bind = domElement.getAttribute("bind").split(":");
var domAttr = bind[0].trim(); // the attribute on the DOM element
var itemAttr = bind[1].trim(); // the attribute the object
// when the object changes - update the DOM
Object.observe(obj, function (change) {
domElement[domAttr] = obj[itemAttr];
});
// when the dom changes - update the object
new MutationObserver(updateObj).observe(domElement, {
attributes: true,
childList: true,
characterData: true
});
domElement.addEventListener("keyup", updateObj);
domElement.addEventListener("click",updateObj);
function updateObj(){
obj[itemAttr] = domElement[domAttr];
}
// start the cycle by taking the attribute from the object and updating it.
domElement[domAttr] = obj[itemAttr];
}
Run Code Online (Sandbox Code Playgroud)
这是一些用法:
HTML:
<div id='projection' bind='textContent:name'></div>
<input type='text' id='textView' bind='value:name' />
Run Code Online (Sandbox Code Playgroud)
JavaScript的:
var obj = {
name: "Benjamin"
};
var el = document.getElementById("textView");
dataBind(el, obj);
var field = document.getElementById("projection");
dataBind(field,obj);
Run Code Online (Sandbox Code Playgroud)
这是一个工作小提琴.请注意,此解决方案非常通用.可以使用Object.observe和变异观察器填充.
Kir*_*use 27
我想补充一下我的前言.我建议采用略有不同的方法,只需在不使用方法的情况下为对象分配新值即可.必须注意的是,特别是旧版浏览器不支持这一点,IE9仍然需要使用不同的界面.
最值得注意的是我的方法没有利用事件.
我的建议充分利用了吸气剂和制定者相对年轻的特征,特别是限制者.一般来说,mutators允许我们"定制"某些属性如何赋值并检索的行为.
我将在这里使用的一个实现是Object.defineProperty方法.它适用于FireFox,GoogleChrome和 - 我认为 - IE9.没有测试过其他浏览器,但因为这只是理论...
无论如何,它接受三个参数.第一个参数是您希望为其定义新属性的对象,第二个参数是类似于新属性名称的字符串,最后一个是"描述符对象",提供有关新属性行为的信息.
两个特别有趣的描述符是get和set.一个例子看起来像下面这样.请注意,使用这两个禁止使用其他4个描述符.
function MyCtor( bindTo ) {
// I'll omit parameter validation here.
Object.defineProperty(this, 'value', {
enumerable: true,
get : function ( ) {
return bindTo.value;
},
set : function ( val ) {
bindTo.value = val;
}
});
}
Run Code Online (Sandbox Code Playgroud)
现在使用它变得略有不同:
var obj = new MyCtor(document.getElementById('foo')),
i = 0;
setInterval(function() {
obj.value += ++i;
}, 3000);
Run Code Online (Sandbox Code Playgroud)
我想强调一点,这只适用于现代浏览器.
工作小提琴:http://jsfiddle.net/Derija93/RkTMD/1/
在过去的 7 年里,事情发生了很大的变化,现在大多数浏览器都拥有原生 Web 组件。IMO 问题的核心是元素之间共享状态,一旦你知道了状态变化时更新 ui 就很简单了,反之亦然。
要在元素之间共享数据,您可以创建一个 StateObserver 类,并从中扩展您的 Web 组件。一个最小的实现看起来像这样:
// create a base class to handle state
class StateObserver extends HTMLElement {
constructor () {
super()
StateObserver.instances.push(this)
}
stateUpdate (update) {
StateObserver.lastState = StateObserver.state
StateObserver.state = update
StateObserver.instances.forEach((i) => {
if (!i.onStateUpdate) return
i.onStateUpdate(update, StateObserver.lastState)
})
}
}
StateObserver.instances = []
StateObserver.state = {}
StateObserver.lastState = {}
// create a web component which will react to state changes
class CustomReactive extends StateObserver {
onStateUpdate (state, lastState) {
if (state.someProp === lastState.someProp) return
this.innerHTML = `input is: ${state.someProp}`
}
}
customElements.define('custom-reactive', CustomReactive)
class CustomObserved extends StateObserver {
connectedCallback () {
this.querySelector('input').addEventListener('input', (e) => {
this.stateUpdate({ someProp: e.target.value })
})
}
}
customElements.define('custom-observed', CustomObserved)Run Code Online (Sandbox Code Playgroud)
<custom-observed>
<input>
</custom-observed>
<br />
<custom-reactive></custom-reactive>Run Code Online (Sandbox Code Playgroud)
我喜欢这种方法,因为:
data-属性小智 7
我认为我的答案将更具技术性,但并没有不同,因为其他人使用不同的技术呈现相同的东西.
所以,首先,这个问题的解决方案是使用一种称为"观察者"的设计模式,它让你将数据与你的演示文稿分离,使得一件事的变化被广播给他们的听众,但在这种情况下它是双向的.
要将数据从DOM绑定到js对象,您可以以data属性(或类,如果需要兼容性)的形式添加标记,如下所示:
<input type="text" data-object="a" data-property="b" id="b" class="bind" value=""/>
<input type="text" data-object="a" data-property="c" id="c" class="bind" value=""/>
<input type="text" data-object="d" data-property="e" id="e" class="bind" value=""/>
Run Code Online (Sandbox Code Playgroud)
这种方式可以通过js使用querySelectorAll(或老朋友getElementsByClassName兼容).
现在,您可以将侦听更改的事件绑定到以下方式:每个对象一个侦听器或容器/文档的一个大侦听器.绑定到文档/容器将触发事件,对于它或它的子进行的每个更改,它将具有更小的内存占用但将产生事件调用.
代码看起来像这样:
//Bind to each element
var elements = document.querySelectorAll('input[data-property]');
function toJS(){
//Assuming `a` is in scope of the document
var obj = document[this.data.object];
obj[this.data.property] = this.value;
}
elements.forEach(function(el){
el.addEventListener('change', toJS, false);
}
//Bind to document
function toJS2(){
if (this.data && this.data.object) {
//Again, assuming `a` is in document's scope
var obj = document[this.data.object];
obj[this.data.property] = this.value;
}
}
document.addEventListener('change', toJS2, false);
Run Code Online (Sandbox Code Playgroud)
您将需要两件事:一个元对象将保存女巫DOM元素的引用绑定到每个js对象/属性以及一种监听对象更改的方式.它基本上是相同的方式:您必须有一种方法来监听对象中的更改然后将其绑定到DOM节点,因为您的对象"不能拥有"元数据,您将需要另一个以某种方式保存元数据的对象属性名称映射到元数据对象的属性.代码将是这样的:
var a = {
b: 'foo',
c: 'bar'
},
d = {
e: 'baz'
},
metadata = {
b: 'b',
c: 'c',
e: 'e'
};
function toDOM(changes){
//changes is an array of objects changed and what happened
//for now i'd recommend a polyfill as this syntax is still a proposal
changes.forEach(function(change){
var element = document.getElementById(metadata[change.name]);
element.value = change.object[change.name];
});
}
//Side note: you can also use currying to fix the second argument of the function (the toDOM method)
Object.observe(a, toDOM);
Object.observe(d, toDOM);
Run Code Online (Sandbox Code Playgroud)
我希望我有所帮助.
昨天,我开始编写自己的绑定数据的方法.
玩它很有趣.
我认为它很漂亮,非常有用.至少在我使用firefox和chrome的测试中,Edge也必须正常工作.不确定其他人,但如果他们支持代理,我认为它会工作.
https://jsfiddle.net/2ozoovne/1/
<H1>Bind Context 1</H1>
<input id='a' data-bind='data.test' placeholder='Button Text' />
<input id='b' data-bind='data.test' placeholder='Button Text' />
<input type=button id='c' data-bind='data.test' />
<H1>Bind Context 2</H1>
<input id='d' data-bind='data.otherTest' placeholder='input bind' />
<input id='e' data-bind='data.otherTest' placeholder='input bind' />
<input id='f' data-bind='data.test' placeholder='button 2 text - same var name, other context' />
<input type=button id='g' data-bind='data.test' value='click here!' />
<H1>No bind data</H1>
<input id='h' placeholder='not bound' />
<input id='i' placeholder='not bound'/>
<input type=button id='j' />
Run Code Online (Sandbox Code Playgroud)
这是代码:
(function(){
if ( ! ( 'SmartBind' in window ) ) { // never run more than once
// This hack sets a "proxy" property for HTMLInputElement.value set property
var nativeHTMLInputElementValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
var newDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
newDescriptor.set=function( value ){
if ( 'settingDomBind' in this )
return;
var hasDataBind=this.hasAttribute('data-bind');
if ( hasDataBind ) {
this.settingDomBind=true;
var dataBind=this.getAttribute('data-bind');
if ( ! this.hasAttribute('data-bind-context-id') ) {
console.error("Impossible to recover data-bind-context-id attribute", this, dataBind );
} else {
var bindContextId=this.getAttribute('data-bind-context-id');
if ( bindContextId in SmartBind.contexts ) {
var bindContext=SmartBind.contexts[bindContextId];
var dataTarget=SmartBind.getDataTarget(bindContext, dataBind);
SmartBind.setDataValue( dataTarget, value);
} else {
console.error( "Invalid data-bind-context-id attribute", this, dataBind, bindContextId );
}
}
delete this.settingDomBind;
}
nativeHTMLInputElementValue.set.bind(this)( value );
}
Object.defineProperty(HTMLInputElement.prototype, 'value', newDescriptor);
var uid= function(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
// SmartBind Functions
window.SmartBind={};
SmartBind.BindContext=function(){
var _data={};
var ctx = {
"id" : uid() /* Data Bind Context Id */
, "_data": _data /* Real data object */
, "mapDom": {} /* DOM Mapped objects */
, "mapDataTarget": {} /* Data Mapped objects */
}
SmartBind.contexts[ctx.id]=ctx;
ctx.data=new Proxy( _data, SmartBind.getProxyHandler(ctx, "data")) /* Proxy object to _data */
return ctx;
}
SmartBind.getDataTarget=function(bindContext, bindPath){
var bindedObject=
{ bindContext: bindContext
, bindPath: bindPath
};
var dataObj=bindContext;
var dataObjLevels=bindPath.split('.');
for( var i=0; i<dataObjLevels.length; i++ ) {
if ( i == dataObjLevels.length-1 ) { // last level, set value
bindedObject={ target: dataObj
, item: dataObjLevels[i]
}
} else { // digg in
if ( ! ( dataObjLevels[i] in dataObj ) ) {
console.warn("Impossible to get data target object to map bind.", bindPath, bindContext);
break;
}
dataObj=dataObj[dataObjLevels[i]];
}
}
return bindedObject ;
}
SmartBind.contexts={};
SmartBind.add=function(bindContext, domObj){
if ( typeof domObj == "undefined" ){
console.error("No DOM Object argument given ", bindContext);
return;
}
if ( ! domObj.hasAttribute('data-bind') ) {
console.warn("Object has no data-bind attribute", domObj);
return;
}
domObj.setAttribute("data-bind-context-id", bindContext.id);
var bindPath=domObj.getAttribute('data-bind');
if ( bindPath in bindContext.mapDom ) {
bindContext.mapDom[bindPath][bindContext.mapDom[bindPath].length]=domObj;
} else {
bindContext.mapDom[bindPath]=[domObj];
}
var bindTarget=SmartBind.getDataTarget(bindContext, bindPath);
bindContext.mapDataTarget[bindPath]=bindTarget;
domObj.addEventListener('input', function(){ SmartBind.setDataValue(bindTarget,this.value); } );
domObj.addEventListener('change', function(){ SmartBind.setDataValue(bindTarget, this.value); } );
}
SmartBind.setDataValue=function(bindTarget,value){
if ( ! ( 'target' in bindTarget ) ) {
var lBindTarget=SmartBind.getDataTarget(bindTarget.bindContext, bindTarget.bindPath);
if ( 'target' in lBindTarget ) {
bindTarget.target=lBindTarget.target;
bindTarget.item=lBindTarget.item;
} else {
console.warn("Still can't recover the object to bind", bindTarget.bindPath );
}
}
if ( ( 'target' in bindTarget ) ) {
bindTarget.target[bindTarget.item]=value;
}
}
SmartBind.getDataValue=function(bindTarget){
if ( ! ( 'target' in bindTarget ) ) {
var lBindTarget=SmartBind.getDataTarget(bindTarget.bindContext, bindTarget.bindPath);
if ( 'target' in lBindTarget ) {
bindTarget.target=lBindTarget.target;
bindTarget.item=lBindTarget.item;
} else {
console.warn("Still can't recover the object to bind", bindTarget.bindPath );
}
}
if ( ( 'target' in bindTarget ) ) {
return bindTarget.target[bindTarget.item];
}
}
SmartBind.getProxyHandler=function(bindContext, bindPath){
return {
get: function(target, name){
if ( name == '__isProxy' )
return true;
// just get the value
// console.debug("proxy get", bindPath, name, target[name]);
return target[name];
}
,
set: function(target, name, value){
target[name]=value;
bindContext.mapDataTarget[bindPath+"."+name]=value;
SmartBind.processBindToDom(bindContext, bindPath+"."+name);
// console.debug("proxy set", bindPath, name, target[name], value );
// and set all related objects with this target.name
if ( value instanceof Object) {
if ( !( name in target) || ! ( target[name].__isProxy ) ){
target[name]=new Proxy(value, SmartBind.getProxyHandler(bindContext, bindPath+'.'+name));
}
// run all tree to set proxies when necessary
var objKeys=Object.keys(value);
// console.debug("...objkeys",objKeys);
for ( var i=0; i<objKeys.length; i++ ) {
bindContext.mapDataTarget[bindPath+"."+name+"."+objKeys[i]]=target[name][objKeys[i]];
if ( typeof value[objKeys[i]] == 'undefined' || value[objKeys[i]] == null || ! ( value[objKeys[i]] instanceof Object ) || value[objKeys[i]].__isProxy )
continue;
target[name][objKeys[i]]=new Proxy( value[objKeys[i]], SmartBind.getProxyHandler(bindContext, bindPath+'.'+name+"."+objKeys[i]));
}
// TODO it can be faster than run all items
var bindKeys=Object.keys(bindContext.mapDom);
for ( var i=0; i<bindKeys.length; i++ ) {
// console.log("test...", bindKeys[i], " for ", bindPath+"."+name);
if ( bindKeys[i].startsWith(bindPath+"."+name) ) {
// console.log("its ok, lets update dom...", bindKeys[i]);
SmartBind.processBindToDom( bindContext, bindKeys[i] );
}
}
}
return true;
}
};
}
SmartBind.processBindToDom=function(bindContext, bindPath) {
var domList=bindContext.mapDom[bindPath];
if ( typeof domList != 'undefined' ) {
try {
for ( var i=0; i < domList.length ; i++){
var dataTarget=SmartBind.getDataTarget(bindContext, bindPath);
if ( 'target' in dataTarget )
domList[i].value=dataTarget.target[dataTarget.item];
else
console.warn("Could not get data target", bindContext, bindPath);
}
} catch (e){
console.warn("bind fail", bindPath, bindContext, e);
}
}
}
}
})();
Run Code Online (Sandbox Code Playgroud)
然后,设置,只需:
var bindContext=SmartBind.BindContext();
SmartBind.add(bindContext, document.getElementById('a'));
SmartBind.add(bindContext, document.getElementById('b'));
SmartBind.add(bindContext, document.getElementById('c'));
var bindContext2=SmartBind.BindContext();
SmartBind.add(bindContext2, document.getElementById('d'));
SmartBind.add(bindContext2, document.getElementById('e'));
SmartBind.add(bindContext2, document.getElementById('f'));
SmartBind.add(bindContext2, document.getElementById('g'));
setTimeout( function() {
document.getElementById('b').value='Via Script works too!'
}, 2000);
document.getElementById('g').addEventListener('click',function(){
bindContext2.data.test='Set by js value'
})
Run Code Online (Sandbox Code Playgroud)
现在,我刚刚添加了HTMLInputElement值bind.
如果您知道如何改进它,请告诉我.
在这个链接"JavaScript中的简单双向数据绑定"中有一个非常简单的完整的双向数据绑定实现
之前的链接以及来自knockoutjs,backbone.js和agility.js的想法,导致了这个轻量级和快速的MVVM框架,ModelView.js 基于jQuery 与jQuery很好地配合,我是谦虚(或者可能不那么谦逊)的作者.
下面重现示例代码(来自博客帖子链接):
DataBinder的示例代码
function DataBinder( object_id ) {
// Use a jQuery object as simple PubSub
var pubSub = jQuery({});
// We expect a `data` element specifying the binding
// in the form: data-bind-<object_id>="<property_name>"
var data_attr = "bind-" + object_id,
message = object_id + ":change";
// Listen to change events on elements with the data-binding attribute and proxy
// them to the PubSub, so that the change is "broadcasted" to all connected objects
jQuery( document ).on( "change", "[data-" + data_attr + "]", function( evt ) {
var $input = jQuery( this );
pubSub.trigger( message, [ $input.data( data_attr ), $input.val() ] );
});
// PubSub propagates changes to all bound elements, setting value of
// input tags or HTML content of other tags
pubSub.on( message, function( evt, prop_name, new_val ) {
jQuery( "[data-" + data_attr + "=" + prop_name + "]" ).each( function() {
var $bound = jQuery( this );
if ( $bound.is("input, textarea, select") ) {
$bound.val( new_val );
} else {
$bound.html( new_val );
}
});
});
return pubSub;
}
Run Code Online (Sandbox Code Playgroud)
对于JavaScript对象的问题,为了这个实验,用户模型的最小实现可能如下:
function User( uid ) {
var binder = new DataBinder( uid ),
user = {
attributes: {},
// The attribute setter publish changes using the DataBinder PubSub
set: function( attr_name, val ) {
this.attributes[ attr_name ] = val;
binder.trigger( uid + ":change", [ attr_name, val, this ] );
},
get: function( attr_name ) {
return this.attributes[ attr_name ];
},
_binder: binder
};
// Subscribe to the PubSub
binder.on( uid + ":change", function( evt, attr_name, new_val, initiator ) {
if ( initiator !== user ) {
user.set( attr_name, new_val );
}
});
return user;
}
Run Code Online (Sandbox Code Playgroud)
现在,每当我们想要将模型的属性绑定到一个UI时,我们只需要在相应的HTML元素上设置适当的数据属性:
// javascript
var user = new User( 123 );
user.set( "name", "Wolfgang" );
<!-- html -->
<input type="number" data-bind-123="name" />
Run Code Online (Sandbox Code Playgroud)
将变量绑定到输入(双向绑定)的一种简单方法是直接访问 getter 和 setter 中的输入元素:
var variable = function(element){
return {
get : function () { return element.value;},
set : function (value) { element.value = value;}
}
};
Run Code Online (Sandbox Code Playgroud)
在 HTML 中:
<input id="an-input" />
<input id="another-input" />
Run Code Online (Sandbox Code Playgroud)
并使用:
var myVar = new variable(document.getElementById("an-input"));
myVar.set(10);
// and another example:
var myVar2 = new variable(document.getElementById("another-input"));
myVar.set(myVar2.get());
Run Code Online (Sandbox Code Playgroud)
var variable = function(element){
return function () {
if(arguments.length > 0)
element.value = arguments[0];
else return element.value;
}
}
Run Code Online (Sandbox Code Playgroud)
使用方法:
var v1 = new variable(document.getElementById("an-input"));
v1(10); // sets value to 20.
console.log(v1()); // reads value.
Run Code Online (Sandbox Code Playgroud)
Object.defineProperty这是一个直接修改属性访问方式的想法。
代码:
function bind(base, el, varname) {
Object.defineProperty(base, varname, {
get: () => {
return el.value;
},
set: (value) => {
el.value = value;
}
})
}
Run Code Online (Sandbox Code Playgroud)
用法:
var p = new some_class();
bind(p,document.getElementById("someID"),'variable');
p.variable="yes"
Run Code Online (Sandbox Code Playgroud)
小提琴:这里
聚会迟到了,特别是因为我在几个月/几年前写了 2 个相关的库,我稍后会提到它们,但看起来仍然与我相关。为了避免剧透,我选择的技术是:
Proxy用于观察模型MutationObserver用于跟踪 DOM 的更改(出于绑定原因,而不是值更改)addEventListener处理程序处理恕我直言,除了OP之外,数据绑定实现也很重要:
user.address.blockshift等splice)考虑到所有这些,我认为不可能仅仅抛出几十行 JS 代码。我尝试将其作为一种模式而不是库来实现- 对我来说不起作用。
接下来,删除了having Object.observe,但考虑到模型的观察是至关重要的部分——这整个部分必须与另一个库分离。现在谈谈我如何解决这个问题的原则 - 正如OP所要求的:
型号(JS部分)
我对模型观察的看法是Proxy,这是使其工作的唯一明智的方法,恕我直言。功能齐全的observer库值得拥有自己的库,因此我object-observer为此开发了库。
模型应该通过一些专用的 API 进行注册,这就是 POJO 变成Observables 的地方,这里看不到任何快捷方式。被视为绑定视图(见下文)的 DOM 元素首先使用模型的值进行更新,然后在每次数据更改时更新。
视图(HTML 部分)
恕我直言,表达绑定的最简洁的方式是通过属性。许多人以前这样做过,许多人以后也会这样做,所以这里没有新闻,这只是这样做的正确方法。就我而言,我使用了以下语法:<span data-tie="modelKey:path.to.data => targerProperty"></span>,但这不太重要。对我来说重要的是,HTML 中没有复杂的脚本语法 - 恕我直言,这又是错误的。
首先应收集指定为绑定视图的所有元素。从性能方面来看,管理模型和视图之间的一些内部映射对我来说似乎是不可避免的,这似乎是一个正确的情况,应该牺牲内存+一些管理来节省运行时查找和更新。
正如我们所说,视图首先从模型更新(如果可用),并在稍后模型更改时更新。更重要的是,应该通过观察整个 DOM 来MutationObserver对动态添加/删除/更改的元素做出反应(绑定/取消绑定)。此外,所有这些都应该复制到 ShadowDOM(当然是打开的)中,以免留下未绑定的黑洞。
具体细节列表可能确实更进一步,但在我看来,这些是使数据绑定实现的主要原则,一方面可以实现功能完整性,另一方面可以实现合理的简单性。
因此,除了object-observer上面提到的之外,我确实还编写了data-tier库,它沿着上述概念实现数据绑定。
| 归档时间: |
|
| 查看次数: |
134155 次 |
| 最近记录: |