深入理解JS面向对象 - JavaScript实现继承的五种方式
2018-09-05 本文已影响73人
番茄沙司a
一、类与实例
1. 类的声明
- 传统function类的声明
function Animal() {
this.name = 'name';
}
- ES6中的class声明
class Animal2 {
//构造函数
constructor (){
this.name = name;
}
}
1.1 ES6 class与ES5 function的区别:
Class的特点:
Class在语法上更加贴合面向对象的写法
Class实现继承更加易读,更加容易理解
更易于写Java等后端语言的使用
本质还是是语法糖,使用prototype
- 重复定义
- function会覆盖之前定义的方法
- class会报错
- 构造器 constructor
- 在function定义的构造函数中,其prototype.constructor属性指向构造器自身
- 在class定义的类中,constructor也相当于定义在prototype属性上
- 原型或者类中方法的枚举
-
class
中定义的方法不可用Object.keys(Point.prototype)
枚举到 -
function
构造器原型方法可被Object.keys(Point.prototype)
枚举到,除了constructor
- 所有原型方法属性都可用
Object.getOwnPropertyNames(Point.prototype)
访问到 - 不管是
class
还是function
,constructor
属性默认不可枚举
2. 生成实例
如何通过类实例类的对象
console.log(new Animal(), new Animal2());
注:如果构造函数没有声明的话,new函数名后面的括号可以省略
二、类与继承
2.1 如何实现继承
2.2 继承的几种方式
- 借助构造函数实现继承
原理:在子类的函数体内执行父类,用call和apply都可以改变函数运行的上下文,导致父类执行的实例都会挂载到子类上面。
function Parent1 () {
this.name = 'parent1';
}
Parent1.prototype.say = function () {};/*new Child1().say() 报错*/
function Child1 () {
Parent1.call(this);/*apply也可以改变函数运行的上下文*/
this.type = 'child1';
}
控制台输出:
console.log(new Child1);
缺点:构造函数除了函数体里面的内容,还可能有原型链上的,但是这种方式中构造函数原型链上的东西并没有被继承。
扩展学习:javascript中apply、call和bind方法的区别
- 借助原型链实现继承(弥补构造函数继承不足)
function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3];
}
function Child2() {
this.type = 'child2';
}
Child2.prototype = new Parent2();
//缺点
console.log(new Child2());
var o1 = new Child2();
var o2 = new Child2();
console.log(o1.play, o2.play);//都为1,2,3
o1.play.push(4);//将o1重新复制
console.log(o1.play, o2.play);//都为1,2,3,4
实现原理:低级构造函数的原型是高级构造函数的实例,new Child2().__proto__
就是Parent2
父类的一个实例对象。即new Child2().__proto__ === Parent2.prototype
为true
。new Child2().__proto__.name
的值为parent2
。
缺点:因为原型链中的原型是共用的,所以两个对象不隔离。改变一个影响另一个。o1.__proto__ = o2.__proto__
。
- 组合继承方式(构造函数+原型链)
function Parent3() {
this.name = 'Parent3';
this.play = [1, 2, 3];
}
function Child3() {
Parent3.call(this);
this.type = 'Child3';
}
Child3.prototype = new Parent3();
//检验
console.log(new Child3());
var o1 = new Child3();
var o2 = new Child3();
o1.play.push(4);
console.log(o1.play, o2.play);//这时候结果就不一样了 分别是 [1,2,3,4] [1,2,3]
缺点:实例化子类的时候父类构造函数执行两次。是没有必要的。
- 组合继承优化一
function Parent4() {
this.name = 'Parent4';
this.play = [1, 2, 3];
}
function Child4() {
Parent4.call(this);
this.type = 'Child4';
}
Child4.prototype = Parent4.prototype;
console.log(new Child4());
var o1 = new Child4();
var o2 = new Child4();
o1.play.push(4);
console.log(o1.play, o2.play);
console.log(o1 instanceof Child4, s5 instanceof Parent4);//true, true
console.log(o1.constructor);
怎么区分,是否是直接实例化的?
直接拿的父类实例
- 组合继承优化二
function Parent5() {
this.name = 'Parent5';
this.play = [1, 2, 3];
}
function Child5() {
Parent5.call(this);
this.type = 'Child5';
}
Child5.prototype = Object.create(Parent5.prototype);
创建中间对象,完美~
2.3 Class和普通构造函数实现继承对比
Class实现(ES6)
class Animal {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat`)
}
}
class Dog extends Animal {
constructor(name) {
super(name)
this.name = name
}
say() {
console.log(`${this.name} say`)
}
}
const dog = new Dog('husky')
dog.say()
dog.eat()
普通构造函数实现
function Animal() {
this.eat = function () {
alert('Animal eat')
}
}
function Dog() {
this.bark = function () {
alert('Dog bark')
}
}
Dog.prototype = new Animal()
var husky = new Dog()
husky.bark()
husky.eat()