ES6中的类

1、Class

类是一种可选(而不是必须)的设计模式,而且在 JavaScript 这样的 \[Prototype] 语言中实现类是很别扭的。大致解决了以下问题:

  1. 不再引用杂乱的 .prototype 了
  2. Button 声 明 时 直 接" 继 承 " 了 Widget, 不 再 需 要 通 过 Object.create(...) 来 替换 .prototype 对象,也不需要设置 .proto 或者 Object.setPrototypeOf(...)。
  3. 可以通过 super(...) 来实现相对多态,这样任何方法都可以引用原型链上层的同名方法。这可以解决第 4 章提到过的那个问题:构造函数不属于类,所以无法互相引用------super() 可以完美解决构造函数的问题。
  4. class 字面语法不能声明属性(只能声明方法)。看起来这是一种限制,但是它会排除掉许多不好的情况,如果没有这种限制的话,原型链末端的"实例"可能会意外地获取其他地方的属性(这些属性隐式被所有"实例"所"共享")。所以,class 语法实际上可以帮助你避免犯错。
  5. 可以通过 extends 很自然地扩展对象(子)类型,甚至是内置的对象(子)类型,比如Array 或 RegExp。没有 class ...extends 语法时,想实现这一点是非常困难的,基本上只有框架的作者才能搞清楚这一点。但是现在可以轻而易举地做到!

2、Class陷阱

class 语法并没有解决所有的问题,在 JavaScript 中使用"类"设计模式仍然存在许多深层问题。

你可能会认为 ES6 的 class 语法是向 JavaScript 中引入了一种新的"类"机制,其实不是这样。class 基本上只是现有 \[Prototype](委托!)机制的一种语法糖。

也就是说,class 并不会像传统面向类的语言一样在声明时静态复制所有行为。如果你(有意或无意)修改或者替换了父"类"中的一个方法,那子"类"和所有实例都会受到影响,因为它们在定义时并没有进行复制,只是使用基于 \[Prototype] 的实时委托。

js 复制代码
class C {
	constructor() {
		this.num = Math.random();
	}
    rand() {
        console.log( "Random: " + this.num );
    }
}
var c1 = new C();
c1.rand(); // "Random: 0.4324299..."
C.prototype.rand = function() {
	console.log( "Random: " + Math.round( this.num * 1000 ));
};
var c2 = new C();
c2.rand(); // "Random: 867"
c1.rand(); // "Random: 432" ------噢!

但是他依然存在一些问题:

  1. class 语法无法定义类成员属性(只能定义方法),如果为了跟踪实例之间共享状态必须要这么做,那你只能使用 .prototype 语法,但是这种方法最大的问题是,它违背了class语法的本意,在实现中暴露(泄露了.prototype
  2. class 语法仍然面临意外屏蔽的问题
  3. super 并不是动态绑定的,它会在声明时"静态"绑定。所以super可能不会绑定到合适的对象,需要使用toMethod(...)来手动绑定super(类似于bind(...))
js 复制代码
// 意外屏蔽问题
class C {
    constructor(id) {
        // 噢,郁闷,我们的 id 属性屏蔽了 id() 方法
        this.id = id;
    }
    id() {
    	console.log( "Id: " + id );
    }
}
var c1 = new C( "c1" );
c1.id(); // TypeError -- c1.id 现在是字符串 "c1"
js 复制代码
// 动态绑定问题
class P {
	foo() { console.log( "P.foo" ); }
}
class C extends P {
	foo() {
		super();
	}
}
var c1 = new C();
c1.foo(); // "P.foo"
var D = {
	foo: function() { console.log( "D.foo" ); }
};
var E = {
	foo: C.prototype.foo
};
// 把 E 委托到 D
Object.setPrototypeOf( E, D );
E.foo(); // "P.foo"

3、静态大于动态吗

通过上面的这些特性可以看出,ES6 的 class 最大的问题在于,(像传统的类一样)它的语法有时会让你认为,定义了一个 class 后,它就变成了一个(未来会被实例化的)东西的静态定义。你会彻底忽略 C 是一个对象,是一个具体的可以直接交互的东西。但是 JavaScript 最强大的特性之一就是它的动态性,任何对象的定义都可以修改(除非你把它设置成不可变)

4、总结

class 很好地伪装成 JavaScript 中类和继承设计模式的解决方案,但是它实际上起到了反作用:它隐藏了许多问题并且带来了更多更细小但是危险的问题。

class 加深了过去 20 年中对于 JavaScript 中"类"的误解,在某些方面,它产生的问题比解决的多,而且让本来优雅简洁的 \[Prototype] 机制变得非常别扭。

相关推荐
AI_零食1 小时前
鸿蒙PC Electron跨平台应用开发:24时区时间表应用详解
前端·华为·electron·开源·harmonyos·鸿蒙
Electrolux2 小时前
[onlyoffice-v9]纯前端怎么实现编辑预览office
前端·javascript·github
码云之上2 小时前
聊聊如何设计一个高效、稳定的 Node.js 接入层
前端·后端·node.js
kyriewen2 小时前
我读了一遍 Babel 编译后的 async/await,终于搞懂了它的原理(附 20 行手写实现)
前端·javascript·面试
IT_陈寒3 小时前
Vite项目build后路由404了?你可能漏了这个小配置
前端·人工智能·后端
lichenyang4533 小时前
AI 聊天从纯文本到结构化卡片:SSE done 帧携带 card + 历史记录卡片恢复实战
前端
梦曦i3 小时前
@meng-xi/vite-plugin v0.1.5:告别手动 import,精简工具层
前端
梦曦i4 小时前
Vite 0.1.6重磅更新:智能导入+路由安全
前端
gxf5203088069884 小时前
Flutter 裁剪图片
前端·app
ITMan彪叔4 小时前
赋能UE运行态编辑平台: 网络图片下载的插件改造与复盘
前端