feng xiaohan
connect-flash

跨请求存储数据,需要一个 session。但如果不想将错误信息永久存储在 session 中,而是想在错误信息中添加一些别的信息,将其快速发送到 session 中。一旦我们使用了这个在 session 中的错误信息,这个错误信息将会从 session 中删除,这样对于后面的请求,session 中将不会存在这个错误信息。

flash 是在 session 中用于储存信息的特殊区域,在其中存储的值使用过一次便被清空。我们将消息入到 flash 中,跳转目标页中显示该消息。它常和redirect一起使用,用于一次性消息提示。

下载

npm install --save connect-flash

引入注册

app.js

const flash = require('connect-flash');

const app = express();
app.use(flash());
...

将信息存入 flash 中

通过req.flash('name', 'value')来向 falsh 中存入一条信息:

exports.postLogin = (req, res, next) => {
  const email = req.body.email;
  const password = req.body.password;
  User.findOne({ email: email })
    .then((user) => {
      if (!user) {
        req.flash("error", "Invalid email or password");
        return res.redirect("/login");
      }
    })
    .catch((err) => console.log(err));
};

获取 falsh 中的信息

通过req.flash('name')来获取设置的信息,并将它发送到页面:

exports.getLogin = (req, res, next) => {
  let message = req.flash("error"); // 获取flash中存入的信息
  if (message.length > 0) {
    message = message[0];
  } else {
    message = null;
  }
  res.render("auth/login", {
    path: "/login",
    pageTitle: "Login",
    errorMessage: message, // 发送到页面中
  });
};
Bcrypt

一个跨平台的加密工具。

在 Node 上使用

下载

npm install --save bcryptjs

加密数据

使用bcrypt.hash()加密数据,该方法接收两个参数:

  • 第一个参数:需要加密的数据

  • 第二个参数:表示 hash 加密的轮数

    安全度越高,花费的时间就越长。12被认为是较高安全度的。

返回一个 Promise,内部传递一个加密后的数据:

const bcrypt = require("bcryptjs"); // 引入bcryptjs

const password = "sdfdsfdsafdsf";

// 注册
exports.postSignup = (req, res, next) => {
  const email = req.body.email;
  const password = req.body.password;
  const confirmPassword = req.body.confirmPassword;
  User.findOne({ email: email })
    .then((userDoc) => {
      if (userDoc) {
        return res.redirect("/signup");
      }
      return bcrypt
        .hash(password, 12) // 使用hash加密密码
        .then((hashedPassword) => {
          // 加密后的数据
          const user = new User({
            email: email,
            password: hashedPassword, // 用加密后的密码替换原密码传给数据库
            cart: { items: [] },
          });
          return user.save();
        })
        .then((result) => {
          res.redirect("/login");
        });
    })
    .catch((err) => {
      console.log(err);
    });
};

对比数据

由于数据经过 bcrypt 通过某种算法加密后,我们无法得知原信息是否与需要验证的信息相匹配,所以要通过 bcrypt 内部的来对比。可以使用bcrypt.compare()来进行对比,该方法接收两个参数:

  • 第一个参数:未经加密的数据
  • 第二个参数:经过 hash 加密后的数据

返回一个 Promise,内部传递一个 boolean 表示是否匹配:

// 登录
exports.postLogin = (req, res, next) => {
  const email = req.body.email;
  const password = req.body.password;
  User.findOne({ email: email })
    .then((user) => {
      if (!user) {
        return res.redirect("/login");
      }
      bcrypt
        .compare(password, user.password) // 将浏览器(用户)输入的密码和数据库中存储的hash密码进行匹配
        .then((doMatch) => {
          // 是否匹配
          if (doMatch) {
            req.session.isLoggedIn = true;
            req.session.user = user;
            return req.session.save((err) => {
              console.log(err);
              res.redirect("/");
            });
          }
          res.redirect("/login");
        });
    })
    .catch((err) => console.log(err));
};
Cookie 和 Session

Cookie 是一段不超过 4KB 的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。它通常是网站为了辨别用户身份储存在客户端(用户本地)上的数据(通常经过加密),是由用户客户端计算机暂时或永久保存的信息。

问题:express 所有的 render(redirect 等)都会发送一个新的请求,在执行 render 的中间件之前存储数据,可以让整个 render 的中间件里访问到这个数据。但是如果在 render 的中间件里写入数据,则每次执行该数据都会被刷新,无法存储。

  • 可以将数据存储为全局变量。但因为该变量在所有请求之间共享,所以也会在用户之间共享(不安全);
  • 使用 cookie。cookie 可以帮助我们将数据存储在单个用户的浏览器中,不会影响其他用户。

通过setHeader()Set-Cookie关键字来设置一个 cookie,以=分隔来设置 cookie 的 name 和 value:

res.setHeader("Set-Cookie", "name=value");

cookie 设置完以后,默认情况下浏览器会将它发送到服务器的每个请求

  • 设置多个 cookie

    我们可以同时设置多个 cookie 值:

    res.setHeader("Set-Cookie", "name=value; name2=666 ");
    
  • 设置 cookie 失效时长

    默认情况下的 cookie 在关闭浏览器后就会失效,我们可以 cookie 设置某个过期时间:

    res.setHeader(
      "Set-Cookie",
      "name=value; expires=" +
        new Date(new Date().getTime() + 86409000).toUTCString()
    );
    
  • 设置 cookie 的 Secure

    设置 Secure 意味着cookie 只有在页面通过 HTTPS 请求时才会被设置保护 cookie 在浏览器和 Web 服务器间的传输过程中不被窃取和篡改。

    res.setHeader('Set-Cookie', 'name=value; Secure');
    
  • 设置 cookie 的 HttpOnly

    设置 HttpOnly 意味着我们无法通过客户端的 JS 访问和修改 cookie 的值(前端也可以使用 document.cookie 去获取和修改 cookie ),它保护 cookie 不被跨站脚本攻击窃取或篡改

    res.setHeader("Set-Cookie", "name=value; HttpOnly");
    

    跨域脚本攻击:客户端 JS 中可能被注入了恶意代码,如果没有限制客户端 JS 访问 cookie,可能会存在身份泄露等问题。

通过请求的get('Cookie')方法来获取一个 cookie:

req.get("Cookie"); // 获取的cookie为name=value形式,需要可以自己进行拆分

拆分 cookie:

req.get("Cookie").trim().split("=")[0]; // 获取cookie的name
req.get("Cookie").trim().split("=")[1]; // 获取cookie的value

我们可以从浏览器端修改 cookie 的值,所以 cookie 通常用于跨域请求存储数据(cookie 是存储在本地浏览器上,所以它可以发送到我们每个发起请求的页面,所以它可以从一个页面发送到另一个页面,即使这两个页面存在于不同的服务器上),但是一些敏感的数据不能让用户修改,这时候就可以使用 session:

Session

session 是当我们访问服务某个网页的时候,在服务器端的内存开辟一块内存。它存储在服务器的内存中,session 只有对应的浏览器才能访问。

客户端需要告诉服务器它对应的 session,因为 session 也是存储在内存或数据库中的一条条数据。

使用(实现)原理:

  • 我们使用cookie 来存储 session 的 ID,但实际上存储的值并不是 ID,而是经过哈希处理只有服务器可以识别的、以加密方式存储的 ID,这样在 cookie 中就存储了一个我们无法浏览器内部改变、较为安全的值;

  • 当用户保持当前浏览器的情况下再去访问服务器时,会把 session 的 ID 传给服务器,服务器根据 session 的 ID来为用户提供相应的服务。

在 Node(Express)中使用 Session

安装

使用时需下载express-session这个包:

npm install --save express-session

它是 Express 官方拓展包,但并不包含在 Express 里。

初始化 session 中间件

app.js

const express = require("express");
const session = require("express-session"); // 引入express-session

const app = express();

app.use(
  session({
    // 初始化设置session
    secret: "my secret", // 用于标记存储在cookie中的ID的hash
    resave: false, // session不会再每完成一个请求就保存 @1
    saveUninitialized: false, // 确保不为不需要保存的请求保存session
    // cookie: {} // 设置cookie相关配置
  })
);

@1:设置resave: false意味着当 session 发生变化时才会保存,有利于提高性能;

设置 session 的值

使用req.session.xxx = value;来定义存储一个 sessionID 的 cookie,cookie 的 value 为 session 加密的 ID:

exports.postLogin = (req, res, next) => {
  // res.setHeader('Set-Cookie', 'loggedIn=true; Secure');
  req.session.isLoggedIn = true; // 设置一个session
  res.redirect("/");
};

设置的 session 的名字是为了方便我们获取,其在 cookie 中存储的名字统称为connect.sid

获取 session 的值

使用req.session.xxx来获取一个 session 的值:

exports.getLogin = (req, res, next) => {
  console.log(req.session.isLoggedIn); // 获取session的值
};

存储 session 的值

通过req.session.xxx = value;设置的 session 是存储在内存中的,但内存是有限的,所以我们需要根据不同情况采用不同的存储方式。

对于开发者来说需要存储的用户的 session 较少,内存够用;但生产服务器可能会存储上千万的用户,每个用户都有自己的 session,这很快就会造成内存溢出。

  • 使用 MongoDB 数据库存储

    对于 express-session,需要下载connect-mongodb-session这个包:

    npm install --save connect-mongodb-session
    

    引入 connect-mongodb-session:

    app.js

    const session = require("express-session");
    const MongoDBStore = require("connect-mongodb-session")(session); // 实际上提供了一个需要传递session执行函数
    // ...
    const app = express();
    const store = new MongoDBStore({
      // 创建数据库存储
      uri: "mongodb+srv://<name>:<password>@cluster0.mnle1m2.mongodb.net/shop", // 数据库连接地址
      collection: "sessions", // 集合名称(如果不存在就创建)
    });
    
    app.use(
      session({
        secret: "my secret",
        resave: false,
        saveUninitialized: false,
        store: store, // 设置初始化存储
      })
    );
    

删除 session 的值

通过req.session.destroy()来删除一个 session,它内部包含一个错误信息的回调函数:

exports.postLogout = (req, res, next) => {
  req.session.destroy((err) => {
    console.log(err);
  });
};

注意:该删除会将数据库中的 session 删除,浏览器里的 cookie 不会删除,但是其中 cookie 存储的 session 的 ID 在删除后会在数据库中找不到匹配。

确保 session 保存/更新后处理逻辑

有时候我们执行代码还没有等到 session 保存/更新好之后就已经执行完了,如果想等 session 完全保存好之后再进行执行,可以使用req.session.save()

req.session.save((err) => {
  console.log(err);
  res.redirect("/");
});

小结

Cookie:

  • 存储在浏览器上(客户端);

  • 可以被用户看到和操纵,不应该用来存储一些敏感的信息;

  • 通常不会直接设置 cookie 的值,而是通过类似 session 的包使用 cookie;

  • 可以配置各种各样的 cookie,如什么时候失效(expire);

    默认情况下在浏览器关闭时过期,而这类 cookie 被称为session cookie

    • session cookie不一定用来识别 session,之所以被称为session cookie是因为它们只在使用当前浏览器页面时存在;
  • 设置 cookie 的失效时间到具体某个日期、年龄,这相当于某种意义上的永久 cookie(它不会在关闭浏览器时就消失);

  • cookie 能和 session 一起使用,但它并不仅限于和 session 一起使用

Session:

  • 存储在服务器上(服务端);
  • 用户无法查看或操纵,适合存储跨域请求保留的敏感数据;
  • 可以在 session存储任何形式的数据,通常用来存储用户数据或身份验证状态信息;
  • seesion 需要一个 cookie 来识别(这个 cookie 不是session cookie,而是永久 cookie),一个 cookie 识别一个 session;
  • 可以选择不同的方式存储 session 数据,如:数据库存储、文件存储等;
Sequelize

简介

Sequelize 是一个基于promiseNode.js ORM(对象关系映射库—Object-Relational Mapping Library)。它具有强大的事务支持,关联关系,预读和延迟加载,读取复制等功能。

它将所有的SQL语句代码映射到Javascript对象中(封装),然后导出变成我们可调用的方法,这样我们就不用自己编写SQL语句代码

例如:

INSERT INTO users VALUE ('Jon', 28, 'HJDFKLS')

——>

const user = User.create({ name: 'Jon', age: 28, password: 'HJDFKLS'})

安装

注意:sequelize需要安装数据库的驱动程序,我使用的数据库是MySQL,所以需要安装mysql2包!

npm install --save sequelize

使用

连接数据库

database.js

const Sequelize = require('sequelize'); // 引入sequelize

const sequelize = new Sequelize('node-complete', 'root', 'rootpassword', { // 数据库的库名,用户名,密码以及其他配置
  dialect: 'mysql', // 数据库语言
  host: 'localhost', // 连接服务器
});

module.exports = sequelize; // @1

@1:导出的sequelize对象本质上也是数据库连接池(同mysql2连接),只不过通过sequelize管理后我们能使用其他的一些功能。

定义新模型

使用define()来定义一个新的模型(数据表),它的第一个参数为模型名称(表名),第二个参数为模型的各个参数(表中字段):

models/product.js

const Sequelize = require('sequelize');

const sequelize = require('../util/database');

const Product = sequelize.define('product', {
  id: {
    type: Sequelize.INTEGER, // 类型
    autoIncrement: true, // 是否自增
    allowNull: false, // 是否为Null
    primaryKey: true // 主键
  },
  title: Sequelize.STRING,
  price: {
    type: Sequelize.DOUBLE,
    allowNull: false
  },
  imageUrl: {
    type: Sequelize.STRING,
    allowNull: false
  },
  description: {
    type: Sequelize.STRING,
    allowNull: false
  }
});

module.exports = Product;

创建新的表

将创建的模型转为数据库中的表

app.js

...
const sequelize = require('./util/database');

sequelize
  .sync() // @2
  .then(result => {
    app.listen(3000);
  })
  .catch(err => {
    console.log(err);
  });
...

注:如果数据库中没有这个表,则创建;如果已经存在这个表则不会覆盖原先的表

@2:如果已经创建了表,想要新建一个覆盖原来的表,可以设置force

.sync( { force: true })

表创建完需删除force,避免每次更新项目都重新创建新表。

之后数据库会自动创建一个与模型字段及信息对应的表。

向表中新增一条数据

通过create()向表中新增一条数据:

...
const Product = require('../models/product');

router.post('/product', (req, res, next) => {
    const title = req.body.title;
    const imageUrl = req.body.imageUrl;
    const price = req.body.price;
    const description = req.body.description;

    Product.create({
      title: title,
      price: price,
      imageUrl: imageUrl,
      description: description
    })
    .then(result => {
      console.log('Created Product');
      res.redirect('/admin/products');
    })
    .catch(err => {
      console.log(err);
    })
});

查询表中数据

findAll()

通过findAll()来查询表中所有数据:

  • 查询表中所有数据:

    router.get('/products', (req, res, next) => {
      Product.findAll()
        .then(products => {
          res.render('shop/index', {
            prods: products, 
            pageTitle: 'Shop',
            path : "/",
          });
        })
        .catch(err => console.log(err));
    });
    
  • 查询id为prodId的数据:

    router.get('/products/:productId', (req, res, next) => {
      const prodId = req.params.productId;
      Product.findAll({ where: { id: prodId} })
        .then(products => {
          res.render('shop/index', {
            prods: products[0], 
            pageTitle: products[0].title,
            path : "/products",
          });
        })
        .catch(err => console.log(err));
    });
    

    注意:通过where子句返回的then的参数是一个数组

findByPk()

使用提供的主键从表中仅获得一条数据:

const Product = require('../models/product');
// 此处主键在数据库中为id
router.get('/products/:productId', (req, res, next) => {
  const prodId = req.params.productId;
  Product.findByPk(prodId)
    .then((product) => {
      res.render('shop/product-detail', { 
        product: product,
        pageTitle: product.title,
        path: '/products'
      })
    })
    .catch(err => console.log(err));
});

修改表中数据

通过主键(id)找到对应数据后,在本地修改数据,修改完后通过save()方法将修改完的数据发送给数据库,如果数据库中存在修改的数据就覆盖它,如果不存在就新建它:

...
router.post('/edit-product', (req, res, next) => {
  const prodId = req.body.productId;
  const updatedTitle = req.body.title;
  const updatedPrice = req.body.price;
  const updatedImageUrl = req.body.imageUrl;
  const updatedDesc = req.body.description;
  Product.findByPk(prodId)
    .then(product => {
      product.title = updatedTitle; // 在本地修改数据
      product.price = updatedPrice; // 在本地修改数据
      product.description = updatedDesc; // 在本地修改数据
      product.imageUrl = updatedImageUrl; // 在本地修改数据
      return product.save(); // 将修改好的数据发给数据库,返回一个Promise
    })
    .then(result => {
      console.log('UPDATED PRODUCT!');
      res.redirect('/admin/products');
    })
    .catch(err => console.log(err));
});
...

删除数据

通过destroy()来删除数据:

  • 删除指定ID数据:

    ...
    router.post('/delete-product', (req, res, next) => {
      const prodId = req.body.productId;
      Product.findByPk(prodId)
        .then(product => {
          return product.destroy();
        })
        .then(result => {
          console.log('DESTROYED PRODUCT');
          res.redirect('/admin/products');
        })
        .catch(err => console.log(err));
    });
    

    也可以通过destroy()的where语句来进行指定删除。

关联

Sequelize 支持标准关联关系:一对一、一对多和多对多。

为此,Sequelize 提供了四种关联类型,并将它们组合起来以创建关联:

  • HasOne 关联类型
  • BelongsTo 关联类型
  • HasMany 关联类型
  • BelongsToMany 关联类型

一对一

每个User只有一个Cart,Cart属于User:

...
User.hasOne(Cart);
Cart.belongsTo(User);
...

一对多

Product属于User,每个User可以有多个Product:

...
Product.belongsTo(User, {
  constraints: true, // 开启定义关系约束
  onDelete: 'CASCADE' // 级联删除
});
User.hasMany(Product);
...

多对多

一个Cart里可以有多个Product,每个Product可以在不同的Cart中:

...
Cart.belongsToMany(Product, {
  through: CartItem // 这些连接存储的位置
});
Product.belongsToMany(Cart, { through: CartItem });
...

添加到实例的特殊方法

当两个模型之间定义了关联时,这些模型的实例将获得特殊的方法来与其关联的另一方进行交互。

// 定义Product和User的关系
Product.belongsTo(User, {
  constraints: true, // 开启定义关系约束
  onDelete: 'CASCADE' // 级联删除
});
User.hasMany(Product);
createXXX()

使用instance.createProduct()来创建Product将会自动生成关联User的id:

const Product = require('../models/product');

router.post('/product', (req, res, next) => {
    const title = req.body.title;
    const imageUrl = req.body.imageUrl;
    const price = req.body.price;
    const description = req.body.description;

    // 创建Product模型,并自动添加userId(关联主键)
    req.user.createProduct({ // req.user是sequelize保存在数据库中的数据(一个辅助方法)
      title: title,
      price: price,
      imageUrl: imageUrl,
      description: description,
    })
    .then(result => {
      console.log('Created Product');
      res.redirect('/admin/products');
    })
    .catch(err => {
      console.log(err);
    })
});
getXXX()

使用instance.getProducts()来获取Product:

router.get('/edit-product/:productId',(req, res, next) => {
  const editMode = req.query.edit;
  if (!editMode) {
    return res.redirect('/')
  }
  const prodId = req.params.productId;
  req.user
    .getProducts({ where: { id: prodId } }) // 获取对应id的Product
  // Product.findByPk(prodId) // 也可以使用findByPk()来寻找
    .then(products => {
      const product = products[0];
      if (!product) {
        return res.redirect('/');
      }
      res.render('admin/edit-product',{
        pageTitle: 'Edit Product',
        path: '/admin/edit-product',
        editing: editMode,
        product: product
      });
    })
    .catch(err => console.log(err));
});
addXXX()

使用instance.addProducts()来添加Product:

...
addProduct(product, {
    through: { quantity: newQuantity }
  });
})
...
身份认证(Authentication)

原理

  • 用户发动登录请求(之前已经注册了用户信息)到服务端,服务端验证用户信息是否有效,数据库中是否存有该用户的信息(此时服务器会为用户创建一个识别用户的 session)。

    如果没有 session,即使用户发送的登录信息是有效的,用户下一个请求仍然会被注销登录,因为请求和请求之间是完全独立分开的,所以我们需要一个 session 来连接它们。

  • 验证成功后服务器会返回一个状态码为 200 的响应,将对应存储的 seesion ID 返回给浏览器并使用 cookie 存储 。

  • cookie 会伴随每个请求发送,服务器可以根据 cookie 中的 session ID 来找到对应的 session 数据(如是否登录等信息)。

MVC

MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,是一种架构模式,它将业务逻辑、数据和显示(M 和 V)的代码分离,从而使同一个程序可以使用不同的表现形式。

M(Model)

模型,针对应用程序数据,包含业务逻辑状态,也包含一些HTTP 库,对数据进行操作(如保存,获取数据)。

V(View)

视图,针对表示逻辑层,用户能看到的部分。

C(Controller)

控制器,包含应用(程序)逻辑的内容。位于 Model 和 View 之间,为两者创建桥梁(如 Routes)。它规定了使用哪个模型渲染哪个视图。

Node.js

简介

什么是 Node.js

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它可以让 JavaScript 在服务端运行

注意:Node.js不仅仅可以在服务端运行(这是在 JS 运行时),也可以在本地脚本或构建工具中使用。(例如 Vue 等框架实际上运用了 Node.js 构建多功能脚本,因为它可以访问文件系统,进行文件读写操作等)

  • 引擎:将在浏览器或 node 中运行的 JavaScript 代码编译为机器代码;

Node 与 JS 组成差别

Node.js JavaScript
ECMAScript(语言基础,如:语法、数据类型结构以及一些内置对象) ECMAScript(语言基础,如:语法、数据类型结构以及一些内置对象)
OS(操作系统) DOM(一些操作页面元素的方法)
file(文件系统) BOM(一些操作浏览器的方法)
net(网络系统)
database(数据库)

JavaScript 本身是只能在浏览器中运行的,但是现在通过 Node.js 我们可以让它在服务器端运行,这样可以让它执行一些在浏览器内因为性能或安全问题导致我们不能或不想执行的任务(如连接数据库和存储数据,用户身份验证,输入验证,业务逻辑)。

因为浏览器端始终是面向用户的,用户可以看到甚至修改浏览器端的信息,所以需要服务端来做这些。

Node.js 在 Web 开发中的作用

  • 建立一个服务端:监听我们从浏览器端发送的请求;
  • 运行业务逻辑:处理请求数据、文件,连接并使用数据库等;
  • 处理响应:返回响应数据(例如 HTML 页面动态内容或数据的 json 文件等)给客户端

Web 的工作原理

  • 用户(浏览器)输入网址;

    该网址会进行域名解析并最终找到正确的目标 IP 地址。

  • 浏览器带着正确的 IP 地址向服务器发送请求;

  • 具有该 IP 的服务器端代码开始运行;

    此时 Node.js 就开始了工作(当然也可以有其他的语言代替),在这里 node.js 可以进行用户输入验证,连接数据库。

  • 服务端执行完成后向客户端返回一些响应;

    • 响应可以是一些 HTML 文本或者 HTML 代码,还有可以被客户端处理的一些其他类型的数据,例如 json 或 xml 文件等;
    • 响应不仅仅有内容,还有请求体和请求头等元信息;

    请求和响应传输是通过某种协议(标准化的沟通方式)完成的:

HTTP/HTTPS

HTTP:超文本传输协议,定义了请求的格式以及数据如何从浏览器传输到服务器等;

HTTPS:超文本传输加密协议,传输的数据是经过 SSL 加密的;

使用 Node

REPL

控制终端输入node后进入,在这可以进行 REPL 的操作。

R:read,读取用户的输入;

E:eval,评估用户的输入 ;

P:print,打印输出的结果;

L:loop,循环返回并等待新一轮的用户输入;

ndoe
> 1 + 1
4

注意:一旦控制终端关闭或者退出 node 我们将不能再进入到刚才的 node 文件空间!

执行 Node.js 代码文件

将 node 代码写在一个文件中,执行这个文件。

node app.js

通常使用这种模式执行 node 代码。

Node.js 框架

  • Express.js:最流行最常用的 Node.js 框架。
  • Adonis.js:受 laravel(PHP)启发的框架。
  • Koa
  • Sails.js
Node.js程序的生命周期以及事件循环

Node.js 程序的生命周期

当我们使用node app.js执行文件时,node.js 会运行整个文件,解析代码,注册变量和函数等,我们会一直处于这个运行文件中,这是因为 node.js 的事件循环。

事件循环

事件循环处理处理所有的回调

由 node.js 管理的循环进程。只要其中的工作没有做完,它就会一直运行程序。

这意味着这样注册了事件监听器程序就会一直执行(没有手动强制退出的情况下)。

  • 告诉 node.js 这里的工作都执行完了,强制退出:
process.exit();

node.js 不止在服务器上使用事件循环,也在其他地方使用,例如数据库。

原因

node.js 使用事件循环是因为它实际上执行的 JS 是单线程,整个 node 程序使用的是计算机里的一个线程;而如果用 node 创建一个服务器,那它应该能够处理传入的多个数据请求,而不是因为一个请求暂停而导致所有请求等待

当我们执行 node.js 时事件循环就自动开启了,它负责处理事件回调函数。它会优先处理快速完成的回调函数。

而 fs 模块的功能会被发送到一个工作池中,这个工作池运行在不同的线程中,跟 JS 代码是完全分离的。这个工作池和操作系统密切相关,可以启动多个线程 。我们读取或写入文件就在这个工作池中进行,而当读写或写入(工作)完成后,它会触发事件循环里对应的回调函数进入事件循环的队列。

image-20230202102955839

事件循环()

Node.js 在主线程里维护了一个事件队列,当接到请求后,就将该请求作为一个事件放入这个队列中,然后继续接收其他请求。当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:如果是非 I/O 任务,就亲自处理,并通过回调函数返回到上层调用;如果是 I/O 任务,就从 **线程池 **中拿出一个线程来处理这个事件,并指定回调函数,然后继续循环队列中的其他事件。

当线程中的 I/O 任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。 这个过程就叫 **事件循环 **(Event Loop)

Node.js 本身是一个多线程平台,而它对 JavaScript 层面的任务处理是单线程的。

Node.js 事件循环和 JS 事件循环

image-20230202103531435

treams&BuffersS

Streams(数据流)

从浏览器发送的数据是作为数据流(stream)传递的。

数据流是 JS 中一种特殊的概念,JavaScript 自身不具备直接操作二进制文件的能力,所以实质上 Buffer 在 nodejs 底层上还有一个 C++模块。这是因为 IO 操作是非常消耗性能的,所以 nodejs 在 Buffer 模块构建设计上,将性能部分用 C++实现,将非性能部分用 JavaScript 实现。

例如:接收浏览器请求

数据流(stream)是一个持续的过程。浏览器请求被 Node.js 分为多个块(chunks)的形式读取,最后在某个事件完成整个请求的读取;而单个块并不需要等到完整请求被读取后执行,这就需要缓冲区(buffers)来实现:

Buffers(缓冲区)

通常,数据的移动是为了处理或者读取它,并根据它进行决策。伴随着时间的推移,每一个过程都会有一个最小或最大数据量。如果数据到达的速度比进程消耗的速度快,那么少数早到达的数据会处于等待区(缓冲区)等候被处理。

缓冲区就像一个公共汽车站,公共汽车(数据流)总是在行驶,需要乘坐或离开这个汽车的人(请求块)需要在公共汽车站(缓冲区)等待公共汽车。