面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class(ES6 引入了class 语法),而是通过“原型对象”(prototype)实现。那么在JS中常见的继承方式有几种呢?
首先我们先来通过 es5 里的方法做一个类,即构造方法,如下:
function Person() {
// 定义参数
this.name = "张三";
this.age = 18;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
// 通过类添加属性和静态方法
Person.city = "北京";
Person.eat = function () {
console.log(this.name + "在吃")
};
// 实例化 Person
var p = new Person();
p.run(); // 张三在运动
p.work(); // 张三在工作
// p.eat(); // 报错 p.eat is not a function
Person.eat(); // Person在吃 // this 指向发生改变,为全局,该 name 不是 Person 类中定义的 name
在上面的代码中,我们根据构造函数方法创建了一个 Person 类,并通过内部定义参数方法,原型链扩展属性和方法,通过类添加属性和方法。接下来我们就根据这个 Person 类来实现集中继承的方法。
1、对象冒充实现继承
function Person() {
// 定义参数
this.name = "张三";
this.age = 18;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
function Student() {
Person.call(this);
}
var s = new Student();
console.log(s.name); // 张三
s.run(); // 张三在运动
/**
* 对象冒充继承不能实现
* 父类原型链上的属性和方法
*/
console.log(s.sex); // undefined
s.work(); // 报错 s.work is not a function
在上面的代码中,我们可以通过对象冒充的方法进行继承,但是原型链上的属性和方法是不能被继承的。
2、原型链继承
function Person() {
// 定义参数
this.name = "张三";
this.age = 18;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
// 通过类添加属性和静态方法
Person.city = "北京";
Person.eat = function () {
console.log(this.name + "在吃")
};
function Student() {
}
Student.prototype = new Person();
var s = new Student();
console.log(s.name); // 张三
s.run(); // 张三在运动
console.log(s.sex); // 男
s.work(); // 张三在工作
在上面的代码中,我们通过原型链的方法进行继承,这样构造方法内的方法和原型链上的方法我们都能继承了,看似很不错,但是其实存在着一些问题,如下:
function Person(name,age) {
// 定义参数
this.name = name;
this.age = age;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
var p = new Person("张三",18);
p.run(); // 张三在运动
p.work(); // 张三在工作
function Student(name,age) {
}
Student.prototype = new Person();
var s = new Student("李四", '20');
console.log(s.name); // undefined
s.run(); // undefined在运动
console.log(s.sex); // 男
s.work(); // undefined在工作
在上面的代码中,我们可以看出,通过原型链继承,在实例化子类的时候没法给父类进行传参。
3、原型链+对象冒充组合继承
function Person(name, age) {
// 定义参数
this.name = name;
this.age = age;
// 定义方法
this.run = function () {
console.log(this.name + "在运动");
}
}
// 通过原型链扩展属性和方法
Person.prototype.sex = "男";
Person.prototype.work = function () {
console.log(this.name + "在工作");
};
var p = new Person("张三", 18);
p.run(); // 张三在运动
p.work(); // 张三在工作
function Student(name, age) {
Person.call(this, name, age); // 对象冒充
}
Student.prototype = new Person();
var s = new Student("李四", '20');
console.log(s.name); // 李四
s.run(); // 李四在运动
console.log(s.sex); // 男
s.work(); // 李四在工作