继承的写法
上面几篇文章提到面向对象并没有明确的定义,只是一种编程套路。
js 本身就是一种动态语言。何为动态语言呢?字面意思来讲就是一会是这个状态,一会是那个状态,即 js 本身就是满足多态的。那么,如何用 js 来实现 Java 中的面向对象(封装、继承、多态)呢?
问:什么是类?
答:士兵的配方:独有属性(ID和生命值)、共有属性(兵种,死亡动画);
人类的配方:独有属性(名字、肤色)、共有属性(吃喝拉撒)。
若 士兵 继承 人类,s = new 士兵, 则 s 直接就可以吃喝拉撒
那么,接下来要做的就是怎样用 js 模拟继承。
原型链实现继承
var personCommon = {
吃: '',
喝: '',
拉: '',
撒: ''
}
var soldierCommon = {
兵种: "美国大兵",
攻击力: 5,
行走: function () { /* 行走的代码 */},
奔跑: function () { /* 奔跑的代码 */},
死亡: function () { /* 死亡的代码 */},
攻击: function () { /* 攻击的代码 */},
防御: function () { /* 防御的代码 */}
}
soldierCommon.__proto__ = personCommon
var s = {}
s.__proto__ = soldierCommon
s.名字 = ''
s.肤色 = ''
s.ID = ''
s.生命值 = ''
将如上代码运行,打印 s 则可看到:s 拥有的自有属性为名字、肤色、ID以及生命值,其共有属性包括 soldier 以及 person 中的属性。
上述代码太散,于是我们使用构造函数将零散的代码封装一下。
// 先定义一个Human的构造函数,拥有自有属性name和肤色
function Human(options){
this.name = options.name
this.肤色 = options.肤色
}
// Human的公有属性 eat,drink,poo
Human.prototype.eat = function(){}
Human.prototype.drink = function(){}
Human.prototype.poo = function(){}
// 定义一个Soldier的函数,其name和肤色来自人类,故先调用Human获取name和肤色
// Soldier的自有属性为id和生命值
function Soldier(options) {
Human.call(this, options)
this.id = options.id // ID 不能重复
this.生命值 = 42
}
// Soldier的公有属性
Soldier.prototype.兵种 = "美国大兵",
Soldier.prototype.攻击力 = 5
Soldier.prototype.行走 = function () { /* 行走的代码 */}
Soldier.prototype.奔跑 = function () { /* 奔跑的代码 */}
Soldier.prototype.死亡 = function () { /* 死亡的代码 */}
Soldier.prototype.攻击 = function () { /* 攻击的代码 */}
Soldier.prototype.防御 = function () { /* 防御的代码 */}
// 实现Soldier有Human的公有属性
Soldier.prototype.__proto__ = Human.prototype
var s = new Soldier({name: 'John', id:1, 肤色:'yellow'})
上述,我们使用构造函数实现了 new 一个 Soldier,即可拥有 Human 的自有属性和公有属性。一切看起来都是那么的完美。但是,我们在生产环境中不可以使用 __proto__,也就是上述代码中的 Soldier.prototype.__proto__ = Human.prototype 在生产环境中是不可以使用的。但是,我们又知道 js 自己的 new 是可以使用 __proto__ 的。
思考:如下代码中发生了什么?
// new一个对象可以做如下几件事情
function Human(){
this = {}
this.__proto__ = Human.prototype
return this
}
Soldier.prototype = new Human()
// Soldier.prototype === Human中的this
// Soldier.prototype.__proto__ === this.__proto === Human.prototype
function Human(){}
h = new Human()
h.__proto__ === Human.prototype // true
Soldier.prototype = new Human()
Soldier.prototype.__proto__ === Human.prototype // true
那么,我们可以使用new来改造一下之前代码。将 Soldier.prototype.__proto__ = Human.prototype 替换成 Soldier.prototype =newSoldier({name:'Jack', 肤色:'white'})
// 先定义一个Human的构造函数,拥有自有属性name和肤色
function Human(options){
this.name = options.name
this.肤色 = options.肤色
}
// Human的公有属性 eat,drink,poo
Human.prototype.eat = function(){}
Human.prototype.drink = function(){}
Human.prototype.poo = function(){}
// 定义一个Soldier的函数,其name和肤色来自人类,故先调用Human获取name和肤色
// Soldier的自有属性为id和生命值
function Soldier(options) {
Human.call(this, options)
this.id = options.id // ID 不能重复
this.生命值 = 42
}
// Soldier的公有属性
Soldier.prototype.兵种 = "美国大兵",
Soldier.prototype.攻击力 = 5
Soldier.prototype.行走 = function () { /* 行走的代码 */}
Soldier.prototype.奔跑 = function () { /* 奔跑的代码 */}
Soldier.prototype.死亡 = function () { /* 死亡的代码 */}
Soldier.prototype.攻击 = function () { /* 攻击的代码 */}
Soldier.prototype.防御 = function () { /* 防御的代码 */}
// 实现Soldier有Human的公有属性
Soldier.prototype = new Soldier({name: 'Jack', 肤色:'white'})
var s = new Soldier({name: 'John', id:1, 肤色:'yellow'})
打印 s 查看,于是我们发现了 s 的公有属性中不仅有 Human 的公有属性,还有 Human 的自有属性。一个没有自有属性的构造函数使用 new 可以实现 __proto__ 的赋值。
因此,我们定义一个假的 Human 构造函数 FakeHuman,使得 FakeHuman.prototype = Human.prototype,如此我们便有了一个没有自有属性但是公有属性跟 Human 的相同的构造函数。
function Soldier(options) {
Human.call(this, options)
this.id = options.id // ID 不能重复
this.生命值 = 42
}
function Human(options){
this.name = options.name
this.肤色 = options.肤色
}
Human.prototype.eat = function(){}
Human.prototype.drink = function(){}
Human.prototype.poo = function(){}
// 声明一个假的Human对象,只保留Human的公有属性,不要其私有属性
// 兼容IE的
function fakeHuman(){}
fakeHuman.prototype = Human.prototype
Soldier.prototype = new fakeHuman()
// 上述语句 Soldier.prototype.__proto__ === fakeHuman.prototype === Human.prototype
Soldier.prototype.兵种 = "美国大兵",
Soldier.prototype.攻击力 = 5
Soldier.prototype.行走 = function () { /* 行走的代码 */}
Soldier.prototype.奔跑 = function () { /* 奔跑的代码 */}
Soldier.prototype.死亡 = function () { /* 死亡的代码 */}
Soldier.prototype.攻击 = function () { /* 攻击的代码 */}
Soldier.prototype.防御 = function () { /* 防御的代码 */}
var s = new Soldier({name: 'John', id:1, 肤色:'yellow'})
于是,上述代码避开了生产环境中不能使用的 __proto__,实现了继承。
我们再来看下面代码:
function A(){
this.age = 22
}
A.prototype.行动 = function(){}
B.prototype = Object.create(A.prototype)
var b = new B()
可以看到,B 继承了 A 的公有属性,但是并没有继承 A 的自有属性。所以,我们可以使用Object.create() 代替之前代码的假的 Human 函数 FakeHuman ,但是这种写法不支持IE浏览器。
于是,代码在不需要支持 IE 浏览器的情况下,可以变成如下的样子:
function Soldier(options) {
Human.call(this, options)
this.id = options.id // ID 不能重复
this.生命值 = 42
}
function Human(options){
this.name = options.name
this.肤色 = options.肤色
}
Human.prototype.eat = function(){}
Human.prototype.drink = function(){}
Human.prototype.poo = function(){}
// 不兼容 IE的
Soldier.prototype = Object.create(Human.prototype)
Soldier.prototype.兵种 = "美国大兵",
Soldier.prototype.攻击力 = 5
Soldier.prototype.行走 = function () { /* 行走的代码 */}
Soldier.prototype.奔跑 = function () { /* 奔跑的代码 */}
Soldier.prototype.死亡 = function () { /* 死亡的代码 */}
Soldier.prototype.攻击 = function () { /* 攻击的代码 */}
Soldier.prototype.防御 = function () { /* 防御的代码 */}
var s = new Soldier({name: 'John', id:1, 肤色:'yellow'})
如上,便使用原型链的方式实现了继承。
class实现继承
MDN定义class:类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类表达式和类声明。
我们将构造函数中的 function 改成 class,将其自有属性写在 constructor 中。
class Human{
constructor(options){
this.name = options.name
this.肤色 = options.肤色
}
eat(){}
drink(){}
poo(){}
}
class Soldier extends Human {
constructor(options){
super(options)
this.id = options.id // ID 不能重复
this.生命值 = 42
this.兵种 = "美国大兵"
this.攻击力 = 5
}
行走() { /* 行走的代码 */}
奔跑() { /* 奔跑的代码 */}
死亡() { /* 死亡的代码 */}
攻击() { /* 攻击的代码 */}
防御() { /* 防御的代码 */}
}
var s = new Soldier({name: 'John', id:1, 肤色:'yellow'})
然而,上述代码中存在两个问题。
- 上述定义的Human,我们使用 typeof Human 打印出来的是 function,也就是说是一个假的class,本质还是 function,但是如果我们使用 Human.call() 的时候,会报错 Class constructor Human cannot be invoked without 'new' ,意思是说只能通过 new 去调用。因此我们只能将 class 理解为是一个特殊的函数,只能通过 new 去调用的函数。
- class 中的公有属性只能是 function,自有属性都必须写在 constructor 中。