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.
- Vehicle.ignition增加了一个属性,同步Car.ignition也会增加这个属性。本质上是复制了一个引用!它们是共享相同的函数!!!
- 另外如果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模式用于模仿类复制行为,但是它通常导致丑陋和脆弱的句法。这难以理解也难以维护。