资讯专栏INFORMATION COLUMN

Node 基础学习

alaege / 1933人阅读

摘要:既然这样,怎么理解中的单线程再捋一捋和的关系。在线程上,不会等待操作完成,继续执行后续的代码。这就是单线程异步。在中除了代码,一切都是并行的由于中主任务的执行是以单线程的方式进行,如果程序出错导致崩溃,就会终止整个流程。

node是什么

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

Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型。

Node使用包管理器NPM。

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

运行环境, 不是一门语言,不是一个框架。只是能够作为JavaScript代码运行的一个环境。

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

V8做了什么?

创建了一个callstack。

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

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

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

What? libuv是什么鬼?

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

Node is designed to build scalable network applications.

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

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

并发存在的两个问题:

Execution stacks take up memory: 资源有限

Context switching is not free:占用额外资源

Node的解决这个问题思路:

在网络应用中,比较慢的环节主要在 磁盘读取 或 网络请求阶段,这时候CPU处于闲置状态。

如果在等待I/O操作时,能够释放处于闲置状态的CPU,则可以很大程度上利用资源;

那么接下来的问题就在于,如何在I/O 操作完成后,继续执行后续的操作;

解决方案:事件驱动,采用回调。这和浏览器中的Event Loop是一样的道理。

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

EventMachine:处理网络请求的框架,主要是面向ruby;

Twisted:用Python实现的基于事件驱动的网络引擎框架;

具体是怎么做的?

在程序运行时,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!!!!!");

负责异步程序调度的工作就是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.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的构成

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对象,直接传递给第三方调用。

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

require的规则

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

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

此时则先在node平台所提供的核心模块当中找;

然后再寻找NPM模块(即第三方模块包,或自己写的模块包)

在寻找NPM模块包时,会从当前目录出发,向上搜索各级当中的node_modules文件夹当中的文件,但若有两个同名文件,则遵循就近原则。(module.paths是一个模块路径数组。)

如果require当中的参数字符串以/开头:则表示从系统根目录开始寻找该模块文件。

如果require当中的参数字符串以./(从当前目录出发)或../(从上一级目录出发)开头:表示按照相对路径,从当前文件所在的文件夹开始寻找要载入的模块文件。

按js文件来执行(先找对应路径当中的module.js文件来加载)

按json文件来解析(若上面的js文件找不到时,则找对应路径当中的module.json文件来加载)

按照预编译好的c++模块来执行(寻找对应路径当中的module.node文件来加载)

若参数字符串为一个目录(文件夹)的路径,则自动先查找该文件夹下的package.json文件,然后再再加载该文件当中main字段所指定的入口文件。(若package.json文件当中没有main字段,或者根本没有package.json文件,则再默认查找该文件夹下的index.js文件作为模块来载入。)

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

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

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

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

Nodejs的运行原理-科普篇

视频:callstack 和异步 基本原理

Awesome Micro npm Packages

libuv 官网

[深入浅出Node.js]()

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98694.html

相关文章

  • JAVA基础整理(五)---手写简单的linkedlist来学习linkedlist

    摘要:类链表容器也是通过对比源码进行对比学习。增加一个结点不带,直接尾插法当链表里没有一个元素时,头尾都是该结点,并且该结点的前后都是空的。尾结点是该结点的前驱结点,该结点是尾节点的后继结点,更新尾节点。 LinkedList类 链表容器也是通过对比jdk源码进行对比学习。 1.定义结点类型 class Node{ E item; Node next; Node prev; Node(No...

    dantezhao 评论0 收藏0
  • 学习Vue2.0的建议顺序

    摘要:官方文档官方文档,官方文档永远是学习资料的第一步起步扎实的基本功。学习的新特性,理解,建议可以看看阮一峰的教程。的学习曲线会比较长,需要了解到的常用命令,以及和的模块规范,的也很多,其实更多的是属于一项后端语言。 学习Vue2.0的建议顺序 注:本文是看过其他关于vue文章之后的想法,欢迎转载,请注明出处。   Vue官方文档:Vue2.0官方文档,官方文档永远是学习资料的第一步 起步...

    iamyoung001 评论0 收藏0
  • JAVA基础整理(六)---手写简单的HashMap来学习hashmap

    摘要:学习手动写一个简单的进行理解结点的定义的基础时一个数组,数组里每个元素是一个,他必须包括值,键值,,下一个结点,同一个值的结点用一条链栓起来。第一个结点的特殊操作第一个对上了修改一个元素查找一个元素 HashMap学习--手动写一个简单的HashMap进行理解 1.结点Node的定义 public class Node { public int hash; public...

    Travis 评论0 收藏0
  • 十步零基础JavaScript学习路径

    摘要:之前写过一篇天学通前端开发,内容主要讲的就是前端学习路径,今天再来写一篇零基础的学习路径,希望能帮编程零基础的前端爱好者指明方向。十框架三选一,零基础的初学者强烈推荐,如果是后台转前端推荐,如果技术型前端,推荐。 之前写过一篇26天学通前端开发,内容主要讲的就是前端学习路径,今天再来写一篇零基础的JavaScript学习路径,希望能帮编程零基础的前端爱好者指明方向。 一、开发环境和Ja...

    incredible 评论0 收藏0
  • node学习系列之基础(二)

    摘要:由于这种特性,某一个任务的后续操作,往往采用回调函数的形式进行定义。另外,回调函数本身的第一个参数,约定为上一步传入的错误对象。这种写法有一个很大的好处,就是说只要判断回调函数的第一个参数,就知道有没有出错,如果不是,就肯定出错了。 REPL环境 在命令行键入node命令,后面没有文件名,就进入一个Node.js的REPL环境(Read–eval–print loop,读取-求值-输出...

    zhaot 评论0 收藏0
  • 教你如何打好根基快速入手react,vue,node

    摘要:谨记,请勿犯这样的错误。由于在之前的教程中,积累了坚实的基础。其实,这是有缘由的其复杂度在早期的学习过程中,将会带来灾难性的影响。该如何应对对于来说,虽然有大量的学习计划需要采取,且有大量的东西需要学习。 前言倘若你正在建造一间房子,那么为了能快点完成,你是否会跳过建造过程中的部分步骤?如在具体建设前先铺设好部分石头?或直接在一块裸露的土地上先建立起墙面? 又假如你是在堆砌一个结婚蛋糕...

    ddongjian0000 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<