node 程序传递参数
正常情况下执行一个node程序,直接跟上我们对应的文件即可:
node index.js
但是,在某些情况下执行node程序的过程中,我们可能希望给node传递一些参数︰
node index.js kouwenjie age=18
获取参数是在process的内置对象中的 其中的argv属性:
console.log(process.argv)
// 输出结果如下:
/*
argv: [
'C:\\Program Files\\nodejs\\node.exe',
'D:\\BaiduNetdiskDownload\\NodeJs coderwhy\\node笔记\\01_给Node传递参数\\index.js',
'kouwenjie',
'age=18'
],
*/
node的输出
console.log() // 最常用的输出方式
console.clear() // 清空控制台
console.trace() // 打印函数的调用栈
还有一些其他的方法,其他的一些console方法: nodejs.cn/api/console…
全局对象
特殊的全局对象
-
这些全局对象实际上是模块中的变量,只是每个模块都有,看来像是全局变量;
-
在命令行交互中是不可以使用的;
-
包括:__dirname、__filename、exports、module、require();
__dirname
获取当前文件所在路径:
注意:不包括后面的文件名
__filename
获取当前文件所在的路径和文件名称
注意:包括后面的文件名称
常见的全局对象
process对象
process提供了Node进程中相关的信息:比如Node的运行环境、参数信息等
console对象
提供了简单的调试控制台,在前面讲解输入内容时已经学习过了
定时器函数
// 1000毫秒后执行一次
setTimeout(() => {
console.log("setTimeout")
}, 1000)
// 1000毫秒重复执行一次
setInterval(() => {
console.log("setInterval")
}, 1000)
// 立即执行
setImmediate(() => {
console.log("setImmediate")
})
// 添加到下一次tick队列中
process.nextTick(() => {
console.log("process.nextTick")
})
/*
输出顺序
process.nextTick
setImmediate
setTimeout
setInterval
*/
// 后两个涉及到事件循环的问题,后续进行讲解
global和window的区别
在浏览器中,全局变量都是在window上的,比如有document、setInterval、setTimeout、alert、console等等
在Node中,我们也有一个global属性,并且看起来它里面有很多其他对象
- 在浏览器中执行的JavaScript代码,如果我们在顶级范围内通过var定义的一个属性,默认会被添加到window对象上:
var name = 'kouwenjie'
console.log(window.name) // kouwenjie
- 但是在node中,我们通过var定义一个变量,它只是在当前模块中有一个变量,不会放到全局global中:
var name = 'kouwenjie'
console.log(name) // undefined
JavaScript模块化
什么是模块化呢?
- 事实上模块化开发最终的目的是将程序划分成一个个小的结构;
- 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构
- 这个结构可以将自己**希望暴露的变量、函数、对象等导出****给其结构使用;
- 也可以通过某种方式,导入另外结构中的变量、函数、对象等;
CommonJS
介绍
我们需要知道CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJS,后来为了体现它的广泛性,修改为CommonJS,平时我们也会简称为CJS。
所以,Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发∶
- 在Node中每一个js文件都是一个单独的模块;
- 这个模块中包括CommonJS规范的核心变量:exports、module.exports、require ;
- 我们可以使用这些变量来方便的进行模块化开发;
前面我们提到过模块化的核心是导出和导入,Node中对其进行了实现︰
- exports 和 module.exports 可以负责对模块中的内容进行导出;
- require 函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;
exports
// bar.js
const name = "kouwenjie"
const age = 18
function sayHello(name) {
console.log("Hello " + name)
}
exports.name = name
exports.age = age
exports.sayHello = sayHello
require
// main.js
/**
* node 中实现 CommonJs 的本质就是对象的引用赋值(浅拷贝)
* exports {name,age,sayHello}
*/
// bar = {name,age,sayHello}
const bar = require("./bar")
console.log(bar.name)
console.log(bar.age)
bar.sayHello("zs")
// 如果修改导入的name,bar.js中的 exports.name 也会被修改了
setTimeout(() => {
bar.name = "我被修改了"
}, 1000)
module.exports
我们通常导出东西时,使用的是 module.exports;
module.exports 和 exports 的区别:
- 为了实现模块的导出,Node中使用的是 Module 的类,每一个模块都是 Module 的一个实例,也就是 module ;
- 所以在Node中真正用于导出的其实根本不是 exports ,而是 module.exports ;
- 因为 module 才是导出的真正实现者;
但是,为什么 exports 也能导出呢?
- 这是因为 module 对象的 exports 属性是 exports 对象的一个引用;
- 也就是说 module.exports = exports = main 中的 bar ;
// bar.js
const name = "kouwenjie"
const age = 18
function sayHello(name) {
console.log("Hello " + name)
}
// 修改不会对导入产生影响 因为 exports 导出的被 module.exports= {} 覆盖了
setTimeout(() => {
exports.name = "jack"
}, 1000)
exports.name = name
exports.age = age
exports.sayHello = sayHello
// 当使用 module.exports= {} 时,上面 exports 导出的东西会被覆盖
module.exports = {
name: "kwj",
age: 88,
sayHello: function (name) {
console.log("hello---" + name)
},
}
require 细节
我们使用 require 的导入格式是 require(x) ,这里我总结比较常见的查找规则:
情况一:x 是一个核心模块,比如 path、fs、http
- 直接返回核心模块停止查找
情况二:x 是以 ./ 或 ../ 或 /(根目录)开头的
第一步:将 x 当做一个文件夹在对应的目录查找;
- 如果有后缀名,按照后缀名的格式查找对应的文件
- 如果没有后缀名,会按照如下顺序:
- 直接查找文件 x
- 查找 x.js 文件
- 查找 x.json 文件
- 查找 x.node 文件
第二步:没有找到对应的文件,将 x 当做一个目录
- 查找目录下面的 index 文件
- 查找 x/index.js 文件
- 查找 x/index.json 文件
- 查找 x/index.node 文件
情况三:直接是一个 x (没有路径),并且 x 不是核心模块
- 会查找文件夹中的 node_modules 里有没有对应的模块,一直向上层文件夹查找
- 如果一直没有找到,就会报错 :not found
模块的加载过程
结论一:
模块在被第一次引入时,模块中的 js 代码会执行一次
结论二:
模块被多次引入时,会缓存,最终只加载(运行) 一次
- 为什么只会加载运行一次呢?
- 这是因为每个模块对象 module 都有一个属性: loaded。
- 为 false 表示还没有加载,为 true 表示已经加载;
结论三∶
如果有循环引入,那么加载顺序是什么?
如果出现下图模块的引用关系,那么加载顺序是什么呢?
- 这个其实是一种数据结构∶图结构;
- 图结构在遍历的过程中,有深度优先搜索(DFS, depth first search )和广度优先搜索(BFS, breadth first search ) ;
- Node采用的是深度优先算法: main -> aaa -> ccc -> ddd -> eee ->bbb
CommomJs规范的缺点
-
CommonJS加载模块是同步的:
- 同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行;
- 这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;
-
如果将它应用于浏览器呢?
-
浏览器加载js文件需要先从服务器将文件下载下来,之后在加载运行;
-
那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作;
-
所以在浏览器中,我们通常不使用CommonJS规范︰
- 当然在webpack中使用CommonJS是另外一回事;
- 因为它会将我们的代码转成浏览器可以直接执行的代码;
-
在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD :
- 但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换;
- AMD和CMD已经使用非常少了;
ES Module
ES Module模块采用export和import关键字来实现模块化:
- export负责将模块内的内容导出;
- import负责从其他模块导入内容;
export 导出
有三种导出方式
-
方式一:
export const name = 'kouwewnjie' export const age = 18 export const sayHello = function(name){ console.log('hello' + name) } -
方式二:
// {} 大括号,但是不是一个对象 // {} 放置要导出的的变量的引用列表 export { name, age, sayHello } -
方式三:
// {} 导出时,可以用 as 给变量取别名 export { name as kName, age as kAge, sayHello as ksayHello }
import 导入
有三种导入方式
-
方式一:
import { name,age,sayHello } from './modules/foo.js' -
方式二:
// 导入时也能用 as 取别名 import { name as kName,age as kAge,sayHello as ksayHello } from './modules/foo.js' -
方式三:
import { * as foo } from './modules/foo.js' console.log(foo.name) console.log(foo.age) foo.sayHello('kouwenjie')
export 和 import 结合使用
export { name,age,sayHello } from './modules/foo.js'
export default 默认导出
- 默认导出 export 时可以不需要指定名字;
- 在导入时不需要使用 { },并且可以自己来指定名字;
// foo.js
export default function sayHello(name){
console.log('hello ' + name)
}
// main.js
import say from './foo.js'
say('world') // hello world
注意:在一个模块中,只能有一个默认导出 ( export default ) ;
import() 函数
let flag = true
// 不能使用这种写法
if(flag){
import { name,age } from './foo.js'
}
// 可以使用这种写法
if(flag){
// import() 函数返回一个 Promise 对象
import('./foo.js').then(res=>{
console.log(res.name)
console.log(res.age)
}).catch(err=>{
console.log(err)
})
}
Node 对 ES Module 的支持
-
方式一:在 package.json 中配置 type: module(后续学习,我们现在还没有讲到 package.json 文件的作用);
-
方式二:文件以 .mjs 结尾,表示使用的是ES Module ;