首先从名字提及,网上查阅资料的时候会发现关于node的写法五花八门,到底哪种写法最标准呢?遵循官方网站的说法,一直将项目称之为“Node”或者“Node.js”。html
简单来讲,Node就是运行在服务器端的JavaScript。
JavaScript是一门脚本语言(能够用来编程的而且直接执行源代码的语言,就是脚本语言),脚本语言都须要一个解析器才能运行。对于写在html中的js,一般是由浏览器去解析执行。对于独立执行的js代码,则须要Node这个解析器解析执行。前端
每一种解析器就是一个运行环境,不但容许js定义各类数据结构,进行各类计算,还容许js使用运行环境提供的内置对象和方法作一些事情。例如运行在浏览器中的js的用途是操做DOM,浏览器提供了document之类的内置对象。而运行在node中的js的用途是操做磁盘文件或搭建HTTP服务器,node就相应提供了fs、http等内置对象。node
Node不是js应用,而是js的运行环境。
看到Node.js这个名字,可能会误觉得这是一个JavaScript应用,事实上,node采用c++语言对Google V8引擎进行了封装,是一个JavaScript运行环境。V8引擎执行JavaScript的速度很是快,性能也很是好。node是一个让开发者能够快速建立网络应用的服务端JavaScript平台,同时运用JavaScript进行前端与后端编程,从而开发者能够更专一于系统的设计以及保持其一致性。c++
// 快速构建服务器 const http = require('http') http.createServer((req,res)=>{ res.writeHead(200, {'Content-Type': 'text/plain'}) res.end('hello World!') }).listen(8088) $ node helloWorld.js
Node采用事件驱动、异步编程
node的设计思想以事件驱动为核心,它提供的绝大多数API都是基于事件的、异步的风格。开发者须要根据本身的业务逻辑注册相应的回调函数,这些回调函数都是异步执行的。这意味着虽然在代码结构中,这些函数看似是依次注册的,可是它们并不依赖自身出现的顺序,而是等待相应的事件触发。git
在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会致使资源浪费和时间延迟。经过事件注册、异步函数,开发者能够充分利用系统资源,执行代码无须阻塞等待,有限的资源能够用于其余的任务。web
Node以单进程、单线程模式运行
这点和JavaScript的运行方式一致,事件驱动机制是node经过内部单线程高效率地维护事件循环队列来实现的,没有多线程的资源占用和上下文切换,这意味着面对大规模的http请求,node凭借事件驱动搞定一切。由此咱们是否能够推测这样的设计会致使负载的压力集中在CPU(事件循环处理?)而不是内存。淘宝共享数据平台团队对node的性能测试:数据库
眼见为实,虽然看不太懂这些测试数据,可是最终测试结果是:它的性能让人信服。npm
为了让Node.js的文件能够相互调用,Node.js提供了一个简单的模块系统。模块系统是Node组织管理代码的利器也是调用第三方代码的途径。
模块是Node应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件多是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。编程
理想状况下,开发者只须要实现核心的业务逻辑,其余均可以加载别人已经写好的模块。可是,json
要想实现模块化编程首先须要解决的问题是,命名冲突以及文件依赖问题。
因而便有了CommonJS规范的出现,其目标是为了构建JavaScript在包括web服务器,桌面,命令行工具,以及浏览器方面的生态系统。CommonJS制定了解决这些问题的一些规范,而node就是这些规范的一种实现。node自身实现了require方法做为其引入模块的方法,同时npm也基于CommonJS定义的包规范,实现了依赖管理和模块自动安装等功能。
原生模块即为Node API提供的核心模块(如:os、http、fs、buffer、path等模块),原生模块在node源代码编译的时候编译进了二进制执行文件,加载的速度最快。
const http = require('http');
为动态加载模块,动态加载的模块主要由原生模块module来实现和完成。原生模块在启动时已经被加载,而文件模块须要经过调用module的require方法来实现加载。
首先定义一个文件模块,以计算圆形的面积和周长两个方法为例:
const PI = Math.PI; exports.area = (r) => { return PI * r * r; }; exports.circumference = (r) => { return 2 * PI * r; };
将这个文件存为circle.js,并新建一个app.js文件,并写入如下代码:
// 调用文件模块必须指定路径,不然会报错 const circle = require('./circle.js'); console.log( 'The area of a circle of radius 4 is ' + circle.area(4));
在require了这个文件以后,定义在exports对象上的方法即可以随意调用。
Node Packaged Modules 简称NPM,是随同node一块儿安装的包管理工具。Node自己提供了一些基本API模块,可是这些基本模块难以知足开发者需求。Node须要经过使用NPM来管理开发者自我研发的一些模块,并使其可以公用与其余的开发者。
NPM创建了一个node生态圈,node开发者和用户能够在里边互通有无。当你须要下载第三方包时,首先要知道有哪些包可用 npmjs.com 提供了能够根据包名来搜索的平台。知道包名后就可使用命令去安装了。
npm -v // 测试是否安装成功。
npm install moduleNames
npm install moduleNames -g // 全局安装 npm install moduleNames@2.0.0 // 安装特定版本依赖 npm install moduleNames --save // --save 可简写为 -S // 会在package.json的dependencies属性下添加moduleNames依赖 即生产依赖插件 npm install moduleNames --save-dev // --save-dev 可简写为 -D // 会在package.json的devDependencies属性下添加moduleNames依赖 即开发依赖插件
卸载模块
npm uninstall moduleNames
更新模块
npm update moduleNames
搜索模块
npm search moduleNames
切换模板仓库源:
npm config set registry https://registry.npm.taobao.org/ npm config get registry // 执行验证是否切换成功
第一次使用NPM发布本身的包须要在 npmjs.com 注册一个帐号。也可使用命令 npm adduser,提示输入帐号,密码和邮箱,而后将提示建立成功('Logged in as Username on https://registry.npmjs.org/.')。
输入npm init命令,根据提示配置包的相关信息,生成相应的package.json。npm命令运行时会读取当前目录的 package.json 文件和解释这个文件
经过npm publish发包,包的名称和版本就是你项目里package.json的name和vision。此处注意:
一个模块中的JavaScript代码仅在模块第一次被使用时执行一次,并在执行过程当中初始化模块的导出对象。以后,缓存起来的导出对象被重复利用。其中原生模块都被定义在lib这个目录下面,文件模块则不定性。
模块加载的优先级:已经缓存模块 > 原生模块 > 文件模块 > 从文件加载
尽管require方法很简单,可是内部的加载倒是十分复杂的
,其加载优先级也各自不一样。以下图示:
原生模块的优先级仅次于文件模块缓存的优先级。require方法在解析文件名以后,优先检查模块是否在原生模块列表中。
原生模块也有一个缓存区,一样也是优先从缓存区加载。若是缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。
实际上,在文件模块中又分为三类模块,之后缀为区分,node会根据后缀名来决定加载方法。
当文件模块缓存中不存在,并且也不是原生模块的时候,node会解析require方法传入的参数,并从文件系统中加载实际的文件。
加载文件模块的工做主要有原生模块module来实现和完成,该原生模块在启动时已经被加载,进程直接调用到runMain静态方法。
Module.runMain = function () { Module._load(process.argv[1], null, true); };
_load静态方法在分析文件名以后执行
var module = new Module(id, parent);
并根据文件路径缓存当前模块对象,该模块实例对象则根据文件名加载。
module.load(filename);
以.js后缀的文件为例,node在编译js文件的过程当中实际完成的步骤是对js文件内容进行头尾包装。例如刚才的app.js,在包装以后变成这个样子:
(function (exports, require, module, __filename, __dirname) { var circle = require('./circle.js'); console.log('The area of a circle of radius 4 is ' + circle.area(4)); });
这段代码拥有明确的上下文,不污染全局,返回为一个具体的function对象。最后传入module对象的exports,require方法,module,文件名,目录名做为实参并执行。
这就是为何require并有定义在app.js文件中,可是这个方法却存在的缘由。在这个主文件中,能够经过require方法去引入其他的模块。而其实这个require方法实际调用的就是load方法。
load方法在载入、编译、缓存了module后,返回module的exports对象。这就是circle.js文件中只有定义在exports对象上的方法才能被外部调用的缘由。
以上所描述的模块载入机制均定义在module模块之中。
require方法接受如下几种参数的传递:
在进入路径查找以前有必要描述如下module path这个node中的概念。对于每个被加载的文件模块,建立这个模块对象的时候,这个模块便会有一个paths属性,它的值根据当前文件的路径计算获得。
例:
咱们建立modulepath.js这样一个文件,其内容为:
console.log(module.paths);
执行node modulepath.js,将获得如下的输出结果:
[ '/Users/zhaoyunlong/Node/demo/node_modules', '/Users/zhaoyunlong/Node/node_modules', '/Users/zhaoyunlong/node_modules', '/Users/node_modules', '/node_modules' ]
Windows下:
[ 'E:\\Extra\\miniprogram\\gm-xcc-demo\\gm-demo\\node_modules', 'E:\\Extra\\miniprogram\\gm-xcc-demo\\node_modules', 'E:\\Extra\\miniprogram\\node_modules', 'E:\\Extra\\node_modules', 'E:\\node_modules' ]
能够看出module path的生成规则为:从当前文件目录开始查找node_modules目录;而后依次进入父目录,查找父目录的node_modules目录;依次迭代,直到根目录下的node_modules目录。
除此以外还有一个全局module path,是当前node执行文件的相对目录(../../lib/node)。若是在环境变量中设置了HOME目录和NODE_PATH目录的话,整个路径还包含NODE_PATH和HOME目录下的.node_libraries与.node_modules。其最终值大体以下:
[ NODE_PATH,HOME/.node_modules,HOME/.node_libraries,execPath/../../lib/node ]
简单说就是,若是require绝对路径的文件,查找时不会去遍历每个node_modules目录,其速度最快。其他流程以下:
整个查找过程十分相似JavaScript原型链的查找和做用域的查找。不一样的是node对路径查找实现了缓存机制,不然每次判断路径都是同步阻塞式进行,会致使严重的性能消耗。