Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【JavaScript】面向对象之继承 #16

Open
Tracked by #6
swiftwind0405 opened this issue Feb 29, 2020 · 1 comment
Open
Tracked by #6

【JavaScript】面向对象之继承 #16

swiftwind0405 opened this issue Feb 29, 2020 · 1 comment

Comments

@swiftwind0405
Copy link
Owner

swiftwind0405 commented Feb 29, 2020

image

类(构造函数)

构造函数创建实例对象的过程和工厂模式类似

function createPerson(name) {
  var person = new Object();
  person.name = name;
  person.getName = function() {
    return this.name;
  }
  return person;
}

let person1 = createPerson('ziyi1');
let person2 = createPerson('ziyi2');

console.log(person1.getName === person2.getName);  // => false

工厂模式虽然抽象了创建具体对象的过程,解决了创建多个相似对象的问题,但是没有解决对象的识别问题,即如何知道对象的类型,而类(构造函数)创建的实例对象可以识别实例对象对应哪个原型对象(需要注意原型对象是类的唯一标识,当且仅当两个对象继承自同一个原型对象,才属于同一个类的实例,而构造函数并不能作为类的唯一标识)。

构造函数的创建过程:

  • 创建一个新对象
  • 将构造函数的作用域赋给新对象(this 新对象)
  • 执行构造函数中的代码
  • 返回新对象(最终返回的就是 new 出来的实例对象,因此 this 指向实例对象)

原型链继承

基本思路:
利用原型让一个引用类型继承另一个引用类型的属性和方法。即重写原型对象,代之以一个新类型的实例。

function SuperType() {
    this.name = 'Tom';
    this.colors = ['pink', 'blue', 'green'];
}
SuperType.prototype.getName = function () {
    return this.name;
}
function SubType() {
    this.age = 22;
}
SubType.prototype = new SuperType();
SubType.prototype.getAge = function() {
    return this.age;
}
SubType.prototype.constructor = SubType;
let instance1 = new SubType();
instance1.colors.push('yellow');
console.log(instance1.getName()); //'Tom'
console.log(instance1.colors);//[ 'pink', 'blue', 'green', 'yellow' ]

let instance2 = new SubType();
console.log(instance2.colors);//[ 'pink', 'blue', 'green', 'yellow' ]

缺点:

  1. 通过原型来实现继承时,原型会变成另一个类型的实例,原先的实例属性变成了现在的原型属性,该原型的引用类型属性会被所有的实例共享。
  2. 在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数。

构造函数继承

基本思路:
在子类型的构造函数中调用超类型构造函数。

function SuperType(name) {
  this.name = name;
  this.colors = ["pink", "blue", "green"];
}
function SubType(name) {
  SuperType.call(this, name);
}
let instance1 = new SubType("Tom");
instance1.colors.push("yellow");
console.log(instance1.colors); // => ['pink', 'blue', 'green', yellow]

let instance2 = new SubType("Jack");
console.log(instance2.colors); // => ['pink', 'blue', 'green']

优点:

  1. 可以向超类传递参数
  2. 解决了原型中包含引用类型值被所有实例共享的问题

缺点:

  1. 方法都在构造函数中定义,函数复用无从谈起,另外超类型原型中定义的方法对于子类型而言都是不可见的。
  2. 父类的方法没有被共享,造成内存浪费

组合继承(原型链+构造函数)

组合继承指的是将原型链和借用构造函数技术组合到一块,从而发挥二者之长的一种继承模式。

基本思路:
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性。

image

function SuperType(name) {
    this.name = name;
    this.colors = ['pink', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
    console.log(this.name);
}
function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
    console.log(this.age);
}
let instance1 = new SubType('Tom', 20);
instance1.colors.push('yellow');
console.log(instance1.colors); // => [ 'pink', 'blue', 'green', 'yellow' ]
instance1.sayName(); // => Tom

let instance2 = new SubType('Jack', 22);
console.log(instance2.colors); // => [ 'pink', 'blue', 'green' ]
instance2.sayName();// => Jack

优点:

  1. 可以向超类传递参数
  2. 每个实例都有自己的属性
  3. 实现了函数复用

缺点:

  1. 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
  2. 子类的原型对象中还存在父类实例对象的实例属性。只是因为子类实例中有同名属性,所以没有继续往子类的原型对象上去找属性。

原型式继承

基本思想:
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

function objectCreate(o) { 
	function F() { } 
	F.prototype = o; 

	return new F();
}

在 objectCreate() 函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例,从本质上讲,objectCreate() 对传入的对象执行了一次浅拷贝。

ECMAScript5 通过新增 Object.create(proto[, propertiesObject])方法规范了原型式继承。这个方法接收两个参数:

  • proto:新创建对象的原型对象
  • propertiesObject:可选,新对象定义额外属性的对象(可以覆盖原型对象上的同名属性)

在传入一个参数的情况下,Object.create() 和 objectCreate() 方法的行为相同。

var person = {
    name: 'Tom',
    hobbies: ['reading', 'photography']
}
var person1 = Object.create(person);
person1.name = 'Jack';
person1.hobbies.push('coding');
var person2 = Object.create(person);
person2.name = 'Echo';
person2.hobbies.push('running');

console.log(person.hobbies); // => [ 'reading', 'photography', 'coding', 'running']
console.log(person1.hobbies);// => [ 'reading', 'photography', 'coding','running']

优点
在没有必要创建构造函数,仅让一个对象与另一个对象保持相似的情况下,原型式继承是可以胜任的。

缺点:
同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。

寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

function createAnother(original) {
    var clone = Object.create(original);//通过调用函数创建一个新对象
    clone.sayHi = function () {         //以某种方式增强这个对象
        console.log('hi');
    };
    return clone;                       //返回这个对象
}
var person = {
    name: 'Tom',
    hobbies: ['reading', 'photography']
};

var person2 = createAnother(person);
person2.sayHi(); //hi

基于 person 返回了一个新对象 -—— person2,新对象不仅具有 person 的所有属性和方法,而且还有自己的 sayHi() 方法。在考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。

缺点:

  • 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。
  • 同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。

寄生组合式继承

image

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法,基本思路:

不必为了指定子类型的原型而调用超类型的构造函数,我们需要的仅是超类型原型的一个副本,本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承的基本模式如下所示:

function inheritPrototype(subType, superType) {
    var prototype = objectCreate(superType.prototype); // 创建对象
    prototype.constructor = subType;                   // 增强对象
    subType.prototype = prototype;                     // 指定对象
}
  • 第一步:创建超类型原型的一个副本
  • 第二步:为创建的副本添加 constructor 属性
  • 第三步:将新创建的对象赋值给子类型的原型

至此,我们就可以通过调用 inheritPrototype 来替换为子类型原型赋值的语句:

function SuperType(name) {
  this.name = name;
  this.colors = ['pink', 'blue', 'green'];
}

SuperType.prototype.sayName = function () {console.log(this.nmae)}
function SubType(name, age) {
  SuperType.call(this, name);
  this.age = age;
}

SubType.prototype = new SubType();
inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function () {console.log(this.age)}

优点:
只调用了一次超类构造函数,效率更高。避免在SubType.prototype上面创建不必要的、多余的属性,与其同时,原型链还能保持不变。

寄生组合继承是引用类型最理性的继承范式

class继承(es6)

ES6 中的类只是 ES5 封装后的语法糖而已。

// ES6中实现的Person类
class Es6Person {
  constructor(name, age) {
    // 实例对象的属性
    this.name = name
    this.age = age
    // 实例对象的方法
    this.getName = () => {
      return this.name
    }
  }

  // Person类原型对象的方法
  getAge() {
    return this.age
  }
}


// ES5中实现的Person类
function Es5Person (name, age) {
  // 实例对象的属性
  this.name = name
  this.age = age
  // 实例对象的方法
  this.getName = () => {
    return this.name
  }
}

// Person类原型对象的方法
Es5Person.prototype.getAge = function() {
  return this.age
}

在 ES5 中类的原型对象的方法是可枚举的,但是 ES6 中不可枚举:

console.log(Object.keys(Es6Person.prototype));   // => []
console.log(Object.keys(Es5Person.prototype));   // => ["getAge"]

在 ES5 中如果不用 newthis 指向 window 全局变量,在 ES6 如果不用 new 关键字则会报错处理:

// Uncaught TypeError: Class constructor Person cannot be invoked without 'new'
let person2 = Es6Person('ziyi2', 111)

ES6 中的类是不会声明提升的,ES5 可以:

console.log(Es5Person);     // => Es5Person {}
let es6 = new Es6Person();  // => Uncaught ReferenceError: Es6Person is not defined
console.log(es6);

class Es6Person {}
function Es5Person {}

在 ES6 中如果不写构造方法:

class Es6Person {}

// 等同于
class Es6Person {
  constructor() {}
}

在 ES6 中类的属性名可以采用表达式:

const getAge = Symbol('getAge')

class Es6Person {
  constructor(name, age) {
    this.name = name
    this.age = age
    this.getName = () => {
      return this.name
    }
  }
  
  // 表达式
  [getAge]() {
    return this.age
  }
}

let es6Person = new Es6Person('ziyi2', 28)
es6Person[getAge]()

ES5 的继承使用借助构造函数实现,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面。ES6 的继承机制完全不同,实质是先创造父类的实例对象 this(所以必须先调用 super 方法),然后再用子类的构造函数修改 this

子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。

ES6 extends 继承做了什么操作

看这个例子:

// ES6
class Parent{
    constructor(name){
        this.name = name;
    }
    static sayHello(){
        console.log('hello');
    }
    sayName(){
        console.log('my name is ' + this.name);
        return this.name;
    }
}
class Child extends Parent{
    constructor(name, age){
        super(name);
        this.age = age;
    }
    sayAge(){
        console.log('my age is ' + this.age);
        return this.age;
    }
}
let parent = new Parent('Parent');
let child = new Child('Child', 18);

用原型图表示:
image

结合代码和图可以知道。
ES6 extends 继承,主要就是:

  1. 把子类构造函数(Child)的原型(__proto__)指向了父类构造函数(Parent)
  2. 把子类实例(child)的原型对象(Child.prototype) 的原型(__proto__)指向了父类(parent)的原型对象(Parent.prototype)。

上面这两点也就是图中用不同颜色标记的两条线。

  1. 子类构造函数(Child)继承了父类构造函数(Preant)的里的属性。使用 super 调用的(ES5 则用 call 或者 apply 调用传参)。也就是图中用不同颜色标记的两条线。

把上面的例子转化为 ES5(也即是寄生组合式继承的方法):

function Parent() {
  this.name = name;
}

Parent.sayHello = function() {
  console.log("hello");
}

Parent.prototype.sayName = function() {
  console.log("my name is " + this.name);
  return this.name;
}

function Child(name, age) {
  // 相当于super
  Parent.call(this, name);
  this.age = age;
}

function _inherits(Child, Parent) {
  // Object.create
  Child.prototype = Object.create(Parent.prototype);
  // __proto__
  // Child.prototype.__proto__ = Parent.prototype;
  Child.prototype.constructor = Child;
  // ES6
  // Object.setPrototypeOf(Child, Parent);
  // __proto__
  Child.__proto__ = Parent;
}
_inherits(Child, Parent);

Child.prototype.sayAge = function() {
  console.log("my age is " + this.age);
  return this.age;
}

let parent = new Parent("Parent");
let child = new Child("Child", 18);

参考资料

@swiftwind0405 swiftwind0405 changed the title 面向对象 【Day20】面向对象 Mar 16, 2020
@swiftwind0405 swiftwind0405 changed the title 【Day20】面向对象 【Day20】面向对象以及类与继承 Mar 16, 2020
@swiftwind0405 swiftwind0405 changed the title 【Day20】面向对象以及类与继承 【Day20】类与继承 Mar 19, 2020
@swiftwind0405 swiftwind0405 changed the title 【Day20】类与继承 【JavaScript】类与继承 Apr 29, 2020
@swiftwind0405 swiftwind0405 changed the title 【JavaScript】类与继承 【JavaScript】面向对象之继承 Apr 29, 2020
@swiftwind0405
Copy link
Owner Author

swiftwind0405 commented Dec 15, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant