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

  • 相关阅读:
    vue-fullcalendar插件
    iframe 父框架调用子框架的函数
    关于调试的一点感想
    hdfs 删除和新增节点
    hadoop yarn 实战错误汇总
    Ganglia 安装 No package 'ck' found
    storm on yarn(CDH5) 部署笔记
    spark on yarn 安装笔记
    storm on yarn安装时 提交到yarn失败 failed
    yarn storm spark
  • 原文地址:https://www.cnblogs.com/hmking/p/2216320.html
Copyright © 2011-2022 走看看