You Don't Know JS: this & Object Prototypes( 第4章 Mixing "Class" Objects)

本章移到“Object oriented programming”和"classes"。

看‘class orientation‘ 的设计模式: instantiation, inheritance, relative 多态性。

会重点讲解oop理论,当讲到Mixins, 我们会最终联系这些思想到真实的JS代码。

在这之前会先说大量的概念,pseudo-code,所以不要迷失,stick with it!!


 

 

Class Theory

OO or class oriented programming , 通过适当的设计把数据和行为一起打包。

在正式的计算机科学,也叫做数据结构。

 

Classes也暗示了类化某个数据结构的方法。

 

And thus, classes, inheritance, and instantiation emerge. 如:车辆, 小轿车, 具体一辆小轿车。

 

另一个概念是:多态。

描述了从父类继承基本的行为后,在一个子类重写这些基本的行为赋予更多特色。

因此,父类和子类对某一行为应当共享相同的name,以便子类重写。

如此做,会导致JS code变得脆弱和让人frustration.

 

"Class" Design Pattern

 OOp这种设计模式只是一种可选的代码概念。

 有些语言如C/PHP还有 procedural syntaxes。

 

JavaScript "Classes"

JS 有一些类的句法元素:new , instanceof 

在ES6中添加了更多的,如class关键字。(见Appednix A)

但是,JavaScript 实际上是没有classes的 

 

JS试图满足非常普遍的愿望:通过提供类似类的句法,用类设计!

 

事实:其他语言的类和JS中的“classes”是不一样的。

 

类是一种可选的设计模式,因此你可以选择在JavaScript中使用或者不用。

 

因为很多开发者喜欢用oop设计模式,所以本章会探索:JS提供了什么导致类的错觉,和我们经历的错误点。

 


 

Class Mechanics

 

在许多对象编程语言,标准库提供了a 'stack' 数据结构,作为一个Stack类。

这个类有内部的一系列变量用于存储数据。并且它会提供一系列的公共地可存取的行为(方法) 。

这让你的代码有和这个data交互的能力。

但是,你无需直接在Stack上操作。Stack 类只是一个抽象的解释关于任意的stack应当做什么,它自己不是一个'stack'。 你必须实例这个Stack类。然后才能有一个具体的数据结构的东西来操作。

 

Building 类的比喻:建筑物蓝图。

依照蓝图建立实物,这个实物就是instance。继承就是从一个基本蓝图,增加内容变成子蓝图。

 

Constructor

一般和类名字相同。实际就是初始化这个实例的任何状态。

例子:

Joe = new ClassName()

实例化了一个类。

 


 

Class Inheritance

没啥新的。略

class Vehicle {
    engines = 1

    ignition() {
        output( "Turning on my engine." )
    }

    drive() {
        ignition()
        output( "Steering and moving forward!" )
    }
}

class Car inherits Vehicle {
    wheels = 4

    drive() {
        inherited:drive()    //有些语言使用super
        output( "Rolling on all ", wheels, " wheels!" )
    }
}

 

 

Polymorphism 多态。

子类被他们的父类给予继承的行为的复制。 

如果一个行为在子类被重写,不影响父类的行为,父类的原先版本的行为不会改变的。

 

Class inheritance implies copies.

类的继承就是暗含了复制!!

 

Multiple Inheritance

少数语言提供多重继承。

JS不提供原生的多重继承的机制!

但可以模仿。比如👇Minins.


 

 

Mixins

JavaScript's object mechanism does not automatically perform copy behavior when you "inherit" or "instantiate". 

JS中没有类可以继承,只有对象。并且对象不会从其他对象得到复制。它们使用链接,连接在一起。

所谓的子对象和父对象实际上是共享了一个函数。

 

既然其他语言的类暗示了复制!JS开发者伪造这种丢失的复制行为:用minins.

分为: explicit and implicit.

 

Explicit Mixins

因为JS不能自动复制行为从父类到子类,所以需要创建一个utility手动复制。

这样一个utility在许多库/框架叫做 extend()

这里我们使用mixin(..)方法,做一个演示:

//这是一个mixin函数

function
mixin( sourceObj, targetObj ) { for (var key in sourceObj) { // 只复制不存在的属性 if (!(key in targetObj)) { targetObj[key] = sourceObj[key]; } } return targetObj; } var Vehicle = { engines: 1, ignition: function() { console.log( "Turning on my engine." ); }, drive: function() { this.ignition(); console.log( "Steering and moving forward!" ); } }; var Car = mixin( Vehicle, { wheels: 4,    //明确的假多态! drive: function() { Vehicle.drive.call( this ); console.log( "Rolling on all " + this.wheels + " wheels!" ); } } );

 

⚠️:重要:我们没有用类处理,因为在JavaScript中没有类! Vehicle, Car仅仅是对象,我们从Vehicle复制行为到Car。

 

ignition函数

使用mixin()复制,但实际上是2个对象共用一个函数!

所以子对象Car修改这个函数,也会影响到Vehicle对象。

Technically, functions are not actually duplicated, but rather references to the functions are copied.  

  1. Vehicle.ignition增加了一个属性,同步Car.ignition也会增加这个属性。本质上是复制了一个引用!它们是共享相同的函数!!!
  2. 另外如果Vehicle.ignition = function() {...} 这是新定义一个函数,并让之前的identifier指向这个新函数,那么Vehicle就失去了原来函数的标示符号。此时,Car的ignition函数还是指向原来的那个函数。此时,Vehicle和Car中的相同名字的identifier已经指向不同的函数了!

 

"Polymorphism" Revisited  再看多态(复制)

drive()函数:

Vehicle.drive.call( this );

这就是我所调用的"explict pseudo-polymorphism" --明确的假多态

  • 明确指的是.call(this), 
  • 多态指:对继承的Vehicle.drive函数进行了修改。

 

JavaScript没有一个适用相对多态(relative polymorphism)的场所。(ES6以前,见附加A)。

如此,因为Car和Vehicle有相同的函数名字: driver(), 为了区别它们的调用,我们必须做absolute reference。

我们明确地指定Vehicle对象通过name, 并调用drive()函数。

因此,我们使用Vehicle.drive调用Vehicle的drive函数,并使用call(this)指定,调用这个函数的对象是Car。

 

这样既模拟了继承(Car继承了Vehicle的函数的功能),也增加了自己的功能(多态!)

即,Car的drive函数,和Vehicle的drive函数,是指向不同位置的函数。

只是Car.drive引用了Vehicle.drive的功能。

本质还是对象和对象之间共享了一个函数!

 

缺陷:

这种方式创建了脆弱/手动的联系linkage, 在每个单独的函数(你需要如此一个伪多态引用)。

增加了维护成本。更可怕的是,明确的伪多态(explict pseudo-polymorphism)能够模仿多重继承(multiple inheritance)的行为,这增加了复杂性和脆弱性!

这里只是演示,避免使用!!

 

Mixing Copies

JavaScript functions can't really be duplicated (in a standard, reliable way), so what you end up with instead is a duplicated reference to the same shared function object (functions are objects;)

 

如果你使用明确的混入(explicitly  mixin)多个对象给你的目标对象,就是一种多重继承行为! 

 

还是尽量避免这种混入方式。能不用就不用。在章节6,我们试图使用更简单的方式完成但不会有fuss。

 

Parasitic Inheritance 寄生的继承

一个明确混入模式的变种。 使用explicit也使用implicit。

// "Traditional JS Class" `Vehicle`
function Vehicle() {
    this.engines = 1;
}
Vehicle.prototype.ignition = function() {
    console.log( "Turning on my engine." );
};
Vehicle.prototype.drive = function() {
    this.ignition();
    console.log( "Steering and moving forward!" );
};

// "Parasitic Class" `Car`
function Car() {
    // first, `car` is a `Vehicle`
    var car = new Vehicle();

    // now, let's modify our `car` to specialize it
    car.wheels = 4;

    // save a privileged reference to `Vehicle::drive()`
    var vehDrive = car.drive;

    // override `Vehicle::drive()`
    car.drive = function() {
        vehDrive.call( this );  //明确的伪多态(伪复制)
        console.log( "Rolling on all " + this.wheels + " wheels!" );
    };

    return car;
}

var myCar = new Car();

myCar.drive();
// Turning on my engine.
// Steering and moving forward!
// Rolling on all 4 wheels!

 


 

Implicit Mixins

 和明确的假多态一样。有相同的问题,尽量避免使用。

 


  

Review (TL;DR)

类是一种设计模式,很多语言都提供了句法。

JS也有一个类似的syntax, 但是它非常不同于你在其他语言中使用的类模式。 

 

Classes mean copies.

从类到实例是拷贝,从父类到子类也是拷贝!

Polymorphism多态看起来它暗示了从子到父的一个相关性连接,但它仍然只是复制行为的一种结果。

 

JavaScript does not automatically create copies (as classes imply) between objects.

Mixins模式用于模仿类复制行为,但是它通常导致丑陋和脆弱的句法。这难以理解也难以维护。

 

posted @ 2018-10-07 16:31  Mr-chen  阅读(100)  评论(0编辑  收藏  举报