# 原型与继承
我们在认识对象一文中介绍了类和对象实例的关系
这篇我们主要关注 JS是将类和对象之间关联起来的
即使我们没有学过Java、C++等 语言,我们也一定听说过 Java、C++ 等是面向对象的(Object-Oriented)的编程语言。JavaScript 也是一种面向对象的编程语言, 由于实现方式的怪异, 很多时候,我们也会听到很多人说它是基于对象(Object-Based)的编程语言。我们这里不过多的讨论这些概念,本文更多的关注 JavaScript 的面向对象是如何实现的, 刚兴趣的小伙伴可以自行搜索
# 两个特征
在JS世界中,实现对象的两个必要条件
- 构造函数 - 定义不同事物之间的边界, 前面文章有介绍过
- 原型链
# 原型链
原型链的一大作用就是用来实现 面向对象概念中的一个特别重要的概念: 继承
继承简单的理解 就是为了代码的复用, 比如作为一个程序员,再设计一款程序的时候,我希望某些属性和方法能够复用, 对于其他语言来讲, 有很多方式,比如定义静态(static)类型、或者定义父类。
但是JavaScript 没有区分 这些, JavaScript 用原型实现了继承,或者说叫代码复用。
原型链我们可以拆开理解
- 原型
- 链
每个函数都有一个 prototype 的引用, 这个引用指向原型对象。
对于原型对象来说, 它有一个 consturctor 属性指向 函数。
原型对象的出现为多个实例共享属性和方法提供了可能。
<script>
function People(name, sex) {
this.name = name;
this.sex = sex;
}
console.log(People.prototype);
console.log(typeof People.prototype)
// true
console.log(People.prototype.constructor === People)
</script>
# 原型链的继承
我们都知道,对象是可以通过 new 构造函数() 来创建, 那么创建出的对象也会带有一个 proto 的属性指向原型对象
<script>
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var xiaoLuo = new People('小罗', 12, '男');
// true
console.log(xiaoLuo.__proto__ == People.prototype);
</script>
一些公用的属性和方法就可以放在 原型对象当中,虽然在 原型链中定义了属性,但是 子类同样也能方位这些属性和方法。
子类既能继承原型对象中的方法和属性,又能重写、新增自己的属性,这就叫做继承
<script>
function People(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
People.prototype.nation = '中国';
People.prototype.nationFun = function() {
console.log(this.nation);
}
var xiaoLuo = new People('小罗', 22, '男');
var xiaoHong = new People('小红', 22, '女');
// 子类可以调用原有的属性
console.log(xiaoLuo.nation);
xiaoLuo.nationFun();
// 子类可以调用原有的属性
console.log(xiaoHong.nation);
xiaoHong.nationFun();
// true
console.log(xiaoLuo.nationFun == xiaoHong.nationFun);
</script>
我们在有些场景下需要区分某些属性是属于对象,还是属于上层的原型对象
站在 对象的角度来说, 在有一些场景下需要区分 是否是定义在 自己 这里的属性或方法 ,而不是定义在公共原型对象上, 这里就需要一个方法 hasOwnProperty, 返回true 表示是自己的属性,否则为false
xiaoLuo.hasOwnProperty('name'); // true
xiaoLuo.hasOwnProperty('age'); // true
xiaoLuo.hasOwnProperty('sex'); // true
xiaoLuo.hasOwnProperty('nation'); // false
而在另一些场合中, 可以使用 in 关键子 提前知道 某个对象 的方法或者属性是否可以被访问,这里不再区 该属性或方法是否属于对象本身
'name' in xiaoLuo ; // true
'age' in xiaoLuo; // true
'sex' in xiaoLuo; // true
'nation' in xiaoLuo; // true
# 多重继承
有了原型链,我们就可以实现更多层级的继承, 比如 小罗作为人的同时有事一个球员。这里我们就可以抽象出两个类
People(人)和Player(球员) 两个类, 然后将 People 的原型对象指向 Player, 就实现了多重继承
<script>
//父类 人类
function People(name, age , sex) {
this.name = age;
this.age = age;
this.sex = sex;
}
People.prototype.hello = function() {
console.log('你好,我是:' , this.name);
}
//子类 , 球员
function Player(name, age ,sex , jerseyNumber) {
this.name = name;
this.age = age;
this.sex = sex;
this.jerseyNumber = jerseyNumber;
}
// 关键语句实现继承 player 的原型对象指向 People
Player.prototype = new People();
Player.prototype.playing = function() {
console.log(this.name , '正在踢足球');
}
// 重写父类方法
Player.prototype.hello = function() {
console.log('你好,我是:' , this.name, "我是一名球员, 我的球员号码是:", this.jerseyNumber);
}
// 实例化
var xiaoLuo = new Player('小罗', 22, '男', 10);
var xiaoMei = new Player('小美', 23, '女', 12);
xiaoLuo.playing();
xiaoLuo.hello();
xiaoMei.hello();
xiaoMei.playing();
</script>
# 原型链的终点
我们前面创造了一个People 的构造函数知道它的 原型对象 是 People.prototype , 那么原型对象的原型对象是什么呢。
其实原型链也是有尽头的, 这个尽头就在 Object.prototype , Object的原型对象的原型对象就指向null了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function People(name ,age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var xiaoLuo = new People('小罗', 22, '男');
// 输出true
console.log(xiaoLuo.__proto__.__proto__ === Object.prototype);
// 输出null
console.log(Object.prototype.__proto__);
</script>
</body>
</html>
# 总结
JS中的继承是通过原型链来实现的, 继承的优点很多, 其中子类可以继承父类的全部方法和属性,而且还可以扩展这些属性和方法
正式这种复用性, 是的我们可以在实际开发过程中少写很多代码