老生常谈ES6中的类(2)

这段代码中有两处personType2声明:一处是外部作用域中的let声明,一处是立即执行函数表达式(IIFE)中的const声明,这也从侧面说明了为什么可以在外部修改类名而内部却不可修改。在构造函数中,先检查new.target是否通过new调用,如果不是则抛出错误;紧接着,将sayName()方法定义为不可枚举,并再次检查new.target是否通过new调用,如果是则抛出错误;最后,返回这个构造函数

尽管可以在不使用new语法的前提下实现类的所有功能,但如此一来,代码变得极为复杂

【常量类名】

类的名称只在类中为常童,所以尽管不能在类的方法中修改类名,但可以在外部修改

class Foo { constructor() { Foo = "bar"; // 执行时抛出错误 } } // 但在类声明之后没问题 Foo = "baz";

以上代码中,类的外部有一个Foo声明,而类构造函数里的Foo则是一个独立存在的绑定。内部的Foo就像是通过const声明的,修改它的值会导致程序抛出错误;而外部的Foo就像是通过let声明的,可以随时修改这个绑定值

类表达式

类和函数都有两种存在形式:声明形式和表达式形式。声明形式的函数和类都由相应的关键字(分别为function和class)进行定义,随后紧跟一个标识符;表达式形式的函数和类与之类似,只是不需要在关键字后添加标识符

类表达式的设计初衷是为了声明相应变量或传入函数作为参数

【基本的类表达式语法】

下面这段代码等价于之前PersonClass示例的类表达式

let PersonClass = class { // 等价于 PersonType 构造器 constructor(name) { this.name = name; } // 等价于 PersonType.prototype.sayName sayName() { console.log(this.name); } }; let person = new PersonClass("huochai"); person.sayName(); // 输出 "huochai" console.log(person instanceof PersonClass); // true console.log(person instanceof Object); // true console.log(typeof PersonClass); // "function" console.log(typeof PersonClass.prototype.sayName); // "function"

类声明和类表达式仅在代码编写方式略有差异,二者均不会像函数声明和函数表达式一样被提升,所以在运行时状态下无论选择哪一种方式,代码最终的执行结果都没有太大差别

二者最重要的区别是name属性不同,匿名类表达式的name属性值是一个空字符串,而类声明的name属性值为类名,例如,通过声明方式定义一个类PersonClass,则PersonClass.name的值为"PersonClass"

【命名类表达式】

类与函数一样,都可以定义为命名表达式。声明时,在关键字class后添加一个标识符即可

let PersonClass = class PersonClass2 { // 等价于 PersonType 构造器 constructor(name) { this.name = name; } // 等价于 PersonType.prototype.sayName sayName() { console.log(this.name); } }; console.log(typeof PersonClass); // "function" console.log(typeof PersonClass2); // "undefined"

上面的示例中,类表达式被命名为PersonClass2,由于标识符PersonClass2只存在于类定义中,因此它可被用在像sayName()这样的方法中。而在类的外部,由于不存在一个名为PersonClass2的绑定,因而typeof PersonClass2的值为"undefined"

// 直接等价于 PersonClass 具名的类表达式 let PersonClass = (function() { "use strict"; const PersonClass2 = function(name) { // 确认函数被调用时使用了 new if (typeof new.target === "undefined") { throw new Error("Constructor must be called with new."); } this.name = name; } Object.defineProperty(PersonClass2.prototype, "sayName", { value: function() { // 确认函数被调用时没有使用 new if (typeof new.target !== "undefined") { throw new Error("Method cannot be called with new."); } console.log(this.name); }, enumerable: false, writable: true, configurable: true }); return PersonClass2; }());

在JS引擎中,类表达式的实现与类声明稍有不同。对于类声明来说,通过let定义的外部绑定与通过const定义的内部绑定具有相同名称;而命名类表达式通过const定义名称,从而PersonClass2只能在类的内部使用

尽管命名类表达式与命名函数表达式有不同的表现,但二者间仍有许多相似之处,都可以在多个场景中作为值使用

一等公民

在程序中,一等公民是指一个可以传入函数,可以从函数返回,并且可以赋值给变量的值。JS函数是一等公民(也被称作头等函数),这也正是JS中的一个独特之处

ES6延续了这个传统,将类也设计为一等公民,允许通过多种方式使用类的特性。例如,可以将类作为参数传入函数中

function createObject(classDef) { return new classDef(); } let obj = createObject(class { sayHi() { console.log("Hi!"); } }); obj.sayHi(); // "Hi!"

在这个示例中,调用createObject()函数时传入一个匿名类表达式作为参数,然后通过关键字new实例化这个类并返回实例,将其储存在变量obj中

类表达式还有另一种使用方式,通过立即调用类构造函数可以创建单例。用new调用类表达式,紧接着通过一对小括号调用这个表达式

let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }("huochai"); person.sayName(); // "huochai"

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyjypx.html