node中是没有window这样一个全局对象的,有的是global
全局对象有很多,不需要全部都学,因为有的不常用
有的不用特别学是因为用着用着就会了
比较重要的有:module、export、require(模块化)、buffer
这个对象特殊在哪?
这些全局对象特殊就特殊在是模块中的变量,只是每个模块都有,像是全局变量
在命令行中是不可以使用的
包括:_dirname、_filename、exports、module、require()
//类似于window的全局对象
console.log(global)
// 特殊的全局对象
// 当前文件所在的目录结构
console.log(__dirname)
// 当前目录+文件名
console.log(__filename)
// 模块化
console.log(module)
console.log(exports)
console.log(require)
// 进程
console.log(process)
// 定时器方法
setTimeout(()=>{
console.log("setTimeout")
},3000);
setInterval(()=>{
console.log("setInterval")
},3000);
// 立即执行的
setImmediate(()=>{
console.log("setImmediate")
});
// 额外执行函数
process.nextTick(()=>{
console.log("nextTick")
});
global是一个全局对象,process、console、setTimeout等都有被放到global中
在新的 标准中还有一个globalThis也是指向全局对象的
类似于浏览器中的window
但是又有区别
如果你用var定义变量是会被自动添加到window中的
但是不会被添加到global中
由于两个不一样容易记混,所以在新标准中他们有一个共同的名字:globalThis
模块化开发最终的目的是将程序划分成一个个小的结构
在这个结构中编写属于自己的逻辑代码,有自己的作用域,定义变量名词的时候不会影响到其他的结构
这个结构可以将自己希望暴露的变量函数对象等导出给其结构使用
也可以通过某种方式导入另外结构中的变量、函数、对象等
按照结构划分开发程序的过程就是模块化开发的过程
举个例子:
const moduleA = (function(){
let name="why"
let age=18
let height=1.88
console.log(name)
return {
name,
age,
height
}
}())
console.log(moduleA.name)
这就是设计的一种模块化的方案
但是如果有新人来了不知道你怎么做的就很难受
即没有一个统一的标准
直至ES6官方推出了一个自己的模块化方案:ESModule
CommonJS是一个规范,最初提出来是在浏览器以外的地方使用,当时被命名为ServerJS,后来为了体现它的广泛性,修改为了CommonJS,平时我们也会简称为CJS
Node是CommonJS在服务器端一个具有代表性的实现
Browerserify是 CommonJS在浏览器中的一种实现
webpack打包工具具备对CommonJS的支持和转换
Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以更方便的进行模块化开发
在Node中每一个js文件都是一个单独的模块
包括CommonJS规范的 核心变量:exports、module.exports、require
可以用这些变量方便的进行模块化开发
exports和module.exports可以负责对模块中的内容进行导出
require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容
举个例子:
const UTIL_NAME = "util_name"
function formatCount(){
return "200"
}
function formatDate(){
return "2022-01-01"
}
exports.UTIL_NAME = UTIL_NAME
exports.formatCount = formatCount
exports.formatDate = formatDate
这样就可以成功的导出
const util = require("./util.js")
console.log(util.UTIL_NAME)
console.log(util.formatCount())
导入是这样的
exports是一个对象,可以在这个对象中添加很多个属性,添加的属性可以导出
const util = require("./util.js")
这行代码完成的操作是main中的bar变量就等于exports对象
require通过各种查找方式最终找到了exports这个对象
并且将这个exports对象赋值给了bar变量
所以bar变量就是exports对象了
举个例子,这是bar.js:
let name = "bar"
exports.name = name
setTimeout(()=>{
exports.name = "why"
},3000)
// const util = require("./util.js")
// console.log(util.UTIL_NAME)
// console.log(util.formatCount())
const bar = require("./bar.js")
console.log(bar.name)
//4s后重新获取home
setTimeout(()=>{
console.log(bar.name)
},4000)
这是把导出的变量改变,所以输出的结果也会随之而改变
还有另一个实例
这是main.js:
const bar = require("./bar.js")
console.log(bar.name)
setTimeout(()=>{
bar.name = "why"
},3000)
介素bar.js:
let name = "bar"
exports.name = name
setTimeout(()=>{
console.log(exports.name)
},4000)
最后打印出来依然是bar、why
这个赋值本质上是引用赋值
这种导出是用的比较多的一种
具体这样使用:
const name = "foo"
const age = 18
function sayHello(){
console.log("hello")
}
module.exports.name = name
module.exports.age = age
module.exports.sayHello = sayHello
const foo=require("./foo")
console.log(foo.name)
console.log(foo.age)
console.log(foo.sayHello())
他和普通的exports导出有什么区别捏?
这张图可以解释他们的结构
可以看出在做修改的时候是一样的,那么为什么有两种写法呢?这个module对象 又好在哪呢
首先一个重要的结论:Node导出的本质是在导出module.exports对象
在开发中的常见写法是这样的:
module.exports = {
}
这样的写法是创建新对象
那么为什么还要有exports呢?
还是看CommonJS的规范
CommonJS中是没有module.exports的概念的
但是为了实现模块的导出,Node中使用的是Module的类,每一个模块都是Module的一个实例,也就是module
在Node中真正用于导出的不是exports,而是module.exports
因为module才是导出的真正实现者
为什么exports也可以导出呢?
是因为module对象的 exports属性是exports对象的 一个引用
所以module.exports = exports = main中的bar
讲一下require的细节
有些情况下路径是可以省掉的
我们在本地的路径下先写一个这样的结构,在main.js中使用这个导出的函数,可以发现:
const utils = require("./utils")
console.log(utils.formatDate())
不用写特别完全的路径
这和require的查找规则有关(require X):
1.当X是一个Node的核心模块,比如path、http:直接返回核心模块并停止查找
2.X以./、../、或者/开头:
第一步:
将X当做一个文件在对应的目录下查找(如果有后缀名则按照后缀名格式查找对应的文件,如果没有则按照这个顺序查找:直接查找文件X-->查找X.js-->查找X.json-->查找X.node)
第二步:
没有找到对应的文件,将X作为一个目录
查找目录下面的index文件
查找X/index.js文件
查找X/index.json文件
查找X/index.node文件
如果还没找到那就报错:not found
3.X既不是路径也不是核心模块:
先在当前目录的 node_modules下找,再逐层向上的去对应的nodemodules上找
直至找到根目录下的node_modules
如果都没找到那就报错:not found
模块在第一次被引入的时候模块中的js代码会被运行一次
被多次引入时会缓存,最终只加载(运行)一次
为什么呢?
因为每个模块对象module都有一个属性:loaded
为false表示还没有加载,为true表示已经加载
如果有循环引入的话加载顺序是什么?
这是图结构
图结构在遍历的过程中,有深度优先搜索和广度优先搜索
Node采用的是深度优先算法
首先它的加载是同步的,同步就意味着只有等到对应的模块加载完毕,当前模块中的内容才能运行
这个在服务器是没问题的(因为服务器加载的js文件都是本地文件,加载速度很快)
如果将它应用于浏览器
浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行
采用同步就意味着后序的 js代码无法正常运行,即使是简单的DOM操作
所以在浏览器中通常不使用CommonJS规范
在webpack中使用CommonJS是另外一回事
因为它会把代码转成浏览器可以直接执行的
这是应用于浏览器的一种规范,是异步加载模块
首先是下载require.js
然后是定义html的script标签引入require和定义入口文件
data-main属性的作用是在加载完src的文件后加载执行该文件
CMD也是应用于浏览器的一种模块化规范
采用的是异步加载模块,将CommonJS的优点吸收了过来
CMD的自己比较优秀的实现方案是SeaJS
也是先下载后使用