zoukankan      html  css  js  c++  java
  • MooTools Class.Mutators 如何建立一个我们自己的Mutator

    Mutator是一个可以改变你的类的结构的一个很特殊的函数,它们是产生特别功能和优雅化继承和掺元的的有力工具。MooTools有两个内建的Mutators: Extends和Implements:Extends Mutator取得传给它的类的名字,然后直接继承它;而Implements取得传给它的掺元类(Mixin Class)的名字后,把那些类的方法添加到新类中。这两个Mutator如何使用可参看前面的博文MooTools Class 使用、继承详解

    Mootools把Mutators储存在Class.Mutators对象中,新建一个Mutator就是为Class.Mutators对象添加一个键,键名为Mutator的关键字(既Mutator的名字),键值为Mutator的实际函数。

    在MooTools中Mutators是基于key-to-key对应的方式进行工作的,MooTools在构建一个新类时,会检查传送给新类的构造函数的对象的每一个键,在Class.Mutators对象中是不是有mutator函数的对应的名字在里面。如果找到了,它就调用这个函数并且把键的值传给它做处理。所以为了使你新建的Class中使用一个Mutator,你必须在传送给类的构造函数的对象上有一个与这个mutator相同名字的一对键值(例如,要使用Extends Mutator来实现类的继承,必须在你的类的声明对象上写Extends:ParentClassName,这个比较好理解)。

    这里有两点需要注意:

    首先,MooTools提供了两个Mutators: Extends和Implements,如果你使用MooTools-More的话,还有一个Binds Mutator。你新建的Mutator的关键字不能与上面三个Mutator重名,那样的后果简直无法想象,呵呵......

    其次,你新建一个Mutator时不能使用对象字面量的方式建立,看看下面代码:

    View Code
            Class.Mutators = {
    Keyword1: function (argus) {
    ...
    },

    Keyword2: function (argus) {
    ...
    }
    };

    为什么?因为这样语法,相当于以对象字面量形式创建了一个新的对象然后在赋予Class.Mutators,本质上完全重写了Class.Mutators,这样MooTools提供的Mutators(包括你之前建立的)都会消失不见,还想要继承?还想要掺元?呵呵......正确的代码是这样:

    View Code
            Class.Mutators.Keyword1 = function (argus) {
    ...
    };

    Class.Mutators.Keyword2 = function (argus) {
    ...
    };

    好了说了那么多,只是为了明白Mutators运行原理,接下来介绍几个比较实用的Mutators:


    Statics Mutator

    MooTools Class 使用、继承详解中我们讲解了怎样使用extend方法为类添加静态成员。首先你要先建立一个类,然后才能调用类的extend方法添加静态成员,这样两段代码是分离的,来看下面代码:

    View Code
            var Person = new Class({
    initialize: function (name, age) {
    this.name = name;
    this.age = age;
    },

    log: function () {
    console.log(this.name + ',' + this.age);
    }
    });

    // 添加静态成员
    Person.extend({
    count: 0,

    addPerson: function () {
    this.count += 1;
    },

    getCount: function () {
    console.log('Person count: ' + this.count);
    }
    });

    // 建立一个Person类的实例
    var mark = new Person('Mark', 23);
    mark.log();

    // 访问Person类的静态方法
    Person.addPerson();
    Person.getCount(); // returns: 1

    我如果想在传送给类的构造函数的对象中为类添加静态成员呢?我们建立一个简单的Mutator就可以了,来看下面代码:

    View Code
            Class.Mutators.Static = function (items) {
    this.extend(items);
    };

    var Person = new Class({
    // 添加静态成员
    Static: {
    count: 0,

    addPerson: function () {
    this.count += 1;
    },

    getCount: function () {
    console.log('Person count: ' + this.count);
    }
    },

    initialize: function (name, age) {
    this.name = name;
    this.age = age;
    },

    log: function () {
    console.log(this.name + ',' + this.age);
    }
    });

    // 建立一个Person类的实例
    var mark = new Person('Mark', 18);
    mark.log();

    // 访问Person类的静态方法
    Person.addPerson();
    Person.getCount(); // returns: 1

    当MooTools在解析Person的构造函数时,会发现传递给它的对象中的键名字:Satatic。因为我们有一个有相同名字的新的Mutator,Class就调用这个Mutator函数并且把键值传给它(在这个例子中是一个有属性和方法的对象)。我们的Static Mutator非常简单,使用this.extends把传过来的的对象的属性和方法变成Class的属性和方法(Mutator函数总是绑定到class本身上的,因此this指向你的类)。


    GetterSetter Mutator

    之前我们讲过,在JavaScript中没有私有成员的概念,所有对象的属性都是共有的。那么我们需要为Class添加私有变量进行属性封装怎么实现呢,前面在MooTools Class 使用、继承详解中我们介绍了使用静态私有变量的方法,但这种方法所建立的私有变量是为类的所有实例共享的。所以一般情况下我们只能通过命名规范来区别私有属性,MooTools的风格是在类的属性名前加一'$'标识符来表示这是一个私有变量。

    通常在MooTools下实现属性封装是这样实现的,看代码示例:

    View Code
            var Person = new Class({
    $name: '',
    $age: 0,
    $occupation: '',

    setName: function (name) {
    this.$name = name;
    return this;
    },

    getName: function () {
    return this.$name;
    },

    setAge: function (age) {
    this.$age = age;
    return this;
    },

    getAge: function () {
    return this.$age;
    },

    setOccupation: function (occupation) {
    this.$occupation = occupation;
    return this;
    },

    getOccupation: function () {
    return this.$occupation;
    }
    });

    var mark = new Person();
    mark.setName('Mark');
    mark.setAge(23);
    mark.setOccupation('JavaScript Developer');

    console.log(mark.getName() + ', ' + mark.getAge() + ': ' + mark.getOccupation());
    // 'Mark, 23: JavaScript Developer'

    通过这种方式来实现封装,如果属性少的话还好,属性一多你的代码长度就客观了,呵呵,在《Pro JavaScript with MooTools》一书中介绍了一个巧妙的Mutator,减少我们书写的代码量:

    View Code
            Class.Mutators.GetterSetter = function (properties) {
    var klass = this; // 缓存this对象,这里指向的是Class本身,如果不缓存会怎样?$%#&^@#......
    Array.from(properties).each(function (property) {
    var captProp = property.capitalize(), // 把要添加的属性名第一个字母变为大写
    $prop = '$' + property; // 为属性名添加'$'标识符,表明这个属性为私有变量

    // setter method
    klass.implement('set' + captProp, function (value) {
    this[$prop] = value;
    return this;
    });

    // getter method
    klass.implement('get' + captProp, function (value) {
    return this[$prop];
    });
    });
    };

    var Person = new Class({
    GetterSetter: ['name', 'age', 'occupation']
    });

    var mark = new Person();
    mark.setName('Mark');
    mark.setAge(23);
    mark.setOccupation('JavaScript Developer');

    console.log(mark.getName() + ', ' + mark.getAge() + ': ' + mark.getOccupation());
    // 'Mark, 23: JavaScript Developer'

    看看设计一个类实现同样的功能,使用GetterSetter Mutator是不是减少很多代码呢。等等,这时有看官可能会说了,我们为什么要对属性进行封装呢,如果只是实现上面的功能直接为Class添加name、age、occupation三个属性,然后直接对它们进行访问不就得了。嗯呐,这个嘛,上面的代码的确体现不出封装的好处。如果我们需要对类的某个私有变量进行一些逻辑控制,比如在setter方法中对值进行校验、属性值改变后我们需要触发一个事件来应对值的改变等功能,这样上面的GetterSetter Mutator就不能胜任了,我们就必须对他进行扩展,还好在MooTools Forge中我找到了这样一个插件Class.Attributes,已经提供了这些功能,这样我们只需要'拿来主义'就可以了,呵呵。这个源代码进行了稍稍的修改:

    View Code
            Class.Mutators.Attributes = function (attributes) {

    var $setter = attributes.$setter,
    $getter = attributes.$getter;
    delete attributes.$setter;
    delete attributes.$getter;

    this.implement({

    /**
    * @property $attributes
    * @description storage for instance attributes
    */
    $attributes: attributes,

    /**
    * @method get
    * @param name {String} - attribute name
    * @description attribute getter
    */
    get: function (name) {
    var attr = this.$attributes[name];
    if (attr) {
    // valueFn()对属性的值进行初始化,不需要任何参数
    if (attr.valueFn && !attr.initialized) {
    attr.initialized = true;
    attr.value = attr.valueFn.call(this);
    }

    // 优先执行属性中定义的getter方法来获取属性的值
    if (attr.getter) {
    return attr.getter.call(this, attr.value);
    } else {
    return attr.value;
    }
    } else {
    // 如果get的属性没有定义,则通过$getter方法来取得属性值
    // $getter方法通过使用name的值if或switch扩展更多的属性,不过这些属性的readOnly,validator等方法需要自己在$getter中定义
    return $getter ? $getter.call(this, name) : undefined;
    }
    },

    /**
    * @method set
    * @param name {String} - attribute name
    * @param value {Object} - attribute value
    * @description attribute setter
    */
    set: function (name, value) {
    var attr = this.$attributes[name];
    if (attr) {
    // 首先判断是不是只读属性
    if (!attr.readOnly) {
    // 先缓存旧的属性值
    var oldVal = attr.value, newVal;

    // 判断属性的校验函数存不存在,如果存在则通过校验函数确认可不可以赋值
    if (!attr.validator || attr.validator.call(this, value)) {
    // 优先使用属性中定义的setter方法为属性赋值
    if (attr.setter) {
    newVal = attr.setter.call(this, value);
    } else {
    newVal = value;
    }
    attr.value = newVal;

    // #region - Extended by 苦苦的苦瓜 -

    /**
    # 如果新旧属性值一样,则不触发事件
    *
    */
    if (oldVal !== value) {
    // 触发自定义事件
    this.fireEvent(name + 'Change', { newVal: newVal, oldVal: oldVal });
    }

    // #endregion
    }
    }
    } else if ($setter) {
    // 如果属性没有定义
    if ($setter) { $setter.call(this, name, value); }
    }
    },

    /**
    * @method setAttributes
    * @param attributes {Object} - a list of attributes to be set to the instance
    * @description set passed attributes passing it through .set method
    */
    setAttributes: function (attributes) {
    Object.each(attributes, function (value, name) {
    this.set(name, value);
    }, this);
    },

    /**
    * @method getAttributes
    * @description returns a key-value object of all instance attributes
    * @returns {Object}
    */
    getAttributes: function () {
    var attributes = Object.clone(this.$attributes);
    return attributes;
    },

    /**
    * @method addAttributes
    * @param attributes {Object} - a list of new attributes to be added to the instance
    * @description adds list of attributes to the instance
    */
    addAttributes: function (attributes) {
    Object.each(attributes, function (value, name) {
    this.addAttribute(name, value);
    }, this);
    },

    /**
    * @method addAttribute
    * @param name {String} - new attribute name
    * @param value {Object} - new attribute value
    * @description adds new attribute to the instance
    */
    // 这里的value属性是一个对象,可以包含下面这些属性方法value, valueFn(),getter(), setter(), readOnly, validator()
    addAttribute: function (name, value) {
    var attr = this.$attributes[name];

    // #region - Extended by 苦苦的苦瓜 -

    /**
    # 如果属性已经存在则不覆盖前属性
    *
    */
    if (!attr) {
    attr = value;
    }

    // #endregion

    return this;
    }

    });

    };

    功能多多,好处多多,呵呵,下面是示例代码:

    View Code
            var Product = new Class({

    Implements: [Options, Events],

    Attributes: {

    hmkhan: {
    valueFn: function () {
    return 'my name is HmKhan';
    }
    },

    brand: {
    validator: function (val) {
    return val.trim().length > 1;
    }
    },

    model: {
    validator: function (val) {
    return val.trim().length > 1;
    }
    },

    name: {
    readOnly: true,
    getter: function () {
    return this.get('brand') + ' ' + this.get('model');
    }
    },

    price: {
    getter: function (val) {
    return val * (100 - this.get('discount')) / 100
    }
    },

    discount: {
    value: 0 // Default value
    }

    },

    initialize: function (attributes) {
    this.setAttributes(attributes);
    }

    });

    var product = new Product({
    brand: 'Porsche',
    model: '911',
    price: 100000,
    discount: 5
    });

    console.log(product.get('name'));
    console.log(product.get('price'));
    product.addEvent('discountChange', function (event) {
    console.log("New discount: {newVal}% instead of {oldVal}%!".substitute(event));
    });

    product.set('discount', 30); // -> alerts "New discount: 30% instead of 5!"
    console.log(product.get('discount'));

    product.addAttribute('model', { value: '110' });
    console.log(product.get('model'));

    console.log(product.get('hmkhan'));

    Binds Mutator

    MooTools More提供了一个Mutator:Binds,用来绑定类的方法的作用域(至于为什么要绑定方法的作用域还有mootools中bind方法介绍这里略过不说了,你懂的......)。来看下它的源代码,很简单:

    View Code
        Class.Mutators.Binds = function (binds) {
    // 如果传送给新类的构造函数的对象中没有定义initialize方法,则为新类定义一个空的initialize方法。
    if (!this.prototype.initialize) {
    this.implement('initialize', function () { });
    }
    // 把定义的Binds键值与类中已定义的Binds(继承自父类)属性值合并为一个数组
    return Array.from(binds).concat(this.prototype.Binds || []);
    };

    // 绝妙的构思,把initialize定义为一个Mutator,这里传递过来的参数就是类构造函数中的initialize(初始化)方法
    Class.Mutators.initialize = function (initialize) {
    // 返回闭包作为类的initialize方法
    return function () {
    // 绑定Binds属性中包含的每个方法的作用域,替代原方法
    Array.from(this.Binds).each(function (name) {
    var original = this[name];
    if (original) { this[name] = original.bind(this); }
    }, this);
    // 执行类设计时构造函数中定义的initialize方法
    return initialize.apply(this, arguments);
    };
    };

    为什么要把它拿出来单独说一下呢,大家看看代码,其实它是由两个Mutators组成的:Binds和initialize。Binds Mutator把需要绑定作用域的方法名称保存到类的Binds属性中;initialize Mutator就耐人寻味了,我们定义一个新类时一般需要为它定义一个initialize(初始化)方法,那么他们两个是什么关系呢?还记得前面我们所说的“会检查传送给新类的构造函数的对象的每一个键,在Class.Mutators对象中是不是有mutator函数的对应的名字在里面。如果找到了,它就调用这个函数并且把键的值传给它做处理”,所以这时类构造函数中的initialize方法就作为参数传递给initialize Mutator,initialize Mutator所做的工作就是生成并返回一个闭包,这个闭包执行的操作先是绑定Binds属性中存储的方法的作用域,然后再执行最初定义的initialize方法,MooTools会把这个返回的闭包在赋给initialize成员,实际上就是重写构造函数的对象中定义的initialize方法。先看看下面的示例代码:

    View Code
            var MyClass = new Class({
    Binds: ['say'],
    initialize: function (element, message) {
    this.el = $(element);
    this.message = message;
    },
    monitor: function () {
    this.el.addEvent('click', this.say); //say is already bound to 'this'
    },
    stopMonitoring: function () {
    this.el.removeEvent('click', this.say);
    },
    say: function () {
    alert(this.message);
    }
    });

    var my = new MyClass('btnSay', 'this is a test.');
    my.monitor();

    Binds Mutator是在MyClass定义时就执行的,作用于定义的MyClass本身,为MyClass的原型添加了一个Binds属性,值为包含'say'的一个数组,initialize Mutator也是在MyClass定义时就执行的,但它这时只是重写了initialize方法,并没有开始执行绑定方法作用域的操作,这些操作需要在initialize方法运行时才会执行的,也就是建立MyClass类的实例my时才执行的,作用的是my对象,也就是说在执行绑定时,say方法绑定的是my对象。

    看到这儿,大家应该对Mutators的运行和设计有了一个比较全面的了解了,MooTools Class设计必备利器啊......

    下一篇我们介绍一下在MooTools中接口的实现。

    苦苦的苦瓜  2011-10-18

  • 相关阅读:
    bootstrapValidator表单验证插件
    sublime自动保存(失去焦点自动保存)
    js、jquery实现放大镜效果
    mysql 添加索引语句
    mybatis sql语句中的foreach标签
    android ListView 刷新卡顿问题
    JFrame 桌面右下角弹窗
    取没有date的邮件发送时间
    java 判断图片是否损坏
    java 后台poi导入导出Excel到数据库
  • 原文地址:https://www.cnblogs.com/hmking/p/2216320.html
Copyright © 2011-2022 走看看