目录
dependencies 和 devDependencies
Node.js 介绍
Node.js 是什么
以下引自 Node.js 官网:
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
- Node.js 是一个构建于 Chrome V8 JavaScript 引擎) 之上的一个 JavaScript 运行时
- Node.js 采用 事件驱动,非阻塞IO模型 使得它轻量和高效
- Node.js 包生态系统 npm 是世界上最大的开源库生态系统
Node.js 不是一种独立的语言,与 PHP、Python、Perl、Ruby 的“既是语言也是平台”不同。Node.js 也不是一个 JavaScript 框架,不同于 CakePHP、Django、Rails。Node.js 更不是浏览器端的库,不能与 jQuery、ExtJS 相提并论。Node.js 是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为脚本语言世界的一等公民,在服务端堪与 Java、PHP、Python、Perl、Ruby 平起平坐。
Node 的特点
- 事件驱动
- 非阻塞 IO 模型
- 单线程
- 跨平台
Node 发展历史
为什么要学习 Node
- 企业需求
- 进一步理解 Web
- 大前端必备技能
- 为了更好的学习前端框架
- 增加职业竞争力
Node 能做什么
知乎 - JavaScript能做什么,该做什么? Atwood's Law: any application that can be written in JavaScript, will eventually be written in JavaScript. 凡是能用 JavaScript 写出来的,最终都会用 JavaScript写出来。
- Web 服务器
- 命令行工具
- 网络爬虫
- 桌面应用程序开发(Electron)
- ......
这门课程你能学到啥?
- 更进一步的理解 B/S 编程模型
- 模块化编程
- Node常用API
- JavaScript 异步编程
- 回调函数
- Promise
- Generator
- async/await 函数
- Express Web 开发框架
- Ecmascript 6
- ...
一些资源
- Node.js 官方文档
- Node.js 中文文档(非官方)
- 深入浅出 Node.js
- Node.js 权威指南
- Node.js 实战
- Node.js 实战
- Node.js实战(第2季)
- Node.js 中文社区
- Node.js 包教不包会
- EcmaScript 6 入门
- 七天学会 NodeJS
- ......
起步
预备知识
- HTML
- CSS
- JavaScript
- 掌握基本的命令行操作
- 具有服务端开发经验更佳
安装 Node 环境
- 下载:https://nodejs.org/en/download/
- 安装
- 傻瓜式的一路
next
就可以了 - 对于已经装过的,重新安装就会升级
- 傻瓜式的一路
- 确认 Node 环境是否安装成功
- 打开命令行,输入
node --version
- 或者
node -v
- 打开命令行,输入
REPL
- read
- eval
- loop
类似于浏览器中的 Console ,可以做一些代码测试。
Hello World
1. 新建一个 hello.js 并写入以下示例代码
var message = 'Hello Node.js!'
console.log(message)
2. 打开命令行并定位到 hello.js
文件所属目录
3. 在命令行中输入 node hello.js
回车执行
注意:
- 文件名不要起名为
node.js
- 文件名或者文件路径最好不要有中文
- 文件路径或者文件名不要出现空格
文件读写
文件读取:
const fs = require('fs')
fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});
文件写入:
const fs = require('fs')
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
if (err) throw err;
console.log('The file has been saved!');
});
HTTP 服务
// 接下来,我们要干一件使用 Node 很有成就感的一件事儿
// 你可以使用 Node 非常轻松的构建一个 Web 服务器
// 在 Node 中专门提供了一个核心模块:http
// http 这个模块的职责就是帮你创建编写服务器的
// 1. 加载 http 核心模块
var http = require('http')
// 2. 使用 http.createServer() 方法创建一个 Web 服务器
// 返回一个 Server 实例
var server = http.createServer()
// 3. 服务器要干嘛?
// 提供服务:对 数据的服务
// 发请求
// 接收请求
// 处理请求
// 给个反馈(发送响应)
// 注册 request 请求事件
// 当客户端请求过来,就会自动触发服务器的 request 请求事件,然后执行第二个参数:回调处理函数
server.on('request', function () {
res.end('Hello Node.js!')
})
// 4. 绑定端口号,启动服务器
server.listen(3000, function () {
console.log('服务器启动成功,请求访问 http://127.0.0.1:3000/')
})
核心概念
Node.js 中的 JavaScript
模块化
异步操作
模块系统
什么是模块化
现实角度(手机、电脑、活动板房):
- 生产效率高
- 可维护性好
程序角度(就是把大一个文件中很多的代码拆分到不同的小文件中,每个小文件就称之为一个模块,例如我们看到的 jQuery 真正的源码)
- 开发效率高(不需要在一个文件中翻来翻去,例如 jQuery 不可能在一个文件写 1w+ 代码,按照功能划分到不同文件中)
- 可维护性好(哪个功能出问题,直接定位该功能模块即可)
模块化的概念有了,那程序中的模块到底该具有哪些特性就满足我们的使用了呢?
- 模块作用域
- 好处就是模块不需要考虑全局命名空间冲突的问题
- 模块通信规则
- 所有模块如果都是封闭自然不行,我们需要让组件与组件相互组织联系起来,例如 CPU 需要读取内存中的数据来进行计算,然后把计算结果又交给了我们的操作系统
- 所以我们的模块也是需要具有通信的能力的
- 所谓的通信说白了也就是输入与输出
下面我们具体来看一下在 Node.js 中如何在多模块之间进行输入与输出。
Node 中的模块分类
在开始了解具体的规则之前,我们先来了解一下在 Node 中对不模块的一个具体分类,一共就三种类别:
- 核心模块
- 由 Node 本身提供,具名的,例如 fs 文件操作模块、http 网络操作模块
- 第三方模块
- 由第三方提供,使用的时候我们需要通过 npm 进行下载然后才可以加载使用,例如我们使用过的
mime
、art-template
、marked
- 注意:不可能有第三方包的名字和核心模块的名字是一样的,否则会造成冲突
- 由第三方提供,使用的时候我们需要通过 npm 进行下载然后才可以加载使用,例如我们使用过的
- 用户模块(自己写的)
- 我们在文件中写的代码很多的情况下不好编写和维护,所以我们可以考虑把文件中的代码拆分到多个文件中,那这些我们自己创建的文件就是用户模块
核心模块
Node 中都以具名的方式提供了不同功能的模块,例如操作文件就是:fs
核心模块(系统模块)由 Node 提供,使用的时候都必须根据特定的核心模块名称来加载使用。例如使用文件操作模块:fs
var fs = require('fs')
// fs.readFile
// fs.writeFile
// fs.appendFile
模块名称 | 作用 |
---|---|
fs | 文件操作 |
http | 网络操作 |
path | 路径操作 |
url | url 地址操作 |
os | 操作系统信息 |
net | 一种更底层的网络操作方式 |
querystring | 解析查询字符串 |
util | 工具函数模块 |
... | ... |
模块通信之输入 require
// 核心模块
var fs = require('fs')
// 第三方模块
// npm install marked
var marked = require('marked')
// 用户模块(自己写的),正确的,正确的方式
// 注意:加载自己写的模块,相对路径不能省略 ./
var foo = require('./foo.js')
// 用户模块(自己写的),正确的(推荐),可以省略后缀名 .js
var foo = require('./foo')
模块通信输出 exports
导出多个成员:写法一(麻烦,不推荐):
// 导出多个成员:写法一
module.exports.a = 123
module.exports.b = 456
module.exports.c = 789
导出多个成员:写法二(推荐)
Node 为了降低开发人员的痛苦,所以为 module.exports
提供了一个别名 exports
(下面协大等价于上面的写法)。
console.log(exports === module.exports) // => true
exports.a = 123
exports.b = 456
exports.c = 789
exports.fn = function () {
}
导出多个成员:写法三(代码少可以,但是代码一多就不推荐了):
// module.exports = {
// d: 'hello',
// e: 'world',
// fn: function () {
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// // fs.readFile(function () {
// // })
// }
// }
导出单个成员:(唯一的写法):
// 导出单个成员:错误的写法
// 因为每个模块最终导出是 module.exports 而不是 exports 这个别名
// exports = function (x, y) {
// return x + y
// }
// 导出单个成员:必须这么写
module.exports = function (x, y) {
return x + y
}
注意:导出单个只能导出一次,下面的情况后者会覆盖前者:
module.exports = 'hello'
// 以这个为准,后者会覆盖前者
module.exports = function (x, y) {
return x + y
}
为什么 exports = xxx
不行
exports 和 module.exports
的一个引用:
function fn() {
// 每个模块内部有一个 module 对象
// module 对象中有一个成员 exports 也是一个对象
var module = {
exports: {}
}
// 模块中同时还有一个成员 exports 等价于 module.exports
var exports = module.exports
console.log(exports === module.exports) // => true
// 这样是可以的,因为 exports === module.exports
// module.exports.a = 123
// exports.b = 456
// 这里重新赋值不管用,因为模块最后 return 的是 module.exports
// exports = function () {
// }
// 这才是正确的方式
module.exports = function () {
console.log(123)
}
// 最后导出的是 module.exports
return module.exports
}
var ret = fn()
console.log(ret)
exports 和 module.exports 的区别
- exports 和 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
的方式 完全可以:expots.xxx = xxx
- 当一个模块需要导出单个成员的时候,这个时候必须使用:
module.exports = xxx
的方式 - 不要使用
exports = xxx
不管用 - 因为每个模块最终向外
return
的是 module.exports - 而
exports
只是module.exports
的一个引用 - 所以即便你为
exports = xx
重新赋值,也不会影响module.exports
- 但是有一种赋值方式比较特殊:
exports = module.exports
这个用来重新建立引用关系的 - 之所以让大家明白这个道理,是希望可以更灵活的去用它
特殊的导出方式
exports = module.exports = function () {
console.log('默认函数被调用了')
}
exports.ajax = function () {
console.log('ajax 方法被调用了')
}
exports.get = function () {
console.log('get 方法被调用了')
}
深入模块加载机制
探索模块原理
包与 npm
npm
npm 全称 Node Package Manager
,它的诞生是为了解决 Node 中第三方包共享的问题。 和浏览器一样,由于都是 JavaScript,所以前端开发也使用 npm 作为第三方包管理工具。 例如大名鼎鼎的 jQuery、Bootstrap 等都可以通过 npm 来安装。 所以官方把 npm 定义为 JavaScript Package Manager
。
npm 的两层含义
npm 网站
npmjs.com,提供了存放数据包的能力,类似于 Github ,但是不提供版本管理
开发者可以把一些 JavaScript 相关工具放到这个系统中
npm 命令行工具
npm 的第二层含义就是一个命令行工具,只要你安装了 node 就已经安装了 npm。
npm 也有版本这个概念。
可以通过在命令行中输入:
npm --version
升级 npm(自己升级自己):
npm install --global npm
常用命令
# 在项目中初始化一个 package.json 文件
# 凡是使用 npm 来管理的项目都会有这么一个文件
npm init
# 跳过向导,快速生成 package.json 文件
# 简写是 -y
npm init --yes
# 一次性安装 dependencies 中所有的依赖项
# 简写是 npm i
npm install
# 安装指定的包,可以简写为 npm i 包名
# npm 5 以前只下载,不会保存依赖信息,如果需要保存,则需要加上 `--save` 选项
# npm 5 以后就可以省略 --save 选项了
npm install 包名
# 一次性安装多个指定包
npm install 包名 包名 包名 ...
# 安装指定版本的包
npm install 包名@版本号
# 卸载指定的包
npm uninstall 包名
# 安装全局包
npm install --global 包名
# 查看包信息
npm view 包名
# 查看使用帮助
npm help
# 查看某个命令的使用帮助
# 例如我忘记了 uninstall 命令的简写了,这个时候,可以输入 `npm uninstall --help` 来查看使用帮助
npm 命令 --help
全局命令行工具
我们除了可以使用 npm 安装项目中适应的依赖包之外,还有一些包比较特殊,这种不是在项目 require()
使用的,而是 通过全局安装之后使用它提供的命令来完成某种功能,就像咱们学过的 git 一样。
凡是往全局安装的包我们称之为全局命令行工具,这种包安装完毕之后会在命令行中为你提供了一个命令来完成某种功能。
程序交互方式分为两种:
- GUI(Graphical User Interface)图形交互软件
- CLI(Command Line Interface) 命令行交互软件,某些方面命令行操作要比图形界面更高效
提示:安装全局包必须加
--global
参数
http-server
nodemon
less
参考文档:http://lesscss.org/ 作用:支持在命令行中打命令完成 less 文件的编译
安装:
npm i -g less
安装完毕之后,我们可以在终端中输入以下命令进行测试:
# 因为在我们的 Linus 操作系统中,有一个操作系统默认命令:less
lessc --version
基本使用:
# 编译 less 文件,将结束输出到命令行中
lessc styles.less
# 编译 less 文件,将结果输出到指定的路径
lessc styles.less styles.css
browser-sync
参考文档:https://browsersync.io/ 作用:文件改变浏览器自动刷新(同步测试)
安装:
npm install -g browser-sync
基本使用:
browser-sync start --server --files "css/*.css, js/*.js, *.html"
全局包到底安装到了哪里
我们可以通过在命令行输入来查看全局包的安装路径:
npm root -g
现在这个东西只需要了解就行了。
因为以前的 node 版本一旦升级就会导致丢失了全局包,新版的 node 已经没有这个问题了。
解决 npm 被墙问题
npm 存储包文件的服务器在国外,有时候会被墙,速度很慢,所以我们需要解决这个问题。
国内淘宝的开发团队把 npm 在国内做了一个备份,网址是:http://npm.taobao.org/。
最简单的方式就是我们在安装包的时候告诉 npm 你去哪个服务器下载。
例如使用淘宝的 npm 镜像源下载 jquery:
npm install jquery --registry=https://registry.npm.taobao.org
但是每次手动往后面加 --registry=https://registry.npm.taobao.org
很麻烦, 所以我们可以通过修改配置文件的方式来处理解决。
# 配置到淘宝服务器
npm config set registry https://registry.npm.taobao.org
# 查看 npm 配置信息
npm config list
只要经过了上面命令的配置,则你以后所有的 npm install
都会使用你配置的 registry
下载。
package.json
我们的项目会放到云端的仓库中,例如 github ,第三方包没有上传的意义,我们只需要把我们的源码放到云端仓库,node_modules
目录中存储的就是第三方包(不用担心丢失问题),如果没有 package.json
文件则你就找不回来了。
我们建议每一个项目都要有一个 package.json
文件(包描述文件,就像产品的说明书一样),给人踏实的感觉最重要的就是保存这个项目的第三方依赖信息(因为我们不需要提交第三方包到我们的云端仓库,只需要提交我们自己的代码),有了这个文件中的依赖信息结合 npm install
命令我们就可以放心了。
这个文件可以通过 npm init
的方式来自动初始化出来。
C:\Users\lpz\Desktop\npm-demo>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> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (npm-demo) 项目的名称
version: (1.0.0) 0.0.1 项目版本
description: 项目简介
entry point: (index.js) main.js 项目入口
test command: 测试命令,暂且不用关系
git repository: 仓库地址
keywords: 关键字,如果是一个开源项目,则可以填一些关键字被别人在 npm 中搜索到
author: lipengzhou 项目的开发者
license: (ISC) 开源协议
About to write to C:\Users\lpz\Desktop\npm-demo\package.json:
{
"name": "npm-demo",
"version": "0.0.1",
"description": "这是一个测试项目",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "lipengzhou",
"license": "ISC"
}
Is this ok? (yes) yes
对于咱们目前来讲,最有用的是那个 dependencies
选项,可以用来帮我们保存第三方包的依赖信息。
如果你的 node_modules
删除了也不用担心,我们只需要:npm install
就会自动把 package.json
中的 dependencies
中所有的依赖项都下载回来。
- 建议每个项目的根目录下都有一个
package.json
文件- 不同的项目有不同依赖,各自保存各自的
- 建议执行
npm install 包名的
的时候都加上--save
这个选项,目的是用来保存依赖项信息- npm 5 以前不会自动保存依赖信息到 package.json 文件中,必须手动加
--save
选项才可以 - npm 5 以后不需要加
--save
选项了,因为它会自动保存依赖项
- npm 5 以前不会自动保存依赖信息到 package.json 文件中,必须手动加
dependencies
main
scripts
package-lock.json
npm 5 以前是不会有 package-lock.json
这个文件的。(被开发者诟病,吐槽的问题)。
以前会自作多情的自动给你升级。
npm 5 以后才加入了这个文件。
当你安装包的时候,npm 都会生成或者更新 package-lock.json
这个文件。
- npm 5 以后的版本安装包不需要加
--save
参数,它会自动保存依赖信息 - 当你安装包的时候,会自动创建或者是更新
package-lock.json
这个文件 package-lock.json
这个文件会保存node_modules
中所有包的信息(版本、下载地址)- 这样的话重新
npm install
的时候速度就可以提升
- 这样的话重新
- 从文件来看,有一个
lock
称之为锁- 这个
lock
是用来锁定版本的 - 如果项目依赖了
1.1.1
版本 - 如果你重新 isntall 其实会下载最新版本,而不是 1.1.1
- 我们的目的就是希望可以锁住 1.1.1 这个版本
- 所以这个
package-lock.json
这个文件的另一个作用就是锁定版本号,防止自动升级新版
- 这个
dependencies 和 devDependencies
--save
会保存到dependencies
依赖项中--save-dev
会保存到devDependencies
依赖项中
我们把一些辅助开发的工具包安装到 devDependencies
中。这样做的目的是对包进行分类,项目上线的时候可以使用 npm install --production
命令只安装 dependencies
依赖项中的包。
npm scri pts
学习参考:http://www.ruanyifeng.com/blog/2016/10/npm_scripts.html