YUI3:Attribute
YUI的Attribute功能允许你通过一个可扩展的Attribute接口为类添加属性。该接口为你的类添加get和set方法,用来存取类的属性值。该接口还为类添加了“属性change事件(attribute change event)”支持,我们可以监听“属性改变事件”,在属性改变时做相应的操作。
另外,属性还可以添加自定义的getter、setter、validator,让开发者可以对存取的数据进行规范化。属性还可以设置成”read-only”(只读)或者”write-once”(只能赋值一次)。
使用Attribute功能
下面详细说明如何使用Attribute功能,分以下几部分:
- 用Attribute功能扩展你的类
- 添加属性
- 属性的配置属性
- 属性改变事件
- 属性赋值流程图
- 存取子属性值
用Attribute功能扩展类
Attribute类被设计成用以扩展已经存在的类(我们把它称为宿主类),让宿主类拥有属性管理功能。举个例子:假设你有一个名为MyClass的类构造器,你想向它添加attribute支持,你可以简单地用Attribute类扩充MyClass类,如下:
YUI().use("attribute", function(Y) { function MyClass() { ... } Y.augment(MyClass, Y.Attribute); });
现在,你的MyClass的实例就有了Attribute类的一些方法,你可以使用这些方法配置MyClass实例对象的属性,MyClass实例对象的使用者也可以使用这些方法在存取属性值。Attribute类提供的方法的完整列表在此:Attribute API documentation。
需要注意的是,大部分类都是扩展Base类,而不是直接用Attribute类扩充自己。因为Base类自身就扩充了Attribute类,同时还对属性做了配置的。默认情况下,为了提高性能,Base类的属性被设置成“惰性初始化”(在第一次存或取时才初始化)。
添加属性
一旦你的类用Attribute类扩充后,你就可以使用addAttrs方法添加多个属性,或者使用addAttr一个一个地添加。addAttrs方法是针对宿主类设计的,这样就可以在一次调用中初始化多个属性,这个方法还接受一组额外的名/值,这样就可以让用户在初始化Attribute驱动的类时能初始化某些属性的初始值。如下:
... function MyClass(userValues) { // Use addAttrs, to setup default attributes for // your class, and mixing in user provided initial values. var attributeConfig = { attrA : { // ... Configuration for attribute "attrA" ... }, attrB : { // ... Configuration for attribute "attrB" ... } }; this.addAttrs(attributeConfig, userValues); };
这样,使用这个类的用户就可以将属性值传入构造器中,或者使用set方法来设置属性值,如下
// Set initial value for attrA during instantiation var o = new MyClass({ attrA:5 }); // Set attrB later on o.set("attrB", "Hello World!");
属性的配置属性
每一个添加的属性都有如下配置:
属性名 | 类型 | 描述 |
---|---|---|
value | Any | 该属性的默认值 |
valueFn | Function | 一个函数,函数的返回值是这个属性的值。如果属性值需要访问实例的状态(如:this.state)获得,就可以使用这个属性,而value属性是用来定义静态的配置的。
如果value属性和valueFn属性都有值,如果valueFn的函数不返回undefined,则valueFn属性的权重比value属性的权重要大。 |
getter | Function | 自定义”get”操作,当调用get方法时调用这个函数。它可以用来修改或规范返回的值。
属性值通过该函数的第一个参数传入函数中,属性名通过该函数的第二个参数传入函数中,如果配置好了,该函数的返回值将被返回给用户。 |
setter | Function | 自定义”set”操作,当调用set方法时调用这个函数。它可以用来修改或规范储存的值。
属性值通过该函数的第一个参数传入函数中,属性名通过该函数的第二个参数传入函数中,如果配置好了,该函数的返回值将被储存。 getter 和setter可以在数据存储过程中对数据进行规范,并且可以将数据储存为内部操作最有效的格式。 |
validator | Function | 验证函数,如果定义了,在调用setter之前被调用。用户想储存的值将通过第一个参数传入函数中,属性名以第二个参数传入。
如果该验证函数返回false,属性值不会被更新(setter不会被调用)。如果返回true,调用setter,属性值被更新。 如果验证是一个复杂的任务,而且其中的一些代码与setter中重复(比如将字符串转化成Node引用),你可以将验证逻辑并入setter函数中。在setter函数中,如果碰到一个无效的值,则返回Attribute.INVALID_VALUE 。 |
readOnly | boolean | 将属性设置成只读。用户将不能通过Attribute类的公共API设置这个属性的值。 |
writeOnce | boolean or "initOnly" | 降属性设置为只能写一次。用户只能通过Attribute类的方法设置该属性一次。一旦一个值被设置给该属性后,将不能在改变这个值。
如果设置为 "initOnly", 该属性只能在初始化阶段进行设置,也就是说,该属性只能通过构造器设置。 类内的代码能通过_set私有方法更新readOnly属性和writeOnce属性的值。 |
broadcast | int | 默认情况下,属性的change事件不会广播到YUI实例或者全局YUI对象上。通过该broadcast属性,我们可以设置使某些change事件广播到YUI实例或者YUI全局对象上。详细见broadcast 。 |
lazyAdd | boolean | 是否将属性的初始化延迟至第一次存取。当通过addAttrs方法添加属性时,这个标识可以覆盖每一个属性的初始化方式。
注意:NOTE: 扩展Base的类的属性都是惰性初始化的。这个标识可以用来覆盖默认的初始化方式。 |
cloneDefaultValue | "shallow", "deep", true, false | 这个配置值事实上不是由Attribute类独立提供支持的。但是构建扩展自Base的类时,通过Base类提供的ATTRS静态属性配置类的属性时,这个配置值是可用的。
当Base的ATTRS配置中的value属性的静态值被用来参与对象实例化时,cloneDefaultValue这个属性控制实例化过程中对value静态值的处理方式。默认情况下(如:该属性未赋值)对象字面量和数组会被深度克隆,防止原始值被修改。在某些情况下,你可能想通过引用使用对象和数组(比如:指向真正实体变量),这个属性就派上用场了。如果cloneDefaultValue 属性被设置成"shallow",则实例化时只是 “浅克隆”(克隆对象的引用),cloneDefaultValue 属性被设置成“deep”或“true”时,实例化时会进行深度克隆。 |
配置属性
以上的属性通过一个名/值对对象设置。这个对象被当做参数传给addAttrs方法或者addAttr方法。比如:
... var attributeConfig = { attrA : { // Configuration for attribute "attrA" value: 5, setter: function(val) { return Math.min(val, 10); }, validator: function(val) { return Y.Lang.isNumber(val); } }, attrB : { // Configuration for attribute "attrB" } }; this.addAttrs(attributeConfig, userValues); ...
或者使用addAttr方法:
this.addAttr("attrA", { // Configuration for attribute "attrA" value: 5, setter: function(val) { return Math.min(val, 10); }, validator: function(val) { return Y.Lang.isNumber(val); } });
属性change事件
使用Attribute属性而不是常规的对象属性来储存对象状态的一个好处就是可以使用属性change事件。当一个attribute的set方法被调用时,属性change事件就会被触发,让你可以在属性发生变化时执行某些代码。
监听属性的change事件
属性的change事件是自定义事件,格式是这样的:“[attributeName]Change”,这里的[attributeName]是你监听的属性的属性名。
比如:如果你想监听“enabled”属性的改变,你就订阅“enabledChange”事件。
o.on(“enabledChange”,function(e){ //在enabled属性被重新赋值前执行的代码 })
注意:监听函数的执行环境和额外的参数可以用YUI的bind方法定义,或者将执行环境对象和额外参数传递给on方法。
属性的change事件可以用on方法或after方法注册监听。
ON 和 AFTER
on方法
用on方法注册的监听器,会在属性值被更新之前得到通知。注册的监听函数通过第一个参数获取到一个Event对象(实际上是EventFacade的一个实例),这个Event对象包含属性值变化的相关信息。
由于这些监听函数是在属性值改变之前调用的,所以,通过执行传入的Event对象的preventDefault方法,或者改变event.newVal的值,他们可以阻止属性值改变的发生。
o.on(“enabledChange”,function(e){ var val = e.newVal; if(val !== someCondition){ e.preventDefault(); } });
after方法
使用after方法注册的监听函数,在属性的状态发生改变后的到通知。和on方法一样,监听函数通过第一个参数接收一个Event facade对象。
基于以上的定义,如果属性改变被阻止,after的监听函数将不会被调用。比如:在监听函数的Event对象上调用preventDefault。
o.after(“enabledChange”,function(e){ var val = e.newVal; e.preventDefault();//在after的监听函数中调用preventDefault不会阻止属性值的改变。 });
Event对象
传入监听函数的Eventfacade实例对象有关于这次属性更改的属性,还有管理事件传播的方法。
newVal
属性将被赋予(on方法注册的情况下)或者已被赋予(after方法注册的情况下)的值。
prevVal
属性当前的值(on方法注册的情况下)或者以前(after方法注册的情况下)的值。
attrName
被更改的属性名。
subAttrName
如果一个属性的属性值是一个对象,Attribute可以直接修改这个对象的属性。使用set方法:o.set(“X.a.b”,5)。attrName属性包含被修改的对象属性的完整路径(X.a.b)。
比如:在o.set(“X.a.b”,5);过程中,event.subAttrName会是被修改的属性的路径“X.a.b”。而event.attrName则是“X”,被改变的属性名。
preventDefault()
该方法可在“on”监听函数中调用,用以阻止属性值被更改。在“after”监听函数中调用将不会阻止属性值的改变,因为默认的行为已经执行了。
stopImmediatePropagation()
该方法可以在“on”监听函数或者“after”监听函数中调用。调用后,以后的监听器列队将接受不到通知。调用这个方法不会阻止属性值的改变(也就是说不会阻止默认行为的执行)。
Attribute set方法流程图
该流程图展示在进行set操作时,Attribute的setter、validator、和change事件的subscribers的调用顺序。
判断区块中,没有显式标注退出路径的,set操作将停止,而不会储存新的值。之所以不显式地标注退出路径,是为了流程图看起来不会太乱。
存取子属性值
如果有一个属性的值是一个对象(相对基本类型值(如数字和布尔值)而言),set方法可以让你直接设置该对象的某个属性。使用点标记语法。比如:你有一个“string”属性,有如下值:
o.set("strings", { ui : { accept_label : "OK", decline_label : "Cancel", }, errors : { e1000 : "Not Supported", e1001 : "Network Error" } });
你可以设置“string”属性对象上的某一个属性的值。而不需要设置整个“string”属性。通过使用点标记语法引用属性值的某一个属性,可以修改该属性的值:
//设置属性 o.set(“strings.ui.accept_label”,”Yes”); o.set(“strings.ui.decline_label”,”No”); //添加属性 o.set(“string.errors.e2000”,”new Error”); //不能添加新的中间属性 //因为strings.message不存在,所以下面这个语句无效 o.set(“strings.messages.intro”,”welcome”);
设置字属性值会促发主属性的改变事件(上面的例子触发”stringsChange”事件)。而传入监听器的Event对象有subAttrName属性,指向被设置属性的完整路径(上面代码的第二行的set调用会使得event.subAttrName返回“strings.ui.accept_label”)。
你一可以使用点标记语法获取相应的属性值。
var lbl = o.get(“strings.ui.accept_label”);
需要注意的是:getter、setter、validator这些属性配置函数只为顶级属性定义(在这个例子中是“string”属性)。在存取子属性的时候,这些函数也会被调用,但是只会返回与顶级属性相关的信息,而不会返回关于子属性的信息。被设置的子属性会从getter\setter\validator配置函数的第二个参数传入,这样在函数中可以对子属性进行必要的操作。