核心的JavaScript语法定义了最小限度的API,可以操作数值、文本数组等,但不包含输入输出功能。输出入是内嵌JavaScript的宿主环境的责任 。
宿主环境:浏览器、node
与限制JavaScript只能使用浏览器提供的API不同,Node给予JavaScript可以访问整个操作系统的权限,允许JavaScript读写文件、通过网络发送和接收数据
node.js的全局对象有global、process、console、module、exports
process获得node进程相关的信息,比如运行node程序时的命令行参数。或者设置进程相关信息,比如设置环境变量。
@TOC
可以在https://node.green/上获取到Node各个版本对ES语法的支持
Node.js的结构图
libuv是提供异步功能的C库,在运行时负责一个事件循环,一个线程池、文件系统I/O、DNS相关的IO和网络IO。
node.js启动后,会开启一个JS主线程和libuv提供的线程池和Event Loop。当发现有IO操作就交给线程池并注册回调函数。
应用:高并发、实时聊天、实时消息推送、客户端逻辑强大的SPA
Restful API,可以处理数万条链接
实时websocket应用
前端工具链
桌面开发
Runtime :数据类型的确定由编译推迟至运行时,所以需要一个运行时系统来处理编译后的代码。
JavaScript引擎负责解析和JIT编译,例如编译成机器码。Runtime提供内建的库(例如常用数据类型、Window对象、DOM API),可以在运行时使用。
vm:通常认为是硬件和二进制文件之间的中间层
c++编译好的二进制文件交给OS直接调用
Java编译好的二进制文件交给Java虚拟机运行,对开发者屏蔽了不同操作系统的差异
Node的一个核心模块vm提供了一系列API用于在V8虚拟机环境中编译和运行代码。
Node.js是使 JavaScript 运行在浏览器之外,即服务端的开发平台, 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,也是一个库。
前端的JavaScript是由ECMAScript、DOM、BOM组合而成,Node.js是由ECMAScript、OS、File、Net、DB组成。
允许通过命令行运行用JavaScript编写的程序。大多数前端开发工具都要使用Node.js。Node自带两个命令行程序:node和npm。
使用 Node.js,可以开发具有复杂逻辑的网站、基于社交网络的大规模 Web 应用、Web Socket 服务器、 TCP/UDP 套接字应用程序、命令行工具、交互式终端程序、带有图形用户界面的本地应用程序、单元测试工具、客户端 JavaScript 编译器
Node.js 内建了 HTTP 服务器支持,也就是可以轻而易举地实现一个网站和服务器的组合。这和 PHP、Perl 不一样,因为在使用 PHP 的时候,必须先搭建一个 Apache 之类的HTTP 服务器,然后通过 HTTP 服务器的模块加载或 CGI 调用,才能将 PHP 脚本的执行结果呈现给用户。而当你使用 Node.js 时,不用额外搭建一个 HTTP 服务器,因为 Node.js 本身就内建了一个。这个服务器不仅可以用来调试代码,而且它本身就可以部署到产品环境,它的性能足以满足要求。
Node.js 还可以部署到非网络应用的环境下,比如一个命令行工具。Node.js 还可以调用C/C++ 的代码,这样可以充分利用已有的诸多函数库,也可以将对性能要求非常高的部分用C/C++ 来实现。
REPL【Read-eval-print loop输入—求值—输出循环】
和python一样,Node.js 也有这样的功能,运行无参数的 node 将会启动一个 JavaScript 的交互式 shell
打开命令提示符,然后输入 node,进入 REPL 模式以后,会出现一个“>”提示符提示你输入命令,输入后按回车,Node.js 将会解析并执行命令。如果你执行了一个函数,那么 REPL 还会在下面显示这个函数的返回值。连续按两次 Ctrl + C 即可推出Node.js 的 REPL 模式。
console 是 Node.js 提供的控制台对象,其中包含了向标准输出写入的操作,如 console.log、console.error 等。console.log 是我们最常用的输出指令,它和 C 语言中的 printf 的功能类似,也可以接受任意多个参数,支持 %d、%s 变量引用。
undefined 就是 console.log 的返回值。如果你输入了一个错误的指令,REPL 则会立即显示错误并输出调用栈。
process.argv命令行参数
node中的同步异步
node.js是单线程,异步是通过循环事件队列来实现,同步则是阻塞式IO,这在高并发环境将是一个很大的性能问题,所以同步一般只在基础框架启动时使用,用来加载配置文件、初始化程序等。
node是异步非阻塞的,这对高并发十分有限,可是我们还有一些同步的应用需求,比如和操作系统的shell命令交互,调用可执行文件等,子进程就是负责node阻塞的工作
JavaScript是单线程的,意思是JavaScript的执行是单线程,他的宿主环境(node、浏览器)都是多线程的。
Node与进程相关的模块由process、child_process、cluster
Node.js是单进程单线程应用程序,但是V8引擎提供的异步执行回调接口可以处理大量的高并发。所以Node.js是单线程且支持高并发的脚本语言,IO密集型处理是强项,因为Node.js的IO请求都是异步的。
Node.js在底层访问I/O其实还是多线程,可以翻看fs模块源码,里面用libuv来处理I/O
主线程是单线程,所有阻塞的部分交给一个线程池处理,然后主线程通过一个队列跟线程池协作。为了处理异步 I/O,线程必须有事件循环,不断地检查有没有未处理的事件,依次予以处理。
传统的架构是多线程模型,为每个业务逻辑提供一个系统线程,通过系统线程切换来弥补同步式【即时回复】 I/O 调用时的时间开销。
Node.js 使用的是单线程模型,对于所有 I/O 都采用异步式的请求方式,避免了频繁的上下文切换。因为它使用了事件式编程和异步 I/O。
Node.js 在执行的过程中会维护一个事件队列,程序在执行时进入事件循环等待下一个事件到来,每个异步式 I/O 请求完成后会被推送到事件队列,等待程序进程进行处理。
例如,对于简单而常见的数据库查询操作,按照传统方式实现的代码如下:
res = db.query(‘SELECT * from some_table’);
res.output();
以上代码在执行到第一行的时候,线程会阻塞,等待数据库返回查询结果,然后再继续处理。由于数据库查询可能涉及磁盘读写和网络通信,其延时可能相当大,线程会在这里阻塞等待结果返回。对于高并发的访问,一方面线程长期阻塞等待,另一方面为了应付新请求而不断增加线程,会浪费大量系统资源,同时线程的增多也会占用大量的 CPU 时间来处理内存上下文切换,而且还容易遭受低速连接攻击。
看看Node.js是如何解决这个问题的:
db.query(‘SELECT * from some_table’, function(res) {
res.output();
});
这段代码中 db.query 的第二个参数是一个回调函数。进程在执行到db.query 的时候,不会等待结果返回,而是直接继续执行后面的语句,直到进入事件循环。当数据库查询结果返回时,会将事件发送到事件队列,等到线程进入事件循环以后,才会调用之前的回调函数继续执行后面的逻辑。
Node.js 的异步机制是基于事件的,所有的磁盘 I/O、网络通信、数据库查询都以非阻塞的方式请求,返回的结果由事件循环来处理。
在支持HTML5的浏览器里,我们可以使用web worker来处理一些耗时运算。
对应Node.js也提供了cluster、child_process模块的web worker来解决。
主要功能:订阅和发布消息,用于解决多模块交互而产生的耦合问题
应用:
在模块间传递消息
在回调函数内外传递修消息
处理流数据,因为流是在EventEmitter的基础上实现的
运用观察者模式收发消息的相关应用
基于EventEmitter的数据管理模式,由各种不同的抽象接口组成,主要包括可写、可读、可读写、可转换等类型
流是非阻塞数据处理模式,可以提升效率、节省内存、有助于处理管道且可扩展等。
流的应用:文件读写、网络请求、数据转换、音频、视频等方面有很广泛的应用
监听error事件,可以捕获流的错误事件
文本编辑器,在其中输入:
console.log('Hello World');
console.log('%s: %d', 'Hello', 25);
将文件保存为 helloworld.js,打开终端,进入 helloworld.js 所在的目录,执行以下命令:
node helloworld.js
在成功运行 PHP 之前先要配置一个功能强大而复杂的 HTTP 服务器,譬如 Apache、IIS 或 Nginx,还需要将 PHP 配置为 HTTP 服务器的模块,或者使用FastCGI 协议调用 PHP 解释器。这种架构是“浏览器 HTTP 服务器 PHP 解释器”的组织方式,而Node.js采用了一种不同的组织方式,Node.js 将“HTTP服务器”这一层抽离,直接面向浏览器用户。
创建一个 HTTP 服务器吧。建立一个名为 app.js 的文件
//app.js
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('Node.js
');
res.end('Hello World
');
}).listen(3000);
console.log("HTTP server is listening at port 3000.");
Node.js 提供了 require 函数来调用其他模块,而且模块都是基于文件的。var http = require(‘http’),其中 http 是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装。我们通过require 函数获取了这个模块,然后才能使用其中的对象。
运行 node app.js命令,打开浏览器访问 http://127.0.0.1:3000
这个程序调用了 Node.js 提供的http 模块,对所有 HTTP 请求答复同样的内容并监听 3000 端口。在终端中运行这个脚本时,我们会发现它并不像 Hello World 一样结束后立即退出,而是一直等待,直到按下 Ctrl + C 才会结束。这是因为 listen 函数中创建了事件监听器,使得 Node.js 进程不会退出事件循环。
因为 Node.js 只有在第一次引用到某部份时才会去解析脚本文件,以后都会直接访问内存,避免重复载入,开发 Node.js 实现的 HTTP 应用时会发现,无论你修改了代码的哪一部份,都必须终止Node.js 再重新运行才会奏效。
supervisor 可以监视你对代码的改动,并自动重启 Node.js。【相当于持续重启,有选择的使用】
首先使用 npm 安装 supervisor:
$ npm install -g supervisor
接下来,使用 supervisor 命令启动 app.js:
$ supervisor app.js
模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。
在 Node.js 中,创建一个模块即创建一个文件。Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象
//module.js
var name;
exports.setName = function(thyName) {
name = thyName;
};
exports.sayHello = function() {
console.log('Hello ' + name);
};
//getmodule.js
var myModule1 = require('./module');
myModule1.setName('1');
var myModule = require('./module');
myModule.setName('BYVoid');
myModule1.sayHello();
运行node getmodule.js,结果是:
Hello BYVoid
require 不会重复加载模块,也就是说无论调用多少次获得的模块都是最后一次。
//hello.js
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;//如果是exports.Hello = Hello;
//gethello.js
var Hello = require('./hello'); //那么是require('./hello').Hello
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
包管理工具,何为包package,Node.js 根 据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。
用于描述一个文件或一个目录,一个包的配置:
NPM脚本: NPM允许在package.json文件中使用script定义脚本命令
npm run;//查看所有脚本命令(脚本文件一般位于node_modules/.bin子目录里)
Node.js 在调用某个包时,会首先检查包中 package.json 文件的 main 字段,将其作为包的接口模块,如果 package.json 或 main 字段不存在,会尝试寻找 index.js 或 index.node 作为包的接口。
{
//...
"script":{
"build":"node index.js"
}
//...
"main" : "./lib/interface.js"
}
//npm run build等同于node index.js
二进制文件应该在 bin 目录下;
JavaScript 代码应该在 lib 目录下;
文档应该在 doc 目录下;
单元测试应该在 test 目录下
JavaScript 的单元测试可使用一个 Node.js 的 模 块,Karma(https://github.com/karma-runner/karma)在多浏览器里执行,并且每当修改并保存源文件后,就会自动执行。这一点很重要。如果你能自律编写单元测试,每当文件保存时就执行单元测试这一能力将能够在早期发现缺陷。这点对 JavaScript 来说尤其有用,因为 JavaScript 没有编译器,不能在早期验证代码的合法性。有效的单元测试常常扮演一个伪编译器的角色,它们能够立刻反馈代码的质量,并且一有缺陷,就能检测到。
JavaScript 中有很多自动化文档生成工具。JSDoc(https://github.com/jsdoc3/jsdoc)有着和Javadoc 类似的标记和输出,Dox(https://github.com/visionmedia/dox)是一个生成文档的Node.js 模块。
Docco(http://jashkenas.github.io/docco/)是一个 Node.js 模块,它将代码和注释组织成一种类似文章的格式。虽然Docco 不直接验证和执行代码规范,使用它却能鼓励大家使用良好的代码结构和注释,而不是不假思索地复制粘贴。
package.json 是 CommonJS 规定的用来描述包的文件,完全符合规范的 package.json 文件应该含有以下字段。
{
"name": "mypackage",
"description": "Sample package for CommonJS. This package demonstrates the required
elements of a CommonJS package.",
"version": "0.7.0",
"keywords": [
"package",
"example"
],
"maintainers": [
{
"name": "Bill Smith",
"email": "[email protected]",
}
],
"contributors": [
{
"name": "BYVoid",
"web": "http://www.byvoid.com/"
}
],
"bugs": {
"mail": "[email protected]",
"web": "http://www.example.com/bugs"
},
"licenses": [
{
"type": "GPLv2",
"url": "http://www.example.org/licenses/gpl.html"
}
],
"repositories": [
{
"type": "git",
"url": "http://github.com/BYVoid/mypackage.git"
}
],
"dependencies": {
"webkit": "1.2",
"ssl": {
"gnutls": ["1.0", "2.0"],
"openssl": "0.9.8"
}
}
}
在使用 npm 安装包的时候,有两种模式
本地模式:把某个包作为工程运行时的一部分时
使用 npm 安装包的命令格式为:
npm [install/i] [package_name]
安装成功放置在当前目录的 node_modules 子目录下,require 在加载模块时会尝试搜寻 node_modules 子目录
npm 本地模式仅仅是把包安装到 node_modules 子目录下,其中的 bin 目录没有包含在 PATH 环境变量中,不能直接在命令行中调用。
全局模式:要在命令行下使用包
npm [install/i] -g [package_name]
使用全局模式安装时,npm 会将包安装到系统目录,譬如 /usr/local/lib/node_modules/,同时 package.json 文件中 bin 字段包含的文件会被链接到 /usr/local/bin/。/usr/local/bin/ 是在PATH 环境变量中默认定义的,因此就可以直接在命令行中运行 xxx.js命令了。
注:使用全局模式安装的包并不能直接在 JavaScript 文件中用 require 获得,因为 require 不会搜索 /usr/local/lib/node_modules/。
https://engineering.fb.com/2016/10/11/web/yarn-a-new-package-manager-for-javascript/
yarn主要有以下特色:
安装速度快(cache和依赖解析做得好)
模块安装保证幂等性
锁定各依赖模块的版本(npm 使用的semver默认是指定了一个range)
兼容性好(兼容旧有npm的工作流)
(见上面的链接,以及yarn的github)
npm 提供了一个命令 npm link,在本地包和全局包之间创建符号链接。
npm link 命令不支持 Windows。
(1)使用全局模式安装的包不能直接通过 require 使用,但通过 npm link命令可以打破这一限制。
通过 npm install -g express 安装了 express,
在工程的目录下运行命令:
$ npm link express
./node_modules/express -> /usr/local/lib/node_modules/express
在 node_modules 子目录中发现一个指向安装到全局的包的符号链接。通过这种方法,就可以把全局包当本地包来使用了。
(2)将本地的包链接到全局。
在包目录( package.json 所在目录)中运行 npm link 命令。
开发包时,利用这种方法可以非常方便地在不同的工程间进行测试。
e.g:
npm install -g browser-sync
显示:
E:\nodejs\node_global\browser-sync -> E:\nodejs\node_global\node_modules\browser-sync\dist\bin.js
browser-sync start --server
在包目录下运行
npm init :生成一个符合 npm 规范的 package.json 文件
npm adduser :获得一个账号用于今后维护自己的包
npm whoami :测验是否已经取得了账号
npm publish:发布
访问 http://search.npmjs.org/ 就可以找到刚刚发布的包了。可以在世界的任意一台计算机上使用 npm install byvoidmodule 命令来安装它
更新:
在 package.json 文件中修改 version 字段,然后npm publish
npm unpublish 命令来取消发布。
node debug debug.js:打开了一个 Node.js 的调试终端
node-inspector
控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元。
一个基于Node.js和浏览器的集合各种特性的JavaScript测试框架