node

299 阅读4分钟

node 如何解决循环引用的问题

// b module require('./a.js')如果已引用过a.js时不会load新的a module, 而是将已经load但是还未完成的a module的exports属性返回给b module

工具

  • nodemon 自动监听文件的变化
pageage.json中添加scripts
 "scripts": {
        "dev": "nodemon ./indexc.js"
    },

__dirname与__filename

  • __dirname:当前运行脚本所在文件位置:
  • __filename:当前运行脚本所在位置 :
console.log(__filename)  //  /Users/chenyifan/myOcean/node/index.js
console.log(__dirname)  //  /Users/chenyifan/myOcean/node

常用api

内置对象

  • resolve 为url或http插入或者替换原有标签 (总结:替换 域名后面第一个“/”后的内容)
var url = require('url');
var a = url.resolve('/one/two/three', 'four') ,
b = url.resolve('http://example.com/', '/one'),
c = url.resolve('http://example.com/one', '/two');
console.log(a +","+ b +","+ c);
//输出结果
/one/two/four
http://example.com/one
http://example.com/two

glob 匹配文件路径

  • 单个星号* : 用于匹配单个片段中的零个或多个字符
 1. `src/*.js` 表示 `src` 目录下所有以 `js` 结尾的文件,
 但是不能匹配 `src` 子目录中的文件,例如 `src/login/login.js`
  • 两个星号** 可以跨片段匹配零个或多个字符,也就是说**是递归匹配所有文件和目录的,如果后面有分隔符,即 **/ 的话,则表示只递归匹配所有目录
 1. `/var/log/**` 匹配 `/var/log` 目录下所有文件和文件夹,以及文件夹里面所有子文件和子文件夹
 2. `/var/log/**/*.log` 匹配 `/var/log` 及其子目录下的所有以 `.log` 结尾的文件

示例:

//`src` 目录下所有 `js` 和 `jsx` 文件

const glob = require('glob')
const files = glob.sync('src/**/*.js{,x}')
console.log(files)

读取文件

  • readFile:异步读取
  • readFileSync:同步读取
  • 使用node的读写流时,若data是string,则可以指定encoding参数,以指定的编码类型来读写字符串;若data是buffer,则忽略encoding参数
var fs = require('fs');                //获取fs模块
console.log('start async read');        //异步读取开始
fs.readFile('test2.js',function(error,date){    //读取文件,回调函数第一个参数表示错误信息,第二个参数为读取的文本内容
    if(error){
        console.log(error);
    }else{
        console.log('end async read');    //异步读取结束
    }
});


console.log('start sync read'); //同步读取开始
var date = fs.readFileSync('log.txt','utf-8');//指定为utf格式
console.log('end sync read');    //同步读取结束
  • readFile 和createReadStream 函数的区别
1. createReadStream读取时被分块发送,需要的内存小,readFile将读取整个文件,需要的内存大
2. 如果在100MB的文件上运行这两个函数,第一个函数将使用100MB内存来加载文件,而后者最多只使用4KB
3. pipe()只是可读流的方法,也就是说只能从可读流中通过pipe方法拷贝数据到可写流,反之则不行,写的时候要注意顺序
fs.createReadStream('a.mp4').pipe(fs.createWriteStream('b.mp4)); // pipe自动调用了data,end等事件
  • 创建buffer
  1. Buffer.from()从字符串或者数组创建一个buffer,
  2. Buffer.alloc()是创建一个指定大小的buffer。
  • Nodejs不能控制数据传输的速度和到达时间,只能决定何时发送数据,如果还没到发送时间,则将数据放在Buffer
  • Buffer对象中的每个元素都是16进制的两位数(即0到255的数值)
const buffer1 = Buffer.from('geekbanghh')//<Buffer 67 65 65 6b 62 61 6e 67 68 68>
const buffer2 = Buffer.from([1, 2, 3, 4])//<Buffer 01 02 03 04>
const buffer3 = Buffer.alloc(20)//<Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>

/// 向buffer2的buffer中写入数字12,类型为int8,(int8 只占用两位),从图中第1个位置写入,
buffer2.writeInt8(12, 1)//<Buffer 01 0c 03 04>

// 向buffer2中写入数字512,并且写入位置从第2个位置写入, 因为是Int16,所以需要占用两位,512的十六进制数字为 02 00 2*16^2=512
buffer2.writeInt16LE(512, 2)//<Buffer 01 0c 02 00>

process 进程对象

  • process.argv 属性返回一个数组,这个数组包含了启动Node.js进程时的命令行参数,(可以获取到启动Node.js输入的参数)
chenyifanMacdeMacBook-Pro:section8 chenyifan$ node my-spr.js my-spr
[
  '/usr/local/bin/node',
  '/Users/chenyifan/myOcean/node/geek-nodejs/section8/my-spr.js',
  'my-spr'
]

模块规范

  • require返回的对象,和***.js里的exports对象属于同一个引用
---index.js
console.log('start require');
var lib = require('./lib')
console.log('end require', lib);
// 参见lib.js注释里的知识点1
console.log(lib.tencent);
// 参见lib.js注释里的知识点2
// require返回的对象,和lib.js里的exports对象属于同一个引用
// 因此此处加的属性能在里面体现出来。
lib.additional属性 = 'test'

---lib.js
console.log('this is module');
exports.geekbang = { 'hello': 'haha' }
exports.tencent = function () {
    console.log('good')
}
setTimeout(()=> {
    // 验证index.js里加的additional属性是否生效
    // 用于确定外部require到的对象和此处的exports是否是同一个属性
    console.log(exports)//导出对象中有additional属性
}, 2000)
  • 最终导出的是module.exports 所以exports={}会断开与module.exports的连接,只能听过exports.= 的方法导出变量

image.png

npm

pageage.json 表示该目录是一个npm包

  • 安装包:npm install *** -S / npm install *** -D
  • 卸载包:npm uninstall ***

非阻塞 I/O

阻塞 I/O 和非阻塞 I/O 的区别就在于系统接收输入再到输出期间,能不能接收其他输入

eventLoop

node的事件循环会重新开启一个调用栈,所以,错误捕获如果用try catch会抛出到全局,一般使用callback的方法

function interview(callback) {
    setTimeout(() => {
        if (Math.random() > 0.2) {
            callback(null, 'success')

        } else {
            // throw new Error('fail');//捕获不到,开启了一个全新的作用栈,故不会像下传递
            callback(new Error('fail'))
        }

    }, 500)
}
 interview(function (err, res) {
        if (err) {//捕获错误
            console.log('cry')
            return;
        }
        console.log('smile')
    })

解析url

  • new URL() //最新方式
  • querystring

Koa2 vs Express

  • Express request/response 简化
  • request: pathname、query 等
  • response: send()、json()、jsonp() 等

中间件

  • 更好地组织流程代码

  • 异步会打破 Express 的洋葱模型

  • koa

  • ctx.status = 200

  • ctx.body = 'hello world' 使用 async function 实现的中间件

  • 有“暂停执行”的能力

  • 在异步的情况下也符合洋葱模型

http通信和RPC通信

  • html编码格式:json/html格式
  • RPC编码格式:二进制 [0001 1110 0000 0000]更小的数据包体积,更快的编解码速率
  1. Buffer.from
  2. Buffer.alloc Buffer对象中的每个元素都是16进制的两位数(即0到255的数值)
const buffer1 = Buffer.from('geekbangqq')//10为长度的buffer
const buffer2 = Buffer.from([1, 2, 3, 4])//4为长度的buffer
const buffer3 = Buffer.alloc(20)//长度为20的空buffer

console.log(buffer1)
console.log(buffer2)
console.log(buffer3)
//打印结果如下
<Buffer 67 65 65 6b 62 61 6e 67 71 71>
<Buffer 01 02 03 04>
<Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>

3.protocol-buffers 编码二进制/解码二进制

//test.proto 定义数据格式
message Course {
    required float id = 1;
    required string name = 2;
    repeated Lesson lesson = 3;
}
//protobuf.js
const schemas = protobuf(fs.readFileSync(`${__dirname}/test.proto`));
//编码
const buffer = schemas.Course.encode({
    id: 4,
    name: 'hh',
    lesson: []
})
console.log( buffer);//<Buffer 67 65 65 6b 62 61 6e 67 71 71>
//解码
console.log(schemas.Course.decode(buffer));//解码出定义的对象

项目

  • easy_sock 与服务端进行RPC通信的
const easySock = new EasySock({ 
    ip: '127.0.0.1',
    port: 4000,
    timeout: 500,
    keepAlive: true //是否为全双工通信
})
//server.js
const app = new (require('koa'))()
const graphqlHTTP = require('koa-graphql')
app.use(
    graphqlHTTP({
        schema: require('./schema')
    })
)
app.listen(3000)

//schema.js
const { graphql, buildSchema } = require('graphql');

const schema = buildSchema(`
    type Comment {
        id: Int
        avatar: String
        name: String
        isTop: Boolean
        content: String
        publishDate: String
        commentNum: Int
        praiseNum: Int
    }
    type Query {
        comment: [Comment]
    }
`)

schema.getQueryType().getFields().comment.resolve = () => {
    return [{
        id: 1,
        avatar: "https://static001.geekbang.org/account/avatar/00/0f/52/62/1b3ebed5.jpg",
        name: "testName",
        isTop: true,
        content: "哈哈哈哈",
        publishDate: "今天",
        commentNum: 10,
        praiseNum: 5
    }]
}

module.exports = schema;
  • 发送post请求

image.png

//schema.js新增type Mutation 
const schema = buildSchema(`
   ...
    type Mutation {
        praise(id: Int): Int
    }
`)
schema.getMutationType().getFields().praise.resolve = (args0, { id }) => {
    mockDatabase[id].praiseNum++;
    return mockDatabase[id].praiseNum
}

node 性能分析

计算性能优化的本质

  1. 减少不必要的计算
  2. 空间换时间

性能分析评估

压测工具

  • ab -c200 -n1600 http:127.0.0.1:3000/download //-c:ab模拟多少个客户端(200人同时执行我们的网页服务器) -n:总空执行1600次请求

devtools

1.node --inspect-brk entry.js //压测:-brk启动调试会暂停代码的执行
2.浏览器输入:chrome://inspect/#devices ->Target->inspect
3.
  • node --inspect --inspect-brk entry.js //-brk启动调试会暂停代码的执行
  • 将计算移除到中间件之外

image.png