初学node node开发环境搭建 node模块化 commonJS原理
由于Node.js平台是在后端运行JavaScript代码,所以,必须首先在本机安装Node环境。
学习node,首先要装node,和它的包管理工具,这两个都是傻瓜式安装,百度一下就安装了。
安装完之后,打开终端,用
node -v
查看node版本
用
npm -v
查看npm版本
以上就是学习node的准备。
node有两种模式:1、命令行模式 2、node交互模式
命令行模式:就是自己建一个js文件 然后用node+xxx.js 来执行此js文件
node 交互模式:就是在终端输入 node 按回车 就进入了node交互模式 我们可以在上面输入js代码 然后回车 会直接执行(按两次ctrl + c 可以退出node交互模式)
比如输入一个100+200 按回车,会直接打印出300
但是如果在js里写100+200 然后用node+xxx.js运行的话,不会打印出结果,如果想打印出结果,必须用console.log来打印,这也是命令行模式和node交互模式的一个区别
我们以后的js代码都要在严格模式下执行,所以没个js文件的开头 都要加上
'use strict'
但是,这样显得有些费劲,每个文件都要加,还有一种办法是给node 命令加参数
node --use_strict xxx.js
这样就可以确保,js在严格模式下运行了。
下面建立一个文件加,叫nodeTest吧,里面建一个hello.js,内容是console.log("hello world");
然后打开终端 cd +文件名
node hello.js
就可以看到终端打印出了hello world,
好了,我们的第一个node程序写出来了!!!
当然,想要方便,我们需要选一个IDE来开发node
那么廖老师推荐的是我现在正在用的vsCode!!!
我已经比较了解了,但是有一点需要说一下,想要给某个工程目录建立.vscode文件,需要选择到该目录下,点击vscode左侧的调试按钮(蜘蛛状),然后点击上面的设置按钮,就可以建立.vscode文件,然后.vscode文件下有一个launch.json文件,是用来配置的
我们将hello.js改造一下
'use strict' var s='hello'; function greet(name){ console.log(s+','+name+'!'); } module.exports=greet;
上面代码将hello.js改造成一个模块,
用module.exports将greet方法暴露出来,那么在其它文件应该如何用它呢???
在同样的工程目录下建立一个main.js
'use strict' //引入hello模块 var greet=require("./hello"); var s="qinghai"; greet(s);//hello,qinghai!
注意到引入hello
模块用Node提供的require
函数
在使用require()
引入模块的时候,请注意模块的相对路径
如果只写模块名:
var greet = require('hello');
则Node会依次在内置模块、全局模块和当前模块下查找hello.js
然后调用了它!!!
可以把launch.json文件改一下
{ // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch Program", "program": "${workspaceFolder}\\hello\\main.js" } ] }
将原来的hello.js改成main.js
然后点击蜘蛛(调试),点击上面绿色小三角 开始调试,就会打印出hello qinghai了。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CommonJS规范
这种模块加载机制被称为CommonJS规范。在这个规范下,每个.js
文件都是一个模块,它们内部各自使用的变量名和函数名都互不冲突,例如,hello.js
和main.js
都申明了全局变量var s = 'xxx'
,但互不影响。
一个模块想要对外暴露变量(函数也是变量),可以用module.exports = variable;
,一个模块要引用其他模块暴露的变量,用var ref = require('module_name');
就拿到了引用模块的变量。
结论
要在模块中对外输出变量,用:
module.exports = variable;
输出的变量可以是任意对象、函数、数组等等。
要引入其他模块输出的对象,用:
var foo = require('other_module');
引入的对象具体是什么,取决于引入模块输出的对象。
深入了解模块原理
当我们编写JavaScript代码时,我们可以申明全局变量:
var s = 'global';
在浏览器中,大量使用全局变量可不好。如果你在a.js
中使用了全局变量s
,那么,在b.js
中也使用全局变量s
,将造成冲突,b.js
中对s
赋值会改变a.js
的运行逻辑。
也就是说,JavaScript语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。
那Node.js是如何实现这一点的?
其实要实现“模块”这个功能,并不需要语法层面的支持。Node.js也并不会增加任何JavaScript语法。实现“模块”功能的奥妙就在于JavaScript是一种函数式编程语言,它支持闭包。如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。
请注意我们编写的hello.js
代码是这样的:
var s = 'Hello'; var name = 'world'; console.log(s + ' ' + name + '!');
Node.js加载了hello.js
后,它可以把代码包装一下,变成这样执行:
(function () { // 读取的hello.js代码: var s = 'Hello'; var name = 'world'; console.log(s + ' ' + name + '!'); // hello.js代码结束 })();
这样一来,原来的全局变量s
现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s
也互不干扰。
所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。
但是,模块的输出module.exports
怎么实现?
这个也很容易实现,Node可以先准备一个对象module
:
// 准备module对象: var module = { id: 'hello', exports: {} }; var load = function (module) { // 读取的hello.js代码: function greet(name) { console.log('Hello, ' + name + '!'); } module.exports = greet; // hello.js代码结束 return module.exports; }; var exported = load(module); // 保存module: save(module, exported);
可见,变量module
是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在hello.js
中可以直接使用变量module
原因就在于它实际上是函数的一个参数:
module.exports = greet;
通过把参数module
传递给load()
函数,hello.js
就顺利地把一个变量传递给了Node执行环境,Node会把module
变量保存到某个地方。
由于Node保存了所有导入的module
,当我们用require()
获取module时,Node找到对应的module
,把这个module
的exports
变量返回,这样,另一个模块就顺利拿到了模块的输出:
var greet = require('./hello');
以上是Node实现JavaScript模块的一个简单的原理介绍。
module.exports vs exports
很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量:
方法一:对module.exports赋值:
// hello.js function hello() { console.log('Hello, world!'); } function greet(name) { console.log('Hello, ' + name + '!'); } module.exports = { hello: hello, greet: greet };
方法二:直接使用exports:
// hello.js function hello() { console.log('Hello, world!'); } function greet(name) { console.log('Hello, ' + name + '!'); } function hello() { console.log('Hello, world!'); } exports.hello = hello; exports.greet = greet;
但是你不可以直接对exports
赋值:
// 代码可以执行,但是模块并没有输出任何变量: exports = {xxxxxxxxxxxxxx hello: hello, greet: greet };
如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:
首先,Node会把整个待加载的hello.js
文件放入一个包装函数load
中执行。在执行这个load()
函数前,Node准备好了module变量:
var module = { id: 'hello', exports: {} };
load()
函数最终返回module.exports
:
var load = function (exports, module) { // hello.js的文件内容 ... // load函数返回: return module.exports; }; var exported = load(module.exports, module);
也就是说,默认情况下,Node准备的exports
变量和module.exports
变量实际上是同一个变量,并且初始化为空对象{}
,于是,我们可以写:
exports.foo = function () { return 'foo'; }; exports.bar = function () { return 'bar'; };
也可以写:
module.exports.foo = function () { return 'foo'; }; module.exports.bar = function () { return 'bar'; };
换句话说,Node默认给你准备了一个空对象{}
,这样你可以直接往里面加东西。
但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports
赋值:
module.exports = function () { return 'foo'; };
给exports
赋值是无效的,因为赋值后,module.exports
仍然是空对象{}
。
结论
如果要输出一个键值对象{}
,可以利用exports
这个已存在的空对象{}
,并继续在上面添加新的键值;
如果要输出一个函数或数组,必须直接对module.exports
对象赋值。
所以我们可以得出结论:直接对module.exports
赋值,可以应对任何情况:
module.exports = { foo: function () { return 'foo'; } };
或者:
module.exports = function () { return 'foo'; };
最终,我们强烈建议使用module.exports = xxx
的方式来输出模块变量,这样,你只需要记忆一种方法。