继承的概念
在开发过程中, ⾯向对象是⼀种处理代码的思考⽅式. 在⾯向对象中继承就是其中⼀个⾮常重要的概念. 接下来本节详细讨论继承的概念.
为何需要使⽤继承
在开发中, 经常会发现多个不同类型的对象都有共同的⽅法. 例如代码:
var o1 = new Number(144);
console.log("打印 Number 对象 o1 = " + o1);
var o2 = new Date();
console.log("打印 Date 对象 o2 = " + o2);
var o3 = new Array(1, 4, 4);
console.log("打印 Array 对象 o3 = " + o3);
var o4 = new Error("a test");
console.log("打印 Error 对象 o4 = " + o4);
function MyConstructor() {
}
var o5 = new MyConstructor();
console.log("打印⾃定义对象 MyConstructor o5 = " + o5);
在本例中, 打印对象都有⼀个共同的操作, 即 "和字符串相连接". 该操作在执⾏的时候, 会⾃动的调⽤⼀个⽅法, 即
toSting()
⽅法. 因此, 执⾏代码
console.log("打印⾃定义对象 MyConstructor o5 = " + o5);
等价于执⾏代码:
console.log("打印⾃定义对象 MyConstructor o5 = " + o5.toString());
那么问题来了, 这个类型中没有没有定义
toString()
⽅法呢? 很显然是没有
的. 那么如何实现?
其实很简单, 就是为了 "复⽤".
在开发中常常会有重复的操作, 例如⻚⾯上很多东⻄都可以点击; ⼀个游戏中,常常会有⾓⾊可以⾛动等等. 因此将重复执⾏的代码提取出来, 不⽤再编写代码的时候每次都要将其再写⼀遍. 那么这种拿来主义就是继承.
在继承中, ⼀个对象继承⾃另⼀个对象. 继承的对象中包含被继承对象的所有成员.
例如⼈会说话, 那么将说话的功能提取出来作为⼀个对象. 继承⾃该对象的美国⼈, ⽇本⼈, 或是英国⼈就都具有说话的⽅法了.
因此⼀句话总结继承, 就是 "为了偷懒, 就拿来主义".
继承的实现⽅式
继承的实现⽅式有很多种, 如今主流的继承有类继承和原型继承. 类继承的语⾔有:
C++
,Java
,C#
等, ⽽原型继承有:Self
,Io
,JavaScript
等.
C# 中基于类的继承
在基于类继承的编程语⾔中, 有⼀个对象的模板, 称之为类. 需要对象则⾸先设计⼀个类, 由类来创建对象. ⽽继承是指类之间的继承.
例如写⼀个⽗类
class BaseClass {
public void SayHello() {
System.Console.WriteLine("你好");
}
}
然后提供⼀个⼦类
class SubClass : BaseClass {
}
最后直接创建⼦类独享, 即可调⽤⽅法
public static void Main(string[] args) {
SubClass o = new SubClass();
o.SayHello(); // => 你好
}
这⾥的继承实际上是利⽤模板来实现的. 在模板
BaseClass
中定义了⼀个⽅法SayHello
, 然后设计⼀个⼦类SubClass
继承⾃BaseClass
, 在⼦类中没有规定任何东⻄. 但是由⼦类创建出来的对象具有SayHello
⽅法, 并且可以调⽤.
这个就是利⽤类的继承. 类继承了, 那么由该类创建出来的对象就具有被继承的成员.
原型继承
与类继承不同, 在
JavaScript
中, 使⽤原型来实现继承. 在原型中定义的所有成员都会被其构造⽅法创建的对象所继承. 在JavaScript
中不存在类的概念,因此实现继承的⽅式也不再唯⼀和统⼀.
还是说话的例⼦, 使⽤
JavaScript
来实现
// 定义⼀个对象, 将来作为原型对象
var proto = {
sayHello : function() {
console.log("你好!!!");
}};
// 定义⼀个构造函数
function Person() {
}
// 设置 Person 的原型
Person.prototype = proto;
// 创建对象, 具有 sayHello ⽅法
var p = new Person();
p.sayHello();
在本例中没有类的概念, 继承也不是模板之间的继承. ⽽是给构造⽅法设置⼀个原型对象, 由该构造函数创建出来的对象就具有该原型对象中的所有内容.我们称这个对象就继承⾃原型对象.
注意: 前⾯曾经介绍过
__proto__
的概念, 因此实现继承的⽅法也就不统⼀了, ⽐较随意.
值得说明的是, 所有由该类创建出来的对象, 都具有了原型中定义的属性 (⽅法). 与定义和设置的顺序⽆关. 但是如果重新设置属性就不正确了.
例如, 下⾯的代码可以正常执⾏.
function Person() {
}
var p1 = new Person();
Person.prototype.sayHello = function() {
console.log("hello, 你好 JavaScript!");
};
var p2 = new Person();
p1.sayHello(); // => hello, 你好 JavaScript!
p2.sayHello(); // => hello, 你好 JavaScript!
上⾯的代码, 给
Person
的原型添加了⼀个sayHello
⽅法, 因此两个Person
对象都可以调⽤该⽅法.
如果是直接重新设置构造函数
Person
的原型对象, 那么就会报⼀个TypeError
异常.
function Person() {
}
var p1 = new Person();
Person.prototype = { sayHello : function() {
console.log("hello, 你好 JavaScript!");
}};
var p2 = new Person();
p1.sayHello(); // => 异常
p2.sayHello();
原因很简单, 这个原型赋值修改了构造函数
Person
的原型对象类型.
function Person() { }
var p1 = new Person();
Person.prototype = { sayHello : function() {
console.log("hello, 你好 JavaScript!");
}};
var p2 = new Person();
console.log(p1.constructor); // => function Person() { }
console.log(p2.constructor); // => function Object() { [native code]
可⻅修改后, 原型不再是
Person
类型的了, ⽽是Object
类型.
Object.prototype
在
JavaScript
中, 每⼀个对象都有原型. ⽽且每⼀个原型都直接, 或间接的继承⾃Object.prototype
对象.
function Person() {}
定义了⼀个构造函数, 那么他就有⼀个
Object
类型的原型对象被设置给了Person.prototype
console.log(Person.prototype);
console.log(Person.prototype instanceof Object); // => true
// note: 可⻅ Person.prototype 是 Object 类型的
如此, 由
Person
创建出来的对象就继承⾃Object
类型的对象. ⽽Person.prototype
也是⼀个对象. ⾃然也有⼀个原型对象.
在 Chrome 中可以使⽤
__proto__
引⽤. 因此可以获得Person.prototype
的原型对象
console.log(Person.prototype.__proto__); // => Object {}
那么从逻辑上就有下⾯的继承模型:
需要注意的是,Object
类型的原型对象的原型对象为 null
console.log(Person.prototype.__proto__.__proto__ === null);
// => true
⼀种继承的实现
有了上⾯的分析, 实现继承就可以分为三个步骤:
- 获得构造函数
F
- 获得构造函数
- 设置
F
的原型为被继承的对象
- 设置
- ⽤
F
创建继承对象
- ⽤
简单的实现为:
// 被继承对象
var pareObj = {
sayHello: function() {
console.log("Hello, jk");
}
};
// 创建构造函数
function F() {
}
// 设置原型对象
F.prototype = pareObj;
// 创建继承对象
var obj = new F();
但是这么写太过于繁琐, 因此⼤师
Douglas Crockford
在他的《JavaScript: The Good Parts》
中给出了下⾯的做法:
if (!Object.create) { // ECMAScript 5 中已经⽀持该⽅法
Object.prototype.create = function (pare) {
function F() {}
F.prototype = pare;
return new F();
}
}
⼩结
- 所谓继承就是拿来主义, 将重复的东⻄进⾏复⽤
-
JavaScript
中继承就是给构造函数的prototype
设置对象 - 每⼀个对象都有⼀个原型对象
- ⼀个经典的继承⽅法
此系列笔记均参考自 蒋坤 笔记