Node 基础学习

node是什么

  • Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
  • Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型。
  • Node使用包管理器NPM。

第一句话

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。

运行环境, 不是一门语言,不是一个框架。只是可以做为JavaScript代码运行的一个环境。 javascript

而这个运行环境主要是由V8提供的。html

V8作了什么?

建立了一个callstack。vue

function main(){
    func1();
}
function func1(){
    func2();
}
function func2(){
    console.log(1);
}
main();

V8 Engine

除去V8,Node中还有哪些东西?

除去V8,Node中另一个比较重要的组成就是 libuvjava

What? libuv是什么鬼?node

先说说,关于Node的另一句话:webpack

Node is designed to build scalable network applications.

这句话的底气在哪儿,就是Node自己采用的 事件驱动,非阻塞I/O模型c++

并发模型构建网络的应用中,每一个链接都会生成一个新线程,每一个新线程可能须要 2MB 的配套内存。在一个拥有 8 GB RAM 的系统上,理论上最大的并发链接数量是 4,000 个用户。随着您的客户群的增加,若是但愿您的 Web 应用程序支持更多用户,那么,您必须添加更多服务器。因此在传统的后台开发中,整个 Web 应用程序架构(包括流量、处理器速度和内存速度)中的瓶颈是:服务器可以处理的并发链接的最大数量。这个不一样的架构承载的并发数量是不一致的。并且,多个线程存在的话,也会衍生出线程切换的问题,这也会占用必定资源。 git

并发存在的两个问题:github

  • Execution stacks take up memory: 资源有限
  • Context switching is not free:占用额外资源

traditional-web-server-model

Node的解决这个问题思路:web

  1. 在网络应用中,比较慢的环节主要在 磁盘读取 或 网络请求阶段,这时候CPU处于闲置状态。
  2. 若是在等待I/O操做时,可以释放处于闲置状态的CPU,则能够很大程度上利用资源;
  3. 那么接下来的问题就在于,如何在I/O 操做完成后,继续执行后续的操做;
  4. 解决方案:事件驱动,采用回调。这和浏览器中的Event Loop是同样的道理。

市场上已经有一些使用一样处理思路的框架。

  • EventMachine:处理网络请求的框架,主要是面向ruby;
  • Twisted:用Python实现的基于事件驱动的网络引擎框架;

nodejs-process-model

具体是怎么作的?

在程序运行时,V8 会把I/O操做等耗时较多操做及相关回调一并交给libuv去处理。而V8继续执行后面的代码。等到I/O操做完成后,libuv会将回调方法放到事件队列中。

const fs = require('fs');

const readFile = (file) => {
    fs.readFile(file, (err, data) => {
        if(!err) console.log(data);
    });
}

console.log('program start ......');
readFile('file.json');
console.log('readFile has put the I/O ');
console.log('program end!!!!!');

V8 async

负责异步程序调度的工做就是libuv作的事情。

Libuv is a multi-platform support library with a focus on asynchronous I/O.

在上述程序中,当遇到文件读取操做时,V8会把js接口转成C++接口 同时把回调方法一并交给libvu。此时libuv接管这个文件读取任务。当文件读取完成后,libuv就会把这个事件的回调函数扔到事件队列里。当下一次检查事件队列时,就会执行该回调函数。

question: 既然这样,怎么理解node中的单线程?

再捋一捋Node, V8 和libuv的关系。

1) Node主要由V8 javascript引擎和libuv组成;
2) v8引擎主要负责解释执行js代码,碰到须要异步的操做会交给libuv处理;
3) libuv自己是独立的c语言库,能够直接使用c/c++来调用;

在浏览器端,JS是没有能力进行文件读取操做的。js最初的能力也就限制在表单校验上。而在Node中,JS能够操做本地文件,创建网络链接。这确定是Node干的好事!

再来讲说Node中其它一些组成部分

Node扩展了JS的能力:builtin modules

builtin modules是由C++代码写成各种模块,包含了crypto,zlib, file, net等基础功能。

native modules

除了builtin modules, 还有一个native modules。它们是用js编写的内建模块,提供给程序开发者使用。

  • fs
  • http

builtin modules 和 native modules都属于核心模块。核心模块在Node源码编译的过程当中,编译进了二进制文件。因此在Node进程启动的时候,部分核心模块就已经被直接加载到了内存当中。

至此,Node的基本构成和运行原理已经讲完了。

Node内部结构
Node 构成

补充一句:Node.js的单线程并非真正的单线程,只是开启了单个线程(能够定义为主线程)进行业务处理,同时开启了其余线程专门处理I/O。当一个指令到达主线程,主线程发现有I/O以后,直接把这个事件传给libuv处理。libuv会管理一个线程池,I/O操做就是由线程池里面的线程完成的。等I/O 操做完成,libuv,会把对应I/O操做的回调放到事件循环当中。在线程上,不会等待I/O 操做完成,继续执行后续的代码。这就是“单线程”、“异步I/O”。

IO.js

var fork = require('child_process').fork;
var fs = require('fs');

console.log('start......');

// blocking
// console.log(fs.readFileSync('test.json', 'utf-8'));

// non blocking
var childProcess = fork('another-thread.js');
childProcess.on('message', function(data){
    console.log(data);
})

console.log('end !!!');

another-child.js

var fs = require('fs');
process.send( fs.readFileSync('test.json', 'utf-8'));
Everything runs in parallel except your code! 在Node中)除了代码,一切都是并行的!

因为node中主任务的执行是以单线程的方式进行,若是程序出错致使崩溃,就会终止整个流程。为此,市场上有些Node进程管理工具,它们会维护Node程序的状态,当程序挂掉时,会自动重启。好比咱们使用的pm2

NPM

对于node没有的一些模块(native modules),能够引入外部模块。这些外部模块一般是其它开发者贡献的。

那么问题来了,对于数量众多的模块中,如何快速找到本身想要的并可以快速的引进到本身的项目当中。

这就是npm帮咱们作的工做。

Use npm to install, share, and distribute code; manage dependencies in your projects; and share & receive feedback with others.

npm官网

  • 模块安装:
  • 模块共享
  • 发布代码
  • 管理依赖
  • 共享和反馈

NPM的基本模式

NPM是JavaScript包的管理器。

NPM basic

广义的npm的构成

npm consists of three distinct components:

  • the website: NPM官方站点
  • the Command Line Interface (CLI) : NPM命令行工具
  • the registry: JS模块的数据库

Use the website to discover packages, set up profiles, and manage other aspects of your npm experience. For example, you can set up Orgs (organizations) to manage access to public or private packages.

The CLI runs from a terminal. This is how most developers interact with npm.

The registry is a large public database of JavaScript software and the meta-information surrounding it.

npm中的使用

npm默认随node一块儿安装,在Node安装完成后,npm已经安装。

查找一个包

去npm官网,按关键词查找。

管理模块

  • npm install [module name] :普通安装方式,包安装完成后,会在当前目录生成一个node_modules目录。这是一个存放外部js模块的地方,经过npm安装的包都放在node_modules下。
  • npm install -g [module name]:全局安装,模块被安装在node安装路径下的 node_modules中。
  • npm install [folder path]:能够指定npm 安装某个目录folder path下的的文件,前提是这个目录下包含package.json文件。
  • npm install [module name]@[version]: 安装包的时候,指定对应的版本号。

    • npm isntall chalk@latest : 安装最新版
    • npm install chalk@2.0.0: 安装2.0.0版
    • npm install chalk@">=2.0.0":安装大于2.0.0的版本
  • npm install --save-prod [module name]: 在本地安装包,并将安装信息写入 package.json文件中的dependencies中, 不写--save-prod 或者只写 --save 默认跟 --save-prod同样。
  • npm install --save-dev [module name]:在本地安装包,并将安装信息写入 package.json文件中的devDependencies

    • dependencies:在生产环境中须要用到的依赖
    • devDependencies:在开发、测试环境中用到的依赖
  • npm update [module name]: 更新本地模块
  • npm uninstall [module name]: 卸载模块

package.json

package.json是一个node和npm都会自动读取的配置文件,它里面是个标准的JSON格式字符串。

对于NPM而言, package.json作了如下工做:

  • 存储项目依赖的全部包
  • 容许你指定项目依赖的包的版本规则,不知足项目需求的版本,不须要
  • 让你的项目构建具备可复用性,容易分享你的项目

对于你的项目而言,package.json定义了一些基础信息,好比项目名称,版本等等。

pakcage.json必须具备的两个字段: nameversion。这俩个字段有什么意义呢?
NPM 做为一个包管理平台,当有开发者提交(publish)模块时,必须提供一些基本信息便于管理。

  • name: 项目名称或者模块名称
  • version:版本号,应当遵循 x.x.x的格式
  • description:项目信息描述,方便别人了解你的模块,也利于搜索
  • keywords:项目的关键词,便于搜索
  • homepage:项目的主页;
  • scripts:scripts属性是一个对象,里面的每个属性对应一段脚本;脚本可使用 npm run + 属性名 执行
  • main:指定项目的程序入口文件,该文件的exports对象同时也是require项目时,取到的对象。
  • repository:指明代码存在的地址,便于别人更好的查看你的源码
  • dependencies:一个对象,配置模块依赖的模块列表,key是模块名称,value是版本描述(遵循semantic规则)
  • devDependencies:一个对象,开发或测试过程当中的一些依赖模块,跟上线后的依赖模块区分出来
  • engines: 指定项目运行的Node版本
  • author:项目的开发者
  • contributors:一堆项目的开发者
{
  "name": "vue-todo",
  "version": "1.0.0",
  "description": "a simply todolist using vuejs",
  "scripts": {
    "start": "node server.js",
    "stop": "egg-scripts stop --title=egg-server-example",
    "dev": "egg-bin dev"
  },
  "dependencies": { // 线上生产环境必须,固然开发环境也会用到
    "babel-runtime": "^6.23.0",
    "vue": "^2.0.1",
    "vue-localstorage": "^0.1.1",
    "vuex": "^2.2.1"
  },
  "devDependencies": { // 开发环境会用到的东东
    "webpack": "^1.13.2",
    "webpack-dev-middleware": "^1.8.3",
    "webpack-hot-middleware": "^2.12.2",
    "webpack-merge": "^0.14.1"
  }
}

semantic versioning(语义化版本规则)

版本格式:主版本号.次版本号.修订号, 例如 1.2.3

版本号递增规则以下:

  • 主版本号:当你作了不兼容的 API 修改; 1.2.3 ---> 2.0.0
  • 次版本号:当你作了向下兼容的功能性新增; 1.2.3 ---> 1.3.0
  • 修订号:当你作了向下兼容的问题修正; 1.2.3 ---> 1.2.4

在package.json定义版本规则的时候,能够这么作:

  • 若是只打算接受补丁版本的更新(也就是最后一位的改变),就能够这么写:

    • 1.0
    • 1.0.x
    • ~1.0.4
  • 若是接受小版本的更新(第二位的改变),就能够这么写:

    • 1
    • 1.x
    • ^1.0.4
  • 若是能够接受大版本的更新(天然接受小版本和补丁版本的改变),就能够这么写:

    • *
    • x

在使用npm install --save || npm install --save-dev 安装的时候,写入pakcage.json中的依赖,默认接受小版本的更新,即在版本号前添加 '^'。

NPM document

Node 模块

Node中的模块分为两类:

  • 核心模块(Node中内嵌的,好比fshttp等)
  • 文件模块(开发者本身编写的,NPM上的模块都属于开发者自定义的模块)

文件模块是在运行的时候,动态加载。须要进行路径分析,文件定位 和 编译执行。

Node加载模块时,优先从缓存中加载,若是缓存中不存在该模块,才会按照上述的三个步骤进行模块加载。

模块标识符在Node中,主要有如下几类:

  • 核心模块,好比fs, http
  • ...开头的相对路径文件模块
  • / 开头的绝对路径文件模块
  • 非路径形式的文件模块;

这几类模块的加载速度是依次下降的。

module.js

同浏览器中的window同样,在Node中的全局变量都挂在global下。
先说一下,经常使用到一些变量:

  • __dirname: 当前模块所在目录
  • __filename: 当前模块文件名称
  • require: 引入其它模块
  • module:
  • exports

上面5个变量,貌似全局变量,但不是全局变量。他们都是模块系统下的东西。

question: 它们不在全局变量下,那它们为什么能够在模块中直接调用?

Node引入模块

在Node中,引入模块分为三个步骤:

  • 路径分析
  • 文件定位
  • 编译执行

模块编译

编译和执行是引入文件模块的最后一个阶段。

  • .js文件 : Node 会对js源码进行一个首尾的封装。返回一个function,并将当前的环境的exports,require,module,__dirname,__filename做为形参传递给这个function。

包装后的代码:

(function(exports, require, module, __filename, __dirname){
    // js文件中的源码
})

这就是为何 它们不在 全局变量下,却能够在模块当中使用的缘由。

  • .node文件: 对于.node文件, 实际上并不须要编译过程。由于.node文件自己就是C/C++编译后的文件,它只有加载和执行过程。
  • .json文件: Node 会读取json文件内容,并将它赋予exports对象,直接传递给第三方调用。

Node_module_callstack

在NPM中的模块,基本属于Node中文件模块里的Javascript模块。

require的规则

Node的模块系统参照CommonJS规范实现。

  1. 若是参数字符串不以.//../开头,说明要加载的不是一个文件,而是一个默认提供的核心模块。

    1. 此时则先在node平台所提供的核心模块当中找;
    2. 而后再寻找NPM模块(即第三方模块包,或本身写的模块包)

      1. 在寻找NPM模块包时,会从当前目录出发,向上搜索各级当中的node_modules文件夹当中的文件,但如有两个同名文件,则遵循就近原则。(module.paths是一个模块路径数组。)
  2. 若是require当中的参数字符串以/开头:则表示从系统根目录开始寻找该模块文件。
  3. 若是require当中的参数字符串以./(从当前目录出发)或../(从上一级目录出发)开头:表示按照相对路径,从当前文件所在的文件夹开始寻找要载入的模块文件。

    1. 按js文件来执行(先找对应路径当中的module.js文件来加载)
    2. 按json文件来解析(若上面的js文件找不到时,则找对应路径当中的module.json文件来加载)
    3. 按照预编译好的c++模块来执行(寻找对应路径当中的module.node文件来加载)
    4. 若参数字符串为一个目录(文件夹)的路径,则自动先查找该文件夹下的package.json文件,而后再再加载该文件当中main字段所指定的入口文件。(若package.json文件当中没有main字段,或者根本没有package.json文件,则再默认查找该文件夹下的index.js文件做为模块来载入。)

Node查找模块示意图

process

process 对象是一个全局变量,它提供当前 Node.js 进程的有关信息,以及控制当前 Node.js 进程。

  • process.argv: 包含命令行参数的数组。第一个元素会是'node',第二个元素将是.js文件的名称,接下来的参数依次是命令行参数
  • process.execArgv: 启动进程所需的 node 命令行参数。这些参数不会在 process.argv 里出现,而且不包含 node 执行文件的名字,或者任何在名字以后的参数。这些用来生成子进程,使之拥有和父进程有相同的参数
  • process.env: 获取当前系统环境信息的对象,输出内容是环境变量等内容,这个对象能够修改
  • nextTick(callback): 将callback放到事件轮询队列首位,下一次事件轮询开始时,先执行callback
  • process.abort:结束当前进程
  • process.kill(pid):结束一个进程

process.js

console.log(process.argv);
console.log(process.execArgv);
node process.js
# [ '/usr/local/bin/node', '/root/node-demo/process.js' ]
# []

node process.js abc 234 cvb=cvb
# [ '/usr/local/bin/node',
#   '/root/node-demo/process.js',
#   'abc',
#   '234',
#   'cvb=cvb' ]
# []


node --harmony  --use-openssl-ca  process.js abc 234 cvb=cvb
# [ '/usr/local/bin/node',
#   '/root/node-demo/process.js',
#   'abc',
#   '234',
#   'cvb=cvb' ]
# [ '--harmony', '--use-openssl-ca' ]

Node'Process document

fs

file.js

Node'FileSystem document

questions require('../fs/file.js')这里是异步仍是同步?

deno,下一代Node?

  • package.json
  • node_modules
  • gyp

等等............

the heaviest object

Node之父JS大会介绍deno时的PPT(2018)

Node之父JS大会介绍Node时的PPT(2009)

Nodejs的运行原理-科普篇

视频:callstack 和异步 基本原理

Awesome Micro npm Packages

libuv 官网

[深刻浅出Node.js]()

相关文章
相关标签/搜索