在前文全面理解Javascript的面向对象(一)中详细介绍了面向对象的主要知识点,可以帮助大家很细致的了解js面向对象的概念,本文作为补充,主要从对象的构建和继承的方式两方面进行分析。
一、创建对象主要的几种方式
1 工厂模式
工厂模式抽象了创建具体对象的过程,用函数封装以特定接口创建对象的细节。
function createPerson(name,age,job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert(this.name);
}
return o;
}
var person1 = createPerson("Nike",21,"Doctor");
var person2 = createPerson("Frank",21,"Manager");
缺点:没有解决对象的识别问题,即怎样知道一个对象的类别。于是,新的模式:构造函数模式出现了。
2 构造函数模式
像Object、Array这样的原生构造函数,在运行时就会自动创建。此外也可以自定义构造函数,从而定义自定义对象类型的属性和方法。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name);
}
}
var person1 = new Person("Nike",21,"Doctor");
var person2 = new Person("Frank",21,"Manager");
使用构造函数方法创建对象经历了四个过程:
- 创建新对象
- 将构造函数作用域赋给新对象(this指向了新对象)
- 执行构造函数的方法(为新对象添加属性)
- 返回新对象
自定义的构造函数可将它的实例标识为特定的类型,这是胜过工厂模式的地方
alert(person1 instanceof Object); //ture
alert(person1 instanceof Person); //ture
alert(person2 instanceof Object); //ture
alert(person2 instanceof Person); //ture
缺点:每个方法在每个实例上多次创建。这个问题可以通过原型模式解决。
3 原型模式
function Person() {
}
Person.prototype.name = 'frank';
Person.prototype.age = 21;
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.sayName();
alert(person1.sayName == person2.sayName); //true
更简单的原型语法:
function Person() {
}
Person.prototype = {
constructor : Person,
name:'frank',
age:29,
sayName:function () {
alert(this.name);
}
};
4 组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,同时又共享着方法的引用,最大限度节省内存。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor : Person,
sayName : function () {
alert(this.name);
}
}
var person1 = new Person("Nike",21,"Doctor");
var person2 = new Person("Frank",21,"Manager");
二、继承的实现方式
1 原型链
如果对象无法相应某个请求,他会把这个请求委托给他的构造器的原型对象。
将一个原型对象等于另一个实例,这个原型对象包含一个指向另一个原型的指针,相应的,另一个原型也包括一个指向构造函数的指针,这样层层递进,构成了实例与原型的链条
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperType = function () {
return this.property;
}
function SubTpye() {
this.subproperty = false;
}
SubTpye.prototype = new SuperType();
var ins = new SubTpye();
alert(ins.getSuperType());
缺点:创建子类型的实例时,不能向超类型的构造函数中传递参数。
2 借用构造函数
在子类型构造函数中调用超类型构造函数。
function SuperType() {
this.colors = ['red','blue','green'];
}
function SubTpye() {
SuperType.call(this);
}
var ins1 = new SubTpye();
ins1.colors.push("black");
alert(ins1.colors); //'red','blue','green','black'
var ins2 = new SubTpye();
alert(ins2.colors); //'red','blue','green'
与原型链模式相比,借用构造函数的最大优势是子类可以向超类中传递参数。
function SuperType(name) {
this. name = name;
}
function SubTpye() {
SuperType.call(this,"frank");
}
var ins1 = new SubTpye();
alert(ins1.name); //'frank'
缺点:方法都在构造函数中定义,因此函数复用就无从谈起,超类型的原型中定义的方法,对子类型而言不可见。因此借用构造函数的模式很少单独使用。
3 组合继承
组合继承是将原型链和借用构造函数的方法结合到一起。
思路是
- 使用原型链实现对原型属性和方法的继承;
- 使用借用构造函数的方法实现对实例属性的继承。
这样,通过在原型上定义方法实现函数复用,又能够保证对每个实例有自己的属性。
function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.callName = function(){
alert(this.name);
}
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype={
constructor:SubType,
callAge:function(){
alert(this.age);
}
}
var ins1 = new SubType("nike",29);
ins1.colors.push("black");
console.log(ins1.colors);
console.log(ins1.callName());
console.log(ins1.callAge());
组合模式是javascript中最常用的模式。
4 原型继承
在上一篇博文全面理解Javascript的面向对象(一)中,介绍过,原型继承的本质是基于原型链的委托机制。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在object()函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。object()对传入的对象进行了一次浅复制。
var Person = {
name:"nike",
friends:["frank","lucy"];
}
var another = object(Person);
another.gentle = "male";
another.friends.push("mike");
在ES5中新增Object.create()方法,规范了原型继承。这个方法又两个传入的参数,一个是对象,另一个是==可选==的额外定义的属性。
var another1 = Object.create(Person);
another.gentle = "male";
var another2 = Object.create(Person,{
name:{
value:"Greg"
}
});