1、nodejs的基本原理介绍
参考《深入浏览器工作原理和JS引擎》:juejin.cn/post/705000…
2、node 版本管理
主要有n和nvm两种管理工具。
nvm安装教程:t.csdn.cn/vRmEe
3、nodejs的基本使用
1、运行文件
在终端写入: node index.js
传递参数: node index.js coderwhy
运行index.js文件,获取参数的方法
console.log(process.argv)
2、node中的全局对象
(1)特殊的全局变量,在模块中使用,每个模块都有,但不是全局的量。在命令行交互REPL中不可使用。
例如:process对象、setTimeout()延迟函数、global对象
global对象中都有什么内容?
global对象和window对象的区别?
虽然两者同属全局变量。但是浏览器在执行js代码时,会在顶级范围内使用var
定义一个属性,默认添加到window对象上。Node在执行代码时,var定义的属性只在当前模块下吗,不会放在全局中。如下图所示:
——Buffer
Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。
Buffer 提供了以下 API 来创建 Buffer 类:
- Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0
- Buffer.allocUnsafe(size): 返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据
- Buffer.allocUnsafeSlow(size)
- Buffer.from(array): 返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)
- Buffer.from(arrayBuffer[, byteOffset[, length]]): 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
- Buffer.from(buffer): 复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
- Buffer.from(string[, encoding]): 返回一个被 string 的值初始化的新的 Buffer 实例
——Stream流
Stream 对象常用的事件有:
- data - 当有数据可读时触发。
- end - 没有更多的数据可读时触发。
- error - 在接收和写入过程中发生错误时触发。
- finish - 所有数据已被写入到底层系统时触发。
——回调函数
回调函数就是一个被作为参数传递的函数。
一般用于截获消息、获取系统信息或处理异步事件。比如:箭头函数,原生JS方法,事件绑定,异步。
nodejs的异步编程的直接体现:异步程序依托于回调函数。node中所有的API都支持回调函数。
回调函数是如何实现异步原理的呢?
回调函数和直接在主函数体中调用函数的区别在于:主函数中调用其他函数需要程序员已知具体用到那个函数,而回调函数则是预留函数调用的位置,在需要的时候这里可以放需要的函数。更加灵活,适用于各种api中。
回调地狱:
在ES6中新引入了Promise和Generator、await/async来解决回调地狱。
4、事件循环原理
(1)Node.js的运行机制
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。
(2)介绍
进程和线程:t.csdn.cn/yU0Lg
Event Loop(事件循环):www.ruanyifeng.com/blog/2013/1…
事件循环是JavaScript 的执行机制, 实际上是我们编写js和浏览器或者Node之间的一个桥梁。桥梁之间通过回调函数进行沟通。浏览器中的EventLoop是根据HTML5定义来规范的,是js代码和浏览器API(如:setTImeout,AJAX,监听事件等)调用的一个桥梁。Node中是由libuv实现的,是js代码和系统调用(如:file system,network等)的一个桥梁。
(3)执行机制
浏览器的事件循环机制:
任务队列又分为macro-task(宏任务)与micro-task(微任务)。注意:Promise属于同步任务。
宏任务包括:scrip整体代码、setTimeout、setInterval、setImmediate、I/O操作、UI 渲染 微任务包括:process.nextTick、Promise.then、Async/Await(语法糖,Await相当于Promise,Await之后的内容相当与Promise.then)、MutationObserver
总结:
- 首先执行宏任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。
- 当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务;如果没有,会读取宏任务中排在最前面的任务。
- 若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。
- 以此类推。
Node的事件循环机制:
从一次事件循环的Tick来说,node的事件循环分为:
5、模块化开发
早期的javaScript语言,没有模块化开发,会出现命名冲突等诸多问题。
后来出现了CommonJS规范,最初为ServerJS提出来是浏览器以外的地方使用后来得到了广泛应用。Node是CommonJS在服务端一个具有/代表性的实现。webpack具备对CommonJS的支持和转换。Node中主要的导出方式 module.exports和exports ,导入方式const bar = require('./bar.js')
module.exports和exports两者之间有什么区别?
module和exports是Node.js给每个js文件内置的两个对象。可以通过console.log(module)和console.log(exports)打印出来。实际上,这两个对象指向同一块内存。这也就是说module.exports和exports是等价的。module.exports = exports = {},此时两者导出值类型存储在栈上,这两种写法是一致的。
//console.log(module);
//输出:Module {..., exports:18 , ...} (注:...代表省略了其他一些属性)
info={
name:'2233',
arg:18
}
exports = info
module.exports = exports
info.name = 'syy'
console.log(exports);
console.log(module.exports);
// const bar = require('./global.js')
// console.log(bar)
但是,require默认引入的对象本质上是module.exports。这就产生了一个问题,当 module.exports和exports,两者导出指向的不是同一块内存时,exports的内容就会失效。
exports = {name: '萤火虫老阿姨'}
module.exports = {name: '萤火虫叔叔'}
//exports 输出为 {name: '萤火虫老阿姨'}
//module.exports 输出为 {..., exports:{name: '萤火虫叔叔'}, ...}
//此时bar引入输出为{name: '萤火虫叔叔'}
(1)模块引入的细节
require本质是一个函数,采用的是同步引入,在查找引入过程中的查找规则,比如 查找xyz。
- 如果存在核心模块(path, url)等,可以直接引入即可。
- 第一步,将xyz当作文件名,在对应目录下查找。按后缀名格式查找,无后缀名按默认文件格式查找顺序。
- 第二步,没找到对应文件,将xyz当作目录查找目录的index文件,无后缀名按默认文件格式查找顺序。
- 默认文件格式查找顺序依次为:js文件 、json文件 、node文件。
模块的加载过程:
(2)模块导出方法
其中AMD和CMD等社区规范在早期使用。
CommonJS加载模块是同步的是对模块的浅拷贝,多在服务器中使用,所以浏览器中通常不使用CommonJS规范。但在webpack中仍然使用,因为webpack会将代码转换成浏览器可以直接执行的代码。目前,浏览器已经支持ES Modules,另一方面借助webpack对CommonJS的转换。
//导出方式
module.exports = createCommand
module.exports ={
createProject
}
//导入方式
const createCommand = require('./lib/core/create.js')
const { createProject } = require('./actions.js')
ES Module模块是对模块的引用,采用 export 和import 关键字来实现模块化。ES Modules的规范如下:
//导出方式:直接导出、括号导出、起别名导出、默认导出
export let age = 3
export function sayHello() {
console.log('say')
return 'Hello'
}
export{ name,age,sex,sayHello } // 这里的大括号,并不表示对象
export{
name as name1,
age as age1,
} // 为name起别名为name1
export {name,age,sayHello as default} //默认导出
export default function () {
console.log('say')
return 'Hello'
} //默认导出引用时可以随意命名
//导入方式
import { name,age,sayHello } from './bar.js'
import { name as name2,age as age2, } from './bar.js'
import * as bar from './bar.js'
//相结合:引用进来的同时,导出文件。
export {sum as barSum} from './bar.js'
(3)ES的模块加载过程
ES Module加载js文件的过程是编译(解析)时加载的,并且时异步的:
编译时加载,意味着import不能和运行时相关的内容放在一起,比如:当from后面的路径需要动态获取、import放在if、函数、for等语句的代码块中。
//错误示范
if(type == true){
import { sum } from './bar.js'
}
ES Module是对模块的引用。
commonjs是对模块的浅拷贝。因此import的接口是read-only(只读状态),不能修改其变量值。即不能修改变量的指针指向,但是可以改变变量内部的指针指 向。esmodule赋值会编译报错,但可以对commonjs对重新赋值。
在node中使用ESModule需要js文件改为.mjs的文件类型
一个js文件就是一个模块,但是在node 中默认支持CommonJS的规则。此时运用ES Modules规则时需要改为.mjs的文件类型。
//1、文件类型.mjs
//2、引入
import { foo, bar } from './index.mjs';
//3、终端运行语句
node index.mjs
(4)ES和CS的交互
- 通常情况下,CommonJS不能加载ES Module。因为CommonJS事同步加载的,处于文件加载完成后代码编译过程,但是ES Module必须经过静态分析,在代码编译阶段就完成了导出导入。
- 多数情况下,ES Module可以加载CommonJS。在加载过程中module.exports导出的内容作为default导出方式来使用。
6、node中常见的属性、内置模块 、方法
(1)属性
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。Node.js 中的全局对象是 global。
常见全局变量有:
setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。
clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器t。
console 用于提供控制台标准输出。
process 用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。
(2)内置模块
参考:www.cnblogs.com/wfblog/p/15…
nodejs内置模块有哪些?
nodejs内置模块指的是除默认提供的语法之外,提供的美容,无需下载,直接引入,引入只写名称即可。
- path模块,用于处理文件路径的各类API;
- until模块,用于弥补js功能不足,提供常用函数的集合;
- fs模块,文件操作系统的API;
- events模块,提供了一个“events.EventEmitter”对象;
- jade模块,可通过jade来编写html文件。
- OS 模块提供基本的系统操作函数。
- Net 模块用于底层的网络通信。提供了服务端和客户端的的操作。
- DNS 模块 用于解析域名。
- Domain 模块简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的。
path模块:
path模块知识拓展:www.runoob.com/nodejs/node…
注意MAC 、Linux、window上的路径有着不一样的规范。为了屏蔽操作系统之间的差异,使用path内置模块对路径进行操作。
const path = require('path')
let basePath = '/User/why'
let fileName = 'aer.txt'
const filePath = path.resolve( basePath, fileName)
console.log(filePath)
//打印结果C:\User\why\aer.txt
path模块的常用参数:
- dirname:获取文件的父文件名
- basename:获取文件名
- extname:获取文件扩展名
path模块的常用方法:
- path.resolve()将文件与文件夹拼接。
- resolve()会判断拼接路径前面是否有/ 或 ../ 或 ./。如果有表示绝对路径,如果没有会和当前执行文件所在的文件夹进行路径的拼接。
util模块:
fs模块:
fs 模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。
7、npm包管理工具
(1)npm命令
npm常用命令:
npm install <packageName> 安装
npm install <packageName> --force 强制重新安装
npm update <packageName> 更新
npm init -y 用来初始化一个简单的 package.json 文件
npm link 一个包可以链接到另一个项目
npm ls 查看依赖树
npm install 原理:
关于依赖树的了解以及npm进行扁平结构的依赖树层级管理、package-lock.json文件锁定依赖安装结构
参考《2018 年了,你还是只会 npm install 吗?》:juejin.cn/post/684490…
参考《npm 模块安装机制简介》:www.ruanyifeng.com/blog/2016/0…
npm run xxx的时候,发生了什么?
- 运行 npm run xxx的时候,npm 会先在当前目录的 node_modules/.bin 查找要执行的程序,如果找到则运行;
- 没有找到则从全局的 node_modules/.bin 中查找,npm i -g xxx就是安装到到全局目录;
- 如果全局目录还是没找到,那么就从 path 环境变量中查找有没有其他同名的可执行程序。
npm run xxx原理说明:
1、首先会去项目的package.json文件里找scripts 里找对应的xxx,然后执行 xxx的命令。
例如启动vue项目 npm run serve的时候,实际上就是执行了vue-cli-service serve 这条命令。
2、npm install 在 安装这个依赖的时候,就会node_modules/.bin/ 目录中创建 好vue-cli-service 为名的几个可执行文件了。(操作系统实际上没有存在vue-cli-service这一条指令。)
3、npm install 在 安装这个依赖的时候,读取package-lock.json文件就会将一个个文件软链接到 ./node_modules/.bin 目录下,自动node_modules/.bin加入$PATH,这样就可以直接作为命令运行依赖程序和开发依赖程序,不用全局安装了。
4、 bin 目录,保存了依赖目录中所安装的可供调用的命令行包。打开目录文件可以看到文件顶部写着 #!/bin/sh ,表示一个个脚本,在这个脚本中代替执行了vue-cli-service这一条指令。
另外:npm install -g xxx 来安装,那么会将其中的 bin 文件加入到全局,比如 create-react-app 和 vue-cli ,在全局安装后,就可以直接使用如 vue-cli projectName 这样的命令来创建项目了。
yarn、npm 的区别:
参考《字节的一个小问题 npm 和 yarn不一样吗?》:juejin.cn/post/706084…
了解pnpm
(2)项目配置文件package.json
文件内的属性:
name:项目名称 (必填)
version:项目版本号 (必填)
description:项目的基本描述信息
author:作者相关信息 (发布时用到)
license:开源协议 (发布时用到)
private:记录项目是否私有,当为true时,npm不能发布此项目
main:设置程序的入口。1、在前端项目webpack会自动找到程序的入口,main属性和webpack打包的入口并不冲突。2、当作为一个模块打包开源时,将会通过main属性作为入口文件查找文件。比如使用axios模块,const axios= require('axios')
bin: 字段是命令名到本地文件名的映射
scripts:配置脚本命令,
"scripts": {
"preserve": "npm install kylink_ui@latest",
"serve": "node index.js "
},
//npm run serve 执行node index.js命令.
//在serve之前预执行preserve的命令语句
dependencies:指定开发环境、生产环境所需要的依赖包。通常是项目实际来发过程用到的模块
devDependencies:指定生成环境不需要的包,如webpack、balel。通过npm install webpack --save-dev 安装
8、脚手架工具开发
(1)设置node命令
1、新建项目空目录,添加`index.js`文件,在当前目录下执行终端`npm init -y`生成package.json配置文件。在package.json中设置main属性,将index.js设置为入口文件。
2、在index.js文件顶行顶格输入
#!/usr/bin/env node
console.log('18772960133')
#!英文名叫“Sha-bang”,告诉Shell使用什么命令执行该脚本。后面接相对路径/user/bin/env以及脚本类型为node
3、在package.json配置bin属性,用键值对的方式,将相关命令语句绑定文件。然后,通过执行在终端执行npm link,将bin和环境变量进行连接即“iccry”配置在环境变量中。之后在终端运行命令,就可以取代node index.js
(2)命令语句设置
参考:zhuanlan.zhihu.com/p/354347260
使用commander插件来进行命令行工具的开发工作,首先在终端npm install commander完成安装。熟悉Commander的使用,在index.js中设置。( 将有关commander的部分,封装起来,引入index.js,提高代码整洁性。)
官方学习地址:
const program = require('commander')
// command:自定义执行的命令
// oprton:自定义选项,表示可选参数
// description:命令的描述
// action:命令执行之后执行的方法
// parse:解析命令行参数,放在最后
- .version()方法定义版本,
program.version(‘0.0.1’);也可以从packjson.json文件中引入,例如:program.version(require('./package.json').version,'-v,--version') - .option()方法来定义选项,同时可以附加选项的简介。每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(–后面接一个或多个单词),使用逗号、空格或|分隔。例如:
program.option('-d, --debug', 'options debug')
(3)仿vue-cli操作
详细操作过程,见iccry_cli文件夹实战记录。
两种执行git clone 的方法:
download-git-repo官网:gitlab.com/flippidippi…
创建子进程:child_process
参考:www.jianshu.com/p/692d9d2e7…
Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。
每个子进程总是带有三个流对象:child.stdin, child.stdout 和 child.stderr。他们可能会共享父进程的 stdio 流,或者也可以是独立的被导流的流对象。
Node 提供了 child_process 模块来创建子进程,方法有:
- exec - child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。
- spawn - child_process.spawn 使用指定的命令行参数创建新进程。
- fork - child_process.fork 是 spawn()的特殊形式,用于在子进程中运行的模块,如 fork('./son.js') 相当于 spawn('node', ['./son.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。
拓展:
学习了解VUE_cli脚手架中使用的各种插件。在这个位置中vue-cli/package/@vue/cli文件中的package.json,列举了vue-cli中用到的一些插件。 仿写vue-cli功能,对vue-cli源码进行学习。
设置文件模板EJS插件:
EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码生成 HTML 页面。
??? program.dest
发布在npm:
- 注册npm账号
- 命令行:
npm login,输入用户名称、密码、邮箱以及邮箱验证码,进行账号登录。 - 调整package.json文件,新增各项内容。命令行:
npm publish,发布。
9、服务端——Http模块
(安装nodemon插件,启动服务器时,代码改变后自动更新。)
什么是客户端、什么是服务器?
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。
了解http服务器常用方法 以及 request, response请求对象、响应对象常用属性
var http = require('http');
var fs = require('fs');
var url = require('url');
// 创建服务器
http.createServer( function (request, response) {
//(request, response)请求对象、响应对象
// 解析请求,包括文件名
var pathname = url.parse(request.url).pathname;
// 输出请求的文件名
console.log("Request for " + pathname + " received.");
// 从文件系统中读取请求的文件内容
fs.readFile(pathname.substr(1), function (err, data) {
if (err) {
console.log(err);
response.writeHead(404, {'Content-Type': 'text/html'});
}else{
response.writeHead(200, {'Content-Type': 'text/html'});
// 响应文件内容
response.write(data.toString());
}
// 发送响应数据
response.end();
});
}).listen(8080);
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');
浏览器header属性解析: response header:
Date
表示服务器返回消息的时间,这个时间可用于评估缓存的新鲜度时要用到
Cache-Control
服务端告诉客户端对于响应内容的缓存如何控制
content-type
表示返回数据的类型
content-Encoding
服务端表明自己使用了什么压缩方法压缩响应的内容。
例如:Content-Encoding:gzip
access-control-allow-origin
所请求的服务器,允许来自哪些域名的访问
access-control-allow-methods
允许使用的访问方法