前言
10年积累的成都做网站、成都网站制作经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站制作后付款的网站建设流程,更有兴安盟乌兰浩特免费网站建设让你可以放心的选择与我们合作。
本文将为大家透彻的介绍关于Node的模块化——CommonJS的一切。
看完本文可以掌握,以下几个方面:
一.什么是模块化?
在很多开发的情况下,我们都知道要使用模块化开发,那为什么要使用它呢?
而事实上,模块化开发最终的目的是将程序划分成一个个小的结构;
上面说提到的结构,就是模块;
按照这种结构划分开发程序的过程,就是模块化开发的过程;
二.JavaScript设计缺陷
在网页开发的早期,由于JavaScript仅仅作为一种脚本语言,只能做一些简单的表单验证或动画实现等,它还是具有很多的缺陷问题的,比如:
但随着前端和JavaScript的快速发展,JavaScript代码变得越来越复杂了;
所以,模块化已经是JavaScript一个非常迫切的需求:
到此,我们明白了为什么要用模块化开发?
那如果没有模块化会带来什么问题呢?
三.没有模块化的问题
当我们在公司面对一个大型的前端项目时,通常是多人开发的,会把不同的业务逻辑分步在多个文件夹当中。
2.1 没有模块化给项目带来的弊端
假设有两个人,分别是小豪和小红在开发一个项目
项目的目录结构是这样的
小豪开发的bar.js文件
- var name = "小豪";
- console.log("bar.js----", name);
小豪开发的baz.js文件
- console.log("baz.js----", name);
小红开发的foo.js文件
- var name = "小红";
- console.log("foo.js----", name);
引用路径如下:
最后当我去执行的时候,却发现执行结果:
当我们看到这个结果,有的小伙伴可能就会惊讶,baz.js文件不是小豪写的么?为什么会输出小红的名字呢?
究其原因,我们才发现,其实JavaScript是没有模块化的概念(至少到现在为止还没有用到ES6规范),换句话说就是每个.js文件并不是一个独立的模块,没有自己的作用域,所以在.js文件中定义的变量,都是可以被其他的地方共享的,所以小豪开发的baz.js里面的name,其实访问的是小红重新声明的。
但是共享也有一点不好就是,项目的其他协作人员也可以随意的改变它们,显然这不是我们想要的。
2.2 IIFE解决早期的模块化问题
所以,随着前端的发展,模块化变得必不可少,那么在早期是如何解决的呢?
在早期,因为函数是有自己的作用域,所以可以采用立即函数调用表达式(IIFE),也就是自执行函数,把要供外界使用的变量作为函数的返回结果。
小豪——bar.js
- var moduleBar = (function () {
- var name = "小豪";
- var age = "18";
- console.log("bar.js----", name, age);
- return {
- name,
- age,
- };
- })();
小豪——baz.js
- console.log("baz.js----", moduleBar.name);
- console.log("baz.js----", moduleBar.age);
小红——foo.js
- (function () {
- var name = "小红";
- var age = 20;
- console.log("foo.js----", name, age);
- })();
来看一下,解决之后的输出结果,原调用顺序不变;
但是,这又带来了新的问题:
所以现在急需一个统一的规范,来解决这些缺陷问题,就此CommonJS规范问世了。
三.Node模块化开发——CommonJS规范
3.1 CommonJS规范特性
CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了体现它的广泛性,修改为CommonJS规范。
正是因为Node中对CommonJS进行了支持和实现,所以它具备以下几个特点;
无疑,模块化的核心是导出和导入,Node中对其进行了实现:
3.2 CommonJS配合Node模块化开发假设现在有两个文件:
bar.js
- const name = "时光屋小豪";
- const age = 18;
- function sayHello(name) {
- console.log("hello" + name);
- }
main.js
- console.log(name);
- console.log(age);
执行node main.js之后,会看到
这是因为在当前main.js模块内,没有发现name这个变量;
这点与我们前面看到的明显不同,因为Node中每个js文件都是一个单独的模块。
那么如果要在别的文件内访问bar.js变量
3.3 exports导出
exports是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出。
bar.js文件导出:
- const name = "时光屋小豪";
- const age = 18;
- function sayHello(name) {
- console.log("hello" + name);
- }
- exports.name = name;
- exports.age = age;
- exports.sayHello = sayHello;
main.js文件导入:
- const bar = require('./bar');
- console.log(bar.name); // 时光屋小豪
- console.log(bar.age); // 18
其中要注意的点:
main.js中的bar变量等于exports对象;
- bar = exports
3.4 从内存角度分析bar和exports是同一个对象
在Node中,有一个特殊的全局对象,其实exports就是其中之一。
如果在文件内,不再使用exports.xxx的形式导出某个变量的话,其实exports就是一个空对象。
模块之间的引用关系
为了进一步论证,bar和exports是同一个对象:
我们加入定时器看看
所以综上所述,Node中实现CommonJS规范的本质就是对象的引用赋值(浅拷贝本质)。
把exports对象的引用赋值bar对象上。
3.5 module.exports又是什么?
但是Node中我们经常使用module.exports导出东西,也会遇到这样的面试题:
module.exports和exports有什么关系或者区别呢?
3.6 require细节
require本质就是一个函数,可以帮助我们引入一个文件(模块)中导入的对象。
require的查找规则https://nodejs.org/dist/latest-v14.x/docs/api/modules.html#modules_all_together
3.7 require模块的加载顺序
结论一:模块在被第一次引入时,模块中的js代码会被运行一次
- // aaa.js
- const name = 'coderwhy';
- console.log("Hello aaa");
- setTimeout(() => {
- console.log("setTimeout");
- }, 1000);
- // main.js
- const aaa = require('./aaa');
aaa.js中的代码在引入时会被运行一次
结论二:模块被多次引入时,会缓存,最终只加载(运行)一次
- // main.js
- const aaa = require('./aaa');
- const bbb = require('./bbb');
- /// aaa.js
- const ccc = require("./ccc");
- // bbb.js
- const ccc = require("./ccc");
- // ccc.js
- console.log('ccc被加载');
ccc中的代码只会运行一次。
为什么只会加载运行一次呢?
结论三:如果有循环引入,那么加载顺序是什么?
如果出现下面模块的引用关系,那么加载顺序是什么呢?
多个模块的引入关系
四.module.exports
4.1 真正导出的是module.exports
以下是通过维基百科对CommonJS规范的解析:
但是,为什么exports也可以导出呢?
4.2 module.exports和exports有什么关系或者区别呢?
联系:module.exports = exports
进一步论证module.exports = exports
- // bar.js
- const name = "时光屋小豪";
- exports.name = name;
- setTimeout(() => {
- module.exports.name = "哈哈哈";
- console.log("bar.js中1s之后", exports.name);
- }, 1000);
- // main.js
- const bar = require("./bar");
- console.log("main.js", bar.name);
- setTimeout((_) => {
- console.log("main.js中1s之后", bar.name);
- }, 2000);
在上面代码中,只要在bar.js中修改exports对象里的属性,导出的结果都会变,因为即使真正导出的是 module.exports,而module.exports和exports是都是相同的引用地址,改变了其中一个的属性,另一个也会跟着改变。
注意:真正导出的模块内容的核心其实是module.exports,只是为了实现CommonJS的规范,刚好module.exports对exports对象使用的是同一个引用而已
区别:有以下两点
那么如果,代码这样修改了:
图解module.exports和exports的区别
讲完它们两个的区别,来看下面这两个例子,看看自己是否真正掌握了module.exports的用法
4.3 关于module.exports的练习题
练习1:导出的变量为值类型
- // bar.js
- let name = "时光屋小豪";
- setTimeout(() => {
- name = "123123";
- }, 1000);
- module.exports = {
- name: name,
- age: "20",
- sayHello: function (name) {
- console.log("你好" + name);
- },
- };
- // main.js
- const bar = require("./bar");
- console.log("main.js", bar.name); // main.js 时光屋小豪
- setTimeout(() => {
- console.log("main.js中2s后", bar.name); // main.js中2s后 时光屋小豪
- }, 2000);
练习2:导出的变量为引用类型
- // bar.js
- let info = {
- name: "时光屋小豪",
- };
- setTimeout(() => {
- info.name = "123123";
- }, 1000);
- module.exports = {
- info: info,
- age: "20",
- sayHello: function (name) {
- console.log("你好" + name);
- },
- };
- // main.js
- const bar = require("./bar");
- console.log("main.js", bar.info.name); // main.js 时光屋小豪
- setTimeout(() => {
- console.log("main.js中2s后", bar.info.name); // main.js中2s后 123123
- }, 2000);
从main.js输出结果来看,定时器修改的name变量的结果,并没有影响main.js中导入的结果。
五.CommonJS的加载过程
CommonJS模块加载js文件的过程是运行时加载的,并且是同步的:
- const flag = true;
- if (flag) {
- const foo = require('./foo');
- console.log("等require函数执行完毕后,再输出这句代码");
- }
CommonJS通过module.exports导出的是一个对象:
六.CommonJS规范的本质
CommonJS规范的本质就是对象的引用赋值
后续文章
《JavaScript模块化——ES Module》
在下一篇文章中,
【编辑推荐】
分享文章:「Node.js系列」深入浅出Node模块化开发——CommonJS规范
网页地址:http://www.mswzjz.cn/qtweb/news47/27097.html
攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能