前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

一篇不是标题党的CommonJS和ES6模块规范讲解|博客系列

# 原始写法

在没有CommonJS和ES6的时候,我们想要达到模块化的效果可能有这么三种:

# 1. 一个函数就是一个模块

<script>
  function m1 () {
    // ...
  }
  function m2 () {
    // ...
  }
</script>
@前端进阶之旅: 代码已经复制到剪贴板

缺点:污染了全局变量,无法保证不会与其它模块发生冲突,而且模块成员之间看不出直接关系。

# 2. 一个对象就是一个模块

对象写法 为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

index.html

<script>
  var module1 = new Object({
    _sum: 0,
    foo1: function () {},
    foo2: function () {}
  })
</script>
@前端进阶之旅: 代码已经复制到剪贴板

缺点:会暴露所有模块成员,内部的状态可能被改写。

例如,我们如果只是想暴露出两个方法而不暴露出_sum,就做不到。

而此时,_sum可能被外部改写:

module1._sum = 2;
@前端进阶之旅: 代码已经复制到剪贴板

# 3. 立即执行函数为一个模块

<script>
  var module1 = (function() {
    var _sum = 0;
    var foo1 = function () {};
    var foo2 = function () {};
    return {
      foo1: foo1,
      foo2: foo2
    }
  })();
</script>
@前端进阶之旅: 代码已经复制到剪贴板

利用立即执行函数内的作用域已经闭包来实现模块功能,导出我们想要导出的成员。

此时外部代码就不能读取到_sum了:

console.log(module1._sum) // undefined
@前端进阶之旅: 代码已经复制到剪贴板

# CommonJS规范

这里不做具体的介绍了,我只把一些重要的知识点以及混淆点例举出来。

主要是从这四个方面说:

  • 暴露模块
  • 引用模块
  • 模块标识符
  • CommonJS规范的特点

# 1. 暴露(定义)模块

正确的暴露方式:

暴露模块有两种方式:

  • module.exports = {}
  • exports.xxx = 'xxx'

例如有一个m1.js文件:

第一种暴露方式:

module.exports = {
    name: 'lindaidai',
    sex: 'boy'
}
@前端进阶之旅: 代码已经复制到剪贴板

第二种暴露方式:

exports.name = 'lindaidai';
exports.sex = 'boy'
@前端进阶之旅: 代码已经复制到剪贴板

为什么可以有这两种写法呢?

我是这样理解的:module这个变量它代表的就是整个模块,也就是m1.js。而其实这个module变量是有一个属性exports的,它是一个叫做exports变量的引用,我们可以写一下伪代码:

var exports = {};
var module = {
    exports: exports
}
return module.exports
@前端进阶之旅: 代码已经复制到剪贴板

(当然这只是伪代码啊,实际你这么去用会发现没有效果)

最后导出的是module.exports,而不是exports。

容易混淆的暴露方式:

如果你在代码中试图exports = { name: 'lindaidai' },你会发现在引入的地方根本获取不到name属性。

// m1.js
exports = {
    name: 'lindaidai'
}
@前端进阶之旅: 代码已经复制到剪贴板
// test.js
const math = require('./m1.js')

console.log(m1); // {}
@前端进阶之旅: 代码已经复制到剪贴板

在控制台执行node test.js,发现打印出来的m1是一个空的对象。

我是这样理解的:整个模块的导出是靠module.exports的,如果你重新对整个exports对象赋值的话,它和module.exports就不是同一个对象了,因为它们指向的引用地址都不同:

module.exports -> {} // 指向一个空的对象
exports -> { name: 'lindaidai' } // 指向的是另一个对象
@前端进阶之旅: 代码已经复制到剪贴板

所以你对exports = {}做任何操作都影响不到module.exports。

让我们来看几个正确和错误的示例吧:

// m1.js
// 1. 正确
module.exports = {
    name: 'lindaidai',
    sex: 'boy'
}

// 2. 正确
exports.name = 'lindaidai';
exports.sex = 'boy'

// 3. 正确
module.exports.name = 'lindaidai';
module.exports.sex = 'boy'

// 4. 无效
exports = {
    name: 'lindaidai',
    sex: 'boy'
}
@前端进阶之旅: 代码已经复制到剪贴板

可以看到

  • exports.name = xxx是module.exports.name = xxx的缩写。
  • exports = {}确不是module.exports = {}的缩写。

# 2. 引用(引入)模块

对于模块的引用使用全局方法require()就可以了。

注意⚠️这个全局方法是node中的方法哈,它不是window下面的,所以如果你没做任何处理想直接在html里用肯定就是不行的了:

index.html:

<body>
    <script>
        var m1 = require('./m1.js')
        console.log(m1);
    </script>
</body>
@前端进阶之旅: 代码已经复制到剪贴板

例如上面👆这样你打开页面控制台肯定就报错了:

Uncaught ReferenceError: require is not defined
	at index.html:11
@前端进阶之旅: 代码已经复制到剪贴板

而如果你是在另一个js文件中引用(例如test.js),并在终端执行node test.js是可以用的:

test.js:

var m1 = require('./m1.js')

console.log(m1);
@前端进阶之旅: 代码已经复制到剪贴板

那是因为你的电脑上全局安装了Node.js,所以可以这样玩。

所以我们可以发现require()它是Node.js中的一个全局方法,并不是CommonJS独有的,CommonJS只是众多规范中的其中一种。

这种规范允许我们:

  • 使用module.exports = {}或者exports.name = xxx导出模块
  • 使用const m1 = require('./m1')引入模块

注意⚠️:

另外还有一点比较重要,那就是require()的参数甚至能允许你是一个表达式。

也就是说你可以把它设置为一个变量:

test.js:

var m1Url = './m1.js';
var m1 = require(m1Url);

// 甚至做一些字符串拼接:
var m1 = require('./m' + '1.js');
@前端进阶之旅: 代码已经复制到剪贴板

但是需要注意咯,这个传参可以为表达式并不是require特有的。因为JS语言是传值调用,函数或者方法在调用的时候参数会被先计算出来,因此在我们使用require方法并传入表达式的时候,会先计算出表达式的值再传递给require。(感谢掘友 骑自行车的指出)

# 3. 模块标识符(标识)

模块标识符其实就是你在引入模块时调用require()函数的参数。

你会看到我们经常会有这样的用法:

// 直接导入
const path = require('path');
// 相对路径
const m1 = require('./m1.js');
// 直接导入
const lodash = require('lodash');
@前端进阶之旅: 代码已经复制到剪贴板

这其实是因为我们引入的模块会有不同的分类,像path这种它是Node.js就自带的模块,m1是路径模块,lodash是我们使用npm i lodash下载到node_modules里的模块。

分为以下三种:

  • 核心模块(Node.js自带的模块)
  • 路径模块(相对或绝对定位开始的模块)
  • 自定义模块(node_modules里的模块)

三种模块的查找方式:

  • 核心模块,直接跳过路径分析和文件定位
  • 路径模块,直接得出相对路径就好了
  • 自定义模块,先在当前目录的node_modules里找这个模块,如果没有,它会往上一级目录查找,查找上一级的node_modules,依次往上,直到根目录下都没有, 就抛出错误。

自定义模块的查找过程:

这个过程其实也叫做路径分析。

现在我把刚刚的test.js来改一下:

// var m1 = require('./m1.js');

// console.log(m1);
console.log(module.paths)
@前端进阶之旅: 代码已经复制到剪贴板

然后在终端执行:

node test.js
@前端进阶之旅: 代码已经复制到剪贴板

会发现输出了下面的一个数组:

LinDaiDaideMBP:commonJS lindaidai$ node test.js
[
  '/Users/lindaidai/codes/test/CommonJS和ES6/commonJS/node_modules',
  '/Users/lindaidai/codes/test/CommonJS和ES6/node_modules',
  '/Users/lindaidai/codes/test/node_modules',
  '/Users/lindaidai/codes/node_modules',
  '/Users/lindaidai/node_modules',
  '/Users/node_modules',
  '/node_modules'
]
@前端进阶之旅: 代码已经复制到剪贴板

这里所说的查找,是指查找你现在用的这个模块,我现在用的是test.js,你可能看不出什么效果。现在让我们来模拟一个我们使用npm i安装的一个自定义模块功能。

首先,我在根目录下新建了一个名叫node_modules的文件夹,并在其中新建了一个名叫lindaidai.js的文件,用来模拟一个npm安装的依赖。

目录结构:

稍微编写一下lindaidai.js:

module.exports = {
  print: function () {
    console.log('lindaidai')
  }
}
console.log('lindaidai模块:', module.paths)
@前端进阶之旅: 代码已经复制到剪贴板

然后在test.js中引入这个lindaidai模块:

// var m1 = require('./m1.js');
// console.log(m1);
// console.log(module.paths)

var lindaidai = require('lindaidai');
lindaidai.print();
@前端进阶之旅: 代码已经复制到剪贴板

现在执行node test.js,会发现输出了:

← 你真的能完全运用好防抖节流吗ES9已经来了AreYouReady →

fe
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
  • JavaScript Part 1

    • Ajax总结篇
    • Canvas 绘制八大行星
    • DOM编程之API学习总结篇
    • JavaScript事件机制
    • JavaScript代码片段100个
    • JavaScript作用域分析总结
    • JavaScript原型链回顾
    • JavaScript及jQuery中的各种宽高属性图解
    • JavaScript工程项目的一系列最佳实践
    • JavaScript常用API合集
    • JavaScript数组、字符串、对象常用方法
    • JavaScript数组方法总结篇
    • JavaScript深浅拷贝
    • JavaScript运动框架之速度时间版本
    • JavaScript运行机制Event Loop
    • JavaScript防抖节流原理
    • Javascript中的复制粘贴功能
    • Javascript数组详解
    • OOP之原型与原型链
    • Object.defineProperty详解
    • V8源码浅析JS数组常见方法
    • await 在 forEach 中不生效解决方案
    • iframe+表单跨域提交POST请求
    • javascript笔记总结篇
    • 业务中处理数据结构常用的JS方法
    • 作用域
    • 你真的掌握变量和类型了吗
    • 前后端分离之数据Mock
    • 原型与原型链
    • 原生JS补给(上)
    • 如何写出一个惊艳面试官的深拷贝
    • 带你填一些JS容易出错的坑
    • 彻底弄懂 JavaScript 执行机制
    • 执行上下文 执行栈
    • 正则回顾总结
    • 正则基础知识
    • 正则完整篇
    • 正则表达式
    • 浅析Promise原理
    • 浅谈JavaScript中的异步处理
    • 深拷贝 vs 浅拷贝
    • 聊一聊typeof instanceof 实现原理.
    • 聊一聊闭包
    • 高阶函数map reduce filter
  • JavaScript Part 2

  • CSS

  • HTML

  • Jquery

  • ES6

  • 小程序

  • Vue

  • React

  • 深入React

  • React Native

  • NodeJS

  • Angular

  • TypeScript

  • Webpack

  • 浏览器

  • 移动端

  • 前端工程化

  • Electron

  • HTTP

  • Nginx

  • Linux

  • 数据结构与算法

  • LeetCode算法题

  • 综合