javascript是一门弱类型语言,从不需要类型转换。对象继承关系变得无关紧要。对于一个对象来说重要的是它能做什么,而不是它从哪里来。javascript是一门基于原型的语言,这意味着对象直接从其他对象继承。
1、伪类
javascript不直接让对象从其他对象继承,反而是插入了一个中间层:通过构造器函数产生对象。
当一个函数被创建时,Function 构造器产生的函数对象会运行类似这样的一些代码:
this.prototype = {constructor : this};
新创建的函数对象被赋予了一个prototype属性。对于普通函数,这个属性没有任何作用。因为javascript没有提供一种方法去确定那个函数是用来打算做构造器的,所以给每一个函数对象都赋予了这样一个prototype属性。对于作为构造器的函数对象来说,这个prototype属性是用来存放继承特征的地方。
函数作为构造器去构造新对象时,就是采用构造器调用模式去调用函数 ,也就是使用new 前缀去调用一个函数。此时的函数就相当于构造对象的一个类,当然这不是一个真正意义上的类,只是javascript为了刻意模拟面向对象系统而构造的一种语法。所以构造器函数是一个伪类。
现在我们来实现一个new 方法来替代 new 操作符,通过这个new 方法我们来看看到底new 操作符做了哪些操作:
Function.prototype.method = function(name, fun) {
this.prototype[name]= fun;
};
Object.create = function(pro){
var F = function(){};
F.prototype = o;
return new F();
};
Function.method('new', function(){
var that = Object.create(this.prototype);
var other = this.apply(that, arguments);
return (other && typeof other === 'object') || that;
});
上面例子首先给Function的原型添加一个method方法,这个方法被所有的函数对象继承(Function自己也是函数对象),用于给自己的原型添加某个方法。这个用这个函数构造出来的新对象将会共享(继承)这个方法。
然后又给Object添加了一个create方法,这个方法用于创建一个新的对象,并以参数pro 作为构造函数的原型。这个新创建的对象将会共享(继承)pro的所有属性。
最后,我们给Function原型添加了一个new方法,这个方法模拟了new操作符在构建新对象时所作的操作。让我们看一个例子:
var Person = function (name, age ){
this.name = name;
this.age = age;
};
var p = Person.new("clopopo",12);
p.name // "clopopo"
p.age // 12
2、对象说明符
编写构造器函数时,参数用一个对象说明符来代替一大串参数
//使用一大串参数来构造对象
var myObject = make(f, l, m, c, s);
//使用对象说明符来代替一大串参数
myObject = make({
first : f,
middle : m,
last : l,
state : s,
city : c
});
相当于把参数组织成了类似json对象的东西。优点是不需要记住参数的顺序,另一个就是可以直接传入json对象,来生成真正的对象实例。
3、原型
基于原型的继承比基于类的继承模型在概念上要更简单:一个新对象可以继承一个旧对象的属性。利用上面Object的create方法,我们完全可以避免使用new这样令人迷惑的模拟类的继承方式(本质上还是基于原型)。
首先,利用对象字面量来定义一个有用的对象:
var myMammal = {
name : 'Herb the Mammal',
get_name : function() {
return this.name;
},
says : function(){
return this.saying || '';
}
}
一旦有一个想要的对象,我们就可以利用Object的create方法(上文自己定义的)基于这个对象定制新的对象。
var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.getName = function(){
return this.says + ' ' + this.name + ' ' + this.says;
};
这是一种差异化继承,myCat查找属性时,如果自身存在就调用自身的属性,否则就去查找myCat连接的原型对象。
4、函数化
上文的几种继承方式有一个弱点就是无法保护隐私,就是我们无法得到私有函数和私有变量。有一种非常不可取的方法就是被称为“伪装私有”模式。就是给私有方法或变量去一个怪模怪样的名字,并希望使用代码的用户假装看不到这些变量,这是典型的自欺欺人啊。幸亏javascript的闭包特性给我们提供了一种叫做应用模块的模式。
var mammal = function(spec){
var that = {};
that.get_name = function(){
return spec.name;
};
that.says = function(){
return spec.saying || ' ';
};
return that;
}
var myMammal = mammal({name: 'Herb'});
再来看看原型继承
var cat = function(spec){
spec.saying = spec.saying || '';
var that = mammal(spec);
that.get_name = function(){
return that.says() + ' ' + spec.name;
}
return that;
}
var myCat = cat('kitty');
函数化模式还给我们提供了一个处理父类方法的方法。用闭包封存了调用superior方法对象的this和当时name属性对应的方法。
Object.method('superior', function(name){
var that = this,
method = that[name];
return function(){
return method.apply(that, arguments);
};
});
先看看这个方法的用法:
var coolcat = function(spec){
var that = cat(spec),
super_get_name = that.superior('get_name');
that.get_name = function(n){
return 'like ' + super_get_name() + ' baby';
};
return that;
};
super_get_name 得到的是cat对象创建时的get_name方法,根据superior方法定义知道,该方法永远都会以调用superior时的那个对象为上下文,上例中就是cat对象。所以调用super_get_name()时就是以cat为上下文,cat创建时的get_name方法为方法体。