哈喽小伙伴们,从这篇文章开始,我会开始新的专栏 Node;这个专栏里边会收录一些Node的基础知识和项目实战;今天我们开始这个专栏的第一篇文章,带领大家初识一下Node;让我们一起来看看吧
Node.js是一个Javascript运行环境(runtime)。
Node.js
是一个Javascript运行环境
(实际上它是对Google V8引擎进行了封装)。运行在服务器端,主要用于操作服务器文件、数据库、http协议等系统底层的一些东西。
Nodej.s
为了更好让Javascript
运行在浏览器之外的平台,其实现了很多的模块:文件系统
,模块
,包
,操作系统API
,网络通信
等CoreJavascript
中没有或者不完善的功能。
Node保持了JavaScript再浏览器中单线程的特点。
现今多数的Web 服务器中,有一条新的链接就会申请一条线程来负责处理至到这个Request 周期结束,接着执行其他流程。可以想象,成千上万个链接便有成千上万条线程(Thread-spawning )。每条线程姑且以堆栈2MB 的消耗去计算,一条条线程它们的累加都是不小的数目。如何优化和改进本身就是一个大问题,此外,使用系统线程,必须考虑线程锁的问题,否则造成堵塞主进程又是一个令人操心的难题。NodeJS 则通过基于事件的异步模型绕开了基于线程模型的所带来的问题。
NodeJS
使用JavaScript 单线程(Single-threaded
)轮询事件,设计上比较简单,高并发时,不仅根本性的减少了线程创建和切换的开销(因而没有吓人的消耗),而且由于没有锁,也不会造成进程阻塞。
单线程的优点:
单线程的缺点:
操作系统内核对于
I/O
只有两种方式:阻塞与非阻塞.在调用阻塞IO
时,应用程序需要等待I/O
完成才能返回结果.
阻塞I/O
造成CPU
等待I/O
,浪费等待时间,CPU的处理能力不能得到充分利用.为了提高性能,内核提供了非阻塞I/O
.非阻塞I/O
会在调用后立即返回.
关于异步前端开发工程师非常了解,因为我们发起一个ajax
请求就是一个异步调用:
console.log("执行了操作1")
$.post('/login',{user:"admin",pwd:123456},function(data){
console.log('收到响应')
})
console.log("执行了操作2")
在Node中,异步I/O也很常见。以读取文件为例,我们可以看到它与前端ajax调用的方式极其类似:
在Node底层构建了很多异步I/O的API,从文件读取到网络请求等,均是如此。这样的意义在于,在Node中,我们可以从语言层面很自然的进行并行I/O操作。每个调用之间无需等待之前的I/O调用结束。在编程模型上可以极大的提高效率。
下面的两个文件读取任务的耗时取决于最慢的那个文件读取的耗时:
fs.readFile("/file1",function(err,file){
console.log("file1读取完成")
})
fs.readFile("/file2",function(err,file){
console.log("file2读取完成")
})
对于同步I/O而言,它们的耗时是两个任务的耗时之和。异步带来的优势是显而易见的。
Node.js 是跨平台的,也就是说它能运行在 Windows、OSX 和 Linux 平台上。
V8是谷歌开发的,目前公认最快的 Javascript 解析引擎,libuv 是一个开源的、为 Node 定制而生的跨平台的异步 IO 库。
CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起;
什么是cpu密集型操作(复杂的运算、图片的操作)
// 这就是一个cpu密集型的操作
for (let i = 0; i < 100000000; i++) {
console.log(i);
}
解决方案:分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用的发起;
虽然nodejs的I/O操作开启了多线程,但是所有线程都是基于node服务进程开启的,并不能充分利用cpu资源
原因:单进程,单线程
解决方案:
为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。
网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等…开发者不得不使用软件工程的方法,管理网页的业务逻辑。
NodeJS中模块分为2类:原生模块和文件模块。
模块类型 | 描述 |
---|---|
原生模块 | 即Node.jsAPI提供的原生模块,原生模块在启动时已经被加载。(如:os模块、http模块、fs模块、buffer模块、path模块等) |
文件模块 | 为动态加载模块,加载文件模块的主要由原生模块module来实现和完成。原生模块在启动时已经被加载,而文件模块则需要通过调用Node.js的require方法来实现加载。 |
//调用原生模块不需要指定路径
var http = require('http');
//调用文件模块必须指定路径,否则会报错
var sum = require('./sum.js');
在文件模块中,又分为3类模块。这三类文件模块以后缀来区分,Node.js会根据后缀名来决定加载方法。
类型 | 描述 |
---|---|
.js | 通过fs模块同步读取js文件并编译执行。 |
.node | 通过C/C++进行编写的Addon。通过dlopen方法进行加载。 |
.json | 读取文件,调用JSON.parse解析加载。 |
在编写每个模块时,都有require、exports、module三个预先定义好的变量可供使用。
require方法接受以下几种参数的传递:
http
、fs
、path
等,原生模块。/mod
或../mod
,相对路径的文件模块。/pathtomodule/mod
,绝对路径的文件模块。mod
,非原生模块的文件模块。
require
函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。模块名可使用相对路径(以./开头),或者是绝对路径(以/或C:之类的盘符开头)。另外,模块名中的.js扩展名可以省略。以下是一个例子。
var foo1 = require('./foo');
var foo2 = require('./foo.js');
var foo3 = require('/home/user/foo');
var foo4 = require('/home/user/foo.js');
//foo1 ~ foo4 中保存的是同一个模块的导出对象。
nodejs核心模块可以不加路径直接通过require导入:
//加载node 核心模块
var fs = require('fs');
var http = require('http');
var os = require('os');
var path = require('path');
加载和使用json文件
var data = require('./data.json');
exports
对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require
函数使用当前模块时得到的就是当前模块的exports
对象。以下例子中导出了一个公有方法。
//sum.js
exports.sum = function(a,b){
return a+b;
}
//main.js
var m = require("./sum");
var num = m.sum(10,20);
console.log(num);
通过module
对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块导出对象。例如模块默认导出对象默认是一个普通对象,如果想改为一个函数可以通过如下方式:
导出一个普通函数:
//sum.js
function sum(a,b){
return a+b;
}
module.exports= sum;
//main.js
var sum = require('./sum');
sum(10,20);// 30
导出一个构造函数:
//hello.js
function hello(){
this.name ="你的名字";
this.setName = function(name){
this.name = name;
}
this.sayName = function(){
alert(this.name);
}
}
module.exports= hello;
//main.js
var hello = require('./hello.js');
var o = new hello();
o.setName('张三');
o.sayName(); // 张三
每一个js文件都是一个模块,每一个模块都有一个对象这个对象的可以通过module访问到,该对象上保存了一些我们当前模块的状态:
API | 描述 |
---|---|
module.id | 模块的ID,通常是当前模块文件路径,含文件名 |
module.filename | 当前模块文件路径,含文件名 |
module.loaded | 判断模块当前是否已加载 |
module.parent | 加载当前脚本的模块对象。 |
module.children | 当前模块加载的模块对象集合,是一个数组 |
由多个子模块组成的大模块称作包。
在组成一个包的所有子模块中,需要有一个入口模块,入口模块的导出对象被作为包的导出对象。例如有以下目录结构。
d:/node/calc/
/calc
sum.js //加法
subtraction.js //减法
multiplication.js //乘法
division.js //除法
main.js //主模块 入口模块
calc
目录定义了一个包,其中包含了4个子模块,main.js
作为入口模块。
var sum = require('./sum.js');
var subtraction = reuqire('./subtraction.js');
var multiplication = require('./multiplication.js');
var division = require('./division.js');
function calc(a,tag,b){
switch(tag){
case '+':
return sum(a,b);
break;
case '-':
return subtraction(a,b);
break;
case '*':
return multiplication(a,b);
break;
case '/':
return division(a,b);
}
}
module.exports = calc;
然后我们要使用这个包
//d:/node/use.js
var calc = require('./calc/main.js');
console.log(clac(10,'+',100));
但是这样使用感觉不像一个包。
var calc = require('./calc');
如果想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个 package.json
文件,package.json
是包的配置文件。
d:/node/calc/
/calc
sum.js //加法
subtraction.js //减法
multiplication.js //乘法
division.js //除法
main.js //主模块 入口模块
//package.json
{
"name": "calc",
"main": "./calc/main.js"
}
属性 | 描述 |
---|---|
name | 包名。 |
version | 包的版本号。 |
description | 包的描述。 |
homepage | 包的官网 url 。 |
author | 包的作者姓名。 |
contributors | 包的其他贡献者姓名。 |
devDependencies | 开发环境依赖包列表。 |
dependencies | 生产环境依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。 |
repository | 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。 |
main | main 字段是一个模块ID,它是一个指向你程序的主要项目。就是说,如果你包的名字叫 express,然后用户安装它,然后require(“express”)。 |
keywords | 关键字 |
它是这样一个json文件(注意:json文件内是不能写注释的,复制下列内容请删除注释):
{
"name": "test", //项目名称(必须)
"version": "1.0.0", //项目版本(必须)
"description": "This is for study gulp project !", //项目描述(必须)
"homepage": "", //项目主页
"repository": { //项目资源库
"type": "git",
"url": "https://git.oschina.net/xxxx"
},
"author": { //项目作者信息
"name": "surging",
"email": "[email protected]"
},
"license": "ISC", //项目许可协议
"devDependencies": { //项目开发依赖包
"gulp": "^3.8.11",
"gulp-less": "^3.0.0"
},
dependencies:{ //项目生产依赖包
}
}
包是在模块基础上更深一步的抽象,Nodejs 的包类似于 C/C++ 的函数库或者 Java/.Net 的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。Nodejs 根 据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。
Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符 合 CommonJS 规范的包应该具备以下特征:
文件类型 | 描述 |
---|---|
package.json | 必须在包的顶层目录下 |
二进制文件 | 应该在 bin 目录下 |
JavaScript | 应该在 lib 目录下 |
文档 | 应该在 doc 目录下 |
单元测试 | 应该在 test 目录下 |
Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。
/
├── bin/ # 存放命令行相关代码
│
├── src/ # 开发目录
│
├── doc/ # 存放文档
│
├── lib/ # 存放API相关代码
│
├── node_modules/ # 存放三方包
│
├── tests/ # 存放测试用例
│
├── package.json # 元数据文件
│
└── README.md # 说明文件
这篇文章开启了我们的Node专栏,更多组Node知识以及API请大家持续关注,尽请期待。各位小伙伴让我们 let’s be prepared at all times!
✨原创不易,还希望各位大佬支持一下!
点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!