ECMAScript6 从入门到入坟,你敢来挑战吗???(六)

简介: ECMAScript6 从入门到入坟,你敢来挑战吗???(六)

Node.js的模块加载方法


JavaScript 现在是两种模块,一种是ES6模块,简称ESM,另一种是CommonJS模块,简称CJS。

Node.js 要求ES6模块采用 .mjs 后缀名,也就是说,只要脚本文件里面使用import 或者 export 命令,
那么就必需采用.mjs后缀名。Node.js遇到 .mjs 文件,就认为它是ES6模块,默认启用严格模式,不必再
每个模块文件顶部指定 use strict

如果不希望将后缀名改变.mjs,可以在项目的 package.json 文件中,指定 type 字段为 module

{
       "type": "module"
    }

一旦设置之后,改目录里面的JS脚本,就被解释用ES6模块。

   # 解释成 ES6 模块
   $ node my-app.js

如果这是要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成 .cjs,如果没有 type 字段,
或者 type 字段为 commonjs,则 .js 脚本会被解释成 CommonJS 模块。

总结:

.mjs 文件总是以 ES6 模块加载

.cjs 文件总是以 CommonJS 模块加载

.js 文件的加载取决于 package.json 里面的 type 字段的设置

注意:

ES6 模块与 CommonJS 模块尽量不要混用

require 命令不能加载 .mjs 文件会报错,只有 import 命令才可以加载 .mjs 文件

.mjs 文件里面也不能使用 require 命令,必须使用 import

package.json 的 main 字段

package.json 文件有两个字段可以指定模块的入口文件,mainexports

// ./node_modules/es-module-package/package.json
    {
      "type": "module",
      "main": "./src/index.js"
    }

  // 它的格式为 ES6 模块。如果没有type字段,index.js就会被解释为 CommonJS 模块。

package.json 的 exports 字段

exports 字段的优先级高于 main 字段。

子目录别名

package.json 文件的 字段可以指定脚本或子目录的别名。

// ./node_modules/es-module-package/package.json
    {
      "exports": {
        "./features/": "./src/features/"
      }
    }
    import feature from 'es-module-package/features/x.js';
    // 加载 ./node_modules/es-module-package/src/features/x.js

main 的别名

exports 字段的别名如果是.,就代表模块的主入口,优先级高于 main 字段,并且可以
直接简写成 exports 字段的值。

{
      "exports": {
        ".": "./main.js"
      }
    }
    // 等同于
    {
      "exports": "./main.js"
    }
    // 由于exports字段只有支持 ES6 的 Node.js 才认识,所以可以用来兼容旧版本的 Node.js。
    {
      "main": "./main-legacy.cjs",
      "exports": {
        ".": "./main-modern.cjs"
      }
    }

条件加载

利用.这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。目前,这个功能需要在 Node.js

运行的时候,打开 --experimental-conditional-exports 标志。

加载路径

ES6 模块的加载路径必须给出脚本的完整路径,不能省略脚本的后缀名,import 命令和 package.json
文件的
main 字段如果省略脚本的后缀名,会报错。

// ES6 模块中将报错
    import { something } from './index';

为了与浏览器的import加载规则相同,Node.js 的.mjs文件支持 URL 路径。

   import'./foo.mjs?query=1';// 加载 ./foo 传入参数 ?query=1

上面代码中,脚本路径带有参数?query=1,Node 会按 URL 规则解读。同一个脚本只要参数不同,就会被加载多次,

并且保存成不同的缓存。由于这个原因,只要文件名中含有: 、%、#、? 等特殊字符,最好对这些字符进行转义。

目前,Node.js 的import命令只支持加载本地模块(file:协议)data: 协议,不支持加载远程模块。


内部变量

首先,this 关键字。ES6模块之中,顶层的 this 指向 undefined, CommonJS 模块的顶层 this 指向
当前的模块,这是两者的一个重大差异。

CommonJS 模块的加载原理

CommonJS的一个模块,就是一个脚本文件,require 命令第一次加载该脚本,就会执行整个脚本,然后在内存
中生成一个对象。

{
      id: '...',
      exports: { ... },
      loaded: true,
      ...
    }

上面代码就是 Node 内部加载模块后生成的一个对象。该对象的id属性是模块名,exports 属性是模块输出的各个
接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。

以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,
而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,
就返回第一次运行的结果,除非手动清除系统缓存。

1.编程风格

块级作用域

(1)let 取代 var

ES6 提出了两个新的声明变量的命令:letconst。其中, let 完全可以取代var,因为两者语义相同,
而且
let 没有副作用。

var命令存在变量提升效用,let命令没有这个问题。

(2)全局常量和线程安全

letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。

const 优于 let 的几个原因:

一个是const可以提醒阅读程序的人,这个变量不应该改变;

另一个是const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;

最后一个原因是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,
也就是说
letconst的本质区别,其实是编译器内部的处理不同

阅读代码的人立刻会意识到不应该修改这个值

防止了无意间修改变量值所导致的错误

字符串

静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。

// bad
    const a = "foobar";
    const b = 'foo' + a + 'bar';
    // acceptable
    const c = `foobar`;
    // good
    const a = 'foobar';
    const b = `foo${a}bar`;

解构赋值

对象

单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。

// bad
    const a = { k1: v1, k2: v2, };
    const b = {
      k1: v1,
      k2: v2
    };
    // good
    const a = { k1: v1, k2: v2 };
    const b = {
      k1: v1,
      k2: v2,
    };

对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用 Object.assign 方法。

// bad
    const a = {};
    a.x = 3;
    // if reshape unavoidable
    const a = {};
    Object.assign(a, { x: 3 });
    // good
    const a = { x: null };
    a.x = 3;

如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。

// bad
    const obj = {
      id: 5,
      name: 'San Francisco',
    };
    obj[getKey('enabled')] = true;
    // good
    const obj = {
      id: 5,
      name: 'San Francisco',
      [getKey('enabled')]: true,
    };

另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。

var ref = 'some value';
    // bad
    const atom = {
      ref: ref,
      value: 1,
      addValue: function (value) {
        return atom.value + value;
      },
    };
    // good
    const atom = {
      ref,
      value: 1,
      addValue(value) {
        return atom.value + value;
      },
    };

数组

使用扩展运算符 (...) 拷贝数组。

// bad
    const len = items.length;
    const itemsCopy = [];
    let i;
    for (i = 0; i << span=""> len; i++) {
      itemsCopy[i] = items[i];
    }
    // good
    const itemsCopy = [...items];

使用 Array.from 方法,将类似数组的对象转为数组。

const foo = document.querySelectorAll('.foo');
    const nodes = Array.from(foo);

函数

立即执行函数可以写成箭头函数的形式。

(() => {
      console.log('Welcome to the Internet.');
    })();

那些使用匿名函数当作参数的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this

// bad
    [1, 2, 3].map(function (x) {
      return x * x;
    });
    // good
    [1, 2, 3].map((x) => {
      return x * x;
    });
    // best
    [1, 2, 3].map(x => x * x);

箭头函数取代Function.prototype.bind,不应再用 self/_this/that 绑定 this

// bad
    const self = this;
    const boundMethod = function(...params) {
      return method.apply(self, params);
    }
    // acceptable
    const boundMethod = method.bind(this);
    // best
    const boundMethod = (...params) => method.apply(this, params);

简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。


所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。

// bad
    function divide(a, b, option = false ) {
    }
    // good
    function divide(a, b, { option = false } = {}) {
    }

不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替。因为 rest 运算符显式表明
你想要获取参数,而且 arguments 是一个类似数组的对象,而 rest 运算符可以提供一个真正的数组。

// bad
    function concatenateAll() {
      const args = Array.prototype.slice.call(arguments);
      return args.join('');
    }
    // good
    function concatenateAll(...args) {
      return args.join('');
    }
•使用默认值语法设置函数参数的默认值。
    // bad
    function handleThings(opts) {
      opts = opts || {};
    }
    // good
    function handleThings(opts = {}) {
      // ...
    }

Map结构

注意区分 ObjectMap,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要key: value
的数据结构,使用
Map 结构。因为 Map 有内建的遍历机制。

let map = new Map(arr);
    for (let key of map.keys()) {
      console.log(key);
    }
    for (let value of map.values()) {
      console.log(value);
    }
    for (let item of map.entries()) {
      console.log(item[0], item[1]);
    }

Class

总是用 Class,取代需要 prototype 的操作。因为 Class 的写法更简洁,更易于理解。

// bad
    function Queue(contents = []) {
      this._queue = [...contents];
    }
    Queue.prototype.pop = function() {
      const value = this._queue[0];
      this._queue.splice(0, 1);
      return value;
    }
    // good
    class Queue {
      constructor(contents = []) {
        this._queue = [...contents];
      }
      pop() {
        const value = this._queue[0];
        this._queue.splice(0, 1);
        return value;
      }
    }

使用extends实现继承,因为这样更简单,不会有破坏instanceof运算的危险。

// bad
    const inherits = require('inherits');
    function PeekableQueue(contents) {
      Queue.apply(this, contents);
    }
    inherits(PeekableQueue, Queue);
    PeekableQueue.prototype.peek = function() {
      return this._queue[0];
    }
    // good
    class PeekableQueue extends Queue {
      peek() {
        return this._queue[0];
      }
    }

模块

ES6 模块语法是 JavaScript 模块的标准写法,坚持使用这种写法,取代 Node.js 的 CommonJS 语法。

首先,使用import取代require()

// CommonJS 的写法
    const moduleA = require('moduleA');
    const func1 = moduleA.func1;
    const func2 = moduleA.func2;
    // ES6 的写法
    import { func1, func2 } from 'moduleA';

其次,使用export取代module.exports

// commonJS 的写法
    var React = require('react');
    var Breadcrumbs = React.createClass({
      render() {
        return << span="">nav />;
      }
    });
    module.exports = Breadcrumbs;
    // ES6 的写法
    import React from 'react';
    class Breadcrumbs extends React.Component {
      render() {
        return << span="">nav />;
      }
    };
    export default Breadcrumbs;

如果模块只有一个输出值,就使用export default,如果模块有多个输出值,除非其中某个输出值特别重要,
否则建议不要使用
export default,即多个输出值如果是平等关系,export default与普通的export就不要同时
使用。

如果模块默认输出一个函数,函数名的首字母应该小写,表示这是一个工具方法。

function makeStyleGuide() {
    }
    export default makeStyleGuide;

如果模块默认输出一个对象,对象名的首字母应该大写,表示这是一个配置值对象

const StyleGuide = {
      es6: {
      }
    };
    export default StyleGuide;

ESLint的使用

ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。

// 首先,在项目的根目录安装 ESLint。
    $ npm install --save-dev eslint
    // 然后,安装 Airbnb 语法规则,以及 import、a11y、react 插件。
    $ npm install --save-dev eslint-config-airbnb
    $ npm install --save-dev eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
    // 最后,在项目的根目录下新建一个.eslintrc文件,配置 ESLint。
    {
      "extends": "eslint-config-airbnb"
    }

管道运算符

JavaScript 的管道是一个运算符,写作|>。它的左边是一个表达式,右边是一个函数。
管道运算符把左边表达式的值,传入右边的函数进行求值。

eg1:

x |> f
    // 等同于
    f(x)

eg2:

// 传统的写法
    function doubleSay (str) {
      return str + ", " + str;
    }
    function capitalize (str) {
      return str[0].toUpperCase() + str.substring(1);
    }
    function exclaim (str) {
      return str + '!';
    }
    // 传统的写法
    exclaim(capitalize(doubleSay('hello')))
    // "Hello, hello!"
    // 管道的写法
    'hello'
      |> doubleSay
      |> capitalize
      |> exclaim
    // "Hello, hello!"

管道运算符只能传递一个值,这意味着它右边的函数必须是一个单参数函数。如果是多参数函数,就必须进行柯里化,
改成单参数的版本。

 

function double (x) { return x + x; }
    function add (x, y) { return x + y; }
    let person = { score: 25 };
    person.score
      |> double
      |> (_ => add(7, _))
    // 57

冒号运算符

箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,
箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”
(function bind)运算符,
用来取代
call、apply、bind调用。

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,
作为上下文环境(即
this对象),绑定到右边的函数上面。

eg1:

foo::bar;
    // 等同于
    bar.bind(foo);
    foo::bar(...arguments);
    // 等同于
    bar.apply(foo, arguments);
    const hasOwnProperty = Object.prototype.hasOwnProperty;
    function hasOwn(obj, key) {
      return obj::hasOwnProperty(key);
    }

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

eg2:

var method = obj::obj.foo;
    // 等同于
    var method = ::obj.foo;
    let log = ::console.log;
    // 等同于
    var log = console.log.bind(console);

如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法。

eg3:

import { map, takeWhile, forEach } from "iterlib";
    getPlayers()
    ::map(x => x.character())
    ::takeWhile(x => x.strength > 100)
    ::forEach(x => console.log(x));

注:如果想获取全文pdf文件,关注【前端进阶圈】发送 ES6 即可获取哟。

相关文章
|
4月前
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
90 0
|
缓存 JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
|
JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(三)
ECMAScript6 从入门到入坟,你敢来挑战吗???(三)
|
前端开发 JavaScript
ECMAScript6 从入门到入坟,你敢来挑战吗???(二)
ECMAScript6 从入门到入坟,你敢来挑战吗???(二)
|
JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(四)
ECMAScript6 从入门到入坟,你敢来挑战吗???(四)
|
JSON 前端开发 JavaScript
ECMAScript6 从入门到入坟,你敢来挑战吗???(一)
ECMAScript6 从入门到入坟,你敢来挑战吗???
程序媛才能读懂的高级情话
程序媛才能读懂的高级情话
174 0
|
前端开发 JavaScript
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
98 0
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
|
前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
170 0
|
前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(二)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(二)🔥
下一篇
DataWorks