node什么是Node.js是一个基于ChromeV8引擎的JavaScript运行环境。Node.js使用事件驱动、非阻塞I/O模型。Node使用包管理器NPM。第一句Node.js是一个基于ChromeV8引擎的JavaScript运行环境。运行环境不是语言,不是框架。只是一个可以作为JavaScript代码运行的环境。而这个运行环境主要由V8提供。V8做了什么?创建一个调用堆栈。functionmain(){func1();}functionfunc1(){func2();}functionfunc2(){console.log(1);}main();去掉V8,Node还有什么?除去V8,Node中另一个重要的组件是libuv。什么?libuv到底是个什么东西?再说说Node的另一句话:Node是为构建可扩展的网络应用而设计的。这句话的底气在哪里,那就是Node本身采用的事件驱动、非阻塞I/O模型。在并发模型构建网络的应用程序中,每个连接都会产生一个新线程,每个新线程可能需要伴随的2MB内存。在具有8GBRAM的系统上,理论上最大并发连接数为4,000个用户。随着客户群的增长,如果您希望Web应用程序支持更多用户,则必须添加更多服务器。所以在传统的后台开发中,整个Web应用架构(包括流量、处理器速度、内存速度)的瓶颈是:服务器最大能够处理的并发连接数。这种不同架构承载的并发量是不一致的。而且,如果存在多个线程,也会出现线程切换的问题,也会占用一定的资源。并发有两个问题:执行栈占用内存:资源有限上下文切换不自由:占用额外资源Node对这个问题的解决方案:在网络应用中,最慢的环节主要在磁盘读取或网络请求阶段。这时候CPU是空闲的。如果在等待I/O操作时可以释放空闲的CPU,就可以最大限度地利用资源;那么接下来的问题就是I/O操作完成后如何继续进行后续操作;解决方案:事件驱动,使用回调。这与浏览器中的事件循环相同。市面上已经有一些框架采用了相同的处理思路。EventMachine:处理网络请求的框架,主要针对ruby;Twisted:一个用Python实现的事件驱动的网络引擎框架;它是如何完成的?程序运行时,V8会将I/O操作及相关回调等耗时操作交给libuv处理。而V8继续执行下面的代码。I/O操作完成后,libuv会将回调方法放入事件队列。constfs=require('fs');constreadFile=(file)=>{fs.readFile(file,(err,data)=>{if(!err)console.log(data);});}console.log('programstart......');readFile('file.json');console.log('readFile已经把I/O');console.log('程序结束!!!!!');负责异步程序调度的是libuv。Libuv是一个专注于异步I/O的多平台支持库。上述程序中,遇到文件读取操作时,V8会将js接口转换为C++接口,并将回调方法交给libvu。此时libuv接管文件读取任务。当文件被读取时,libuv会把这个事件的回调函数丢到事件??队列中。回调函数将在下次检查事件队列时执行。问题:在这种情况下,如何理解node中的单线程?下面我们来看看Node、V8和libuv的关系。1)Node主要由V8javascript引擎和libuv组成;2)v8引擎主要负责解释执行js代码,遇到异步操作会交给libuv处理;3)libuv本身是一个独立的c语言库,可以直接使用/c++调用;在浏览器端,JS无法读取文件。js最初的能力仅限于表单验证。在Node中,JS可以操作本地文件,建立网络连接。这对Node来说一定是个好工作!让我们谈谈Node.js中的其他一些组件。Node扩展了JS的能力:内置模块内置模块是用C++代码编写的各种模块,包括crypto、zlib、file、net等基本功能。Nativemodules除了builtinmodules,还有一个nativemodule。它们是用js编写的内置模块,提供给程序开发者。fshttpbuiltin模块和native模块都是核心模块。核心模块在Node源代码的编译过程中被编译成一个二进制文件。所以在Node进程启动的时候,一些核心模块已经直接加载到内存中了。至此,Node的基本组成和运行原理已经讲解完毕。多说一句:Node.js的单线程并不是真正的单线程。它只是开一个单线程(可以定义为主线程)进行业务处理,同时开其他线程处理I/O。当指令到达主线程,主线程发现有I/O时,直接将事件交给libuv处理。libuv会管理一个线程池,I/O操作由线程池中的线程执行。I/O操作完成后,libuv会将I/O操作对应的回调放入事件循环中。在线程上,不等待I/O操作完成,继续执行后续代码。这就是“单线程”、“异步I/O”。IO.jsvarfork=require('child_process').fork;varfs=require('fs');console.log('start......');//阻塞//console.log(fs.readFileSync('test.json','utf-8'));//非阻塞varchildProcess=fork('another-thread.js');childProcess.on('message',function(data){console.log(数据);})console.log('end!!!');another-child.jsvarfs=require('fs');process.send(fs.readFileSync('test.json','utf-8'));除了你的代码,一切都并行运行!由于node中主要任务的执行是以单线程的方式进行的,如果程序错误导致崩溃,整个流程就会终止。为此,市面上出现了一些Node进程管理工具,会维护Node程序的状态,当程序挂掉时会自动重启。比如我们使用pm2。npm可以为一些node没有的模块(nativemodules)引入外部模块。这些外部模块通常由其他开发人员贡献。那么问题来了,如何在海量模块中快速找到自己想要的,并快速引入到自己的项目中。这就是npm为我们所做的。使用npm安装、共享和分发代码;管理项目中的依赖关系;npm官网模块安装:模块共享发布代码管理依赖共享与反馈NPM的基本模式NPM是一个JavaScript包管理器。广义npm的组成npm由三个不同的组件组成:网站:NPM官方网站命令行界面(CLI):NPM命令行工具注册表:JS模块数据库使用网站发现包、设置配置文件和管理其他你的npm体验的各个方面。例如,您可以设置Orgs(组织)来管理对公共或私有包的访问。CLI从终端运行。这就是大多数开发人员与npm交互的方式。注册表是JavaScript软件及其相关元信息的大型公共数据库。npm默认使用npm与node一起安装。Node安装完成后,npm就已经安装好了。要查找包,请访问npm官方网站并按关键字搜索。管理模块npminstall[模块名称]:正常安装方式,包安装完成后,会在当前目录下生成一个node_modules目录。这是存放外部js模块的地方,通过npm安装的包放在node_modules下。npminstall-g[modulename]:全局安装,模块安装在node安装路径下的node_modules中。npminstall[文件夹路径]:可以指定npm将文件安装到某个目录文件夹路径下,前提是该目录包含package.json文件。npminstall[模块名称]@[版本]:安装包时,指定对应的版本号。npmisntallchalk@latest:安装最新版本npminstallchalk@2.0.0:安装2.0.0版本npminstallchalk@">=2.0.0":安装大于2.0.0的版本npminstall--save-prod[模块名称]:在本地安装包,将安装信息写入package.json文件中的dependencies。不要写--save-prod或只写--save。默认值与--save-prod相同。npminstall--save-dev[modulename]:在本地安装包,将安装信息写入package.json文件中的devDependencies。dependencies:生产环境需要的依赖devDependencies:开发和测试环境使用的依赖npmupdate[modulename]:更新本地模块npmuninstall[modulename]:卸载模块package.jsonpackage.json是一个配置文件,它node和npm都会自动读取。它包含一个标准的JSON格式字符串。对于npm,package.json做的是:存储项目依赖的所有包,允许你指定项目依赖的包的版本规则,不符合项目要求的版本不需要让你的projectbuildreusable,Easytoshareyourproject对于你的项目,package.json定义了一些基本信息,比如项目名称,版本等等。pakcage.json必须有两个字段:名称和版本。这两个字段是什么意思?NPM作为一个包管理平台,在开发者提交(发布)一个模块时,必须提供一些基础信息以便于管理。name:项目名称或模块名称version:版本号,应该遵循x.x.x的格式description:项目信息描述,方便别人了解你的模块,也方便搜索keywords:项目关键词,便于搜索homepage:project主页;scripts:scripts属性是一个对象,里面的每个属性对应一个脚本;脚本可以使用npmrun+属性名来执行main:指定项目的程序入口文件,该文件的exports对象也是require项目时获取的对象。repository:表示代码存在的地址,方便别人更好的查看你的源码dependencies:一个对象,配置模块所依赖的模块列表,key是模块名,value是版本描述(遵循语义规则)devDependencies:一个对象,开发或测试时的一些依赖模块,与启动后的依赖模块区分开来。engines:指定项目运行的Node版本author:项目的开发者contributors:一堆项目的开发者{"name":"vue-todo","version":"1.0.0","description":"asimplytodolistusingvuejs","scripts":{"start":"nodeserver.js","stop":"egg-scriptsstop--title=egg-server-example","dev":"egg-bindev"},"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"}}语义版本控制(语义版本规则)版本格式:主版本号。次版本号。revisionnumber,例如1.2.3的版本号递增规则如下:majorversionnumber:当你不兼容API修改时;1.2.3--->2.0.0次要版本号:当你做一个向后兼容的功能添加时;1.2.3--->1.3.0修订号:当你做向下兼容时兼容的错误修复;1.2.3--->1.2.4在package.json中定义版??本规则时,可以这样做:如果你只打算接受补丁版本的更新(即最后一位的变化),你可以这样写:1.01.0.x~1.0.4如果你接受小版本的更新(第二位的变化),就可以这样写:11.x^1.0.4如果你能接受大版本更新(自然接受小版本和补丁版本的变化),你可以这样写:*x正在使用npminstall--save||npminstall--安装save-dev时,将依赖写入pakcage.json中,默认接受小版本更新,即在版本号前加'^'。嵌入式,如fs、http等)文件模块(由开发者自己编写,npm上的所有模块都是开发者定义的模块)文件模块在运行时动态加载。需要进行路径分析、文件定位和编译执行。当Node加载模块时,它首先从缓存中加载。如果缓存中不存在该模块,则按照以上三步加载该模块。Node中的模块标识主要包括以下几种:核心模块,如fs、http等。以.开头的相对路径文件模块。或..开头的绝对路径文件模块/非路径形式的文件模块;这些类型模块的加载速度依次降低。module.js和浏览器中的window一样,Node中的全局变量都挂在global下面。首先,一些常用的变量:__dirname:当前模块所在目录__filename:当前模块文件名require:importothermodulesmodule:exports上面的5个变量好像是全局变量,其实不是全局的变量。它们都是模块系统下的东西。问题:不在全局变量下,为什么可以在模块中直接调用?Node导入模块在Node中,导入模块分为三个步骤:模块的路径分析、文件定位、编译、执行编译、编译和执行是导入文件模块的最后阶段。.js文件:Node会把js源码从头到尾封装起来。返回一个函数,将当前环境的exports、require、module、__dirname、__filename作为参数传递给这个函数。打包后的代码:(function(exports,require,module,__filename,__dirname){//sourcecodeinjsfile})这就是为什么它们不在全局变量下,而是可以在模块中使用的原因。.node文件:对于.node文件,实际上不需要编译过程。因为.node文件本身就是一个编译好的C/C++文件,所以只有加载和执行的过程。.json文件:Node会读取json文件的内容,赋值给exports对象,直接传递给第三方调用。NPM中的模块基本上都属于Node中文件模块中的Javascript模块。requireNode的模块系统规则是按照CommonJS规范实现的。如果参数字符串不是以./或/或../开头,则表示要加载的不是文件,而是默认提供的核心模块。这时候先寻找node平台提供的核心模块;然后寻找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文件,作为模块加载。)流程对象是一个全局变量,提供当前Node.js进程的信息,控制当前Node.js进程。process.argv:包含命令行参数的数组。第一个元素是“节点”,第二个元素是.js文件的名称,接下来的参数依次是命令行参数process.execArgv:启动进程所需的节点命令行参数。这些参数不会出现在process.argv中,也不包含节点可执行文件的名称或名称后的任何参数。这些用于生成子进程,以便它们具有与父进程相同的参数。process.env:获取当前系统环境信息的对象,输出内容为环境变量等内容。这个对象可以修改nextTick(callback):把回调放在事件轮询队列的第一位,当下一次事件轮询开始时,执行callbackprocess.abort:结束当前进程process.kill(pid):结束一个进程process.jsconsole.log(process.argv);console.log(process.execArgv);nodeprocess.js#['/usr/local/bin/node','/root/node-demo/process.js']#[]nodeprocess.jsabc234cvb=cvb#['/usr/local/bin/node',#'/root/node-demo/process.js',#'abc',#'234',#'cvb=cvb']#[]node--harmony--use-openssl-caprocess.jsabc234cvb=cvb#['/usr/local/bin/node',#'/root/node-demo/process.js',#'abc',#'234',#'cvb=cvb']#['--harmony','--use-openssl-ca']Node'Processdocumentfsfile.jsNode'FileSystemdocumentquestionsrequire('../fs/file.js')这是异步的还是同步的?deno,下一代Node?package.jsonnode_modulesgyp等......PPT(2018)PPT(2018)Node之父在JS大会上介绍deno视频:CallstackandAsynchronousFundamentalsAwesomeMicronpmPackageslibuv官网【深入Node.js]()
