关于模块化立即执行函数和ESModule的详解

立即执行函数

在了解立即执行函数之前,先明确一下函数声明,函数表达式以及匿名函数的形式

function test(){//函数声明
   console.log('test')
}
var test=function(){//函数表达式
    console.log('test')
}
function(){//匿名函数
    console.log('test')
}
什么是立即执行函数

立即函数就是

  • 声明一个匿名函数
  • 马上调用这个匿名函数
    接下来看看立即执行函数的两种常见形式
(function(){})()

(function(){}())

一个是一个匿名函数包裹在一个括号中,后面跟着一个小括号

另一个是匿名函数后面跟一个小括号,然后整个包裹在一个括号运算符中。

这两种写法是等价的。想要立即执行函数能做到立即执行,要注意两点:
1 函数体后面要有小括号
2 函数体必须是函数表达式而不能是函数声明

;-(function() {
  /* */
})()

+(function() {
 /* */
})()

~(function() {
  /* */
})()

!(function() {
  /* */
})()

从上可以看出,除了使用()运算符之外,!,+,-,=等运算符都能起到立即执行的作用。这些运算符的作用就是将匿名函数或函数声明转换为函数表达式

为什么要使用括号把匿名函数包起来呢?

小括号能把我们的表达式组合分块,并且每一块,也就是每一对小括号,都有一个返回值。这个返回值实际上也就是小括号中表达式的返回值。所以,当我们用一对小括号把匿名函数括起来的时候,实际上小括号对返回的,就是一个匿名函数的Function对象。因此,小括号对加上匿名函数就如同有名字的函数般被我们取得它的引用位置了。所以如果在这个引用变量后面再加上参数列表,就会实现普通函数的调用形式。

立即执行函数有什么作用?

只有一个作用:创建一个独立的作用域。

通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间

ESModule
ES模块引入主要有以下几个优点:

1 可以将代码分隔成功能独立的更小的文件
2 有助于消除命名冲突
3 不在需要对象作为命名空间(比如Math对象),不会污染全局变量
4 ES6模块在编译时就能确认模块的依赖关系,以及输入和输出的变量,从而可以进行静态优化

ES模块的基本用法

模块功能中主要以下几个关键词:export,import,as,default *

  • export用于规定输出模块的对外接口
  • import用于输入模块提供的接口
  • as用于重命名输出和输入接口
  • *表示输入模块的所有接口
export

用法1:直接输出一个变量声明,函数声明或者类声明

export var m = 1;

export function m() {};

export class M {};

用法2:输出内容为大括号包裹的一组变量,切记 export不能直接输出常规的对象

var m1=1

var m2=2

export {m1,m2}

用法3:输出指定变量,并重名,则外部引入时得到是as后的名称

var n =1 

export {n as m}

用户4:使用default输出默认接口,default后可跟值或变量

export default 1

var m=1

dexport default m
错误用法
//用法1
export 1 ;

export {m:'1'};

//用法2
var m=1

export m;

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

用法1 和用法2 错误相同,export必须输入一个接口,不能输出一个值(哪怕对象也不行)
或者一个已赋值的变量,已赋值的变量对应的也是一个值。上述常规用法中,
export default后之所以可以直接跟值是因为default为输出的接口

用法3错误的原因:export只能出现在模块的顶层作用域,不能存在块级作用域中,如果出现在块级
作用域的话,就没法做静态优化了,这违背ES6中的模块的设计初衷

import
//用法1  进执行 moduleA 模块,不输入任何值(没啥用但是合法)
import 'moduleA'

//用法2 输入 moduleA的默认接口,默认接口重命名为m
import m from 'moduleA'

//用法3 输入moduleA 的m接口
import {m} from 'moduleA'

//用法4 输入moduleA的m接口,使用as重命名m接口
import {m as myM} from 'moduleA'

//用法5:导入所有接口
import * as all from 'moduleA'

需要注意的是,如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次

//用法1 重复引入 module1 只执行一次
import 'module1'
import 'module2'


//用法2
import {m1} from 'module'
import {m2} from 'module'

此外,import命名输入的变量都是只读的,加载后不能修改接口

import { m } from 'my_module';

m = 1; // SyntaxError: "m" is read-only

如果m是一个对象,改写m的属性是可以的。但是笔者不建议这么做

需要注意的是,import也必须在顶级作用域内,并且其中不能使用表达式和变量。其常见的错误用法示例如下:

// 用法1:不能使用表达式

import { 'm' + '1' } from 'my_module';

 

// 用法2:不能使用变量

let module = 'my_module';

import { m } from module;

 

// 用法3:不能用于条件表达式

if (x === 1) {

  import { m } from 'module1';

} else {

  import { m } from 'module2';

}
如何在浏览器中下快速使用import?

各大浏览器已经开始逐步支持ES模块了,如果我们想在浏览器中使用模块,可以在script标签上添加一个type="module"的属性来表示这个文件是以module的方式来运行的。如下:

// myModule.js

export default {

  name: 'my-module'

}

 

// script脚本引入


不过,由于ES的模块功能还没有完全支持,在不支持的浏览器下,我们需要一些回退方案,可以通过nomodule属性来指定某脚本为回退方案。如下,在支持的浏览器中进行提示。



 


如上,当浏览器支持type=module时,会忽略带有nomodule的script;如果不支持,则忽略带有type=module的脚本,执行带有nomodule的脚本。

在使用type=module引入模块时还有一点需要注意的,module的文件默认为defer,也就是说该文件不会阻塞页面的渲染,会在页面加载完成后按顺序执行。

当心,不要修改export输出的对象

前面有提到如果export输出的接口是一个对象,那么是可以修改这个对象的属性的。

而我的建议是,尽管你能改,也不要修改。

大家可能都会有这样一个常规的用法,即在编写某个组件时,可能会存在包含基础配置的代码,我们姑且称其为options.js,其输出一堆配置文件。

// options.js

export default {

  // 默认样式

  style: {

    color: 'green',

    fontSize: 14,

  }

}

如果你没有类似需求,你可以想象下,你现在要把EChart的某个图表抽象成自己代码库里的组件,那么这时候应该就有一大堆基础配置文件了。

既然称其为基础配置,那么言外之意就是,根据组件的用法不同,会一定程度上对配置进行修改。比如我们会在引入后将颜色改为红色

// use-options.js

import options from "./options.js";

 

console.log(options); // { style: { color: 'green', fontSize: 14 } }

 

options.style.color = "red";

这时候就需要格外注意了,如果我们直接对输入的默认配置对象进行修改,就可能会导致一些bug。

因为export输出的值是动态绑定的,如果我们修改了其中的值,就会导致其他地方再次引入该值时会发生变化,此时的默认配置就不是我们所设想的默认配置了。如上例,我们再次引入基础配置后,就会发现颜色的默认值已经变成红色了。

这时候就需要格外注意了,如果我们直接对输入的默认配置对象进行修改,就可能会导致一些bug。

因为export输出的值是动态绑定的,如果我们修改了其中的值,就会导致其他地方再次引入该值时会发生变化,此时的默认配置就不是我们所设想的默认配置了。如上例,我们再次引入基础配置后,就会发现颜色的默认值已经变成红色了。

所以,笔者建议,当我们有需求对输入的对象接口进行改变时,可以先对其进行深度复制,然后在进行修改,这样就不会导致上述所说的问题了。如下所示:

// use-options.js

import _ from "./lodash.js";

import options from "./options.js";

 

const myOptions = _.cloneDeep(options);

console.log(myOptions); // { style: { color: 'green', fontSize: 14 } }

myOptions.style.color = "red";

你可能感兴趣的:(JavaScript基础,模块化,立即执行函数)