你可能知道JavaScript不是一个完全基于OOP的语言。当然也有一些方法来处理这个问题,你仍然可以创建类和继承它们。
使用类可以很好的帮助你拆分模块和创建更优秀的程序结构。
下面是最普通的基于构造函数的创建类:
1 2 3
| var Animal = function() { this.name = "animal"; }
|
接下来我们为该类申明 public method,很常见如下:
1 2 3 4 5 6 7 8
| Animal.prototype = { getName: function(){ return this.name; }, setName: function(name){ this.name = name; } }
|
现在我们已经知道的:
1.一个Animal
的类名。
2.一个公共的属性name
。
3.2个公共的方法getName
和setName
,他们主要是为name
属性获取/设置值。
使用方式:
1 2 3 4 5
| var animal = new Animal(); console.log(animal.name); console.log(animal.getName()); animal.setName("another animal"); console.log(animal.getName());
|
原型链继承
原型链的搜索有一定的顺序,我们可以基于原型链了实现继承关系。对象与原型之间的关系请参考javascript prototype。
例如:
1 2 3 4 5 6 7 8 9 10 11
| var Cat = function(){ this.habit = "catch mouse"; } Cat.prototype = new Animal(); var cat = new Cat(); console.log(cat.name); console.log(cat.habit); console.log(cat.getName()); cat.setName("cat"); console.log(cat.getName());
|
Cat的prototype指向了Animal的实例,原型链就相当于cat->Cat.prototype->animal->Animal.prototype
(忽略了Object等的默认原型)。cat实例就可以共享Animal的公共属性和方法。
基于原型链继承的缺点:
1.原型链上的引用类型——在原型链上使用引用类型的时候,会产生所以实例共享一个属性的情况。例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var Dog = function(){ this.habits = ["sleep","eat bone"]; this.name = "Dog"; } var BlackDog = function(){ } BlackDog.prototype = new Dog(); var blackDog1 = new BlackDog(); blackDog1.habits.push("catch frisbee"); console.log(blackDog1.habits); blackDog1.name = "Black Dog 1"; console.log(blackDog1.name); var blackDog2 = new BlackDog(); console.log(blackDog2.habits); console.log(blackDog2.name);
|
因为一般的类型采用是复制的方式实现实例化,而引用类型是采用的地址指向,无论多少个实例,结果都还是指向了同一地址,
所以产生了全部实例共享一个属性的情况。
2.不影响所以对象实例情况下,无法给超类的构造函数传递参数。例如上个代码中,如果直接使用BlackDog的对象blackDog1或者blackDog2,直接给Dog的构造函数传递值是无法办到的。
所以,在实际运用中基于原型链继承是很少单独使用的。
基于构造函数(经典继承)
因为原型链有引用类型的问题,所以又有了另一种方法来解决这个问题。基于构造函数依赖于call
或者apply
方法来实现。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| var Dog = function() { this.name = "animal"; this.habits = ["sleep","eat bone"]; } var BlackDog = function(){ Dog.call(this); } var blackDog1 = new BlackDog(); blackDog1.habits.push("catch frisbee"); console.log(blackDog1.habits); var blackDog2 = new BlackDog(); console.log(blackDog2.habits);
|
基于构造函数继承的缺点:
基于call
或者apply
方法来实现明显会丢失超类的原型方法,而且构造函数中都必须使用call
或者apply
方法,复用麻烦。
1 2 3 4 5 6 7 8 9 10
| var Dog = function() { } Dog.prototype.say = function(){ console.log('wang wang'); } var BlackDog = function(){ Dog.call(this); } var blackDog1 = new BlackDog(); console.log(blackDog1.say());
|
组合继承(伪经典继承)
采用原型链和构造函数来实现的方式。采用原型链来保证原型上方法或者属性的复用,采用构造函数来保证实例属性等的继承。
类似代码:
1 2 3 4 5 6 7 8 9 10 11
| var Dog = function() { } Dog.prototype.say = function(){ console.log('wang wang'); } var BlackDog = function(){ Dog.call(this); } BlackDog.prototype = new Dog(); var blackDog1 = new BlackDog(); blackDog1.say();
|
组合继承的缺点:组合继承是使用最多的,但是它也免不了有一些不足,首先实例化一个对象时候,会调用2次超类的构造函数,子类包括全部超类的全部实例属性。而且原型中也有这些属性。
原型式继承
基于Douglas Crockford提出的一种继承方式,就是原型式继承。原型式继承并没有使用严格意义上的构造函数。 它借助原型可以基于已有对象创建新对象,同时还不必因此创建自定义类型。
类似代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function object(o){ function F(){}; F.prototype = o; return new F(); } var animal = { name:'name', habits:['eat','sleep'] } var cat = object(animal); var dog = object(animal); cat.habits.push("catch mouse"); console.log(cat.habits); console.log(dog.habits);
|
现在的浏览器很多都有原型式继承的实现。
代码如下:
1 2 3 4 5 6 7 8 9
| var animal = { name:'name', habits:['eat','sleep'] } var cat = Object.create(animal); var dog = Object.create(animal); cat.habits.push("catch mouse"); console.log(cat.habits); console.log(dog.habits);
|
原型式继承的缺点:和基于原型链的继承一样引用类型的共享依然存在。
寄生式继承
同样是由Douglas Crockford提出,且和原型式继承联系紧密。主要思路是用一个函数来增强对象功能。
类似代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function create(original){ var clone = object(original); clone.sayHi = function(){ console.log('Hi'); } return clone; } var animal = { name:'name', habits:['eat','sleep'] } var cat = create(animal); cat.sayHi();
|
寄生式继承的缺点:同构造函数继承类似,无法使用超类的原型方法复用。
寄生组合式继承
寄生组合式继承的基本思路是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function object(o){ function F(){}; F.prototype = o; return new F(); } function inheritPrototype(subType,superType){ var prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function Animal(name){ this.name = name; this.body = ["head","foot"]; } Animal.prototype.sayName = function(){ console.log(this.name); } function Cat(name,age){ Animal.call(this,name); this.age = age; } inheritPrototype(Cat,Animal); Cat.prototype.sayAge = function(){ console.log(this.age); } var cat = new Cat('red',1); cat.sayAge(); cat.sayName();
|
寄生组合式继承消除了2次调用超类构造函数,避免了在超类上创建不必要或者多余的属性。同时原型链还不变,instanceof 和 isPrototypeOf()还可以正常使用。YUI基于该方式实现继承。
参考文档