nodejs 学习笔记 (一)

281 阅读8分钟

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 当做一个文件夹在对应的目录查找;

  • 如果有后缀名按照后缀名的格式查找对应的文件
  • 如果没有后缀名,会按照如下顺序:
    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 文件

情况三:直接是一个 x (没有路径),并且 x 不是核心模块

  • 会查找文件夹中的 node_modules 里有没有对应的模块,一直向上层文件夹查找
  • 如果一直没有找到,就会报错 :not found

模块的加载过程

结论一:

模块在被第一次引入时,模块中的 js 代码会执行一次

结论二:

模块被多次引入时,会缓存,最终只加载(运行) 一次

  • 为什么只会加载运行一次呢?
  • 这是因为每个模块对象 module 都有一个属性: loaded。
  • 为 false 表示还没有加载,为 true 表示已经加载;
结论三∶

如果有循环引入,那么加载顺序是什么?

如果出现下图模块的引用关系,那么加载顺序是什么呢?

image-20220403145720302.png

  1. 这个其实是一种数据结构∶图结构;
  2. 图结构在遍历的过程中,有深度优先搜索(DFS, depth first search )和广度优先搜索(BFS, breadth first search ) ;
  3. Node采用的是深度优先算法: main -> aaa -> ccc -> ddd -> eee ->bbb

CommomJs规范的缺点

  • CommonJS加载模块是同步的:

    • 同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行;
    • 这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;
  • 如果将它应用于浏览器呢?

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

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

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

    • 当然在webpack中使用CommonJS是另外一回事;
    • 因为它会将我们的代码转成浏览器可以直接执行的代码;
  • 在早期为了可以在浏览器中使用模块化,通常会采用AMDCMD :

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

ES Module

ES Module模块采用export和import关键字来实现模块化:

  • export负责将模块内的内容导出;
  • import负责从其他模块导入内容;

export 导出

有三种导出方式

  1. 方式一:

    export const name = 'kouwewnjie'
    export const age = 18
    export const sayHello = function(name){
        console.log('hello' + name)
    }
    
  2. 方式二:

    // {} 大括号,但是不是一个对象
    // {} 放置要导出的的变量的引用列表
    export {
    	name,
        age,
        sayHello
    }
    
  3. 方式三:

    // {} 导出时,可以用 as 给变量取别名
    export {
    	name as kName,
        age as kAge,
        sayHello as ksayHello
    }
    

import 导入

有三种导入方式

  1. 方式一:

    import { name,age,sayHello } from './modules/foo.js'
    
  2. 方式二:

    // 导入时也能用 as 取别名
    import { name as kName,age as kAge,sayHello as ksayHello } from './modules/foo.js'
    
  3. 方式三:

    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 ;