JS学习笔记(第六章)(面向对象之继承)

9次阅读

共计 7111 个字符,预计需要花费 18 分钟才能阅读完成。

1、原型链
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。构造函数、原型和实例的关系:每个构造函数都有一个原型对象;原型对象都包含着一个指向构造函数的指针;实力都包含一个指向原型对象的内部指针。如果我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地另一个原型中也包含着指向另一个构造函数的指针……层层递进,就构成了实例与原型的链条,这就是所谓原型链的基本概念。

实现原型链有一种基本模式,其代码大致如下:
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};

function SubType() {
this.subproperty = false;
}

// 继承了 SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function() {
return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue()); //true
(1)不要忘记默认的原型

(2)确定原型和实例的关系
可以通过两种方式来确定原型和实例之间的关系。1)使用 instanceof 操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回 true。
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
2)使用 isPrototypeOf() 方法。只要是原型链中出现过否认原型,都可以说是该原型链所派生的实例的原型,因此 isPrototypeOf() 方法也会返回 true。
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
(3)谨慎地定义方法
子类型有时候需要覆盖超类型中的某个方法或者需要添加超类型中不存在的某个方法。但不管这样,给原型添加方法的代码一定要放在替换原型的语句之后。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};

function SubType() {
this.subproperty = false;
}

// 继承了 SuperType
SubType.prototype = new SuperType();

// 添加新方法
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
// 重写超类型中的方法
SubType.prototype.getSuperValue = function() {
return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); //false
在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。会导致实例与原型链之间的联系被切断。
function SubType() {
this.subproperty = false;
}

// 继承了 SuperType
SubType.prototype = new SuperType();

// 使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
getSubValue : function() {
return this.subproperty;
},
sonOtherMethod : function() {
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!
(4)原型链的问题
1)最主要的问题来自包含引用类型值的原型;
function SuperType() {
this.colors = [“red”, “blue”, “green”];
}

function SubType(){

}
// 继承了 SuperType
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”

var instance2 = new SuperType();
alert(instance2.colors); //”red,blue,green,black”
如上所示,我们对 instance1.colors 的修改能够通过 instance2.colors 反映出来。2)第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数;
2、借用构造函数
思想:在子类型都早函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象,因此通过使用 apply() 和 call() 方法也可以在新创建的度向上执行构造函数。
function SuperType() {
this.colors = [“red”, “blue”, “green”];
}

function SubType(){
// 继承了 SuperType,
SuperType.call(this);// 在子类型构造函数的内部调用超类型构造函数
}

var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”

var instance2 = new SuperType();
alert(instance2.colors); //”red,blue,green”
通过使用 call() 方法或 apply() 方法,我们实际上是在新创建的 SubType 实例的环境下调用了 SuperType 构造函数。这样一来,就会在新 SubType 对象上执行 SuperType() 函数中定义的所有 UI 想初始化代码。结果 SubType 的每个实例就会具有自己的 colors 属性的副本了。
(1)传递参数
相对于原型链而言,借用构造函数的一个很大优势在于:可以在子类型构造函数中向超类型构造函数传递参数。
function SuperType(name) {
this.name = name;
}

function SubType() {
// 继承了 SuperType, 同时还传递了参数
SuperType.call(this, “Nicholas”);
// 实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //”Nicholas”
alert(intance.age); //29
(2)借用构造函数的问题
如果仅仅是借用构造函数,那么也就无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起。除此之外,在超类型的原型中定义的方法,对子类型而言也是不可兼得,结果所有类型都只能用构造函数模式。
3、组合继承
将原型链和借用构造函数的技术组合到一起。思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,即通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。
function SuperType(name) {
this.name = name;
this.color = [“red”,”blue”,”green”];
}

SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name ,age) {
// 继承属性
SuperType.call(this.name);
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; // 指向构造函数
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType(“Nicholas”, 29);
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”
instance1.sayName(); //”Nicholas”
instance1.sayAge(); //29

var instance2 = new SubType(“Greg” ,27);
alert(instance2.colors); //”red,blue,green”
instance2.sayName(); //”Greg”
instance2.sayAge(); //27
instanceof 和 isPrototypeOf() 也能够用于是被基于组合继承创建的对象、
4、原型式继承
借助原型可以基于已有的对象创建新独享,同时还不必因此创建自定义类型
function object(o) {
function F() {}
F.prototype =o;
return new F();
}
在 object() 函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。
var person = {
name : “Nicholas”,
friends : [“Shelby”, “Court”, “Van”]
};
var anotherPerson = object(perosn);
anotherPerson.name = “Greg”;
anotherPerson.friends.push(“Rob”);

var yetAbotherPerson = object(perosn);
yetAbotherPerson.name = “Linda”;
yetAbotherPerson.firends.push(“Barbie”);

alert(person.friends); //”Shelby, Court, Van, Greg, Linda”

ECMAScript5 通过新增 Object.creat() 方法规范了原型式继承。这个方法接收两个参数:用作新独享圆形的对象和为新对象定义额外属性的对象(可选)。在传入一个参数的情况下,Object.create() 与 object() 方法的行为相同。
var person = {
name : “Nicholas”,
friends : [“Shelby”, “Court”, “Van”]
};
var anotherPerson = Object.create(perosn);
anotherPerson.name = “Greg”;
anotherPerson.friends.push(“Rob”);

var yetAbotherPerson = Object.create(perosn);
yetAbotherPerson.name = “Linda”;
yetAbotherPerson.firends.push(“Barbie”);

alert(person.friends); //”Shelby, Court, Van, Greg, Linda”
Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同:每个属性都通过自己的描述定义的。用这种方式指定的任何属性都会覆盖原型对象上的同名属性。
var person = {
name : “Nicholas”,
friends : [“Shelby”, “Court”, “Van”]
};
var anotherPerson = Object.create(perosn,{
name : {
value : “Grag”
}
});
alert(anotherPerson.name); //”Greg”
5、寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。以下代码示范了寄生式继承模式。
function creatAnother(original) {
var clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式来增强这个对象
alert(“Hi”);
};
return clone; // 返回这个对象
}
基于 person 返回一个新对象——anotherPerson。新对象不仅具有 Person 的所有属性和方法,还有自己的 sayHi 方法。
function creatAnother(original) {
var clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式来增强这个对象
alert(“hi”);
};
return clone; // 返回这个对象
}
var person = {
name : “Nicholas”,
friends : [“Shelby”, “Court”, “Van”]
};

var anotherPerson = creatAnother(person);
anotherPerson.sayHi(); //”hi”
6、寄生组合式继承
组合继承最大的问题就是:无论在什么情况下,都会调用两次构造函数,一次是在创建子类型原型的时候,一次是在子类型构造函数的内部。
function SuperType(name) {
this.name = name;
this.color = [“red”,”blue”,”green”];
}

SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name ,age) {
// 继承属性
SuperType.call(this.name); // 第二次调用 SuperType()
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); // 第一次调用 SuperType()
SubType.prototype.constructor = SubType; // 指向构造函数
SubType.prototype.sayAge = function() {
alert(this.age);
};
寄生式组合继承,就是通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其思想是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需的无非就是超类型原型的一个副本而已。本质上就是,使用寄生式继承来继承超类型的原型,然再将结果指定给子类型的原型。其基本模式如下:
function inheritPrototype(SubType,superType) {
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}

这个函数接收两个参数:子类型构造函数和超类型构造函数。第一步是创建超类型原型的一个副本,第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性,第三步将新创建的对象(即副本)赋值给子类型的原型。
function inheritPrototype(SubType,superType) {
var prototype = object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}

function SuperType(name) {
this.name = name;
this.color = [“red”,”blue”,”green”];
}

SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name ,age) {
// 继承属性
SuperType.call(this.name);
this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
alert(this.age);
};
这个例子的高效率体现在它只调用了一次 SuperType 构造函数。

正文完
 0