基于Y.Base的开发流程

Base是YUI中的一个基础类,其它基于属性或者基于事件的类可以从它派生出来。Base提供了创建基于属性的对象的标准模版,并且为类继承体系提供由初始化(initializer)和析构(destructor)方法组成的一致的init()和destroy()序列。

基于Y.Base开发,主要步骤如下:

Extending Base

尽管Base可以被实例化,不过Base是被作为root class设计的,我们可以通过继承它来实现自己的基于Attribute和EventTarget的类:

YUI().use("base", function(Y) {

    function MyClass(config) {

        // Invoke Base constructor, passing through arguments
        MyClass.superclass.constructor.apply(this, arguments);
    }

    Y.extend(MyClass, Y.Base, {
        // Prototype methods for your new class
    });
});

Base本身augment了Attribute,因此也augment了EventTarget,因此Base既是一个Attribute provider 也是一个Event Target.

Base的构造函数需要一个配置对象来完成属性的初始化。

Static Properties

继承Base,我们需要在构造函数总定义两个静态属性-NAMEATTRS。其中NAME用于标志我们创建的class的实例,比如作为事件名的前缀

function Dialog(config) {
    Dialog.superclass.constructor.apply(this, arguments);
}

// Used to identify instances of this class
// For example, to prefix event names

Dialog.NAME = "mt-dialog";

// `ATTRS`是一个关联数组
// 可以设置该属性的初始值,也可以通过参数传值

Dialog.ATTRS = {
    id: {                                                                             
      value: null,
      writeOnce: true
    },
    width: {
      value: '400px'
    }
}

// 为类添加一些原型方法
// 实例中可以直接使用这些方法
var proto = {
    open: function() {
    },
    close: function() {
    }
};

Y.extend(Dialog, Y.Base, proto);

NAME

NAME属性用于标志class。它最核心的用途是作为该类实例的事件的前缀。

事件的前缀可以区分不同类的实例fired的同名函数,例如Menu widget的click事件我们可以监听menu:click,Editor widget的click事件我们可以监听editor:click

// NAME is used to prefix the provided event type, if not already prefixed,
// when publishing, firing and subscribing to events.

Dialog.prototype.open = function() {
    // 实际上fire的事件是"mt-dialog:opened"
    this.fire("opened");
}

...

var o = new Dialog(cfg);

o.on("opened", function() {
    // 事实上监听的是"mt-dialog:opened".
});

o.on("mt-dialog:opened", function() {
    // 同样是监听的"mt-dialog:opened"
});

PS:有两点需要注意的地方:

  • 如果对应的事件已经有prefix,则不会再自动添加prefix。
  • 通常NAME采用class的驼峰式写法,如MyClass对应的NAME一般写成myClass。不过我们没有采用这种方案,我们对NAME采用mt-类名前缀

ATTRS

ATTRS属性是一个关联数组(键值对的形式),用来设置该类创建的实例对象的缺省值,实例将包含该类以及继承链上相关的类添加的所有属性。

看一下我们Dialog的ATTRS:

Dialog.ATTRS = {                                                                                
    // dialogId
    id: {
        value: null,
        writeOnce: true
    },
    width: {
        value: '400px'
    },
    zIndex: {
        value: 102
    },
    align: {
        value: null
    },
    // 是否居中对齐
    centered: {
        value: true
    },
    // 是否使用模态
    modal: {
        value: true
    }
// ...
}

在上面对象字面量中定义的属性将被添加到对应的实例中。

当初始化一个从Base派生的类时,Base的init()方法将按照ATTRS为class hierarchy中的每一个类进行初始化,这样就不必在每个类中的constructor/initializer过程中做相同的重复工作。

初始化的具体顺序是:从Base开始直到具体的要实例化的子类。在一个类中,属性定义的先后顺序无关紧要,如果前面的定义的属性使用了后面的属性(如在它的valueFn或者getter属性中),后面的属性会自动被按需加载。

没有必要为了性能问题而去为Base延迟添加或者初始化属性,因为所有的属性初始化都是直到它第一次被调用(get/set)时才初始化的。如果不想延迟初始化,可以设置属性的lazyAdd配置项为false

Attribute Change Events

ATTRS除了具有上面的延迟初始化的特性之外,通过YUI的Attribute提供的机制,还可以方便的监听属性change事件--每当使用set改变ATTRS定义的属性时,会自动fire一个[attributeName]Change事件。

我们可以通过on或者after监听这些属性change事件。

1、 on

on注册的监听函数在属性改变之前被执行,接收一个event参数(自动传入)。


o.on("enabledChange", function(event) {

    // event.prevVal 保存属性的当前值,即改变之前的值
    var val = event.prevVal;

    if (val !== someCondition) {
        // 因为在attributechange之前执行,可以阻止默认属性改变
        event.preventDefault();  //
    }

});
2、 after

除了使用on监听attr change,我们还可以使用after来监听。与on不同之处在于,用after添加的事件处理函数是是在属性改变之后被触发。

o.after("enabledChange", function(event) {

    // event.newVal will contain the currently set value
    var val = event.newVal;

    // Calling preventDefault() in an "after" listener has no impact
    event.preventDefault();

});

注意一点在after中执行event.preventDefault不能阻止attr change,因为它是在属性改变之后被触发的。

Event Object

传给attr change函数的event对象包含一系列和相对attr相关的信息。

  • newVal
    属性将要被设置的值(相对与on),或者刚被设置成的值(相对于after),总之理解成新值就行了
  • preVal
    属性的旧值,相对于on就是当前值,相对与after就是更改之前的值
  • attrName
    属性名称
  • preventDefault()
    用在on的事件监听函数中可以阻止属性的值改变,用在after的事件中没有任何作用
  • stopImmediatePropagation()
    在on和after时都可用。可以阻止后续的监听当前属性的处理函数被执行,但是不会阻止属性值的改变。

ATTRS配置项

ATTRS的主要配置项如下:















































PropertyName Type Description
value Any 属性默认值
valueFn Function 该配置项值是一个函数,其返回值作为属性的值
如果同时定义了value和valueFn,则优先使用valueFn作为属性的值
如果valueFn返回undefined,则使用value的值
getter Function 用户自定义的get函数
当调用get('propertyName')时自动调用该函数,为用户返回该函数处理过的数据
该函数接收两个参数,第一个参数是属性当前的值,第二个参数是属性名
setter Function 用户自定义的set函数
当调用set('propertyName', value)时自动调用该函数设置属性值
该函数接收两个参数,第一个参数是要设置的值,第二个参数是属性名
validator Function 验证函数。如果设置了该属性,则在调用setter之前会首先调用该函数。
如果函数返回值为false将不会更新对应属性的值,也不会调用setter
readonly Boolean 是否只读
writeOnce Boolean 用户只有一次机会使用set设置属性
lazyAdd Boolean 配置是否使用属性的延迟初始化
默认所有继承Base的类,其ATTRS的初始化是在第一次调用属性的set/get方法时才初始化。将该属性设置为false可以不使用延迟初始化的机制。
不建议设置false,除非在变量初始化的时候做一些变量初始化之外的工作

关于配置项更详细的文档请参照Y.Attribute的文档

Attribute Set Flow Diagram

属性设置过程如图:

attribute set flow

Initialization and Destruction

Base通过调用其init和destroy方法来完成初始化和析构的过程。继承Base的类可以通过定义原型级别的initializerdestructor来做一些初始化的工作。

// Dialog相关的initializer和destructor
var proto = {
    /**
     * @description 初始化
     * @method initializer
     */
    initializer: function() {
        var instance = this;
        instance._timer = null;
        instance._dialog = null;
        instance._scrollHandle = null;

        instance._initAttrEventListener();
        instance.publish('open', {
            emitFacade: true,
            defaultFn: instance._defOpenFn
        });
        instance.publish('close', {
            emitFacade: true,
            defaultFn: instance._defCloseFn
        });
    },
    /**                                                                                           
     * @description 析构
     * @method destructor
     */
    destructor: function() {
    },
    ....
};

Base的init方法会自动调用子类定义的initializerdestructor方法,子类不需要手动去调用父类的init方法,Base会按序逐个调用这些方法。

initializer()

Base的init通过构造函数调用,init将调用class hierarchy中每个类的initalizer方法。调用的顺序是从Base开始到具体的要实例化的子类。initializer方法将在对应类的属性被初始化之后调用,并且接收传给init方法的配置项。

destructor()

Base的destroy方法被调用时将分别为class hierarchy中每个类调用对应的destructor方法,顺序与initializer的顺序相反。

上面两个方法并不是必须的,如果你不需要在init或者destroy阶段做任何工作,则不必为类定义对应的initalizer和destructor方法。

Reference

comments powered by Disqus