阅读 37

面向对象、类与继承

1、前言

2、类与实例

  • 类的申明
// 1、构造函数
function Person(name) {
  this.name = name;
};
// 2、Es6 class
class Person2 {
  constructor(name) {
    this.name = name;
  };
};
复制代码
  • 实例
// 实例化
console.log(new Person('张三'), new Person2('李四'));
复制代码

3、继承

  • 1、借助构造函数实现继承
function Parent1() {
  this.name = 'parent1';
};
Parent1.prototype.say = function() { // 无法被Child1的实例对象继承
  console.log('say hello!');
};
function Child1() {
  Parent1.call(this); // 改变this指向
  this.type = 'child1';
};
console.log(new Child1());
复制代码

PS:缺点是只能继承构造函数中的属性和方法,无法继续原型对象上的属性和方法,如图:

  • 2、借助原型链实现继承
function Parent2() {
  this.name = 'parent2';
};
Parent2.prototype.say = function() {
  console.log('say hello parent2!');
};
function Child2() {
  this.type = 'child2';
};
Child2.prototype = new Parent2();
console.log(new Child2())  // new Child2()_proto_===Child2.prototype
复制代码

继承的原理:new Child2()实例对象的_proto属性指向的是构造函数Child2的prototype原型对象,
            即 new Child2()_proto_===Child2.prototype;
            由于Child2.prototype = new Parent2(),
            则可通过 new Parent2()的_proto_属性找到Parent2的prototype原型对象。
复制代码

PS:缺点是通过一个构造函数实例多个对象时候,修改构造函数的属性,所有的继承自构造函数的实例对象的改属性都将改变。如下所示:

function Parent2() {
  this.name = 'parent2';
  this.arr1 = [1,2,3,4];
  this.arr2 = [1,2,3,4];
};
function Child2() {
  this.type = 'child2';
};
Child2.prototype = new Parent2();
var obj1 = new Child2();
var obj2 = new Child2();
obj1.arr1.push(5)  // 由于obj本身并没有arr1属性,则通过_proto_原型链找到了Parent2的arr2属性
obj1.arr2 = [1,2,3,4,5] // 这种方式并不会修改obj2.arr2属性,相当于给obj1新增加了arr2属性。
console.log(obj1, obj2)
复制代码

PS:从图中可以看到obj1.arr1.push(5)修改使得obj2.arr1的值也被修改。这显然不是我们想要的,我们希望各个实例之间是相互独立的。

  • 3、构造函数和原型链相组合方式
function Parent3() {
  this.name = 'parent3';
  this.arr1 = [1,2,3,4];
};
Parent3.prototype.say = function() {
  console.log('say hello!')
}
function Child3() {
  Parent3.call(this); // 改变了this指向,使得Parent3中的this指向的是Child3的实例对象。
  this.type = 'child3';
};
Child3.prototype = new Parent3()
var o3 = new Child3();
var o4 = new Child3();
o3.arr1.push(5)
console.log(o3, o4)
复制代码

PS: 可以看到这里已经解决了方法1、2中的问题。但是这里有个问题就是Parent3构造函数被执行了两次。

Parent3.call(this); // 改变了this指向,使得Parent3中的this指向的是Child3的实例对象。
Child3.prototype = new Parent3() // 既然上面已经继承了Parent3构造函数中的属性,这里只是为了继承Parent3原型属性
思考:根据之前原型链的相关知识,有以下关系
new Parent3().__proto__ === Parent3.prototype
故这里可以改成:
Child3.prototype = Parent3.prototype  // 缺点Child3和Parent3的constructor是同一个
复制代码
  • 优化
  1. 到这里已经实现了继承,但是这里还有个问题。
var o3 = new Child3(); // 这里o3实例应该是由`Child3`直接生成的实例。
更具根据之前原型链的相关知识
o3 instanceof Child3 // true
o3 instanceof Parent3 // true
instanceof判断o3是Child3原型链上的一个实例如果想要直接判断o3是Child3直接生成的实例,可以通过constructor:
我们期望
o3.constructor === Child3
三实际上的结果:
o3.constructor === Parent3
复制代码

// 代码优化
function Parent4() {
  this.name = 'parent4';
  this.arr1 = [1,2,3,4];
};
function Child4() {
  Parent3.call(this);
  this.type = 'child4';
};
Child4.prototype = Object.create(Parent4.prototype); // Object.create创建的对象只是_proto_的引用
Child4.prototype.constructor = Child4  // 给Child4原型对象添加constructor,覆盖_proto_的constructor
var o5 = new Child4();
var o6 = new Child4();
o5.arr1.push(5)
console.log(o5, o6)
复制代码

4、总结

  • 本文主要讲述了如何申明一个类。
  • 如何实现继承和继承的实现原理。
  • instanceoconstructor的使用。
关注下面的标签,发现更多相似文章
评论