3.Node中的模块系统
3.1node中的JavaScript
使用Node编写应用程序主要就是在使用:
- EcmaScript语言
- 和浏览器一样,但是不同的是,在Node中没有Bom和Dom
- 核心模块
node为JavaScript提供了很多服务器级别的API,这些API绝大多数都被包装到了一个具名的核心模块中了,例如:-
文件操作的fs
-
http服务操作的http
-
url路径操作模块
-
path路径处理模块
-
os操作系统信息
所有核心模块在使用的时候都必须手动的先使用
require
方法来加载,然后才可以使用,以后只要说这个模块是一个核心模块,你就要马上想到如果要使用它,就必须:
-
// 用来获取机器信息的
var os = require('os')
// 用来操作路径的
var path = require('path')
-
第三方模块
- art-template
- 必须通过npm来下载才可以使用
-
自己写的模块
- 自己创建的文件
-
用户自定义模块
- require (加载执行文件模块中的代码)
- exports
// require 是一个方法
// 它的作用就是用来加载模块的
// 在 Node 中,模块有三种:
// 具名的核心模块,例如 fs、http
// 用户自己编写的文件模块 js文件
// 相对路径必须加 ./
// 可以省略后缀名
// 相对路径中的 ./ 不能省略,否则报错
// 在 Node 中,没有全局作用域,只有模块作用域(文件作用域)
// 外部访问不到内部
// 内部也访问不到外部
// 默认都是封闭的
// 既然是模块作用域,那如何让模块与模块之间进行通信
// 有时候,我们加载文件模块的目的不是为了简简单单的执行里面的代码,更重要是为了使用里面的某个成员
// require 方法有两个作用:
// 1. 加载文件模块并执行里面的代码
// 2. 拿到被加载文件模块导出的接口对象
//
// 在每个文件模块中都提供了一个对象:exports
// exports 默认是一个空对象
// 你要做的就是把所有需要被外部访问的成员挂载到这个 exports 对象中
//a.js
var bExports = require('./b')
var fs = require('fs')
console.log(bExports.foo)
console.log(bExports.add(10, 30))
console.log(bExports.age)
bExports.readFile('./a.js')
fs.readFile('./a.js', function (err, data) {
if (err) {
console.log('读取文件失败')
} else {
console.log(data.toString())
}
})
//b.js
var foo = 'bbb'
// console.log(exports)
exports.foo = 'hello'
exports.add = function (x, y) {
return x + y
}
exports.readFile = function (path, callback) {
console.log('文件路径:', path)
}
var age = 18
exports.age = age
function add(x, y) {
return x - y
}
3.2什么是模块化
- 文件作用域(模块是独立的,在不同的文件使用必须要重新引用)【在node中没有全局作用域,它是文件模块作用域】
- 通信规则
- 加载require
- 导出exports
3.3web服务器开发
3.3.1ip地址和端口号
// ip 地址用来定位计算机
// 端口号用来定位具体的应用程序
// 所有需要联网通信的应用程序都会占用一个端口号
端口号的范围从0-65536之间
在计算机中有一些默认端口号,最好不要去使用
- 例如http服务的80
我们在开发过程中使用一些简单好记的就可以了,例如3000、5000等没什么含义(可以改,自己定)
可以同时开启多个服务,但一定要确保不同服务占用的端口号不一致才可以
说白了,在一台计算机上,同一个端口号同一时间只能被一个程序占用。
var http = require('http')
var server = http.createServer()
// 2. 监听 request 请求事件,设置请求处理函数
server.on('request', function (req, res) {
console.log('收到请求了,请求路径是:' + req.url)
console.log('请求我的客户端的地址是:', req.socket.remoteAddress, req.socket.remotePort)
res.end('hello nodejs')
})
server.listen(5000, function () {
console.log('服务器启动成功,可以访问了。。。')
})
3.3.2Content-Type
除了Content-Type可以用来指定编码,也可以在HTML页面中通过meta元数据来声明当前文本的编码格式,浏览器也会识别它。
<meta charset="UTF-8">
关于编码格式工具:https://tool.oschina.net/
https://tool.oschina.net/commons
// require
// 端口号
var http = require('http')
var server = http.createServer()
server.on('request', function (req, res) {
// 在服务端默认发送的数据,其实是 utf8 编码的内容
// 但是浏览器不知道你是 utf8 编码的内容
// 浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统的默认编码去解析
// 中文操作系统默认是 gbk
// 解决方法就是正确的告诉浏览器我给你发送的内容是什么编码的
// 在 http 协议中,Content-Type 就是用来告知对方我给你发送的数据内容是什么类型
// res.setHeader('Content-Type', 'text/plain; charset=utf-8')
// res.end('hello 世界')
var url = req.url
if (url === '/plain') {
// text/plain 就是普通文本,
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('hello 世界')
} else if (url === '/html') {
// 如果你发送的是 html 格式的字符串,则也要告诉浏览器我给你发送是 text/html 格式的内容
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('<p>hello html <a href="">点我</a></p>')//node当作是字符串,而浏览器会当作标签进行渲染解析 text/html
}
})
server.listen(3000, function () {
console.log('Server is running...')
})
//------------------------------------------------
// 1. 结合 fs 发送文件中的数据
// 2. Content-Type
// http://tool.oschina.net/commons
// 不同的资源对应的 Content-Type 是不一样的
// 图片不需要指定编码
// 一般只为字符数据才指定编码
var http = require('http')
var fs = require('fs')
var server = http.createServer()
server.on('request', function (req, res) {
// / index.html
var url = req.url
if (url === '/') {
// 肯定不这么干
// res.end('<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><h1>首页</h1></body>/html>')
// 我们要发送的还是在文件中的内容
fs.readFile('./resource/index.html', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('文件读取失败,请稍后重试!')
} else {
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end(data)
}
})
} else if (url === '/xiaoming') {
// url:统一资源定位符
// 一个 url 最终其实是要对应到一个资源的
fs.readFile('./resource/ab2.jpg', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('文件读取失败,请稍后重试!')
} else {
// data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
// res.end() 支持两种数据类型,一种是二进制,一种是字符串
// 图片就不需要指定编码了,因为我们常说的编码一般指的是:字符编码
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
}
})
}
})
server.listen(3000, function () {
console.log('Server is running...')
})
3.4CommonJS模块规范
在Node中的JavaScript还有一个重要的概念,模块系统。
-
模块作用域
-
使用require方法来加载模块
-
使用exports接口对象来导出模板中的成员
加载require
语法:var 自定义变量名 = require('模块')
作用:
- 执行被加载模块中的代码
- 得到被加载模块中的
exports
导出接口对象
导出exports
-
Node中是模块作用域,默认文件中所有的成员只在当前模块有效
-
对于希望可以被其他模块访问到的成员,我们需要把这些公开的成员都挂载到
exports
接口对象中就可以了导出多个成员(必须在对象中):
exports.a = 123;
exports.b = function(){
console.log('bbb')
};
exports.c = {
foo:"bar"
};
exports.d = 'hello';
导出单个成员(拿到的就是函数,字符串):
module.exports = 'hello';
以下情况会覆盖:
module.exports = 'hello';
//后者会覆盖前者
module.exports = function add(x,y) {
return x+y;
}
也可以通过以下方法来导出多个成员:
module.exports = {
foo = 'hello',
add:function(){
return x+y;
}
};
3.4.1exports 和 module.exports 的区别
+ 每个模块中都有一个 module 对象
+ module 对象中有一个 exports 对象
+ 我们可以把需要导出的成员都挂载到 module.exports 接口对象中
+ 也就是:
moudle.exports.xxx = xxx
的方式+ 但是每次都
moudle.exports.xxx = xxx
很麻烦,点儿的太多了+ 所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:
exports
+
exports === module.exports
结果为true
+ 所以对于:
moudle.exports.xxx = xxx
的方式 完全可以:exports.xxx = xxx
+ 当一个模块需要导出单个成员的时候(非对象),这个时候必须使用:
module.exports = xxx
的方式+ 不要使用
exports = xxx
不管用+ 因为每个模块最终向外
return
的是module.exports
+ 而
exports
只是module.exports
的一个引用***+ 所以即便你为
exports = xx
重新赋值,也不会影响module.exports
+ 但是有一种赋值方式比较特殊:
exports = module.exports
这个用来重新建立引用关系的***+ 之所以让大家明白这个道理,是希望可以更灵活的去用它
- Node 是一个比肩 Java、PHP 的一个平台
+ JavaScript 既能写前端也能写服务端(如art-template)
3.5模块原理
exports
和module.exports
的一个引用:
console.log(exports === module.exports); //true
exports.foo = 'bar';
//等价于
module.exports.foo = 'bar';
当给exports重新赋值后,exports!= module.exports.
最终return的是module.exports,无论exports中的成员是什么都没用。
3.6总结
// 引用服务
var http = require('http');
var fs = require('fs');
// 引用模板
var template = require('art-template');
// 创建服务
var server = http.createServer();
// 公共路径
var wwwDir = 'D:/app/www';
server.on('request', function (req, res) {
var url = req.url;
// 读取文件
fs.readFile('./template-apche.html', function (err, data) {
if (err) {
return res.end('404 Not Found');
}
fs.readdir(wwwDir, function (err, files) {
if (err) {
return res.end('Can not find www Dir.')
}
// 使用模板引擎解析替换data中的模板字符串
// 去xmpTempleteList.html中编写模板语法
var htmlStr = template.render(data.toString(), {
title: 'D:/app/www/ 的索引',
files:files
});
// 发送响应数据
res.end(htmlStr);
})
})
});
server.listen(3000, function () {
console.log('running....');
})
1.jQuery中的each 和 原生JavaScript方法forEach的区别:
提供源头:
原生js是es5提供的(不兼容IE8),
jQuery的each是jQuery第三方库提供的(如果要使用需要用2以下的版本也就是1.版本),它的each方法主要用来遍历jQuery实例对象(伪数组),同时也可以做低版本forEach的替代品,jQuery的实例对象不能使用forEach方法,如果想要使用必须转为数组([].slice.call(jQuery实例对象))才能使用
2.模块中导出多个成员和导出单个成员
3.301和302的区别:
301永久重定向,浏览器会记住
302临时重定向
4.exports和module.exports的区别:
每个模块中都有一个module对象
module对象中有一个exports对象
我们可以把需要导出的成员都挂载到module.exports接口对象中
也就是module.exports.xxx = xxx
的方式
但是每次写太多了就很麻烦,所以Node为了简化代码,就在每一个模块中都提供了一个成员叫exports
exports === module.exports
结果为true,所以完全可以exports.xxx = xxx
当一个模块需要导出单个成员的时候必须使用module.exports = xxx
的方式,=,使用exports = xxx
不管用,因为每个模块最终return的是module.exports,而exports只是module.exports的一个引用,所以exports
即使重新赋值,也不会影响module.exports
。
有一种赋值方式比较特殊:exports = module.exports
这个用来新建立引用关系的。
4.require的加载规则
深入浅出Nodejs(三),深入Nodejs的模块机制
如果想要了解更多底层细节,可以自行参考,《深入浅出Node.js》中的模块系统章节
https://www.infoq.cn/article/nodejs-module-mechanism/
-
核心模块
- 模块名
-
第三方模块
- 模块名
-
用户自己写的
- 路径
4.1require的加载规则
-
优先从缓存加载
-
判断模块标识符
-
核心模块(fs, http)
-
自己写的模块(路径形式的模块)
-
第三方模块(node_modules)
- 第三方模块的标识就是第三方模块的名称(不可能有第三方模块和核心模块的名字一致)
- npm
- 开发人员可以把写好的框架库发布到npm上
- 使用者通过npm命令来下载
- 使用方式:
var 名称 = require('npm install【下载包】 的包名')
- node_modules/express/package.json main
- 如果package.json或者main不成立,则查找被选择项:index.js
- 如果以上条件都不满足,则继续进入上一级目录中的node_modules按照上面的规则依次查找,直到当前文件所属此盘根目录都找不到最后报错
-
// 如果非路径形式的标识
// 路径形式的标识:
// ./ 当前目录 不可省略
// ../ 上一级目录 不可省略
// /xxx也就是D:/xxx
// 带有绝对路径几乎不用(D:/a/foo.js)
// 首位表示的是当前文件模块所属磁盘根目录
// require('./a');
// 核心模块
// 核心模块本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
require('fs');
// 第三方模块
// 凡是第三方模块都必须通过npm下载(npm i node_modules),使用的时候就可以通过require('包名')来加载才可以使用
// 第三方包的名字不可能和核心模块的名字是一样的
// 既不是核心模块,也不是路径形式的模块
// 先找到当前文所述目录的node_modules
// 然后找node_modules/art-template目录
// node_modules/art-template/package.json
// node_modules/art-template/package.json中的main属性
// main属性记录了art-template的入口模块
// 然后加载使用这个第三方包
// 实际上最终加载的还是文件
// 如果package.json不存在或者mian指定的入口模块不存在
// 则node会自动找该目录下的index.js
// 也就是说index.js是一个备选项,如果main没有指定,则加载index.js文件
//
// 如果条件都不满足则会进入上一级目录进行查找
// 注意:一个项目只有一个node_modules,放在项目根目录中,子目录可以直接调用根目录的文件
var template = require('art-template');
4.2模块标识符中的/
和文件操作路径中的/
文件操作路径:
// 咱们所使用的所有文件操作的API都是异步的
// 就像ajax请求一样
// 读取文件
// 文件操作中 ./ 相当于当前模块所处磁盘根目录
// ./index.txt 相对于当前目录
// /index.txt 相对于当前目录
// /index.txt 绝对路径,当前文件模块所处根目录
// d:express/index.txt 绝对路径
fs.readFile('./index.txt',function(err,data){
if(err){
return console.log('读取失败');
}
console.log(data.toString());
})
// 在文件操作的相对路径中
// ./data/a.txt 相对于当前目录
// data/a.txt 相对于当前目录,,文件操作中的相对路径可以省略 ./
// /data/a.txt 绝对路径,当前文件模块所处 磁盘根目录(比如如果是C盘,则相当于C:\data\a.txt)
// c:/xx/xx... 绝对路径
// fs.readFile('./data/a.txt', function (err, data) {
// if (err) {
// console.log(err)
// return console.log('读取失败')
// }
// console.log(data.toString())
// })
模块操作路径:
// 在模块加载中,相对路径中的./不能省略
// 这里省略了.也是磁盘根目录
require('./index')('hello')
// 在模块加载中,相对路径中的 ./ 不能省略
// Error: Cannot find module 'data/foo.js'
// require('data/foo.js') //模块标识(不一定是路径,核心模块,第三方模块,路径形式的模块)
// require('./data/foo.js')('hello') //.js可以省略,此处比文件操作先执行
5.npm
- node package manage(node包管理器)
- 通过npm命令安装jQuery包(npm install --save jquery),在安装时加上–save会主动生成说明书文件信息(将安装文件的信息添加到package.json里面)
- package.json包描述文件,包说明文件。
5.1 npm网站
http://npmjs.com 网站 是用来搜索npm包的
5.2 npm命令行工具
npm是一个命令行工具,只要安装了node
就已经安装了npm。
npm也有版本概念,可以通过npm --version
来查看npm的版本
升级npm(自己升级自己):
npm install --global npm
5.3 常用命令
- npm init(生成package.json说明书文件)
- npm init -y(-yes的简写,可以跳过向导,快速生成)
- npm install
- 一次性把dependencies选项中的依赖项全部安装
- 简写(npm i)
- npm install 包名
- 只下载
- 简写(npm i 包名)
- npm install --save 包名
- 下载并且保存依赖项(package.json文件中的
dependencies
选项) - 简写(npm i -S 包名)
- 下载并且保存依赖项(package.json文件中的
- npm uninstall 包名
- 只删除,如果有依赖项会依然保存
- 简写(npm un 包名)
- npm uninstall --save 包名
- 删除的同时也会把依赖信息全部删除
- 简写(npm un -S 包名)
- npm help
- 查看使用帮助
- npm 命令 --help
- 查看具体命令的使用帮助(npm uninstall --help)
5.4 解决npm被墙问题
npm存储包文件的服务器在国外,有时候会被墙,速度很慢,所以需要解决这个问题。
https://developer.aliyun.com/mirror/NPM?from=tnpm淘宝的开发团队把npm在国内做了一个镜像(也就是一个备份)。
http://npm.taobao.org/
安装淘宝的cnpm:
npm install -g cnpm --registry=https://registry.npm.taobao.org;
#在任意目录执行都可以
#--global表示安装到全局,而非当前目录
#`--global`不能省略,否则不管用
npm install --global cnpm
安装包的时候把以前的npm
替换成cnpm
。
#走国外的npm服务器下载jQuery包,速度比较慢
npm install jQuery;
#使用cnpm就会通过淘宝的服务器来下载jQuery
cnpm install jQuery;
如果不想安装
cnpm
又想使用淘宝的服务器来下载:–registry=https://registry.npm.taobao.org
npm install jquery --registry=https://registry.npm.taobao.org;
但是每次手动加参数就很麻烦,所以我们可以把这个选项加入到配置文件
中:
npm config set registry https://registry.npm.taobao.org;
#查看npm配置信息
npm config list;
只要经过上面的配置命令
,则以后所有的npm install
都会通过淘宝的服务器来下载
6.package.json
每一个项目都要有一个package.json
文件(包描述文件,就像产品的说明书一样)
--save
会在package.json
中产生dependencies依赖项。不加就不会产生。
这个文件可以通过npm init
自动初始化出来
D:\code\node中的模块系统>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (node中的模块系统)
Sorry, name can only contain URL-friendly characters.
package name: (node中的模块系统) cls
version: (1.0.0)
`description`: 这是一个测试项目
entry point: (main.js)
test command:
git repository:
keywords:
author: xiaochen
license: (ISC)
About to write to D:\code\node中的模块系统\package.json:
{
"name": "cls",
"version": "1.0.0",
"description": "这是一个测试项目",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "xiaochen",
"license": "ISC"
}
Is this OK? (yes) yes
对于目前来讲,最有用的是dependencies
选项,可以用来帮助我们保存第三方包的依赖信息。
如果node_modules
删除了也不用担心,只需要在控制面板中npm install
(自动找到package.json
)就会自动把package.json
中的dependencies
中所有的依赖项全部都下载回来(重新生成node_modules
文件夹)。
- 建议每个项目的根目录下都有一个
package.json
文件 (最重要的作用是用来保存依赖项) - 建议执行
npm install 包名
的时候都加上--save
选项,目的是用来保存依赖信息
6.1package.json和package-lock.json
npm 5以前是不会有package-lock.json
这个文件
npm5以后才加入这个文件
当你安装包的时候,npm都会生成或者更新package-lock.json
这个文件
- npm5以后的版本安装都不要加
--save
参数,它会自动保存依赖信息 - 当你安装包的时候,会自动创建或者更新
package-lock.json
文件 package-lock.json
这个文件会包含node_modules
中所有包的信息(版本,下载地址。。。)- 这样的话重新
npm install
的时候速度就可以提升
- 这样的话重新
- 从文件来看,有一个
lock
称之为锁- 这个
lock
使用来锁版本的 - 如果项目依赖了
1.1.1
版本 - 如果你重新install其实会下载最细版本,而不是
1.1.1
package-lock.json
的另外一个作用就是锁定版本号,防止自动升级
- 这个
6.2path路径操作模块
参考文档:https://nodejs.org/docs/latest-v13.x/api/path.html
- path.basename:获取路径的文件名,默认包含扩展名
- path.dirname:获取路径中的目录部分
- path.extname:获取一个路径中的扩展名部分
- path.parse:把路径转换为对象
- root:根路径
- dir:目录
- base:包含后缀名的文件名
- ext:后缀名
- name:不包含后缀名的文件名
- path.join:拼接路径
- path.isAbsolute:判断一个路径是否为绝对路径