类与模块
一、类
一)类的声明
class Person { constructor(name) { this.name = name; } sayName() { console.log(this.name); } } let tom = new Person('Tom'); tom.sayName(); //Tom console.log(tom instanceof Person); //true console.log(tom instanceof Object); //true console.log(typeof Person); //function
自有属性:属性只出现在实例而不是在原型上,而且只能由构造函数和方法来创建。
差异:
1. 类声明和函数定义不同,它们是不会被提升的。类声明的行为和 let 比较相似,所以当执
行流作用到类声明之前类会存在于暂存性死区(temporal dead zone) 内。
2. 类声明中的代码自动运行在严格模式下,同时没有任何办法可以手动切换到非严格模
式。
3. 所有的方法都是不可枚举的(non-enumerable) ,这和自定义类型相比是个显著的差
异,因为后者需要使用 Object.defineProperty() 才能定义不可枚举的方法。
4. 所有的方法都不能使用 new 来调用,因为它们没有内部方法 [[Construct]]。
5. 不使用 new 来调用类构造函数会抛出错误。
6. 试图在方法内部重写类名的行为会抛出错误。
二)类表达式
设计类表达式的目的主要是为了将它赋值给变量或者传参给函数。
//不会被提升 let Person = class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } };
三)作为一等公民的类
在编程中,如果某些东西能作为值使用,那么它就被称为一等公民。ES6中类也是一等公民:
function createPerson(person) { return new person(); } let tom = createPerson(class { sayHi() { console.log('Hi'); } }); tom.sayHi(); //Hi
类表达式另一个有趣的使用方式是通过立即调用(immediately invoking) 类构造函数来创建单例(singleton):
let tom = new class { constructor(name) { this.name = name; } sayName() { console.log(`My name is ${this.name}`); } }('Tom'); tom.sayName();
四)访问器属性
class Person { constructor(name) { this.name = name; } //这里不能用get phone(){} get phone2() { return this.phone; } set phone2(phoneNumber) { this.phone = phoneNumber; } } let price = new Person('Price', 112); price.name = 'Price2'; price.phone = 119; console.log(`His name is ${price.name} and his phone number is ${price.phone}`);
五)静态成员
//静态成员不能被实例访问。你必须通过类本身来使用它们。 class Person { constructor(name) { this.name = name; } sayName() { console.log(this.name) } //等同于Person.whatIs(){} static whatIs() { return 'I am a person!'; } }
六)继承
class Worker extends Person { /*如果在派生类中定义 constructor 则必须使用super(), 否则会发生错误。如果你选择不使用 constructor, 那么会自动调用 super() 和传入构造函数的参数以创建类的实例*/ constructor(name, age) { super(name); this.age = age; } //注意 syaName() { super.sayName(); console.log(`My name is ${this.name}`); } sayAge (){ console.log(`I am ${this.age}`) } } let jimmy = new Worker('Jimmy', 2); jimmy.sayAge(); //I am 2 jimmy.syaName(); //Jimmy My name is Jimmy
注意: 使用 super() 需要牢记以下几点:
1. 你只能在派生类中使用 super(),否则(没有使用 extends 的类或函数) 一个错误会
被抛出。
2. 你必须在构造函数的起始位置调用 super(),因为它会初始化 this。任何在 super()
之前访问 this 的行为都会造成错误。
3. 在类构造函数中,唯一能避免调用 super() 的办法是返回一个对象。
ECMAScript 6 派生类最强大的地方在于它们可以继承一个表达式。只要该表达式的计算
结果包含 [[Construct]] 的函数和一个原型,那么就可以使用 extends 来继承它。
内置对象的继承
使用ES5很难继承内置对象,ES6做法:
class MyArray extends Array {
}
二、模块
在 ECMAScript 6 之前,每个 JavaScript 文件中定义的内容都由全局作用域共享。
当 web 应用变得复杂并需要书写更多的 JavaScript 代码时,上述加载方式会出现命名冲突或安全方面的问题。
模块是指采取不同于现有加载方式的 JavaScript 文件(与 script 这种传统的加载模式相
对) 。这种方式很有必要,因为它和 script 使用不同的语义:
1. 模块中的代码自动运行在严格模式下,并无任何办法修改为非严格模式。
2. 模块中的顶级(top level) 变量不会被添加到全局作用域中。它们只存在于各自的模块中
的顶级作用域。
3. 模块顶级作用域中的 this 为 undefined 。
4. 模块不允许存在 HTML 式的注释(JavaScript 历史悠久的遗留特性) 。
5. 模块必须输出可被模块外部代码使用的相关内容。
6. 模块可能会引入其它模块中的绑定。
模块真正的好处在于可以输出和引入需要的绑定,而不是文件中的所有内容。
理解输出和引入是领悟模块与 script 之间差异的基础。
一)导出(export)
其他暂略