一、ES5中的两种非常经典的继承
1.组合继承
指的是将原型链
和构造函数
的技术组合到一起来实现继承
。其背后的思路就是使用原型链
实现对原型属性和方法的继承,而通过构造函数
来实现对实例属性的继承。
示例
//构造函数继承实例属性(当然也可以有方法,不过不建议这么做)
function Phone(name){
this.name = name;
this.label=["全面屏","5G通信"];
}
//原型链继承原型属性和方法
Phone.prototype.colors=["red","blue","green"];
Phone.prototype.takePhoto = function(){
console.log("我是父类的原型上的方法--------拍照ing: "+this.name);
}
function Pear(name,inch){
//继承构造函数中的属性
Phone.call(this,name);
//子类自己构造函数中的属性
this.inch = inch;//英寸
}
//继承原型上的属性和方法
Pear.prototype = new Phone();
Pear.prototype.constructor = Pear;
//在原型上添加子类自己的方法或属性
Pear.prototype.listenMusic = function(){
console.log("我是子类在原型上添加的方法------听歌ing+ "+this.inch);
}
//以上我们就实现了Pear继承Phone,并且Pear也拥有了自己独有的属性inch和方法listenMusic。
//下面进行测试:
var p1 = new Pear("梨子手机",6.0);//实例化
p1.label.push("人脸识别")//操作父类构造函数的属性
console.log(p1.label);
p1.takePhoto();//调用父类原型上的方法
p1.listenMusic();//调用子类添加在原型上的方法
以上这种继承方式避免了单独使用原型链和构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承方式。而且,instanceof
和 isPrototypeof()
也能够用于识别基于组合继承的对象。
2.寄生组合继承
这种继承方式是目前认为ES5中最有效的方式,当然组合继承用的也是非常的多的。实现寄生组合继承的关键方法: inhert()
function inherit(son,father){
let prototypeObj = Object.create(father.prototype);
prototypeObj.constructor = son;
son.prototype = prototypeObj;
}
示例
//构造函数继承实例属性
function Phone(name){
this.name = name;
this.label=["全面屏","5G通信"];
}
//原型链继承原型属性和方法
Phone.prototype.colors=["red","blue","green"];
Phone.prototype.takePhoto = function(){
console.log("我是父类的原型上的方法--------拍照ing: "+this.name);
}
function Pear(name,inch){
//继承构造函数中的属性
Phone.call(this,name);
//子类自己构造函数中的属性
this.inch = inch;//英寸
}
//继承原型上的属性和方法:(非常重要)
inherit(Pear,Phone);
//在原型上添加子类自己的方法或属性
Pear.prototype.listenMusic = function(){
console.log("我是子类在原型上添加的方法------听歌ing+ "+this.inch);
}
//以上我们就实现了Pear继承Phone,并且Pear也拥有了自己独有的属性inch和方法listenMusic。
//下面进行测试:
var p1 = new Pear("梨子手机",6.0);//实例化
p1.label.push("人脸识别")//操作父类构造函数的属性
console.log(p1.label);
p1.takePhoto();//调用父类原型上的方法
p1.listenMusic();//调用子类添加在原型上的方法
3.ES5中两种方式的比较
- 对于组合继承:就是
将子类的原型指向父类的实例
,然后子类和父类组合一下构造函数中的实例属性或方法
。这样做就会导致子类原型(此时是父类)上的属性和子类与父类组合的属性重复,即,原本父类的属性此刻在子类本身和子类原型上都有一份。 - 对于寄生组合继承:关键点就是理解上文提到的inhert()方法:
将父类的原型赋给一个临时对象,子类的原型指向该临时对象
,如此得到父类原型上的属性和方法。再通过构造函数组合父类和子类本身上的属性或方法
。
二、ES6中的类与继承
1.class类
介绍
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。 ES6 提供了更接近
传统语言的写法,引入了 Class(类)
这个概念,作为对象的模板。通过class
关键
字,可以定义类。
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做
到,新的class写法只是让对象原型的写法更加清晰更像面向对象编程的语法而已。
所以ES6 的类,完全可以看作构造函数的另一种写法。
方法
在类中可以直接定义方法,实际上类的所有方法都定义在类的prototype
属性上面。
在类的实例上面调用方法,其实就是调用原型上的方法。
class Point {
constructor(){}
toString(){}
toValue(){}
}
由于类的方法都定义在prototype
对象上面,所以类的新方法可以添加在prototype
对
象上面,Object.assign
方法可以很方便地一次向类添加多个方法。
class Point {
constructor(){}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
constructor 方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。
一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默
认添加。
class Point { }
// 等同于
class Point { constructor() {} }
类必须使用new
调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不
用new
也可以执行。
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法
前,加上static
关键字,就表示该方法不会被实例继承
,而是直接通过类来调用
,这
就称为“静态方法”
。
class Foo {
static classMethod() {
console.log('hello');
}
}
Foo.classMethod(); // 'hello'
如果静态方法包含this关键字,这个this指的是类,而不是实例。
实例属性
类的实例属性可以定义在构造函数
中。
class Person{
constructor(id,name,age) {
this.id = id;
this.name = name;
this.age = age;
}
}
静态属性
直接在类上定义
的属性成为是静态属性。
class Foo {...}
Foo.prop = 1;
console.log(Foo.prop); // 1
目前,只有这种写法可行,因为 ES6 明确规定,Class
内部只有静态方法
,没有静
态属性。
实例属性: constructor中this后边的
实例方法: 在类内部没有用static修饰的方法
静态属性: 在类外部使用 ’ 类名.属性名 ’ (比如: Person.staticAttr = ‘a’;)声明的属性,只能通过类名去调用
静态方法: 在类内部使用static修饰的方法,只能通过类名去调用
2.继承
extends
class
可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要
清晰和方便很多。
class Animal {
constructor(name){
this.name = name;
}
sayName(){
console.log(“my name is ”,this.name);
}
}
class Dog extends Animal{...}
子类必须在constructo
r方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this对象
,而是继承父类的this对象
,然后对其进行加工。如果不调用super
方法,子类就得不到this对象
。子类构造函数可以省略。在子类的构造函数中,只有调用super
之后,才可以使用this关键字,否则会报错。
继承有两条线
1.子类对象继承父类对象
2.子类的原型对象继承父类的原型对象
super
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同:
- 函数
子类B的构造函数之中的super()
,代表调用父类的构造函数。 super
虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super
内部的this
指的是B,因此super()
在这里相当于A.prototype.constructor.call(this)
- 对象
在普通方法中,指向父类的原型对象;在静态方法中,指向父类。由于super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super
调用的。
类的 prototype 属性和__proto__属性
class
作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链。
- 子类的__proto__属性,表示构造函数的继承,总是指向父类
- 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
示例
//父类:两个属性,一个普通方法,一个静态方法
class Person {
constructor(name, age) {
//实例属性
this.name = name;
this.age = age;
}
//实例方法
sayName() {
console.log("我的名字是:" + this.name);
}
//静态方法
static sayHello() {
console.log("hello");
}
}
//静态属性
Person.staticProp = '父类的静态属性';
//子类继承父类
class Chinese extends Person { //创建Chinese类
constructor(name, age, addr) {
super(name, age); //继承父类的属性,必须写在任何this的开头,不然会报错
this.addr = addr;
}
sayMyaddr() {
console.log("我是中国人,我住在:" + this.addr);
}
}
//创建子类实例
var curry = new Chinese("curry", 25, "上海");
//测试
console.log(curry); //Chinese { name: 'curry', age: 25, addr: '上海' }
curry.sayName(); //我的名字是:curry
curry.sayMyaddr(); //我是中国人,我住在:上海
Person.sayHello(); //hello
Chinese.sayHello(); // hello (静态方法也可以被继承)
console.log(Person.staticProp); //父类的静态属性
console.log(Chinese.staticProp); //父类的静态属性 (静态属性也可以被继承)
//静态属性和方法的继承---子类继承父类
console.log(Chinese.__proto__ === Person); //true
//实例属性和方法的继承---子类的原型对象继承自父类的原型对象
console.log(Chinese.prototype.__proto__ === Person.prototype); //true