Node服务器端JavaScript(node服务器端渲染)
核心的JavaScript语法定义了最小限度的API,可以操作数值、文本数组等,但不包含输入输出功能。输出入是内嵌JavaScript的宿主环境的责任 。
宿主环境:浏览器、node
前端的JavaScript是由ECMAScript、DOM、BOM组合而成,Node.js是由ECMAScript、OS、File、Net、DB组成
Node.js是使 JavaScript 运行在浏览器之外,即服务端的开发平台, 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,也是一个库
可以在node.green/上获取到Node各个版本对ES语法的支持
Node,JavaScript与底层操作系统绑定的结合,与限制JavaScript只能使用浏览器提供的API不同,Node给予JavaScript可以访问整个操作系统的权限,允许JavaScript读写文件、通过网络发送和接收数据。应用:
替代命令行脚本、交互式终端程序
运行受信程序的编程语言,没有浏览器运行不受信代码带来的安全限制
编写高效、高并发web服务器的流行环境:高并发、实时聊天、实时消息推送、基于社交网络的大规模 Web 应用
客户端逻辑强大的SPA
Restful API,可以处理数万条链接
实时websocket应用
前端工具链,e.g:单元测试工具、客户端 JavaScript 编译器
桌面开发、带有图形用户界面的本地应用程序
TCP/UDP 套接字应用程序
Node.js 还可以部署到非网络应用的环境下,比如一个命令行工具。Node.js 还可以调用C/C++ 的代码,这样可以充分利用已有的诸多函数库,也可以将对性能要求非常高的部分用C/C++ 来实现。
Node.js的结构图 libuv是提供异步功能的C库,在运行时负责一个事件循环,一个线程池、文件系统I/O、DNS相关的IO和网络IO。
node.js启动后,会开启一个JS主线程和libuv提供的线程池和Event Loop。当发现有IO操作就交给线程池并注册回调函数。
node.js的全局对象有global、process、console、module、exports
process :node进程相关的信息,比如运行node程序时的命令行参数。或者设置进程相关信息,比如设置环境变量。
REPL 模式
REPL【Read-eval-print loop输入—求值—输出循环】
和python一样,Node.js 也有这样的功能,运行无参数的 node 将会启动一个 JavaScript 的交互式 shell
打开命令提示符,然后输入 node,进入 REPL 模式以后,会出现一个“>”提示符提示你输入命令,输入后按回车,Node.js 将会解析并执行命令。
执行了一个函数,REPL 会在下一行显示这个函数的返回值。
连续按两次 Ctrl + C 退出Node.js 的 REPL 模式。
如果你输入了一个错误的指令,REPL 则会立即显示错误并输出调用栈。
process.argv命令行参数
第一、第二个的元素是node可执行文件(环境)和被执行JavaScript文件路径
提供给node可执行文件且由它解释的命令行参数会被node可执行文件使用,不会出现在process.argv中
node也会从unix风格的环境变量中获取输入,node把这些环境变量保存在process.env对象中使用
SHELL: '/bin/bash', USER: 'joy', HOME: '/Users/joy' 复制代码
单线程、非阻塞的事件编程模式
传统的架构是多线程模型,为每个业务逻辑提供一个系统线程,通过系统线程切换来弥补同步式【即时回复】 I/O 调用时的时间开销
Node.js 使用的是单线程模型
V8引擎提供的异步执行回调接口可以处理大量的高并发,IO密集型处理是强项
对于所有 I/O 都采用异步请求,避免了频繁的上下文切换
node创建于JavaScript支持promise之前,其异步主要依赖回调函数实现
**事件式编程:在执行的过程中会维护一个事件队列,程序在执行时进入事件循环等待下一个事件到来,每个异步式 I/O 请求完成后会被推送到事件队列,等待程序进程进行处理。
同步的阻塞式IO在高并发环境将是一个很大的性能问题,所以同步一般只在基础框架启动时使用,用来加载配置文件、初始化程序等。
其他同步的应用需求:和操作系统的shell命令交互,调用可执行文件等
Node与进程相关的模块process、child_process、cluster
大多数API都有同步/异步两个版本
注意:Node.js在底层访问I/O其实还是多线程,可以翻看fs模块源码,里面用libuv来处理I/O
异步错误不能被同步线程捕获,有可能会变成致命错误,终止程序。注册全局处理程序
process.on("unhandledRejection", (reason, promise) => { // }); 复制代码
多线程
在支持HTML5的浏览器里,我们可以使用web worker来处理一些耗时运算。 对应Node.js也提供了cluster、child_process模块的web worker来解决。
模块
node在JavaScript支持模块系统之前就诞生,所以它拥有自己的模块系统——module.exports 和 require
模块是 Node.js 应用程序的基本组成部分,文件即模块。文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。
Node 13增加了对ES6模块import、export的支持,同时支持自己的模块系统。
mjs扩展名文件-ES6模块
cjs扩展名文件-CommonJS模块格式
没有明确扩展名的,会向上查找package.json文件,检查JSON对象的顶级type属性的值commonjs、module
运行node并不需要有package.json,如果没有找到默认CommonJS模块
//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(); 复制代码
http模块
http 是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装。 node内建了 HTTP 服务器支持,这和 PHP、Perl 不一样,因为在使用 PHP 的时候,必须先搭建一个 Apache 之类的HTTP 服务器,然后通过 HTTP 服务器的模块加载或 CGI 调用,才能将 PHP 脚本的执行结果呈现给用户。 而使用 Node.js 时,无需额外搭建一个 HTTP 服务器。直接可以用来调试代码,并且它本身就可以部署到产品环境,其性能足以满足要求。
function serve(rootDirectory, port) { let server = new http.Server(); server.listen(port); console.log('listening on port', port); server.on('request', (request,response) => { let temp = url.parse(request.url).pathname; //查找文件,返回状态码及文件 }) } 复制代码
包
包package
NPM
安装node同时会得到npm包管理工具
通常一个包的配置如下
GZIP压缩文件
解析GZIP的URL
为注册表添加< name > @< version >的URL信息
package.json配置文件(在包目录中运行npm init 会根据交互式问答产生一个符合标准的package.json)
package.json文件中使用script定义脚本命令(脚本文件一般位于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 执行
JavaScript 没有编译器,不能在早期验证代码的合法性。有效的单元测试常常扮演一个伪编译器的角色,立刻反馈代码的质量,并且一有缺陷,就能检测到。
JavaScript 中有很多自动化文档生成工具。JSDoc有着和Javadoc 类似的标记和输出,Dox是一个生成文档的Node.js 模块。
Docco将代码和注释组织成一种类似文章的格式。虽然Docco 不直接验证和执行代码规范,使用它却能鼓励大家使用良好的代码结构和注释
package.json
完全符合规范的 package.json 文件应该含有以下字段。
name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格。
description:包的简要说明。
version:符合语义化版本识别规范的版本字符串。
keywords:关键字数组,通常用于搜索。
maintainers:维护者数组,每个元素要包含 name、email(可选)、web(可选)
字段
contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者
数组的第一个元素。
bugs:提交bug的地址,可以是网址或者电子邮件地址。
licenses:许可证数组,每个元素要包含 type (许可证的名称)和 url (链接到
许可证文本的地址)字段。
repositories:仓库托管地址数组,每个元素要包含 type (仓库的类型,如 git )、
url (仓库的地址)和 path (相对于仓库的路径,可选)字段。
dependencies:包的依赖,一个关联数组,由包名称和版本号组成。
{ "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": "bills@example.com", } ], "contributors": [ { "name": "BYVoid", "web": "http://www.byvoid.com/" } ], "bugs": { "mail": "dev@example.com", "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 安装包的命令格式为:
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/
相比npm、yarn有了很多改进:yarn new package/
安装速度快(cache和依赖解析做得好)
模块安装保证幂等性
锁定各依赖模块的版本(npm 使用的semver默认是指定了一个range)
兼容性好(兼容旧有npm的工作流)
npm link创建符号链接
npm 提供了一个命令 npm link,在本地包和全局包之间创建符号链接,解决-使用全局模式安装的包不能直接通过 require 使用、局部包不支持命令行
npm link 命令不支持 Windows。
npm install -g express npm link express // 显示 ./node_modules/express -> /usr/local/lib/node_modules/express 复制代码
在 node_modules 子目录中发现一个指向安装到全局的包的符号链接。通过这种方法,就可以把全局包当本地包来使用了。
将本地的包链接到全局:
在包目录( package.json 所在目录)中运行 npm link 命令。 复制代码
开发包时,利用这种方法可以非常方便地在不同的工程间进行测试。
发布包
在包目录下运行
npm init :生成一个符合 npm 规范的 package.json 文件 npm adduser :获得一个账号用于今后维护自己的包 npm whoami :测验是否已经取得了账号 npm publish:发布 复制代码
访问 search.npmjs.org/ 就可以找到刚刚发布的包了。可以在世界的任意一台计算机上使用 npm install byvoidmodule 命令来安装它 更新:
在 package.json 文件中修改 version 字段,然后npm publish npm unpublish 命令来取消发布。 复制代码
Runtime和vm
Runtime :解释型语言的数据类型的确定由编译推迟至运行时,所以需要一个运行时系统来处理编译后的代码。
JavaScript引擎负责解析和JIT编译,例如编译成机器码。Runtime提供内建的库(例如常用数据类型、Window对象、DOM API),可以在运行时使用。
vm:通常认为是硬件和二进制文件之间的中间层
c++编译好的二进制文件交给OS直接调用
Java编译好的二进制文件交给Java虚拟机运行,对开发者屏蔽了不同操作系统的差异
Node的一个核心模块vm提供了一系列API用于在V8虚拟机环境中编译和运行代码。
node.js中的stream流
基于EventEmitter的数据管理模式,由各种不同的抽象接口组成,主要包括可写、可读、可读写、可转换等类型
流是非阻塞数据处理模式,可以提升效率、节省内存、有助于处理管道且可扩展等。
流的应用:文件读写、网络请求、数据转换、音频、视频等方面有很广泛的应用
监听error事件,可以捕获流的错误事件
node支持四种流
可读流
可写流
双工流
转换流
流常与管道共用
有时候,我们需要把可读流中获取的数据写入可写流。例如,写一个HTTP服务器提供对静态文件目录的访问。需要将文件流写入网络套接字。与其自己写代码来处理读和写,不如把这两个接口连接为一个“管道”,将可写流简单传递给可读流的pipe()方法,即可让node帮我们实现复杂操作
const fs = require('fs'); function pipeFileToSocket(file, socket){ fs.createReadStream(file).pipe(socket); } 复制代码
使用
//app.js var http = require('http'); http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>Node.js</h1>'); res.end('<p>Hello World</p>'); }).listen(3000); console.log("HTTP server is listening at port 3000."); 复制代码
运行 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 debug debug.js:打开了一个 Node.js 的调试终端 node-inspector
控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元。
Mocha
一个基于Node.js和浏览器的集合各种特性的JavaScript测试框架
作者:贾明恣
链接:https://juejin.cn/post/7031816581273681927