NodeJs
Node.js
采用C++
语言编写而成,它不是Javascript
应用,而是一个Javascript
的运行环境,据Node.js
创始人Ryan Dahl
回忆,他最初希望采用Ruby
来写Node.js
,但是后来发现Ruby
虚拟机的性能不能满足他的要求,后来他尝试采用V8
引擎,所以选择了C++
语言。
Node.js
支持的系统包括*nux、Windows
,这意味着程序员可以编写系统级或服务器端的Javascript
代码,交给Node.js
来解释执行。
Node.js
一个能够在服务器端运行JavaScript
的开源代码、跨平台JavaScript
运行环境;
- 采用
Google
开发的V8
引擎运行JS
代码,使用事件驱动、非阻塞、异步I/O
模型等技术提高性能,可优化应用程序的传输量和规模; -
Node
大部分基本模块都是采用JS
编写的,由一个高性能的Web
服务器形成一个Node
生态环境。
-
Node
的服务器是单线程的-
Node
处理请求时是单线程,但在后台拥有一个I/O
线程池; -
Node
有专门处理请求的线程,还有专门处理I/O
的线程。
-
-
JS
没有模块的概念,而Node
遵循CommonJS
规范,将模块划分为:核心模块、文件模块- 核心模块:
node
引擎提供的模块,可以直接引用,不用指定模块路径; - 文件模块:第三方模块,文件模块的标识就是文件的路径;
-
require()
:引入外部模块的函数,返回值就是引入模块的对象; - 如果是相对路径,必须以
./
或../
开头,如require("./req.js")
,后缀名.js
可省略;
- 核心模块:
- 模块的作用域:在
Node
中,每一个JS
文件中的JS
代码都独立运行在一个函数中,而不是全局作用域- 模块之间不能直接共享数据,即
require()
返回的对象无法访问模块内的变量/方法; - 在执行的模块中使用:
console.log(arguments.callee + "")
会发现,当前正在执行的模块代码被node放在了一个函数中:function (exports, require, module, __filename, __dirname) { //模块的JS代码 }
-
exports
:用于将变量/函数暴露到外部的对象,每个模块文件中都有一个exports
对象; -
require
:用来引入外部模块的函数,只要是作为exports
的属性/方法,该函数返回的对象都可以直接访问:req.js:exports.x = "Hello"; exports.fn = function(){}
- 其他模块中引用req.js模块:
var r = require("./req"); # r: {x: 'Hello', fn: [Function]} r.fn(); # 执行模块中的方法 r.x; # 获取模块中的变量
-
module
每个模块都有一个内置的对象属性module
,包含了当前模块所拥有的一些信息; -
module
的属性信息:id(模块的唯一标识)、filename(文件的路径名)、loaded、children、paths、exports、parent、require() --> require()也来自module对象
-
exports
是module
的属性,即module
也可以用于导出:module.exports.x = "Hello";
-
__filename
当前模块的绝对路径;__dirname
当前模块所在目录的绝对路径。
-
- 模块之间不能直接共享数据,即
-
exports
与module.exports
的区别-
exports
只是一个指向module.exports
的引用,module.exports
才是一个对象;exports.a = 10; # 等同于:module.exports.a = 10
- 真正具有向外暴露能力的是
module.exports
,而不是exports
,比如exports={ a: 10 } --> exports
指向了一个新的对象,不再指向module.exports
,也就失去了可以向外暴露的能力; -
exports
只能通过exports.x
的方式向外暴露,而module.exports={ a: 10 }
可以暴露; -
require()
得到的其实也是module.exports
对象的引用。
-
- 在浏览器端的
JS
中,全局变量/函数都作为window
对象的属性/方法保存的;在node
中,也有类似于window
作用的全局对象:global
- 所谓
node
环境,也就是ECMAScript
,global
是ECMAScript
标准提供的; - 本质上,浏览器端的window其实就是扩展自
ECMAScript
中的global
- 所谓
- 包:
package
,由包结构和包描述文件组成,其实就是一个压缩文件,解压还原为目录;- 包结构 用于组织包中的各种文件;
- 包描述文件 描述包的相关信息,以供外部读取分析;
- 目录文件包括:描述文件(
package.json
)、可执行二进制文件(bin
)、js
代码(lib
)、doc
(文档)、test
(单元测试),其中package.json
是必需的; -
package.json
是一个JSON
格式的文件,不能有任何注释,它是配置文件。
npm
npm
Node Package Manager
,node
的包管理器,用于node
的第三方模块的发布、安装、依赖等;
- 借助
npm
,Node
与第三方模块之间形成了一个很好的生态系统; -
npm search
包名:搜索模块包; -
npm install/i
包名:在当前目录安装模块包;- 执行:
npm init --> 当前目录会生成一个package.json
- 安装依赖包,如
npm install math:math
包会在安装到当前目录下; - 在当前目录下会生成一个
node_modules
目录,存放安装的模块,使用这些模块时,不需要指定模块路径:var math = require("math");
-
npm i 包名 --save
:在项目或模块包的package.json
中会生成相关模块的依赖信息;npm i math --save # package.json -> "dependencies": { "math": "0.0.3" }
- 依赖信息的作用:将当前目录(即项目)上传到开源站时,并不会上传
node_modules
目录,因为会影响上传/下载的速度,而且不能保证依赖的模块包一定是最新版本;那么下载并使用该项目后,也就不能直接运行,需要先在该项目的根目录下执行:npm install =>安装依赖
- 执行:
-
npm install/i 包名 -g
:全局模式安装模块包,一般都是一些工具;-
npm root -g
:查看全局安装的根目录; -
npm list
:查看当前目录下已安装的node
包; -
npm info 包名
:查看包的所有版本;npm i jquery@1.8.2
:指定版本安装。
-
-
npm update
:升级依赖包 -
npm remove/r 包名
:删除模块包,同理,--save
也会删除package.json
中的依赖信息; - 镜像服务器:
npm
服务器不在国内,所以npm
下载时可能会很慢,镜像服务器的作用就是把npm服务器的数据拷贝一份,通过国内的镜像服务器下载模块包,就会很快;- 淘宝
NPM
镜像:同步npm
服务器数据的频率为10
分钟,定制的cnpm
代替默认的npm
命令; npm install -g cnpm --registry=https://registry.npm.taobao.org
-
cnpm
与npm
的命令一模一样,但下载的模块目录结构可能不同,也是为了避免相互覆盖。 - 然而,有时候cnpm会带来诡异的Bug,为了解决下载速度问题,可以设置
npm
的镜像npm install --registry=https://registry.npm.taobao.org
- 淘宝
-
require("math")
的查找路径:当前目录的node_modules -> 上一级目录的node_modules -> 再上一级目录的node_modules -> ... -> 磁盘根目录 -> 仍没有则报错
package.json
-
package.json
中的一些节点-
name
包名 -
version
版本,x.x.x
-
main
包的入口主文件 -
scripts
自定义脚本,通过 npm run 脚本名 执行脚本定义的命令 -
dependencies
生产环境下必需的依赖包 -
devDependencies
只在开发环境下使用的依赖包
-
-
dependencies
与devDependencies
中的依赖包版本:-
^2.3.4
表示在执行npm install
时,第一位版本号不变,后两位取最新的; -
~2.3.4
前两位不变,最后一位取最新; -
*2.3.4
表示全部取最新的。
-
开发方向
-
GUI:Graphical User Interface
,图形用户界面,如office
、vscode
、浏览器、播放器... -
CLI:Command Line Interface
,命令行界面,也称为CUI
(字符用户界面)- 相比于
GUI
,CLI
更节省计算机资源,一般用于服务器环境,如babel、vue-cli、webpack...
-
Node
第三方命令行框架:commander、chalk、inquirer
-
commander
:命令行开发工具 -
chalk
:命令行样式风格控制器 -
inquirer
:交互式命令行工具
- 相比于
-
Server
:提供服务,如Web Server、IM...
JS模块化
模块化规范:CommonJs、AMD、CMD、ES6
- CommonJs规范
- 在服务器端:模块的加载是运行时同步加载的;
- 在浏览器端:模块需要提前编译打包处理,因为浏览器不识别
require()
的引入模块方式; - 暴露模块的两种方式:
module.exports = value,exports.xxx = value
- 引入模块:
let xxx = require('xxx');
- 服务器端的实现:
Node.js
- 浏览器端的实现:
Browserify
,用于对js
模块的提前打包处理,<script>
引入处理后的js
-
AMD
规范:专门用于浏览器端,模块的加载是异步的-
CommonJs
最初是基于服务器端的Js模块化规范,AMD
针对浏览器端出了一套JS
模块化规范之后,CommonJs
才有了基于浏览器端的实现; - 暴露模块
-->
定义没有依赖的模块:define(function(){ return 模块 })
- 暴露模块
-->
定义有依赖的模块:define(['module1', 'module2'], function(m1, m2){ # 回调的形参与依赖的模块相对应 return 模块 })
- 浏览器端的实现:
RequireJs
,使用时,<script>
引入require.js
,并指定主模块入口<script data-main="js/main.js" src="js/libs/require.js"></script>
- 在主模块中声明要引入的其他模块,这样只用一个
<script>
就能引入所有需要的模块
-
-
CMD
规范:CMD
是阿里的JS
规范,也是专门用于浏览器端,后来出售给了国外,很少用;-
CMD
规范其实就是把CommonJs
和AMD
进行结合,定义模块使用AMD
,暴露模块使用CommonJS
; - 浏览器端的实现:
SeaJs
,使用时,一个<script>
引入sea.js
,一个<script>
引入主模块;<script type="text/javascript" src="js/libs/sea.js"></script> <script type="text/javascript"> seajs.use('./js/modules/main') </script>
-
-
ES6
规范:依赖模块需要编译打包处理- 导出模块:
export
,引入模块:import
,Vue.js2.0
就是基于ES6
规范 - 浏览器端的实现:
Babel、Browserify
-
Babel
:有的浏览器不支持ES6
的语法,Babel
可以将ES6
编译为ES5
-
Browserify
:用于编译打包JS
- 常规暴露:
export { fun1, fun2 }
,引入:import {fun1, fun2} from './module'
- 默认暴露:可以暴露任意数据类型,而且暴露的数据与引入的数据是一致的;
# 暴露一个方法 export default () => { ... } # 引入 import fun from './module'
- 导出模块:
- 自动化打包工具:
webpack、Grunt、Gulp
,用于简化编译打包JS/Sass/Less
、压缩、合并等过程
CommonJs
-
CommonJs
规范主要是为了弥补JS
没有标准的缺陷,终极目标是提供一个类似Python、Ruby
和Java
的标准库,而不只是停留在小脚本程序的阶段; -
CommonJs
就是模块化的标准,nodeJs
就是CommonJs
的一种实现; -
NodeJs
中的模块分为两类:-
NodeJs
本身提供的核心模块,如http、url、fs
,可以直接引入使用; - 自定义模块:依照
CommonJs
中的规定,实现的第三方模块。
-
第三方模块的规定
- 一个
JS
文件作为一个模块,通过exports
或者module.exports
暴露属性/方法; - 自定义模块的查找过程:
- 如果当前目录下没有,则去当前目录下的
node_modules
目录中查找;var foo = require('foo') # ./node_modules/foo.js
- 如果
node_modules
目录中没有foo.js
,但有foo
文件夹,且foo.js
在foo
目录中;var foo = require('foo/foo.js') # ./node_modules/foo/foo.js
- 如果当前目录下没有,则去当前目录下的
- 如果希望
require('foo')
能直接引用./node_modules/foo/foo.js
- 在
foo
目录下执行npm init --yes
,生成package.json
文件,--yes
表示强制生成; -
require('foo')
在node_modules
中查找到foo
目录,那么就查找foo/package.json
,如果不存在,则提示没有foo.js
模块; - 如果
package.json
存在,则查找main
节点,此时的foo
目录被视为一个模块,main
节点表示模块的入口,也就是真正暴露的模块; - 如果
main
节点指向foo.js
,则查找foo/foo.js
,如果指向index.js
,则require('foo')
实际引用的是foo/index.js
- 在
- 通过
npm
下载第三方模块时,也会自动下载到node_modules
目录中,下载的其实也是目录;-
npm i md5-node --save
与--save-dev
:都会把md5-node
写入package.json
中; -
--save
:同-S
,写入到package.json
中的dependencies
中; -
--save-dev
:同-D
,写入到package.json
中的devDependencies
中;
-
-
dependencies
与devDependencies
的区别- 正常执行
cnpm install
时,dependencies
和devDependencies
中的模块都会下载; -
--save
:运行时依赖,如vue、react
等,没有这些依赖,打包的项目无法运行; -
--save-dev
:开发时依赖,即开发环境所需的依赖,如构建工具,测试工具等等,打包发布时不需要这些工具,如less-loader、webpack、babel
- 正常执行
模块的加载机制
- 模块的分类:文件模块、文件夹模块、核心模块
- 文件模块其实就是一个
JS
文件,通过module.exports
暴露模块的功能; - 文件夹模块可分为
node_modules Folders
和global Folders
-
global folders
是全局模块,由node的环境变量NODE_PATH
控制所在路径。
- 文件模块其实就是一个
- 路径加载模式:
require('./m3')
引入模块时,以./、../、/
开头,表示路径模块加载模式 - 非路径加载模式:
require('m3')
在引入时不指定模块的路径-
module.paths
是一个数组,保存的是查找此模块的路径列表; - 核心模块是
node
的内置模块,require()
引用时也不需要指定路径; - 但是,如果自定义的模块名与核心模块名相冲突,则默认加载核心模块。
-
- 模块文件的后缀名处理机制:
require('./m3') --> m3 -> m3.js -> m3.json -> m3.node
- 主模块与子模块
一个模块直接通过node xxx.js
运行起来,它就是主模块;通过require('xxx.js')
加载时,则是子模块;
主模块与子模块的判断:!module.parent
-
app.js
const Koa = require('koa') const app = new Koa() //... if (!module.parent) { // 作为主模块直接运行 console.log('start server...') app.listen(3000) } else { // 作为子模块导出 console.log('export app...') module.exports = app }
-
index.js
const app = require('./app')
- 执行
node ./app.js --> start server... node ./index.js --> export app...
-
网友评论