Class
MooTools框架的基类。Class Method: constructor
语法:
参数:
- 性能 - 可以是以下类型之一:
- (object)类属性的集合。还接受一些特殊属性,如Extends, Implements和initialize(见下文) 。
- (function)initialize(初始化)函数(见下文) 。
Property: Extends
- (class)这个类将被扩展(基类)。
Property: Implements
- (class)这个类的属性将被复制到目标类。
- (array)类的数组,所有类的属性将被复制到目标类。
Property: initialize
- (function)构造函数,当实例被创建时执行。
Property: toElement
- (function)返回一个html元素。当在document.id()函数里时,此方法会被自动调用。
返回:
- (class)创建的类。
示例:
Class示例:
Extends示例:
Implements示例:
Class Method: implement
改变基类的原型,和new Class(Implements:function(){})一样,但方便修改已存在的类.语法:
参数:
- properties - (object)添加到基类的属性。
示例:
Mootools.js 是如何实现类,以及类的相关属性和作用
实现类的步骤
- 第一步是使用
new Class
新建类,初始化的固定函数是initialize
,不能使用其它名称 - 子类也是使用
new Class
新建,父类在子类中,使用Extends:parentClass
来继承,Extends
与子类的方法名,同一级别 - 子类中与父类的同名方法,如果需要在父类的同名方法上拓展,需要在子类的同名方法内,使用
this.parent(args)
- 如果需要在类的外面增加方法,可以使用
implement
方法
// 使用 Class.create 创建类
var Person = new Class({
// 初始函数固定为 initialize,
initialize:function(name) {
this.name = name;
this.friends = ['jack', 'mark'];
},
getName: function(){
console.log("My name is " + this.name);
},
setFriends:function(friend){
this.friends.push(friend);
},
getFriends:function(){
console.log(this.friends)
}
});
// 使用 implement 给类添加方法,子类可以继承该方法
Person.implement({
getAge:function(age){
console.log("My age is " + age);
}
})
// 子类通过 new Class 创建类
var Chinese = new Class({
// 子类通过 Extends 来继承父类
Extends:Person,
initialize:function(name, addr){
this.parent(name);
this.addr = addr;
},
getAddr:function(){
console.log("My address is " + this.addr);
}
});
var Japanese = new Class({
Extends:Person,
initialize:function(name){
this.parent(name);
}
})
// 实例化类
var men = new Chinese('allen', 'BeiJing');
men.getName(); // My name is allen
men.getAge(23); // My age is 23
men.getAddr(); // My address is BeiJing
// 以下验证 - 子类继承父类的属性,修改了之后,其他子类再次继承父类,父类的属性的值为何不会改变
var allen = new Person();
allen.getFriends(); // ["jack", "mark"]
var women = new Japanese();
women.setFriends("lisa");
women.getFriends(); // ["jack", "mark", "lisa"]
var men = new Chinese();
men.setFriends('peter');
men.getFriends(); //["jack", "mark", "peter"]
var wallen = new Person();
wallen.getFriends(); //["jack", "mark"]
JS
是如何实现类的方法,有几个重要的问题需要搞清楚
- JS是如何创建类的
- 子类是如何实现继承父类属性和方法的
- 子类继承父类的属性,修改了之后,其他子类再次继承父类,父类的属性的值为何不会改变
- 子类和父类的同名函数,使用
this.parent(args)
在函数中使用,是如何做到在子类中的同名函数共存的 - 如何实现,不在类中,而是使用
implement
往类中添加方法的
下面来通过Mootools.js
的class
来具体分析
(function(){
// 新建一个 Class 的类,new Type 也是一个函数
var Class = this.Class = new Type('Class', function(params){
// 如果传入的 参数是方法,就把该函数当作初始化的方法
if (instanceOf(params, Function)) params = {initialize: params};
var newClass = function(){
// 解除属性里对其他对象的引用
reset(this);
// 如果当前类正在构建,就返回当前类,不做任何操作
if (newClass.$prototyping) return this;
// $caller 和 $family 是什么啊
this.$caller = null;
this.$family = null;
// 有初始化函数的话,就传入参数到该初始化函数,没有就返回自身
var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
// 这句又是什么意思,一个 $caller ,一个 caller
this.$caller = this.caller = null;
return value;
// extend(this) 把类的方法,都添加到当前新建的类中
// implement(params) 把 params 的所有方法都添加到当前类中
}.extend(this).implement(params);
//指定 constructor ,以便使用 instanceOf 来验证
newClass.$constructor = Class;
newClass.prototype.$constructor = newClass;
// 指定当前类的父类是哪一个
newClass.prototype.parent = parent;
return newClass;
});
/*
在子类拥有和父类同名方法时,使用 this.parent(args) 方法来调用父类的该方法
*/
var parent = function(){
// :: 如果当前方法没有被调用,那么就说,parent 方法没有被调用
if (!this.$caller) throw new Error('The method "parent" cannot be called.');
// 当前函数被调用的名字 function person(age) { this.age = age },则 age 被调用的就是 person 函数,就是得到 person 这个名字
var name = this.$caller.$name,
// $owner 当前类对象, 得到当前类对象的父类对象
parent = this.$caller.$owner.parent,
// 得到父类相同名字的方法
previous = (parent) ? parent.prototype[name] : null;
if (!previous) throw new Error('The method "' + name + '" has no parent.');
// 父类的该同名函数,添加到当前子类中
return previous.apply(this, arguments);
};
// 解除属性里对其他对象的引用
// 这个解除的例子,可以看 http://hmking.blog.51cto.com/3135992/675856
var reset = function(object){
for (var key in object){
var value = object[key];
switch (typeOf(value)){
case 'object':
var F = function(){};
F.prototype = value;
object[key] = reset(new F);
break;
case 'array': object[key] = value.clone(); break;
}
}
return object;
};
var wrap = function(self, key, method){
if (method.$origin) method = method.$origin;
var wrapper = function(){
// 如果方法是是被保护的,或者这个方法没有 caller ,就不能被调用
if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
var caller = this.caller, current = this.$caller;
this.caller = current; this.$caller = wrapper;
// 将 method 绑定到当前对象中
var result = method.apply(this, arguments);
this.$caller = current; this.caller = caller;
return result;
// 通过extend ,把当前函数的属性附加到 self 里去
}.extend({$owner: self, $origin: method, $name: key});
return wrapper;
};
var implement = function(key, value, retain){
// Mutators 的 key 只有 Extends 和 Implements
if (Class.Mutators.hasOwnProperty(key)){
value = Class.Mutators[key].call(this, value);
if (value == null) return this;
}
if (typeOf(value) == 'function'){
// 隐藏的方法子类就不要再继承使用了
// $hidden 和 $protected 去看函数那章
if (value.$hidden) return this;
this.prototype[key] = (retain) ? value : wrap(this, key, value);
} else {
// merge 应该是同名的函数,这样就直接添加进去就好
Object.merge(this.prototype, key, value);
}
return this;
};
// 为了将父类的的属性继承到子类,会使用中间变量,将父类传递给中间变量,再通过中间变量传递给子类
var getInstance = function(klass){
// 谁知当前当前类正在构建
klass.$prototyping = true;
var proto = new klass;
// 这里就删除 $prototyping ,也就是构建的过程就是上面这一行咯
delete klass.$prototyping;
return proto;
};
// 这里有 overloadSetter ,所以,可能是 Class.implement 方法,来给类额外添加函数的
Class.implement('implement', implement.overloadSetter());
Class.Mutators = {
// 传给 extends 的参数是 parent
Extends: function(parent){
// 指向当前类的父类是 parent 参数
this.parent = parent;
// 使用 getInstance 得到父类的全部方法
this.prototype = getInstance(parent);
},
Implements: function(items){
Array.convert(items).each(function(item){
var instance = new item;
for (var key in instance) implement.call(this, key, instance[key], true);
}, this);
}
};
})();
/*
Extends 其实是分两部分,使用 Extends 的时候,是把父类的所有属性和方法,通过 getInstance 来附加到当前类中
然后当前类的方法中,可以使用 this.parent(args) 方法,来把父类的同名方法加载进来
Implements 方法中没有指代 this.parent = parent ,所以如果当前类写了和父类同名的方法,就会覆盖父类的方法
Implements 只是给当前类添加更多的方法
*/
JS
面向对象系列
- 《javascript高级程序设计》 继承实现方式
- prototype.js 是如何实现JS的类以及类的相关属性和作用
- klass 是如何实现JS的类以及类的相关属性和作用
- 总结:prototype.js,Mootools.js和klass.js 实现类的方法的异同与优劣
以下摘自苦苦的苦瓜
首先需要说明的是,本文将直接讲解创建和使用类的各种技巧,一些基础的东西不再做解释,要理解如何在JavaScript中实现面向对象的设计,请先参考《JavaScript.高级程序设计(第2版)》(前7章)、《javascript.设计模式》(前四章)、《JavaScript.语言精粹》这三部经典之作。
第二种是传递一个普通函数,mootools会自动把这个函数包装成只含一个initialize键值的对象字面量,然后你可以使用implement方法对类进行扩展,例如:
当然推荐使用的还是第一种方式,直观明了嘛。你如果使用标准方式建立了一个类,也是可以使用implement方法对类进行扩展的,如果你真的认为有必要把一个类的设计拆成几个部分的话(例如在使用掺元对象实现多亲继承时,神马?等等...这个MooTools里实现多亲继承继承不是使用Implements Mutator吗,嗯呐,这个在接下来类的继承中将详细讲解......),呵呵,至于MooTools内部如果对构造函数进行解析,有兴趣的可看看MooTools 1.4 源码分析 - Class 修正版
Implement and Extend
Implement方法用来为类添加新的方法、属性。需要注意的是,如果新添加的方法或属性与类中旧有的方法或属性同名,则会覆盖类中旧有的方法、属性。调用Implement方法有两种方式,第一种方式传递两个参数,第一个参数为String类型,存储要添加的方法或属性的名称,第二个参数为方法所对应的函数或属性所对应的值,这种方式每次只能为类添加一个方法或属性:
第二种方式传递一个对象字面量参数,把要添加的方法属性包含在这个对象中,一次添加多个方法、属性,避免重复调用implement:
MooTools关于Class的官方文档中只暴露了implement一个方法,其实对类本身进行操作的还有一个比较重要的方法extend,这个方法之所以没有出现在Class的文档中。这是因为它不是作为Class的特殊方法,而实际上是Type的方法。它的作用是为类创建静态成员,静态成员关联的是类本身,换句话说,静态成员是在类的层次上操作,而不是在实例的层次上操作,每个静态成员都只有一份。调用extend方法的方式同Implement,也是两种方式。
简单一点讲,implement为实例创建方法和属性,extend为类本身创建方法和变量,请看下面的例子:
私有成员
严格来讲,JavaScript中没有私有成员的概念,所有对象的属性都是共有的。不过,倒是有一个私有变量的概念,任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问浙西变量。私有变量包括函数的参数、局部变量和在函数内定义的其他函数。所以我们可以通过使用闭包来为类制造私有成员:
使用这个模式有一个好处就是,私有成员在内存中只会存放一份,是由所有实例共享的,不必为每一个实例生成一个副本。但这也延伸出一个问题,来看下面的代码:
这个例子中的Person构造函数(这里指initialize)与getName()和setName()方法一样,都有权访问私有变量name,在这种模式下,变量name就变成了一个静态的、有所有实例共享的属性,也就是说,在一个实例上调用setName()会影响所有实例,结果就是所有实例getName()都会返回相同的值,而age是实例变量就不存在这个问题。到底是使用实例变量还是静态私有变量,最终还是要视你的需求而定。
当然上面这个问题只是针对私有变量的,私有方法就不存在这个问题,相比实例方法会更有效率(从内存占用的意义上来说),应为它只会被创建一份。
使用闭包还带来一个问题,多查找作用域链中的一个层次,就会在一定程度上影响查找的速度(一般情况下可以忽略不计),鱼与熊掌不可兼得啊......
常量
最简单设置常量的方法是为类添加一个静态属性,然而静态属性是公有的,类的使用者可以随时改变它的值,这个样的操作后果是很严重的。这里我们可以使用前面介绍的为类设置静态私有变量的方式来模拟常量,然后在实例方法中只创建取值器方法而不创建赋值器方法。这样类的使用者只能使用暴露出来的取值器方法来得到私有变量的值而不能改变它的值。来看下面的代码:
如果需要使用多个常量,设置一个私有的对象字面量来存储这些常量,然后设置一个通用的取值器方法来取得这些常量:
继承
继承的主要好处表现在代码的重用方面,通过建立类之间的继承关系,有些方法我们只需要定义一次就可以了。同样,如果需要修改这些方法或排查其中的错误,那么由于其定义只出现在一个位置,所以非常有利于节省时间和精力。MooTools实现类的继承有两种模式:原型式继承和多亲继承,原型式继承由内建的Extends Mutator来实现,多亲继承由Implements Mutator或implement方法来实现。
原型式继承这里就不多讲了,着重讲一下多亲继承。在JavaScript里,因为一个对象只能拥有一个原型对象,所以不允许子类继承多个超类,不过我们可以利用多个掺元类(minix class)或掺元对象对一个类进行扩充,这样类的实例就可以拥有mixin类(对象)中的方法、属性,所以这实际上实现了多继承的效果。通常mixin类(对象)包含一些通用的方法,大家可以看看MooTools里Class.Extras模块中三个mixin类的定义(Chain、Events、Options)。
这里还需要注意的的一点是,在派生具有私有成员的的类或implement mixin类(对象)时,因为在父类(或mixin)中访问这些私有变量的方法是公有的,所以他们也会被遗传下来。所以子类可以间接访问父类(或mixin)的私有成员,但子类自身的实例方法都不能直接访问这些私有属性,而且你也不能在子类中添加能够直接访问他们的方法(作用域变量,你想访问也访问不了啦,呵呵)。
首先我们先建立一个基类:
使用Extends Mutator派生一个子类:
利用Implements Mutator扩充一个类,首先建立一个mixin类:
使用implement方法扩充一个类,首先家里一个mixin对象:
大家都看明白了吧,呵呵,不过通过上面的代码我们引申出另外一个问题,注意上面的Cat类的设计,我们首先设计了Extends,然后是Implements,再就是Cat类本身的方法属性,MooTools内部对Class构造函数解析时是按照我们设计时的顺序解析的吗?答案是按照我们设计时的顺序解释的。简单来讲MooTools通过for-in对对象进行枚举来遍历每个成员进行解释的,等等......那个ECMAScript最新版对for-in语句的遍历机制又做了调整,属性遍历的顺序是没有被规定的,也就是说随机的,那么MooTools是怎样保证按顺序解释的呢?先看下面这段代码:
把它放在各个浏览器都执行一遍,你会发现IE、火狐、Safari浏览器的JavaScript解析引擎遵循的是较老的ECMA-262第三版规范,属性遍历顺序由属性构建的顺序决定,而Chrome、Opera中使用 for-in 语句遍历对象属性时会遵循一个规律,它们会先提取所有 key的 parseFloat 值为非负整数的属性, 然后根据数字顺序对属性排序首先遍历出来,然后按照对象定义的顺序遍历余下的所有属性。其它浏览器则完全按照对象定义的顺序遍历属性。
这下明白了吧,只要你为类设计的方法、属性还有Mutator的名称不为数字就可以了(当然如果你非要有这样的嗜好,我也只能@#%$......)。请看下面的代码:
在这里obj.log()会返回什么呢?对了是'Maxin',这里Sub类首先继承了Super类,Sub的原型实际就是Super类的一个实例,Super的log方法也就是成了Sub的原型上的一个方法,然后执行Implements Mutator 为Sub类的原型扩展了一个Mixin类的实例上的方法,这时Mixin类实例上的log方法就覆盖了Sub类原型上原来的log方法(继承自Super类)。
如果把Extends、Implements的顺序颠倒一下:
这时obj.log()会返回什么呢?还是'Maxin'吗?其实这里返回的是'Super',Why?前面我们介绍了MooTools对Class构造函数解析时是按照我们设计的顺序解析的,所以在这里首先执行的是Implements Mutator,它首先为Sub类的原型扩展了一个Mixin类的实例上的log方法,然后才是对超类Super的继承,因为在JavaScrpt里每个对象只有一个原型,原型式继承的原理就是超类的一个实例赋予子类的原型,子类原来的原型这时会被超类的实例替换掉,所以这是Sub类原型的引用已经指向了超类的实例,而他自己的原型对象这时被消除了,所以之前从Mixin类得来的那个log方法,对不起跟着一起魂飞湮灭了,所以这里返回的是'Super'。
当然如果你嫌不过瘾,那就在为Sub类添加一个log方法:
你可以把Sub类的Implements、Extends、log来回颠倒一下看看效果,呵呵,再用implement方法在扩展一个试试:
呵呵,别晕掉,一切都是为了把问题搞的跟明白不是......
最后不要忘记两个重要的方法:parent()和protect(),这里就不多说了,在前面的Class源码分析里有详细介绍。