Module 的语法

文章编写参考 阮一峰《ECMAScript 6 入门》


1. 概述

模块化开发对于现在的大型的应用系统来说是必不可少的一种模式,【ES6模块的设计思想是尽量的静态化】使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

ES6模块不是对象,而是通过export命令显示指定输出的代码,再通过import命令导入。

// ES6模块
import { stat, exists, readFile } from 'fs';

上面代码从fs模块中加载了3个方法,其他方法不加载。这种加载成为“编译时加载”或者静态加载,即ES6可以在编译时就完成模块的加载。


2. 严格模式

ES6的模块自动采用严格模式,不管你是否在模块的头部加上“use strict”

严格模式中有以下几种限制

  1. 变量必须声明后再使用
  2. 函数的参数不能有同名属性,否则报错
  3. 不能使用with语句
  4. 不能对只读属性赋值,否则报错
  5. 不能使用前缀0表示八进制数,否则报错
  6. 不能删除不可删除的属性,否则报错
  7. 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  8. eval不会在它的外层作用域引入变量
  9. eval和arguments不能被重新赋值
  10. arguments不会自动反映函数参数的变化
  11. 不能使用arguments.callee
  12. 不能使用arguments.caller
  13. 禁止this指向全局对象
  14. 不能使用fn.caller和fn.arguments获取函数调用的堆栈
  15. 增加了保留字(比如protected、static和interface)

其中,尤其需要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。


3. export命令

模块的功能主要由两个命令构成:export和import。export用于在模块中导出接口;import命令用于输入其他模块提供的功能。export和import是配套使用的。

一个模块就是一个JS文件,【模块中所有的变量外部都是无法获取的】。如果你希望外部能访问到模块内部的某个变量,就必须使用【export命令导出变量】。

//// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

上面代码是profile.js文件,保存了用户信息。ES6 将其视为一个模块,里面用export命令对外部输出了三个变量。

export除了上面的那样的写法,还有另外一种更常用的方式

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

上面代码在export命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在var语句前)是等价的,但是应该【优先考虑使用这种写法】。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。

【export命令除了输出变量,还可以输出函数或类(class)】

//输出函数
export function multiply(x, y) {
  return x * y;
};

//输出类
export class Person{

}

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。


function v1() {
    //.....
}
function v2() {
    //...
}
export {
    v1 as streamV1,
    v2 as streamV2,
    v2 as anotherV2
}

上面代码使用as关键字,重命名了函数v1和v2的对外接口。重命名后,v2可以用不同的名字输出两次。

【注意】export命令规定的是对外的接口,必须与模块内部建立一一对应关系。

//报错
let m = 1;
export m;

//报错
export 1;

上面的两种写法都会报错,因为没有提供对外的接口。第一种写法导出一个变量,变量为1,也就是跟第二种写法一样,都是导出1,1是一个值,不是一个接口。

// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

上面三种写法都是正确的,规定了对外的接口m。其他脚本可以通过这个接口,取到值1。它们的实质是,在接口名与模块内部变量之间,建立了一一对应的关系。

同样的,function和class的输出,也必须遵守这样的写法。

//报错
function fun() { }
export fun;

//正确
export function fun() { }

//正确
function fun() { }
export { fun }

【注意】export输出的接口,与其对应的值是动态绑定的,也就是说通过模块接口可以取得模块内部实时的值。

export let name = 'Blue';
setTimeout(function () {
    name = 'Crazy'
}, 500);

上面代码导出的name接口,初始为Blue,但是500毫秒之后就变成了Crazy,模块外部的值与模块内部的值是实时绑定的。

【export可以出现在模块的任何位置,但是必须处于顶层作用域中】

function foo() {
  export default 'bar' // SyntaxError
}
foo()

上面代码由于export命令出现在了函数中,所以导致代码报错。


4. import命令

在模块中使用了export导出接口之后,我们使用import在其他JS文件中导入接口以加载模块。

////module
let sayHi = () => "I am blue";

class Person {

}

let name = 'Blue';

export {sayHi,Person,name}
//导入模块
import { sayHi, Person, name } from './model'

上面代码中,从model模块中导入了三个接口,分别是一个方法、一个类、和一个变量。【import大括号中的名称必须和模块导出的名称一致,又别名的与别名一致】

如果我们想对输出的接口进行重名名,那么可以在import的大括号中进行操作

import { sayHi as sayHello, Person, name } from './model'
sayHello()  //I am blue

import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js路径可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。

import {myMethod} from 'util';

上面代码中,util是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。

【注意】import命令存在命令提升,会提升到整个代码的头部

sayHello()  //I am blue
import { sayHi as sayHello, Person, name } from './model'

上面的代码不会报错,因为import会提升到顶部执行。这种行为的本质其实就是模块的导入是在编译过程中执行的,是在代码运行之前。

【import是静态执行的,不能使用表达式】

// 报错
import { 'f' + 'oo' } from 'my_module';

// 报错
let module = 'my_module';
import { foo } from module;

// 报错
if (x === 1) {
  import { foo } from 'module1';
} else {
  import { foo } from 'module2';
}

上面三种写法都会报错,因为它们用到了表达式、变量和if结构。在静态分析阶段,这些语法都是没法得到值的。

import语句会执行所加载的模块,因此可以有下面的写法。

import 'lodash';

上面代码仅仅执行lodash模块,但是不输入任何值。

如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。

import 'lodash';
import 'lodash';

上面代码加载了两次lodash,但是只会执行一次。

import { foo } from 'my_module';
import { bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';

上面代码中,虽然foo和bar在两个语句中加载,但是它们对应的是同一个my_module实例。也就是说,import语句是 Singleton 模式。


5. 模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。

//逐一列举导出接口
import { sayHi as sayHello, Person, name } from './model'

//整体加载
import * as exportobj from './model'
exportobj;
/*
    { sayHi: [Function: sayHi],
  Person: [Function: Person],
  name: 'Blue' }
*/

上面代码中用*整体加载了一个模块,在这个对象拥有所有模块导出的接口
【注意】模块的整体加载也是静态的,不允许导入模块之后再模块外部运行时对接口进行改变。

import * as circle from './circle';

// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};

6. export default命令

从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

 export-default.js
export default function () {
  console.log('foo');
}

上面代码是一个模块文件export-default.js,它的默认输出是一个函数

他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

//// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号。

export default还可以用于非匿名函数

export default function sayHi() {
  console.log('I am Blue');
}

//等同于

function sayHi() {
    console.log('I am Blue');
}
export default sayHi;

上面代码中,函数名sayHi在模块外部是无效的,在加载的时候视为匿名函数。

下面比较一下默认输出和正常输出

//第一组
export default function crc32() { // 输出
  // ...
}

import crc32 from 'crc32'; // 输入

// 第二组
export function crc32() { // 输出
  // ...
};

import {crc32} from 'crc32'; // 输入

上面代码的两组写法,第一组是使用export default时,对应的import语句不需要使用大括号;第二组是不使用export default时,对应的import语句需要使用大括号。

【一个模块只能有一个默认输出】所以,import命令后面才不用加大括号,因为只可能对应一个方法。

本质上,export default就是输出的一个叫作default的变量或方法,然后系统允许你为它取别名。

// modules.js
function add(x, y) {
  return x * y;
}
export {add as default};
// 等同于
// export default add;

// app.js
import { default as xxx } from 'modules';
// 等同于
// import xxx from 'modules';

【注意】因为export default输出的是一个叫做default的变量,所以他后面不能再跟变量声明语句了。

// 正确
export var a = 1;

// 正确
var a = 1;
export default a;

// 错误
export default var a = 1;

有了export default命令,输入模块时就非常直观了,以输入 lodash 模块为例。

import _ from 'lodash';

如果想在一条import语句中,同时输入默认方法和其他接口,可以写成下面这样。

import _, { each, each as forEach } from 'lodash';

对应上面代码的export语句如下。

export default function (obj) {
  // ···
}

export function each(obj, iterator, context) {
  // ···
}

export { each as forEach };

上面代码的最后一行的意思是,暴露出forEach接口,默认指向each接口,即forEach和each指向同一个方法。

export default也可以用来输出类。

const Person = class {
    constructor(name) {
        this.name = name
    }
    sayHi() {
        console.log('I am ', this.name);
    }
}
export default Person;
import Person from './model';
let p = new Person('Blue');
p.sayHi();  //I am  Blue

7. export 与import的复合写法

如果在一个模块中,先输入后输出同一个模块,import语句可以与export语句写在一起。

export { Person } from './model';

//等同于

import Person from './model';
export { Person };

上面代码中,将model模块导入后导出,写成了一个export和import的复合写法。

【改名输出】

export { Person as People } from './model';

上面代码使用 as 将导入的接口改名后导出。

【整体输出】

export * from './model';

【默认输出】

export { default } from './model';

【具名接口改成默认接口输出】

export { Person as default } from './model';

【默认接口改成具名接口输出】

export { default as Person } from './model';

8. 模块的继承

模块之间产生继承给我的感觉就是把一个模块导入这个模块儿,在该模块中添加新的方法或者变量,然后导出。

假设有一个circleplus模块,继承了circle模块。

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x){
    return Math.exp(x);
}

上面代码中的export **,表示再输出circle模块的所有属性和方法。然后,上面代码又输出了自定义的e变量和默认方法。

【注意】export * 命令会忽略circle模块的default方法。

这时也可以将circle模块的属性或者方法,改名后再输出。

// circleplus.js

export { area as circleArea } from 'circle';

上面代码表示,只输出circle模块的area方法,且将其改名为circleArea。

加载上面模块的写法如下。

// main.js

import * as math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.e));

上面代码中的import exp表示,将circleplus模块的默认方法加载为exp方法。


9. 跨模块常量

介绍const命令的时候说过,const声明的常量只在当前代码块有效。
如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。

// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;

// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3

如果要使用的常量非常多,可以建一个专门的constants目录,将各种常量写在不同的文件里面,保存在该目录下。

// constants/db.js
export const db = {
  url: 'http://my.couchdbserver.local:5984',
  admin_username: 'admin',
  admin_password: 'admin password'
};

// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

然后,将这些文件输出的常量,合并在index.js里面。

// constants/index.js
export {db} from './db';
export {users} from './users';

使用的时候,直接加载index.js就可以了。

// script.js
import {db, users} from './constants';

### Verilog Module Syntax Example and Explanation In Verilog, modules are fundamental building blocks used to define circuits or components. A typical module definition includes inputs, outputs, internal signals (wires), and instances of other modules. #### Basic Structure of a Verilog Module A simple Verilog module has the following structure: ```verilog module module_name ( input_type input_signal, output_type output_signal ); // Internal logic goes here endmodule ``` For more complex designs involving hierarchical structures, additional elements such as wires and sub-module instantiations can be included within the module body[^1]. #### Detailed Example with Hierarchical Design Consider an example where two `add16` modules are instantiated inside a parent module named `top_module`. This demonstrates how hierarchy is managed in Verilog design[^2]: ```verilog module top_module ( input [31:0] a, input [31:0] b, output [31:0] sum ); wire cin; wire cout1; wire cout2; wire [15:0] sum1, sum2; assign cin = 1'b0; // Instantiate first add16 for lower bits add16 instance0 (.a(a[15:0]), .b(b[15:0]), .cin(cin), .sum(sum1), .cout(cout1)); // Instantiate second add16 for higher bits using carry out from previous stage add16 instance1 (.a(a[31:16]), .b(b[31:16]), .cin(cout1), .sum(sum2), .cout(cout2)); // Concatenate results into final 32-bit sum assign sum = {sum2, sum1}; endmodule ``` This code snippet illustrates several key aspects of Verilog syntax: - **Port Declaration**: Inputs and outputs declared at the beginning. - **Internal Signals**: Defined using `wire`. - **Sub-modules Instantiation**: Using dot notation (`.<port>()`) to connect ports explicitly between parent and child modules. - **Concatenation Operator `{}`**: Used to combine multiple bit vectors into one larger vector[^3]. --related questions-- 1. How does port mapping work when connecting different width buses? 2. What happens if there's no explicit connection made during instantiation? 3. Can parameters be passed along with signal connections while instantiating another module? 4. Is it possible to instantiate multiple copies of the same submodule efficiently without repeating lines of code?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值