ES.Next

ES.Next 术语指代新版本的JavaScript.

ES.Next 模块

ES.Next 模块允许将代码奉承多个文件,以提高维护性和可测试性。通过将逻辑封装成容易重用的代码,并且由于变量和珊瑚仅限于模块的作用于(scope),你可以在 不同模块内使用 相同的变量名 而不产生冲突。

命名导出和默认导出

Node.js 有两种导出类型:

  • 命名导出( named exports )

  • 默认导出( default exports )

使用命名导出被认为是最佳实践,因为命名导出为模块的功能定一个清晰且唯一的接口。而默认导出,则可能会以不同的名称导入相同的函数。不过,命名导出,重命名也是可选的,使用 as 语句完成。

根据 TypeScript 建议,如果一个模块有明确的目的和 一个导出 ,则使用默认导出。否则,建议在模块导出多个item时候使用命名导出。

以下是默认导出的案例(使用 default 关键字:

默认导出的例子 default.js
const getFoo = function () {
    return 'foo';
};

export default getFoo;

以下是命名导出的案例:

命名导出的例子 named.js
export const getFooBar = function () {
    return 'foo bar';
};

const getBar = function () {
    return 'bar';
};

const getBaz = function () {
    return 'baz';
};

export {getBar, getBaz};

这里的命名导出定义了匿名函数(anonymous function),存储常量 getFooBar ,然后立即导出;后面又定义了两个匿名函数,然后作为命名导出来导出。

备注

匿名函数: lambda表达式 参见 为什么匿名函数叫lambda 表达式?

无需定义标识符(函数名)的函数或子程序,称为匿名函数lambda。lambda函数没有名字,是一种简单的、在同一行定义函数的方法。

导入模块

导入 ES.next 模块的语法取决于创建的导出类型:

  • 命名导出的导入需要使用花括号 {} 导入

  • 默认导出的导入则不需要花括号导入

导入默认导出
import getFoo from "default.js";
导入命名导出
import {getFooBar, getBar, getBaz} from "named.js";

备注

在JavaScript中,花括号 {} 表示对象,中括号 [] 表示数组(array)

定义变量

JavaScript提供了三种不同的变量声明方法:

  • var

  • let

  • const

变量作用域:

  • 全局(global)

  • 模块(module)

  • 函数(function)

  • 块(block): 块作用域适用于任何用花括号 {} 括起来的代码块,是作用域最小单位

注意, 函数作用域 可以包含 多个 块作用域

提升变量(Hoisted Variables)

注意,在 JavaScript 中, var 顶底变量会自动提升到范围的顶部。也就是说,在Java和C语言中,不能在变量声明前使用变量;而在JavaScript中,解析器会自动将所有使用 var 关键字定义的变量声明移动到范围的顶部,这样在写代码的时候,就可以先写使用变量,后写变量声明,这和先声明再使用的实际效果一致。

在现代JavaScript中引入了 let 关键字来补充 var :

  • 使用 let 声明的 块作用域变量 ,并且必须先声明再使用( let 声明的变量被视为 非提升变量 )

  • 使用 let 声明的 全局变量 不会添加到 window对象 中( var 全局变量会自动称为window对象的成员,全局对象和全局函数也是这样)

现代JavaScript还引入了 const 关键字用于声明常量。和 let 一样, const 在全局声明是不会创建全局对象的属性,也被视为非提升类型(non-hoisted),也就是说, const 常量在声明之前无法访问。

JavaScript中的常量域其他语言不同,只是看起来不可变( look immutable )。实际上只是对其值的只读引用。对原始数据( primitive data )类型的变量标识符不能直接修改值,但是对于对象和数组则是非原始类型数据,则即使使用了 const 也可以通过方法或者直接属性访问来改变值。(也就是 const 对于数组和对象是不能限制其中元素的修改)

  • 以下程序片段演示 const 对原始数据变量和非原始数据变量(数组)的修改:

对原始数据变量和非原始数据变量使用 const 的区别
const primitiveDataType = 1;
try {
    primitiveDataType = 2;
} catch (err) {
    console.log(err);
}

const nonPrimitiveDataType = [];
nonPrimitiveDataType.push(1);

console.log(nonPrimitiveDataType);

箭头函数(Arrow Functions)

现代JavaScript引入了箭头函数(Arrow Functions)作为常规函数的替代方案。

简单来说 => 箭头左方是函数的参数,箭头右方是函数的表达式,也就是简单计算以后要返回一个值:

简单的匿名函数改成箭头函数的例子
const traditional = function (x) {
    return x * x;
}

const conciseBody = x => x * x;

箭头函数的两个概念:

  • 箭头函数语法简单,只需要几个字符和一行代码

  • 箭头函数使用了 词法作用域 ,更为直观不易出错

箭头函数有些类似匿名函数lambda,但是区别在于: 箭头函数内部的 this 是词法作用域,由上下文确定。

  • 箭头函数 this 总是指向词法作用域

箭头函数的 this 调用案例(廖雪峰 JavaScript教程)
let obj = {
    birth: 1990,
    getAge: function () {
        let b = this.birth; // 1990
        let fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(); // 25
  • 可以将传统的匿名函数逐步分解为最简单的箭头函数:

传统匿名函数如何逐步转为简单的匿名函数
// 传统匿名函数
(function (a) {
  return a + 100;
});

// 1. 移除“function”,并将箭头放置于参数和函数体起始大括号之间
(a) => {
  return a + 100;
};

// 2. 移除代表函数体的大括号和“return”——返回值是隐含的
(a) => a + 100;

// 3. 移除参数周围的括号
a => a + 100;

注意,只有当函数只有一个简单参数时,才能省略括号。如果函数由多个参数、无参数、默认参数、重组参数或其余参数,则需要在参数列表周围加上括号:

多参数箭头函数
// 传统匿名函数
(function (a, b) {
  return a + b + 100;
});

// 箭头函数
(a, b) => a + b + 100;

const a = 4;
const b = 2;

// 传统无参匿名函数
(function () {
  return a + b + 100;
});

// 无参箭头函数
() => a + b + 100;

词法作用域(Lexical Scope)

词法作用域案例
this.scope = "lexical scope";

const scopeOf = {
    scope: "define scope",

    traditional: function () {
        return this.scope;
    },

    arrow: () => {
        return this.scope;
    },
};

console.log(scopeOf.traditional());
console.log(scopeOf.arrow());

输出结果是:

词法作用域案例输出结果
define scope
lexical scope

参考