介绍ECMAScript 6(简称 ES6)面向对象设计的知识与实践
ECMAScript 6(简称 ES6)是 JavaScript 语言的新标准版本,它的第一版本6.0,也称ECMAScript 2015(简称 ES2015)已经在 2015 年 6 月正式发布了。其目标为使得 JavaScript 语言可以用来编写复杂的大型应用程序,既可以作为前端开发语言,运行于桌面系统的浏览器环境,及移动终端App的WebView控件环境,或第三方平台的微信小程序环境等;又可以作为后端服务器开发语言,运行于Node.js服务器环境。使其成为跨平台与跨端(前后端,终端)的 企业级开发语言。
JavaScript起源于互联网领域初始阶段的领导者Netscape 公司,并运行于该公司的Netscape浏览器中。1996 年 11 月,该公司决定将 JavaScript 提交给标准化组织 ECMA(欧洲计算机制造商协会,European Computer Manufacturers Association),希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
ECMAScript 1.0 是 1997 年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。很多初学者一开始学习 JavaScript,其实就是在学 3.0 版的语法。
2007 年 10 月,ECMAScript 4.0 版草案发布,各方对于是否通过这个标准,发生了严重分歧,2008 年 7 月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为ECMAScript 3.1。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。2009 年 12 月,ECMAScript 5.0 版正式发布。
2011 年 6 月,ECMAScript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。ECMAScript 5.1为ES5
的最终版。
2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。
在 2015 年 6 月,ES6 的第一个版本发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015),标准委员会决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,只要用年份标记就可以了。
2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,对应年份的ES2017、ES2018、ES2019也特指该年发布的正式版本的语言标准。
ES6是一个泛指,含义是 ECMAScript 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 、ES2018、ES2019等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。
ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: 在代码块外访问a,表示a没有定义;
b // 1,在代码块外访问b,表示b为1;
上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。在这里表示,let声明的变量只在它所在的代码块有效。而var命令声明的变量,在全局范围内都有效。
for循环的循环变量,就很合适使用let命令:
for (let i = 0; i < 10; i++) {
// 循环体内代码;
}
console.log(i); // ReferenceError: i 没有定义
上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错。
用let
声明的变量是块作用域的, 它们仅存在于当前块中。使用var
声明的私有变量是函数作用域的,如先前介绍函数表达式时所见。
左括号“ {”和右括号“}”之间的范围是一个块。 如果您来自Java或C / C ++的背景,那么块作用域的概念将非常熟悉。 在这些语言中,程序员引入了块只是为了定义范围。 但是,在JavaScript中,由于没有与之关联的范围,因此有必要习惯性地引入块。 但是,ES6允许您使用let关键字创建块作用域变量。 如您在前面的示例中所看到的,在块内创建的变量a在该块内可用。 在声明块作用域变量时,通常建议在块的顶部添加let声明。
来看另一个示例,以清楚地区分函数和块作用域:
function swap(a,b){ // <--函数范围从这里开始
if(a>0 && b>0){ // <--块范围从这里开始
let tmp=a;
a=b;
b=tmp;
} // <--块作用域到此结束
console.log(a,b);
console.log(tmp); //未定义tmp,因为它仅在块作用域中可用
return [a,b];
}
swap(1,2);
如上所见,tmp
是用let声明的,并且仅在定义它的块中可用。 出于所有实践目的,应该最大限度地使用块作用域变量。 除非尝试执行某些非常具体的操作,否则必须使用var
声明,请确保使用块范围的变量。 但是,错误地使用let
关键字可能会导致很多问题。 首先,不能使用let关键字在同一函数或块范围内重新声明同一变量:
function blocker(x){
if(x){
let zcj;
let zcj; // 重复声明变量“zcj”
}
}
在ES6中,由let
关键字声明的变量被提升到块作用域。 不过,在声明变量之前引用该变量是错误的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
ES6中引入的另一个关键字是const
。 用const
关键字声明的变量创建对值的只读引用。 这并不意味着引用所保存的值是不可变的。 但是,变量标识符不能重新分配。 常量具有块范围作用域,就像使用let关键字创建的变量一样。 同样,必须在声明变量时为其分配一个值。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const car = {}
car.tyres = 4
在这里,将{}分配给一个常量car。 分配后,该引用将不再变化。 在ES6中,应该执行以下操作:
const
,用于所有其值不变的变量。函数是一种特殊的数据类型。 但是,事实证明,这还不止于此:函数实际上是对象。 有一个称为Function()
的内置引用类型的构造函数,它允许使用另一种(但不一定推荐)创建函数的方式。
以下示例显示了定义函数的三种方法:
function sum(a, b) { // 函数声明
return a + b;
}
sum(1, 2); // 3
var sum = function (a, b) { // 函数表达式
return a + b;
};
sum(1, 2) // 3
var sum = new Function('a', 'b', 'return a + b;');
sum(1, 2) // 3
使用Function()
构造函数时,首先传递参数名称(作为字符串),然后传递函数主体的源代码(再次作为字符串)。 JavaScript引擎需要评估这个传递的源代码并为此创建新函数
。 此源代码评估与eval()
函数具有相同的缺点,因此应尽可能避免使用Function()
构造函数定义函数。
在JavaScript中,传统函数扮演多个角色。 它们是非方法函数
(又名子例程或函数),方法
(对象的一部分)和构造函数
。 当函数执行
子程序的职责时,有一个动态this
的小问题。 由于未在对象上调用子程序,因此在严格模式下未定义其值,否则将其设置为全局范围。 这使得编写回调函数很困难。 考虑以下示例:
var greeter = {
default: "你好 ",
greet: function (names){
names.forEach(function(name) {
console.log(this.default + name); //无法读取未定义的“default”属性
})
}
}
console.log(greeter.greet(['世界', '天堂']));
以上代码正在将一个子例程传递给names数组上的forEach()函数。 该子例程具有一个this
的不确定值,并且不幸的是,它无法访问外部方法的此属性。 显然,此子例程需要一个词法,这是从greet方法的周围范围派生的。 传统上,为了解决此限制,为此将词法分配给一个变量,然后该子例程可通过闭包访问该变量。
前面的示例修订如下:
var greeter = {
default: "你好 ",
greet: function (names){
let that = this
names.forEach(function(name) {
console.log(that.default + name);
})
}
}
console.log(greeter.greet(['世界', '天堂']));
箭头函数的一个重要方面是它们的行为不同于通常函数, 差异是细微的但也是重要的,箭头函数没有它们自己的this值。 箭头函数中的this的值是从封闭的作用域继承的。
使用箭头函数来改变前面的示例以使用词法:
var greeter = {
default: "Hello ",
greet: function (names){
names.forEach(name=> {
console.log(this.default + name); //词法“this”可用于此子例程
})
}
}
console.log(greeter.greet(['世界', '天堂']));
ES6 允许使用“箭头”(=>
)定义函数。
var f = x => x;
// 等同于
var f = function (x) {
return x;
};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 8;
// 等同于
var f = function () { return 8 };
var sum = (parm1, parm2) => parm1 + parm2;
// 等同于
var sum = function(parm1, parm2) {
return parm1 + parm2;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return
语句返回。
var result = (parm1,parm2) => { return parm1 + parm2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上圆括号,否则会报错。
let student = id => ({ id: id, name: "张三" });
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let f = () => void doesNotReturn();
箭头函数有几个使用注意点:
this
对象,就是定义时所在的对象,而不是使用时所在的对象;new
命令,否则会抛出一个错误;arguments
对象,该对象在函数体内不存在。如果要用,可以用rest
参数代替;yield
命令,因此箭头函数不能用作Generator
函数。上述注意事项中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。
function fn() {
setTimeout(() => {
console.log('id:', this.id);
}, 1000);
}
var id = 21;
fn.call({ id: 42 }); //42
上面代码中,setTimeout()
的参数是一个箭头函数,这个箭头函数的定义生效是在fn函数生成时,而它的真正执行要等到 1000 毫秒后。如果是普通函数,执行时this
应该指向全局对象window
,这时应该输出21。但是,箭头函数导致this
总是指向函数定义生效时所在的对象({id: 42}
),所以输出的是42。
箭头函数可以让setTimeout()
里面的this
,绑定定义时所在的作用域,而不是指向运行时所在的作用域。
箭头函数可以让this
指向固定化,这种特性很有利于封装回调函数。this
指向的固定化,并不是因为箭头函数内部有绑定this
的机制,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。正是因为它没有this
,所以也就不能用作构造函数。
// ES6
function fn() {
setTimeout(() => {
console.log('id:', this.id);
}, 1000);
}
// ES5
function fn() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 1000);
}
上面代码中,清楚地说明了,箭头函数里面根本没有自己的this
,而是引用外层的this
。
JavaScript是一种基于原型的语言,并支持原型继承。 在HTML5 基础知识、第 4 部分中,讨论了对象的原型属性以及JavaScript中原型继承的工作方式。 ES6引入了类, 如果您来自传统的面向对象语言(例如Java),则将立即涉及到众所周知的类概念。 但是,它们在JavaScript中并不相同。 JavaScript中的类是在ES5
中讨论的原型继承的语法糖。
在这里介绍的ES6类和模块
,使面向对象编程(OOP)和继承显得更容易。如果您来自传统的面向对象编程语言的学习,那么原型继承可能对您来说有点不合适。 ES6类
为您提供了更传统的语法,以使您熟悉JavaScript中的原型继承。
在尝试深入研究类之前,展示一下为什么应该在ES5
的原型继承
语法上使用ES6
中的类
语法。在以下代码段中,将创建一组简单的Person、Student和PartTimeStudent的类层次结构。 首先,将看到ES5原型继承,其编写方式如下:
清单 1. 人、学生、兼职学生相应构造函数Person、Student、PartTimeStudent及ES5
原型继承
var Person = function(name) {
if(!(this instanceof Person)) {
throw new Error("Person 是一个构造函数");
}
this.name = name;
};
Person.prototype.giveBirth = function() {
// ...给出person的出生日期
};
var Student = function(name, homework) {
if(!(this instanceof Student)) {
throw new Error("Student 是一个构造函数");
}
Person.call(this, name);
this.homework = homework;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.doHomework = function() {
// ...Student 做作业
};
var PartTimeStudent = function(name, homework, department) {
if(!(this instanceof PartTimeStudent)) {
throw new Error("PartTimeStudent 是一个构造函数");
}
Student.call(this, name, homework);
this.department = department;
};
PartTimeStudent.prototype = Object.create(Student.prototype);
PartTimeStudent.prototype.constructor = PartTimeStudent;
PartTimeStudent.prototype.startWorking = function() {
// ...PartTimeStudent 开始工作
};
现在,来看一下使用ES6类语法的等效代码:
清单2. 人、学生、兼职学生的类层次结构,使用ES6编写代码
class Person {
constructor(name) {
this.name = name;
}
giveBirth() {
// ... 给出person的出生日期
}
}
class Student extends Person {
constructor(name, homework) {
super(name);
this.homework = homework;
}
doHomework() {
// ...Student 做作业
}
}
class PartTimeStudent extends Student {
constructor(name, homework, department) {
super(name, homework);
this.department = department;
}
startWorking() {
// ...PartTimeStudent 开始工作
}
}
如果您观察前面的两个清单的代码段,那么,显然第二个清单示例非常简洁。 如果您已知Java程序设计语言,就会感到宾至如归。 但是,要记住的重要一件事是,类没有向语言引入任何新的面向对象的继承模型,而是带来了一种创建对象和处理继承的更好的方法。
ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
在内部,类是特殊函数。 就像可以使用函数表达式和声明来定义函数一样,也可以定义类。 定义类的一种方法是使用类声明。
可以使用class
关键字和类的名称。 此语法与Java的语法非常相似。
class Car {
constructor(model, year) {
this.model = model;
this.year = year;
}
}
console.log(typeof Car); //"function"
为了确定类是特殊函数的事实,如果在控制台日志中记录获得Car
类的类型
,则将得到一个函数类型。
类和常规函数之间有重要区别。 常规函数具有提升功能,而类却没有。 当在作用域的范围内声明了常规函数时,该函数立即可用,意思是在执行代码之前会先读取函数声明。 这个称为提升,意味着可以在范围内的任何地方声明一个常规函数,并且该函数将可用。 但是,类不具有提升作用, 它们只有在声明后才可用。
常规函数与类的声明与调用情况如下:
normalFunction(); // 先使用,函数调用
function normalFunction() {} // 后声明,声明函数
var ford = new Car(); // 类实例化引用错误,不能在声明之前使用该类
class Car {} // 声明类
定义类的另一种方法是使用类表达式
。 类表达式(如函数表达式)可能具有名称,也可能没有名称。
以下示例显示了一个匿名类表达式:
const Car = class {
constructor(model, year){
this.model = model;
this.year = year;
}
}
如果为类表达式命名,则该名称是该类主体的局部名称,在外部不可用:
const NamedCar = class Car{
constructor(model, year){
this.model = model;
this.year = year;
}
getName() {
return Car.name;
}
}
const ford = new NamedCar();
console.log(ford.getName()); // Car
console.log(ford.name); // ReferenceError: name 未定义
如上所见,在这里,将为类命名为Car。 该名称在类的主体内可用,但是当我们尝试在类外部访问它时,会出现引用错误。
分隔类的成员时不能使用逗号。 分号是有效的。 这很有趣,因为ES6忽略分号,并且有关在ES6中使用分号的争论也很激烈。 考虑以下代码片段作为示例:
class NoCommas {
method1(){}
member1; // 这将被忽略,可用于分隔类成员
member2, // 这是一个错误
method2(){}
}
定义后,可以通过新关键字而不是函数调用来使用类; 如下所示:
class Car {
constructor(model, year){
this.model = model;
this.year = year;
}
}
const fiesta = new Car('嘉年华','2020');
到目前为止,已经在示例中使用了constructor
函数。 constructor
是一种特殊方法,称为构造方法,用于创建和初始化使用该类创建的对象。 一个类中只能有一个构造方法。 构造方法与普通的构造函数不同。类构造方法可以通过super()
调用其父类构造方法。 当后续研究继承时,将对此进行进行讨论。
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
上面代码中,定义了一个空的类Point
,JavaScript 引擎会自动为它添加一个空的constructor
方法。
constructor
方法默认返回实例对象(即this
)。类必须使用new调用,否则会报错。这是它跟ES5
普通构造函数的一个主要区别,后者不用new也可以执行。
class Circle {
constructor(r) {
this.r = r
}
}
Circle(5)
// TypeError: Class constructor Circle cannot be invoked without 'new'
原型方法是定义在该类的prototype
属性上,它们被该类的实例继承。
原型方法也可以具有getter和setter方法。 getter和setter的语法与ES5相同:
class Car {
constructor(model, year){
this.model = model;
this.year = year;
}
get model(){
return this.model;
}
set model(val){
this.model = val;
}
calculateCurrentValue(){
return "7000"
} }
}
const fiesta = new Car('嘉年华','2010');
console.log(fiesta.model);
构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this
对象上),否则都是定义在原型上(即定义在class
上)。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(5, 6);
point.toString() // (5, 6)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
上面代码中,x
和y
都是实例对象point
自身的属性(因为定义在this
变量上),所以hasOwnProperty
方法返回true
,而toString
是原型对象的属性(因为定义在Point
类上),所以hasOwnProperty
方法返回false
。
静态方法与该类相关联,而不与该类(对象)的实例相关联。 换句话说,您只能使用类的名称来达到静态方法。 静态方法是在不实例化类的情况下调用的,并且不能在类的实例上调用它们。 静态方法在创建实用程序或辅助方法中很流行。 考虑以下代码:
class Logger {
static log(level, message) {
console.log(`${level} : ${message}`)
}
}
//在类上调用静态方法
Logger.log("ERROR","The end is near") // "ERROR : The end is near"
//不在实例上
const logger = new Logger("ERROR")
logger.log("The end is near") // logger.log 不是一个函数
可以将生成器函数添加为类的一部分,它们称为生成器方法。 生成器方法很有用,因为您可以将其键定义为Symbol.iterator。 以下示例显示如何在类内部定义生成器方法:
class iterableArg {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (const arg of this.args) {
yield arg;
}
}
}
for (const x of new iterableArg('ES6', 'wins')) {
console.log(x);
}
到目前为止,讨论了如何声明类以及可以支持的成员类型。 类的主要用途是用作创建其他子类的模板。 从类创建子类时,您将派生父类的属性并通过添加其自身的更多功能来扩展父类。
继承示例:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' 通常叫声');
}
}
class Cat extends Animal {
speak() {
console.log(this.name + ' 叫 喵喵.');
}
}
var c = new Cat('小花');
c.speak(); //"小花 叫 喵喵."
在此,Animal是基类,而Cat类是从Animal类派生的。 extend子句允许您创建现有类的子类。 本示例演示子类化的语法。 让我们通过编写以下代码来进一步增强此示例:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' 通常叫声');
}
}
class Cat extends Animal {
speak() {
console.log(this.name + ' 叫 喵喵.');
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' 咆哮....');
}
}
var l = new Lion('莱尼');
l.speak();
// "莱尼 叫 喵喵."
// "莱尼 咆哮...."
在这里,使用super关键字从父类中调用函数。 以下是可以使用super关键字的三种方式:
在派生类构造函数中,必须调用super()方法。 例如,以下代码将失败:
class Base {}
class Derive extends Base {
constructor(name){
this.name = name; // 在super()调用之前,不允许使用“ this”
}
}
您不能在默认的构造函数中隐藏super()方法,否则出现错误:
class Base {}
class Derive extends Base {
constructor(){ //constructor中缺少super()调用
}
}
treyt如果不为基类提供构造函数,则默认使用以下构造函数:
constructor() {}
对于派生类,默认构造函数如下:
constructor(...args){
super(...args);
}
JavaScript仅支持单一继承。 一个类最多只能有一个超类。 当您要创建类层次结构但又要从不同来源继承工具方法时,这是有限制的。
假设有一个场景,其中有一个Person
类,再创建了一个子类Employee
:
class Person {}
class Employee extends Person{}
还希望从两个实用程序类中继承函数,BackgroundCheck
类负责员工的背景检查,而Onboard
类处理员工的入职流程,例如打印徽章等:
class BackgroundCheck {
check() {}
}
class Onboard {
printBadge() { }
}
BackgroundCheck和Onboard类都是模板,它们的功能将被多次使用。 这样的模板(抽象子类)被称为混入(mixins
)。
由于JavaScript无法实现多重继承,采用另一种技术来实现这一点。 在ES6中实现混入(mixins
)的一种流行方法是编写一个函数,该函数将超类作为输入,并将扩展该超类的子类作为输出,例如:
class Person {}
const BackgroundCheck = Tools => class extends Tools {
check() {}
};
const Onboard = Tools => class extends Tools {
printBadge() {}
};
class Employee extends BackgroundCheck(Onboard(Person)){
}
从本质上讲,这意味着Employee
是BackgroundCheck
的子类,而BackgroundCheck
则是Onboard
的子类,进一步Onboard
则是Person
的子类。
JavaScript模块不是新的内容。 实际上,已有一段时间的库支持模块了。 不过,ES6提供了内置模块。 传统上,JavaScript的主要用途是 在浏览器中,大多数JavaScript代码都是嵌入式的,或者足够小,可以轻松管理。 现在事情变了,JavaScript项目规模越来越大。如果没有有效的将代码分布到文件和目录中的系统,代码管理将成为一场噩梦。
ES6模块是文件。 每个文件表示一个模块。 没有module关键字。 除非使用导出,否则您在模块文件中写入的任何代码都是模块本地的。 可能在一个模块中有一堆函数,并且只想导出其中一些。 可以通过两种方式导出模块功能。
首先是使用export关键字, 可以导出任何顶级声明的function, class, var, let, 或 const。
以下示例显示了server.js
中的模块,在其中导出函数、类和常量。 不导出processConfig()
函数,而且在任何导入此模块的文件中将无法访问未导出的函数:
//----------------server.js---------------------
export const port = 8080;
export function startServer() {
//...启动服务器
}
export class Config {
//...
}
function processConfig() {
//...
}
任何有权访问server.js的代码都可以导入已导出的功能。
//--------------app.js----------------------------
import {Config, startServer, port} from 'server'
startServer(port);
在这种情况下,另一个JavaScript文件正在从服务器模块导入Config、startServer和port(带有相应响应的JavaScript文件server.js
,将文件扩展名删除)
在了解ES6模块系统的同时,也需要了解ES5如何通过外部库支持它们。 ES5具有两个不兼容的模块系统,如下所示:
ES6的模块的目标是使来自任何这些系统的模块更易于使用。 ES6模块语法也已标准化,并且比其他替代方法更紧凑。
导出列表
为了替换从模块中使用export
关键字标明每个要导出的函数或类,可以只用export
关键字标明单个的列表,其中包含所有需要从模块中导出的事项。
export {port, startServer, Config};
const port = 8080;
function startServer() {
//...启动服务器
}
class Config {
//...
}
function processConfig() {
//...
}
模块的第一行是导出列表。 可以在模块文件中包含多个导出列表,并且该列表可以出现在文件中的任何位置。 也可以在同一模块文件中混合使用导出列表和导出声明,但是只能导出一个名称一次。
在大型项目中,有时会遇到名称冲突。 假设您导入了两个模块,并且它们都导出了具有相同名称的函数。 在这种情况下,可以按以下方式重命名导入:
import {trunc as StringLib} from "../lib/string.js"
import {trunc as MathLib} from "../lib/math.js"
在这里,两个导入的模块都导出了名称trunc,因此造成了名称冲突。 我们可以使用别名来解决此冲突。
实践:
创建一个Web项目,文件结构如下:
MPro
js
app.js
server.js
index.html
index.html文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>模块举例</title>
<script src="js/app.js" type="module"></script>
</head>
<body>
</body>
</html>
app.js文件:
import {Config, startServer, port} from '../js/server.js'
startServer(port);
let obj = new Config()
obj.conf()
server.js文件:
export {port, startServer, Config};
const port = 8080;
function startServer(arg) {
console.log('启动服务器'+arg)
}
class Config {
conf(){
console.log('配置')
processConfig()
}
}
function processConfig() {
console.log('处理配置')
}
在浏览器加载index.html,控制台console显示:
启动服务器8080 # server.js:5
配置 # server.js:10
处理配置 # server.js:16
博文最后更新时间: