四.JS模块化开发

JS模块化开发

1.认识模块化开发

事实上模块化开发最终的目的是将程序划分成一个个小的结构

这个结构中编写属于自己的逻辑代码,有自己的作用域,定义变量名词时不会影响到其他的结构;

这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;[导出]

也可以通过某种方式,导入另外结构中的变量、函数、对象等;[导入]

上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程;

无论你多么喜欢JavaScript,以及它现在发展的有多好,它都有很多的缺陷:

  比如var定义的变量作用域问题;

  比如JavaScript的面向对象并不能像常规面向对象语言一样使用class;

  比如JavaScript没有模块化的问题;

对于早期的JavaScript没有模块化来说,确确实实带来了很多的问题;

模块化的历史 : 

  在网页开发的早期,Brendan Eich开发JavaScript仅仅作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的:

    这个时候我们只需要讲JavaScript代码写到<script>标签中即可;

    并没有必要放到多个文件中来编写;甚至流行:通常来说 JavaScript 程序的长度只有一行

  但是随着前端和JavaScript的快速发展,JavaScript代码变得越来越复杂了:

    ajax的出现,前后端开发分离,意味着后端返回数据后,我们需要通过JavaScript进行前端页面的渲染

    SPA的出现,前端页面变得更加复杂:包括前端路由、状态管理等等一系列复杂的需求需要通过JavaScript来实现;

    包括Node的实现,JavaScript编写复杂的后端程序,没有模块化是致命的硬伤;

  所以,模块化已经是JavaScript一个非常迫切的需求:

    但是JavaScript本身,直到ES6(2015)才推出了自己的模块化方案

    在此之前,为了让JavaScript支持模块化,涌现出了很多不同的模块化规范:AMD、CMD、CommonJS

 

2.CommonJS(模块化方案[社区推出,还有人用])和node

CommonJS 是一个规范

导出 : exports

导入 : require

例子 : 

导出 : a.js

  let  name = 'yjx'

  funciton foo(){ }

  exports.name = name

  exports.foo = foo 

导出 : const  aa = require('./a.js')

  console.log(aa.name)

  console.log(aa.foo)
优化 :
  const {name,foo} = require('./a.js')
  console.log(name)

  console.log(foo)

Node,Browserify,webpack 都有实现过CommonJS的规范.  浏览器没有实现过(所以不能用)

 

进阶 : 

  其实我们通常用的是 module.exports , 而不是exports  .  在CommonJS中是没有module.exports的,但是node加了module类

  所以 :  require('')  ==>  指向的是module.export  而不是export

  默认情况下 : module.exports = exports 

  正式中,我们采用的代码是这样的 :  [都是采用module.export的用法]

导出 : a.js

  let  name = 'yjx'

  funciton foo(){ }

  module.exports =  {
            name,
            foo
     }


导出: 
  const {name,foo} = require('./a.js')
  console.log(name)
  console.log(foo)        

 

require的查找过程 :   导入格式如下:require(X)

  1:X是一个Node核心模块,比如path、http 直接返回核心模块,并且停止查找

  2:X是以 ./ 或 ../ 或 /(根目录)开头的

    第一步:将X当做一个文件在对应的目录下查找;

      1.如果有后缀名,按照后缀名的格式查找对应的文件

      2.如果没有后缀名,会按照如下顺序:

        1> 直接查找文件X

        2> 查找X.js文件

        3> 查找X.json文件

        4> 查找X.node文件

    第二步:没有找到对应的文件,将X作为一个目录

      查找目录下面的index文件

        1> 查找X/index.js文件

        2> 查找X/index.json文件

        3> 查找X/index.node文件

    如果没有找到,那么报错:not found

  3:直接是一个X(没有路径),并且X不是一个核心模块 [axios]

    那么就会通过 node_modules  文件夹查找了

    [ npm i  下载的都存在这个文件夹,会找当前目录的node_modules,没有,再找上层..上上层..直到根目录,如果没有找到,那么报错:not found]

 

模块的加载过程 : 

 

 CommonJS的缺点

  CommonJS加载模块是同步的

    同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行;

    这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;

  如果将它应用于浏览器呢?

    浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行;

    那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作;

  所以在浏览器中,我们通常不使用CommonJS规范

    当然在webpack中使用CommonJS是另外一回事; 因为它会将我们的代码转成浏览器可以直接执行的代码;

  在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD(使用很少),现在基本用ES Modules

    但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换;

    AMD和CMD已经使用非常少了;

 

3.AMD和CMD(了解,模块化规范[社区推出,已经不用了])

AMD主要是应用于浏览器的一种模块化规范:

   AMD是Asynchronous Module Definition(异步模块定义)的缩写;

  它采用的是异步加载模块;

  事实上AMD的规范还要早于CommonJS,但是CommonJS目前依然在被使用,而AMD使用的较少了;

我们提到过,规范只是定义代码的应该如何去编写,只有有了具体的实现才能被应用: AMD实现的比较常用的库是require.js和curl.js

 

CMD规范也是应用于浏览器的一种模块化规范:

  CMD 是Common Module Definition(通用模块定义)的缩写;

  它也采用的也是异步加载模块,但是它将CommonJS的优点吸收了过来;

  但是目前CMD使用也非常少了;

CMD也有自己比较优秀的实现方案:SeaJS

 

4.ESModule用法详解(ES6标准推出来的模块化方案 重点)

采用ESModule的,默认会加上严格模式

简单导出导入  

导出 : a.js
export const name = "why"
export const age = 18

export function sayHello() {
  console.log("sayHello")
}


导入 import
// 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js
import { name, age, sayHello } from "./a.js"

console.log(name)
console.log(age)
sayHello()

简单导入导出 -- 优化导出

导出 : a.js
export const name = "why"
export const age = 18

export function sayHello() {
  console.log("sayHello")
}


导入 import
// 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js
import { name as fname, age as fage, sayHello as fsayHello } from "./a.js"

console.log(fname)
console.log(fage)
fsayHello()

 

进阶导出和导入 :  

导出 : a.js
const name = "why"
const age = 18

function sayHello() {
  console.log("sayHello")
}

// 导出 export
export {
  name,
  age,
  sayHello
}

导入 import
// 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js
import { name, age, sayHello } from "./a.js"

console.log(name)
console.log(age)
sayHello()

进阶导出和导入 --- 优化导出

导出 : a.js
const name = "why"
const age = 18

function sayHello() {
  console.log("sayHello")
}

// 导出 export
export {
  name as  fname,
  age as  fage,
  sayHello as  fsayHello
}

导入 import
// 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js
import { fname, fage, fsayHello } from "./a.js"

console.log(fname)
console.log(fage)
fsayHello()

进阶导出和导入 --- 优化导入

导出 : a.js
const name = "why"
const age = 18

function sayHello() {
  console.log("sayHello")
}

// 导出 export
export {
  name ,
  age,
  sayHello
}

导入 import
// 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js
import * as a from "./a.js"

console.log(a.name)
console.log(a.age)
a.sayHello()

 

同时导入和同时导出的优化写法

// 优化一:export { formatCount, formatDate } from './format.js'export { parseLyric } from './parse.js'

// 优化二:export * from './format.js'export * from './parse.js'

 

默认导出 : 

前面我们学习的导出功能都是有名字的导出(named exports):

  在导出export时指定了名字;

  在导入import时需要知道具体的名字;

还有一种导出叫做默认导出(default export)

  默认导出export时可以不需要指定名字

  在导入时不需要使用 {},并且可以自己来指定名字

  它也方便我们和现有的CommonJS等规范相互操作;

注意:在一个模块中,只能有一个默认导出(default export)

导出 : 
function parseLyric() {
  return ["歌词"]
}

export default parseLyric

引入 : 
import parseLyric from "./parse_lyric.js"
导出 : 
export default function() {
  return ["新歌词"]
}

导入 : 
import parseLyric from "./parse_lyric.js"

 

通过import加载一个模块,是不可以在其放到逻辑代码中的

但是某些情况下,我们确确实实希望动态的来加载某一个模块 : 

  这个时候我们需要使用 import() 函数来动态加载

  import函数返回一个Promise,可以通过then获取结果

let flag = true
if (flag) {

  import("./foo.js").then(res => {
    console.log(res.name, res.age)
  })

  console.log("------")
}

 

posted @ 2022-08-07 23:10  杨建鑫  阅读(199)  评论(0编辑  收藏  举报