学无所长,一事无成
分类: JavaScript
2015-05-27 17:13:54
作者: | Kris Zyp, Marcus Reimann, Kitson Kelly, Jan Dockx |
---|---|
项目所有者: | Kris Zyp |
起自: | V1.5 |
一个通用接口或基础类,实现获取,设置以及监控属性变化(属性的变化也必须是通过 getters 和 setters 操作的),并统一管理方式。你的 model,view model 以及 view 中的类都是有状态的(就是说 object 是可变的),因此都应实现此接口。
dojo/Stateful 能够获取以及设置命名属性(包括自定义访问器修改的),这些功能组合起来能够实现对属性修改的监控。dojo/Stateful 一般作为基础类,需要提供属性监控的 components 可以从其进行扩展。这对于创建实时数据绑定非常有用,这样就可以利用当前实时状态并对属性变化立即作出响应。 It also allows a developer to customize the behavior of accessing the property by providing auto-magic getters and setters (accessors). 更进一步, dojo/Stateful 使得利用 object initialization 创建实例成为可能。
你可以直接创建 dojo/Stateful 实例 ,但建议还是创建一个子类比较好。
调用构造器时,你可以选择传入一个对象作为参数。在构造器(包括继承树上的)执行完毕后,这个对象参数可用来初始化实例。
获取属性值。如果为属性定制了一个 getter,那用这个自定义的。此函数需要一个参数:
参数 | 类型 | 描述 |
---|---|---|
name | String | 要获取的属性名 |
设置属性值。如果为属性定制了一个 setter,那用这个自定义的。此函数需要两个参数:
Argument | Type | Description |
---|---|---|
name | String|Object | The name of the property to set, or a hash of key/value pairs of several properties to set. |
value | Any? | Optional The value of the property to set, or if name is a hash, this argument should be omitted. |
If no custom setter is defined on an object, performing a set() will result in the property value being set directly on the object. This can be convenient, as the property can be accessed directly through standard JS syntax (object.property). But, be aware that setting arbitrary property names could lead to overriding the object’s methods (like set(), get(), etc.), which may be undesirable. If you are setting arbitrary property names, you may wish to guard against reserved method names, or prefix property names to avoid collision.
Sets a callback to be called when the property changes. The function takes up to two arguments:
Argument | Type | Description |
---|---|---|
name | String? | Optional The name of the property to watch. If omitted, all properties will be watched and the callback will be called. |
callback | Function | The callback function that should be called when the property changes. |
watch() returns a handle that allows disconnection of the watch at some point in the future. For example:
The callback function will be passed three arguments:
Argument | Type | Description |
---|---|---|
name | String | The name of the property that changed. |
oldValue | Any | The value of the property before the change. |
value | Any | The value of the property after the change. |
This is a helper function to be used in custom setters that is used in scenarios where calling .set() is not appropriate, but the value of the property needs to be changed and any watches called. The typical scenario is when there are interlinked values, where changing one value affects another value, and therefore can avoid an infinite loop of one property changing the value of the other property. The function takes two arguments:
Argument | Type | Description |
---|---|---|
name | String | The name of the property to change. |
value | Any | The value to change the property to. |
dojo/Stateful supports the ability to define custom accessors (getters and setters) that allow control over how values of properties are set and retrieved. When a custom accessors is defined, a call to .get() or.set() will auto-magically use the custom accessor instead of accessing the property directly.
A custom getter is defined in the format of _xxxGetter and a custom setter is defined in the format of_xxxSetter where the name of the property is xxx. The name of the property is not mutated in any way. For example, the following demonstrates several different examples of how custom accessors would be defined:
In addition, .set() has the ability to detect promise returns from a custom setter. This can be used in situations where the customer setter will not be immediately setting the value of the attribute. For example, if a custom setter needs to validate or post a value to a back end service via XHR before actually setting the value of the attribute. The custom setter can return a Deferred or promise value and any watch callbacks will not be called until the promise is resolved. If the promise is rejected, the watch will not be called. For example:
You should always be able to construct an object of a subclass of Stateful without any arguments:
This should give you an “empty” object, with all properties initialized to default values.
This means subclasses of Stateful cannot have mandatory properties that do not have a sensible default. Such properties require an initial value in the constructor, which violates the requirement for a no-arguments constructor.
You can also call the constructor with an Object argument. This is merely syntactic sugar for object initialization:
is completely equivalent to
Note that this is exactly the same thing as in C# and some other languages. The C# equivalent of the example would be:
For classes in the model, viewmodel and view, that have state (i.e., are mutable), the only good programming idiom is to have only a default, no-arguments constructor. These are exactly the kinds of classes that would be Stateful.
First of all, you always need a no-arguments constructor, because all kinds of frameworks (e.g., the) require it. General code cannot provide specific arguments for a custom constructor.
Second, for model and viewmodel objects, you almost always need to be able to construct an “empty” object. Although semantically a firstName might be mandatory, in a UI you cannot make this an invariant of Person. Sure, every time you get an existing object from the server, it will have a firstName, but most often the end user should also be able to create a new person in the UI, and for that he needs to be able to start out with an “empty” form. It makes things very difficult if you cannot bind a (view)model object to that empty form, so the (view)model object must allow even semantically mandatory fields to be empty. Such an object might not be “valid” for sending to the server, but it must be able to exist. A “correct” JavaScript object (i.e., the instance adheres to its invariants and will function correctly) is not necessarily a semantically valid object. A semantically valid object should always be a “correct” JavaScript object, though.
In a language like Java or C#, you might then add further overloaded constructor methods, for convenience, but you quickly learn that you then have to write overloaded methods for all possible combinations, if that is possible at all. Each of these methods carries a slightly different version of initialisation semantics, needs to have its own unit tests, and needs to be maintained. The gain of all this extra work in a language like Java would be being able to write:
instead of
In C#, given the object initializer syntax, the gain is even smaller.
All in all, it only makes sense for these kinds of classes to have only a default, no-arguments constructor, and Stateful builds on this.
Classes you declare with can have a postscript method that is executed immediately after all the chained constructors in the inheritance chain have finished. In Stateful, this method is used to do object initialization if an instance is constructed with an Object argument.
Your subclass can extend the postscript method (you probably never need to), but should not override it:
The constructor in every subclass in the inheritance chain should do its bit to deliver an “empty” instance with default values for all properties. Most often this resorts to doing nothing at all in Dojo, so you can leave out the constructor method entirely.
In the example, we might choose to represent empty name values by null for all 3 properties (alternatives are undefined or the empty string ""). In a language like Java and C# this would require no work, sincenull is the default value. In Dojo, the default is undefined, but you set the default in the prototype, not in the constructor:
As you can see, there is no need for a constructor.
The only real need to do something in the constructor is when you have instance properties that arereferences, that you don’t want to be null or undefined in the “empty” state. The best example is a to-many association that you need to maintain:
Suppose our Person has siblings:
Here you need to create a distinct array in the constructor of each instance. Setting the prototype property to [] wouldn’t do the trick, because then all instances would share the one array in the prototype, mixing up the siblings of all Person instances.