面向对象
一. 类与实例 JavaScript的所有数据都可以看成对象(一切皆为对象)。那是不是我们已经在使用面向对象编程了呢?
当然不是。如果我们只使用Number
、Array
、string
以及基本的{...}
定义的对象,还无法发挥出面向对象编程的威力。
JavaScript 的ES6标准还没出来之前是不区分类和实例的概念,而是通过原型(prototype)(模拟类的)来实现面向对象编程。
1.类的声明 1 2 3 4 5 6 7 8 9 10 11 function Animal ( ) { this .name = "root" ; } class Animal2 { constructor ( ) { this .name = "root" ; } }
2.生成实例 通过new
关键字来实例化上面的声明
1 console .log(new Animal(), new Animal2());
二. 类与继承 1.如何实现继承 js中实现继承的方式主要是通过原型链完成的。
JavaScript的原型继承实现方式就是:
定义新的构造函数,并在内部用call()
调用希望“继承”的构造函数,并绑定this
;
借助中间函数F
实现原型链继承,最好通过封装的inherits
函数完成;
继续在新的构造函数的原型上定义新方法。
2.继承的几种方式 1.借助构造函数实现继承 核心: 使用父类的构造函数(Parent1.prototype.constructor
)来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)(只实现了部分继承,如果父类的属性都在构造函数上,那没有问题;如果父类的原型对象上还有方法,那子类是拿不到这些方法的)
缺点: 方法都在构造函数中定义, 只能继承父类的实例属性和方法 ,不能继承原型属性/方法 ,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function Parent1 (name ) { this .name = name; this .friends = ['小李' ,'小红' ]; this .getName = function ( ) { return this .name; } }; function Child1 (age ) { Parent1.call(this ,'老明' ); this .age = age; }; var result = new Child1(23 );console .log(result.name); console .log(result.friends); console .log(result.getName()); console .log(result.age); console .log(result.getSex());
2.借助原型链实现继承 弥补构造函数实现继承不足 。
核心: 将父类的实例作为子类的原型
缺点: 在一个类上实例了两个对象,改第一个对象的属性,第二个对象也跟着改变; 引起这个问题的原因:原型链上的原型对象是共用的;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 function Parent2 (name ) { this .name = name; }; Parent2.prototype.getName = function ( ) { return this .name; }; function Child2 (age ) { this .age = age; }; Child2.prototype = new Parent2('老明' ); Child2.prototype.getAge = function ( ) { return this .age; }; var result = new Child2(22 );console .log(result.getName()); console .log(result.getAge());
3.组合方式 组合继承(所有的实例都能拥有自己的属性,并且可以使用相同的方法,组合继承避免了原型链和借用构造函数的缺陷,结合了两个的优点,是最常用的继承方式)
核心: 通过调用父类构造,继承父类的属性并保留传参的优点,然后再通过将父类实例作为子类原型,实现函数复用
缺点: 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function Parent3 (name ) { this .name = name; this .friends = ['小李' ,'小红' ]; }; Parent3.prototype.getName = function ( ) { return this .name; }; function Child3 (age ) { Parent3.call(this ,'老明' ); this .age = age; }; Child3.prototype = new Parent3('老明' ); var result = new Child3(24 );console .log(result.name); result.friends.push("小白" ); console .log(result.friends); console .log(result.getName()); console .log(result.age); var result1 = new Child3(25 ); console .log(result1.name); console .log(result1.friends);
4.寄生组合继承 核心: 通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
缺点: 堪称完美,但实现较为复杂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function Parent4 (name ) { this .name = name; this .friends = ['小李' ,'小红' ]; } Parent4.prototype.getName = function ( ) { return this .name; }; function Child4 (age ) { Parent4.call(this ,"小明" ); this .age = age; } (function ( ) { var Super = function ( ) {}; Super.prototype = Parent4.prototype; Child4.prototype = new Super(); })(); var result1 = new Child4(23 ); console .log(result1.name); result1.friends.push('小白' ); console .log(result1.friends); console .log(result1.getName()); console .log(result1.age); var result2 = new Parent4('root' );