Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NodeJS FullStack Questions 1 #58

Open
llccing opened this issue Jul 3, 2024 · 22 comments
Open

NodeJS FullStack Questions 1 #58

llccing opened this issue Jul 3, 2024 · 22 comments

Comments

@llccing
Copy link
Owner

llccing commented Jul 3, 2024

Node.js 全栈面试题通常涵盖以下几个方面:

一、JavaScript 基础知识

  1. 数据类型和结构
  2. 异步编程
  3. DOM 操作

二、Node.js 基础知识

  1. 模块系统
  2. 事件驱动架构
  3. 文件系统操作
  4. HTTP 模块

三、后端开发

  1. Express 框架
  2. 数据库操作
  3. 身份验证和授权

四、前端开发

  1. React / Angular / Vue
    • 如何使用 Angular 创建组件?
    • Angular 中的状态管理如何实现?
    • Vue 的生命周期钩子有哪些?
  2. 前端路由
    • 如何使用 Angular Router 创建单页应用(SPA)?
  3. 状态管理
    • 什么是 Redux?如何使用?

五、全栈整合

  1. API 设计与调用
    • RESTful API 设计原则是什么?
    • 如何使用 Axios 或 Fetch 调用后端 API?
  2. DevOps
    • 如何使用 Docker 部署 Node.js 应用?
    • 持续集成/持续部署(CI/CD)流程如何实现?
  3. 测试
    • 如何使用 Jest 或 Mocha 进行单元测试?
    • 什么是端到端测试(E2E)?

六、性能优化和安全

  1. 性能优化
    • 如何优化 Node.js 应用的性能?
    • 什么是负载均衡?如何实现?
  2. 安全
    • 如何防止 SQL 注入攻击?
    • 如何防止跨站脚本攻击(XSS)和跨站请求伪造(CSRF)?

这些问题涵盖了全栈开发的主要方面,可以帮助面试官评估候选人在 Node.js 和全栈开发方面的知识和技能。

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

JavaScript 中的数据类型有哪些?


在 JavaScript 中,数据类型主要分为两大类:原始类型(Primitive Types)和对象类型(Object Types)。以下是具体的分类和各类型的详细介绍:

原始类型(Primitive Types)

  1. Undefined:表示变量未定义。
    let x;
    console.log(typeof x); // "undefined"
  2. Null:表示空值,表示一个空对象引用。
    let y = null;
    console.log(typeof y); // "object" (这是一个历史遗留问题)
  3. Boolean:表示布尔值,只有 truefalse 两个值。
    let isTrue = true;
    console.log(typeof isTrue); // "boolean"
  4. Number:表示数值,包括整数和浮点数。
    let num = 42;
    console.log(typeof num); // "number"
  5. BigInt:表示任意精度的整数。
    let bigIntNum = 1234567890123456789012345678901234567890n;
    console.log(typeof bigIntNum); // "bigint"
  6. String:表示文本数据。
    let str = "Hello, World!";
    console.log(typeof str); // "string"
  7. Symbol:表示唯一且不可变的数据类型。
    let sym = Symbol("description");
    console.log(typeof sym); // "symbol"

对象类型(Object Types)

  1. Object:包括普通对象、数组、函数等。

    let obj = { name: "Alice" };
    console.log(typeof obj); // "object"
  2. Array:数组是一种特殊的对象,用于存储有序集合。

    let arr = [1, 2, 3];
    console.log(typeof arr); // "object"
  3. Function:函数也是一种对象,但它们是可调用的。

    function myFunction() {}
    console.log(typeof myFunction); // "function"
  4. Date:用于处理日期和时间。

    let date = new Date();
    console.log(typeof date); // "object"
  5. RegExp:用于匹配文本的正则表达式。

    let regex = /ab+c/;
    console.log(typeof regex); // "object"
  6. Map:表示键值对的集合,键可以是任何类型。

    let map = new Map();
    console.log(typeof map); // "object"
  7. Set:表示值的集合,值必须是唯一的。

    let set = new Set();
    console.log(typeof set); // "object"
  8. WeakMap:类似于 Map,但键是弱引用,不会阻止垃圾回收。

    let weakMap = new WeakMap();
    console.log(typeof weakMap); // "object"
  9. WeakSet:类似于 Set,但值是弱引用。

    let weakSet = new WeakSet();
    console.log(typeof weakSet); // "object"

特殊类型

  • Function:虽然函数是对象的一种,但它有自己的类型标识。
    function func() {}
    console.log(typeof func); // "function"

这些数据类型构成了 JavaScript 的基础,通过这些类型,我们可以进行各种数据操作和处理。

@llccing llccing changed the title [NodeJS FullStack] JavaScript 中的数据类型有哪些? NodeJS FullStack Questions 1 Jul 3, 2024
@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

什么是闭包?如何使用?


闭包(Closure)是编程中的一个概念,指的是在一个函数内部定义另一个函数,并且内部函数引用了外部函数的变量。在这种情况下,外部函数的局部变量被内部函数捕获(或称为闭合),使得这些变量在外部函数执行结束后仍然能够被内部函数访问。

闭包的特性

  1. 自由变量:闭包可以捕获其外部作用域的变量,即使在外部作用域已经结束时仍然可以访问这些变量。
  2. 私有性:闭包中的变量对外部不可见,只能通过闭包提供的接口进行访问。
  3. 持久性:闭包中的变量会一直存在,直到闭包本身被销毁。

闭包的示例

以下是一个简单的闭包示例(以Python为例):

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10)
print(closure(5))  # 输出结果为 15

在这个示例中,outer_function 定义了一个内部函数 inner_function,并返回了 inner_functioninner_function 引用了外部函数 outer_function 的变量 x。即使 outer_function 执行完毕后,x 变量仍然存在于 inner_function 中。

在JavaScript中的闭包

闭包在JavaScript中也很常见,下面是一个JavaScript的例子:

function outerFunction(x) {
    return function(y) {
        return x + y;
    };
}

const closure = outerFunction(10);
console.log(closure(5));  // 输出结果为 15

闭包的使用场景

  1. 数据隐藏:闭包可以用于创建私有变量,避免外部访问和修改。
  2. 回调函数:闭包常用于回调函数,特别是在异步编程中。
  3. 函数工厂:通过闭包,可以创建带有不同配置的函数。
  4. 柯里化:柯里化是一种将多参数函数转换为一系列单参数函数的技术,闭包在其中扮演重要角色。

总结

闭包是一个强大而灵活的编程工具,通过它可以实现许多高级特性,如数据隐藏、回调函数和函数工厂等。在使用闭包时,需要注意避免过多的闭包层级,以免造成代码难以维护。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

什么是原型链?


原型链(Prototype Chain)是JavaScript中实现继承的一种机制。每个对象都有一个原型对象(prototype),该对象也可以有自己的原型,依此类推,形成一个链条。这条链就是原型链,通过它可以实现属性和方法的继承。

原型链的基本概念

  1. 原型对象(Prototype)

    • 每个JavaScript对象(除了 null)都有一个内部链接到另一个对象,这个对象称为其原型(prototype)。
    • 每个函数都有一个 prototype 属性,该属性指向一个对象,这个对象的 constructor 属性指向函数本身。
  2. __proto__ 属性

    • 每个对象都有一个 __proto__ 属性,指向其构造函数的原型对象。
  3. 构造函数(Constructor)

    • 创建对象的函数称为构造函数(constructor)。通过 new 操作符调用构造函数会生成一个新的对象,并将这个对象的 __proto__ 属性指向构造函数的 prototype 属性。

原型链的示例

以下是一个简单的原型链示例:

function Person(name) {
    this.name = name;
}

Person.prototype.greet = function() {
    console.log("Hello, " + this.name);
};

let alice = new Person("Alice");
alice.greet();  // 输出:Hello, Alice

console.log(alice.__proto__ === Person.prototype);  // 输出:true
console.log(Person.prototype.__proto__ === Object.prototype);  // 输出:true
console.log(Object.prototype.__proto__ === null);  // 输出:true

在这个示例中:

  1. Person 是一个构造函数,其 prototype 属性指向一个对象,该对象包含一个 greet 方法。
  2. 通过 new Person("Alice") 创建了一个新的对象 alice。这个对象的 __proto__ 属性指向 Person.prototype
  3. 当调用 alice.greet() 时,JavaScript会在 alice 对象中查找 greet 方法。如果找不到,就会在 alice.__proto__(即 Person.prototype)中查找。

原型链的结构

原型链的结构如下:

alice ---> Person.prototype ---> Object.prototype ---> null

原型链的特性

  1. 继承属性和方法:通过原型链,对象可以继承其原型链上所有原型对象的属性和方法。
  2. 动态性:可以动态地向原型添加属性和方法,所有继承自该原型的对象都会立即受到影响。

原型链的注意事项

  1. 性能问题:在原型链上查找属性和方法时,如果链条过长,会导致性能问题。
  2. 原型污染:修改原型对象时需要小心,因为修改会影响所有继承自该原型的对象。

总结

原型链是JavaScript实现继承的核心机制,通过它可以实现属性和方法的共享。在使用原型链时,需要注意性能和原型污染等问题。了解和掌握原型链有助于更好地理解JavaScript的继承和面向对象编程。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

ES6 和 ES5 的区别有哪些?


ES6(ECMAScript 2015)引入了许多新的特性和改进,与之前的ES5版本相比,显著增强了JavaScript的功能和易用性。以下是ES6和ES5之间的一些主要区别:

1. 变量声明

ES5

  • 使用 var 声明变量,存在变量提升问题。
var x = 5;

ES6

  • 引入了 letconst
    • let 用于声明块级作用域变量。
    • const 用于声明块级作用域常量,声明后不能再赋值。
let y = 10;
const z = 15;

2. 箭头函数

ES5

  • 使用 function 关键字定义函数。
var add = function(a, b) {
    return a + b;
};

ES6

  • 引入箭头函数,具有更简洁的语法,并且不绑定自己的 this
const add = (a, b) => a + b;

3. 模板字符串

ES5

  • 使用字符串拼接来创建多行字符串或插入变量。
var greeting = 'Hello, ' + name + '!';

ES6

  • 引入模板字符串,用反引号(`)包裹,并使用 ${} 插入变量。
const greeting = `Hello, ${name}!`;

4. 解构赋值

ES5

  • 通过手动提取对象或数组中的值。
var person = {name: 'Alice', age: 25};
var name = person.name;
var age = person.age;

ES6

  • 引入解构赋值,可以更简洁地提取对象或数组中的值。
const {name, age} = person;
const [x, y] = [1, 2];

5. 默认参数

ES5

  • 通过逻辑运算符设置函数参数的默认值。
function greet(name) {
    name = name || 'Guest';
    return 'Hello, ' + name;
}

ES6

  • 直接在函数声明时设置默认参数。
function greet(name = 'Guest') {
    return `Hello, ${name}`;
}

6. 块级作用域

ES5

  • 只有函数作用域和全局作用域,没有块级作用域。
if (true) {
    var x = 5;
}
console.log(x); // 5

ES6

  • letconst 提供了块级作用域。
if (true) {
    let y = 10;
}
console.log(y); // ReferenceError: y is not defined

7. 类(Class)

ES5

  • 通过构造函数和原型链模拟类。
function Person(name) {
    this.name = name;
}

Person.prototype.greet = function() {
    console.log('Hello, ' + this.name);
};

ES6

  • 引入类语法,使得定义类更加直观和简洁。
class Person {
    constructor(name) {
        this.name = name;
    }

    greet() {
        console.log(`Hello, ${this.name}`);
    }
}

8. 模块化

ES5

  • 没有原生的模块系统,一般使用库如 CommonJS 或 AMD。
// 使用 CommonJS
var module = require('module');
module.doSomething();

ES6

  • 引入了原生的模块系统,使用 importexport
// 导出模块
export function doSomething() {
    // ...
}

// 导入模块
import { doSomething } from './module';
doSomething();

9. 扩展运算符和剩余参数

ES5

  • 通过 apply 方法展开数组。
var arr = [1, 2, 3];
Math.max.apply(null, arr);

ES6

  • 引入扩展运算符(...)用于展开数组和对象。
const arr = [1, 2, 3];
Math.max(...arr);

// 剩余参数
function sum(...numbers) {
    return numbers.reduce((a, b) => a + b, 0);
}

10. Promise

ES5

  • 使用回调函数处理异步操作。
function fetchData(callback) {
    // 异步操作
    callback(data);
}

ES6

  • 引入 Promise 对象,更加优雅地处理异步操作。
const fetchData = new Promise((resolve, reject) => {
    // 异步操作
    resolve(data);
});

fetchData.then(data => {
    // 处理数据
}).catch(error => {
    // 处理错误
});

总结

ES6 在 ES5 的基础上引入了许多新的特性,使得 JavaScript 更加强大和易用。这些改进包括更简洁的语法、模块化支持、异步编程的改进以及增强的面向对象编程能力。这些变化不仅提高了代码的可读性和维护性,还使开发者能够更高效地编写和管理JavaScript代码。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

什么是回调函数?


回调函数(Callback Function)是指一个函数在另一个函数执行完毕后被调用。回调函数通常作为参数传递给另一个函数,并在特定事件发生或任务完成时执行。回调函数在异步编程和事件驱动编程中非常常见。

回调函数的基本概念

  1. 同步回调:在主函数执行过程中立即调用的回调函数。
  2. 异步回调:在主函数执行完成之后,在某个事件或任务完成时调用的回调函数。

回调函数的示例

以下是一些使用回调函数的示例,以便更好地理解其概念。

同步回调

在同步操作中使用回调函数:

function processUserInput(callback) {
    var name = prompt('Please enter your name.');
    callback(name);
}

function greet(name) {
    console.log('Hello, ' + name);
}

processUserInput(greet);

在这个示例中,processUserInput 函数接受一个回调函数 callback 作为参数,并在获取用户输入后立即调用 callback 函数。

异步回调

在异步操作中使用回调函数:

function fetchData(callback) {
    setTimeout(() => {
        var data = 'Some data';
        callback(data);
    }, 2000);
}

function processData(data) {
    console.log('Received data: ' + data);
}

fetchData(processData);

在这个示例中,fetchData 函数模拟了一个异步操作(使用 setTimeout),在操作完成后调用回调函数 processData 并传递数据。

回调函数在异步编程中的应用

回调函数在处理异步操作(如文件读取、网络请求、定时器等)时非常有用。在JavaScript中,回调函数常用于以下几种场景:

  1. 事件处理:在事件触发时调用回调函数。

    document.getElementById('button').addEventListener('click', function() {
        console.log('Button clicked!');
    });
  2. 定时器:在定时器到期时调用回调函数。

    setTimeout(() => {
        console.log('Timeout reached!');
    }, 1000);
  3. 网络请求:在网络请求完成后调用回调函数。

    fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(data => {
            console.log(data);
        })
        .catch(error => {
            console.error('Error:', error);
        });

回调地狱

当回调函数嵌套过深时,会出现“回调地狱”(Callback Hell)的问题,使代码变得难以阅读和维护。示例:

doSomething(function(result) {
    doSomethingElse(result, function(newResult) {
        doAnotherThing(newResult, function(finalResult) {
            console.log('Final result:', finalResult);
        });
    });
});

解决回调地狱的方法

  1. 使用Promise

    doSomething()
        .then(result => doSomethingElse(result))
        .then(newResult => doAnotherThing(newResult))
        .then(finalResult => console.log('Final result:', finalResult))
        .catch(error => console.error('Error:', error));
  2. 使用async/await

    async function process() {
        try {
            const result = await doSomething();
            const newResult = await doSomethingElse(result);
            const finalResult = await doAnotherThing(newResult);
            console.log('Final result:', finalResult);
        } catch (error) {
            console.error('Error:', error);
        }
    }
    
    process();

总结

回调函数是JavaScript中处理异步操作的基础机制,通过将函数作为参数传递给另一个函数,可以在特定事件或任务完成时调用这些回调函数。虽然回调函数非常强大,但嵌套过多会导致回调地狱问题。使用Promise或async/await可以有效地解决这个问题,提高代码的可读性和可维护性。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

什么是 Promise?如何使用?


Promise 是 JavaScript 中用于处理异步操作的一种机制。它提供了一种更为优雅和简洁的方式来处理异步任务,避免了回调地狱的问题。Promise 对象代表一个异步操作的最终完成(或失败)及其结果值。

Promise 的状态

Promise 对象有三种状态:

  1. pending(等待中):初始状态,既没有被兑现,也没有被拒绝。
  2. fulfilled(已兑现):表示操作成功完成。
  3. rejected(已拒绝):表示操作失败。

Promise 的基本用法

创建一个 Promise 对象,并在异步操作完成时调用 resolvereject 来改变 Promise 的状态。

const myPromise = new Promise((resolve, reject) => {
    // 异步操作
    let success = true; // 模拟操作结果

    if (success) {
        resolve("Operation succeeded!"); // 操作成功时调用 resolve
    } else {
        reject("Operation failed!"); // 操作失败时调用 reject
    }
});

使用 thencatch

then 方法用于指定 Promise 成功时的回调函数,catch 方法用于指定 Promise 失败时的回调函数。

myPromise.then((result) => {
    console.log(result); // 输出: Operation succeeded!
}).catch((error) => {
    console.error(error); // 如果操作失败,则输出: Operation failed!
});

使用 Promise 处理异步操作

以下是一个使用 Promise 处理异步操作的示例:

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = { id: 1, name: "Alice" };
            resolve(data); // 模拟成功的异步操作
            // reject("Failed to fetch data"); // 模拟失败的异步操作
        }, 2000);
    });
}

fetchData().then((data) => {
    console.log("Data received:", data);
}).catch((error) => {
    console.error("Error:", error);
});

使用 Promise.all

Promise.all 方法用于将多个 Promise 实例组合成一个新的 Promise 实例,只有当所有 Promise 实例都成功时,新 Promise 才会成功;如果有任何一个 Promise 失败,新 Promise 就会失败。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
    console.log(values); // 输出: [3, 42, "foo"]
}).catch((error) => {
    console.error("Error:", error);
});

使用 Promise.race

Promise.race 方法用于将多个 Promise 实例组合成一个新的 Promise 实例,只要其中的一个 Promise 实例率先改变状态,新 Promise 的状态就会跟着改变。

const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
    console.log(value); // 输出: "two" 因为 promise2 更快
}).catch((error) => {
    console.error("Error:", error);
});

使用 asyncawait

asyncawait 是基于 Promise 的语法糖,使得异步代码看起来像同步代码。

async function fetchDataAsync() {
    try {
        const data = await fetchData(); // 等待 fetchData() 返回结果
        console.log("Data received:", data);
    } catch (error) {
        console.error("Error:", error);
    }
}

fetchDataAsync();

总结

Promise 提供了一种处理异步操作的更优雅的方式,避免了回调地狱问题。通过 thencatch 方法,可以在异步操作成功或失败时执行相应的回调函数;通过 Promise.allPromise.race 方法,可以组合多个 Promise 实例进行处理;通过 asyncawait 语法,可以使异步代码更加简洁和易读。掌握 Promise 的使用,对于现代 JavaScript 开发非常重要。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

async/await 的工作原理是什么?


asyncawait 是 JavaScript 中用于处理异步操作的语法糖,它们基于 Promise,使得异步代码看起来像同步代码,从而提高了代码的可读性和可维护性。

asyncawait 的工作原理

async 函数

  • 当在一个函数前面加上 async 关键字时,这个函数就变成了一个 async 函数。async 函数总是返回一个 Promise
  • 即使这个函数内部没有返回一个 Promise,它也会自动将返回值包装在一个 Promise 中。
async function example() {
    return 42;
}

example().then(value => {
    console.log(value); // 输出: 42
});

在这个例子中,example 函数返回了一个 Promise,该 Promise 解析为 42

await 关键字

  • await 只能在 async 函数内部使用,用于等待一个 Promise 完成。
  • await 会暂停 async 函数的执行,等待 Promise 完成并返回其结果。
  • 如果 Promise 被拒绝,await 会抛出异常,就像 Promisecatch 方法那样。
async function example() {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve("done!"), 1000);
    });

    let result = await promise; // 等待 promise 完成

    console.log(result); // 输出: done!
}

example();

在这个例子中,await 关键字使得 example 函数等待 promise 完成,然后才继续执行。

async/await 的错误处理

async/await 的错误处理可以通过 try...catch 语句实现。

async function example() {
    try {
        let promise = new Promise((resolve, reject) => {
            setTimeout(() => reject(new Error("Something went wrong!")), 1000);
        });

        let result = await promise; // 等待 promise 完成
        console.log(result);
    } catch (error) {
        console.error(error); // 输出: Error: Something went wrong!
    }
}

example();

在这个例子中,如果 promise 被拒绝,await 会抛出一个异常,该异常会被 catch 块捕获。

多个 await 的顺序执行

多个 await 表达式会按顺序执行,这使得处理多个异步操作更加简单和直观。

async function example() {
    let promise1 = new Promise((resolve, reject) => {
        setTimeout(() => resolve("First!"), 1000);
    });

    let promise2 = new Promise((resolve, reject) => {
        setTimeout(() => resolve("Second!"), 500);
    });

    let result1 = await promise1;
    console.log(result1); // 输出: First!

    let result2 = await promise2;
    console.log(result2); // 输出: Second!
}

example();

在这个例子中,await 表达式按顺序执行,第一个 promise 完成后才会开始等待第二个 promise

并行执行多个异步操作

如果希望多个异步操作并行执行,可以使用 Promise.allawait 结合。

async function example() {
    let promise1 = new Promise((resolve, reject) => {
        setTimeout(() => resolve("First!"), 1000);
    });

    let promise2 = new Promise((resolve, reject) => {
        setTimeout(() => resolve("Second!"), 500);
    });

    let [result1, result2] = await Promise.all([promise1, promise2]);

    console.log(result1); // 输出: First!
    console.log(result2); // 输出: Second!
}

example();

在这个例子中,Promise.all 会等待所有传入的 Promise 完成,然后 await 会返回一个包含所有结果的数组。

async/await 的底层实现

  1. async 函数async 函数返回一个 Promise。在函数内部,可以使用 await 关键字等待一个 Promise 完成。
  2. await 表达式await 暂停 async 函数的执行,等待一个 Promise 完成,然后恢复函数的执行并返回 Promise 的结果。如果 Promise 被拒绝,await 表达式会抛出一个异常。

底层实现实际上是基于生成器(generators)和 Promiseasync 函数被转换成生成器函数,并且使用一个自动执行的迭代器来处理生成器函数的执行。这个迭代器会自动处理 Promise 的完成和拒绝,从而实现异步代码的顺序执行。

总结

asyncawait 提供了一种更加简洁和直观的方式来处理异步操作。通过将异步代码写得像同步代码一样,async/await 提高了代码的可读性和可维护性,同时也简化了错误处理和多个异步操作的组合。了解其工作原理有助于更深入地掌握异步编程在JavaScript中的应用。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

如何使用 JavaScript 操作 DOM 元素?


在 JavaScript 中,操作 DOM(文档对象模型)元素是实现动态网页交互的核心。以下是一些常见的 DOM 操作方法和技巧,包括获取元素、修改内容、添加和删除元素以及事件处理等。

1. 获取 DOM 元素

1.1 通过 getElementById

获取具有指定 ID 的元素:

let element = document.getElementById('myElement');

1.2 通过 getElementsByClassName

获取具有指定类名的所有元素,返回一个类数组(HTMLCollection):

let elements = document.getElementsByClassName('myClass');

1.3 通过 getElementsByTagName

获取具有指定标签名的所有元素,返回一个类数组(HTMLCollection):

let elements = document.getElementsByTagName('div');

1.4 通过 querySelector

获取符合 CSS 选择器的第一个元素:

let element = document.querySelector('.myClass');

1.5 通过 querySelectorAll

获取符合 CSS 选择器的所有元素,返回一个静态节点列表(NodeList):

let elements = document.querySelectorAll('.myClass');

2. 修改元素内容和属性

2.1 修改元素的文本内容

通过 innerTexttextContent 修改元素的文本内容:

element.innerText = 'Hello, World!';
element.textContent = 'Hello, World!';

2.2 修改元素的 HTML 内容

通过 innerHTML 修改元素的 HTML 内容:

element.innerHTML = '<span>Hello, World!</span>';

2.3 修改元素的属性

使用 setAttribute 修改元素的属性:

element.setAttribute('class', 'newClass');

或直接修改属性:

element.id = 'newId';
element.className = 'newClass';

3. 添加和删除元素

3.1 创建新元素

使用 createElement 创建新元素:

let newElement = document.createElement('div');

3.2 向元素中添加子元素

使用 appendChild 添加子元素:

element.appendChild(newElement);

使用 insertBefore 插入子元素到指定位置:

element.insertBefore(newElement, referenceElement);

3.3 删除元素

使用 removeChild 删除子元素:

element.removeChild(childElement);

使用 remove 直接删除元素自身:

element.remove();

4. 修改元素样式

通过 style 属性修改元素的内联样式:

element.style.color = 'red';
element.style.fontSize = '20px';

通过 classList 添加、删除和切换类:

element.classList.add('newClass');
element.classList.remove('oldClass');
element.classList.toggle('active');

5. 事件处理

5.1 添加事件监听器

使用 addEventListener 添加事件监听器:

element.addEventListener('click', function() {
    console.log('Element clicked!');
});

5.2 删除事件监听器

使用 removeEventListener 删除事件监听器:

function handleClick() {
    console.log('Element clicked!');
}

element.addEventListener('click', handleClick);
element.removeEventListener('click', handleClick);

6. 常见的 DOM 操作示例

6.1 动态创建和插入元素

let parentElement = document.getElementById('parent');
let newElement = document.createElement('div');
newElement.innerText = 'This is a new element';
parentElement.appendChild(newElement);

6.2 表单处理

let form = document.querySelector('form');
form.addEventListener('submit', function(event) {
    event.preventDefault(); // 阻止默认提交行为
    let formData = new FormData(form);
    console.log('Form submitted:', Object.fromEntries(formData));
});

总结

通过熟练掌握上述 DOM 操作方法,你可以轻松地获取、修改和操作网页上的元素,从而实现各种动态交互效果。这些基本操作是前端开发的核心技能,可以帮助你构建功能丰富、用户友好的网页应用。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

事件委托是什么?有什么优点?


事件委托(Event Delegation)是一种处理事件的技巧,通过利用事件冒泡机制,可以将一个事件监听器添加到父元素,而不是每个子元素上。当事件触发时,由父元素的事件监听器来处理。这样做可以有效地管理大量的事件监听器,尤其是在动态添加或删除子元素的情况下。

事件冒泡机制

在了解事件委托之前,先了解事件冒泡机制。事件冒泡指的是当一个事件触发在某个元素上时,这个事件会向上冒泡到它的父元素,一直到顶层的 document。例如,当你点击一个按钮时,click 事件首先触发在按钮上,然后冒泡到按钮的父元素,接着冒泡到父元素的父元素,依此类推,直到 document 对象。

事件委托的工作原理

利用事件冒泡机制,事件委托允许我们将事件监听器添加到父元素,而不是每个子元素。当事件冒泡到父元素时,父元素的事件监听器可以识别出事件的目标元素,从而对其进行处理。

事件委托的实现示例

以下是一个使用事件委托的示例:

<!DOCTYPE html>
<html>
<head>
    <title>Event Delegation Example</title>
</head>
<body>
    <ul id="parentList">
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>

    <script>
        // 获取父元素
        const parentList = document.getElementById('parentList');

        // 添加事件监听器到父元素
        parentList.addEventListener('click', function(event) {
            // 确定事件的目标元素
            const target = event.target;
            
            // 检查目标元素是否是列表项
            if (target.tagName === 'LI') {
                console.log('List item clicked:', target.textContent);
            }
        });
    </script>
</body>
</html>

在这个示例中,click 事件监听器被添加到 ul 元素(parentList),当任何一个 li 元素被点击时,事件会冒泡到 ul 元素,然后 ul 元素的事件监听器处理这个事件。

事件委托的优点

  1. 减少内存消耗:将事件监听器添加到父元素而不是每个子元素上,可以减少内存占用,尤其是在有大量子元素时。

  2. 简化代码:通过事件委托,只需要一个事件监听器来处理所有子元素的事件,可以简化代码,提升可维护性。

  3. 动态元素处理:事件委托特别适合处理动态添加或删除的元素,因为父元素的事件监听器可以捕获子元素的事件,无需重新添加事件监听器。

  4. 提高性能:在某些情况下,事件委托可以提高性能,尤其是在需要处理大量 DOM 操作时。

事件委托的注意事项

  1. 事件冒泡:事件委托依赖于事件冒泡,因此并不是所有事件都适合使用事件委托。例如,focusblur 事件不会冒泡。

  2. 事件目标检查:在事件委托中,需要仔细检查事件的目标元素,以确保只对感兴趣的元素进行处理。

总结

事件委托是一种强大且高效的事件处理技巧,利用事件冒泡机制,通过在父元素上添加事件监听器,可以简化代码,减少内存消耗,并提高性能。掌握事件委托有助于更有效地管理复杂的事件处理逻辑,特别是在处理动态内容时。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

Node.js 如何使用 CommonJS 模块?


在 Node.js 中,CommonJS 模块系统是默认的模块系统。它提供了一种简单的方式来组织和重用代码。以下是使用 CommonJS 模块系统的基本方法,包括如何导出和导入模块。

1. 创建和导出模块

在 CommonJS 模块系统中,使用 module.exportsexports 对象来导出模块中的内容,使其能够在其他文件中被导入和使用。

1.1 使用 module.exports

// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;

module.exports = {
    add,
    subtract
};

1.2 使用 exports

exportsmodule.exports 的一个引用,可以用于简化导出语法:

// math.js
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;

2. 导入模块

使用 require 函数导入模块,并将导入的模块赋值给一个变量,以便使用模块中的导出内容。

2.1 导入自定义模块

// app.js
const math = require('./math');

console.log(math.add(2, 3));        // 输出: 5
console.log(math.subtract(5, 3));   // 输出: 2

注意:导入自定义模块时需要指定相对路径或绝对路径。

2.2 导入内置模块

Node.js 提供了一些内置模块,可以直接使用 require 导入。例如:

// app.js
const fs = require('fs');
const path = require('path');

// 使用 fs 模块读取文件内容
fs.readFile(path.join(__dirname, 'example.txt'), 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
});

3. 导出和导入类或构造函数

可以导出类或构造函数,以便在其他文件中创建和使用实例。

3.1 导出类

// person.js
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

module.exports = Person;

3.2 导入类

// app.js
const Person = require('./person');

const alice = new Person('Alice', 30);
alice.greet();  // 输出: Hello, my name is Alice and I am 30 years old.

4. 导出和导入单个函数或对象

可以直接导出单个函数或对象,而不是一个包含多个成员的对象。

4.1 导出单个函数

// greet.js
module.exports = function(name) {
    console.log(`Hello, ${name}!`);
};

4.2 导入单个函数

// app.js
const greet = require('./greet');

greet('Alice');  // 输出: Hello, Alice!

5. 循环依赖

在复杂的应用程序中,可能会出现模块之间相互依赖的情况,即循环依赖。Node.js 可以处理循环依赖,但需要注意避免复杂的依赖关系。

示例

// a.js
const b = require('./b');
module.exports = {
    value: 1,
    getBValue: () => b.value
};

// b.js
const a = require('./a');
module.exports = {
    value: 2,
    getAValue: () => a.value
};

// app.js
const a = require('./a');
const b = require('./b');

console.log(a.getBValue());  // 输出: 2
console.log(b.getAValue());  // 输出: 1

在这个示例中,a.jsb.js 互相依赖,但 Node.js 处理了这个循环依赖,确保了模块的正确导入。

总结

Node.js 的 CommonJS 模块系统提供了一种简单而强大的方式来组织和重用代码。通过 module.exportsrequire,可以轻松地导出和导入模块中的内容。掌握这些基本操作可以帮助你更好地管理 Node.js 应用程序中的代码结构。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

什么是 npm?如何使用?


npm(Node Package Manager)是 JavaScript 的包管理工具和软件包仓库。它是 Node.js 的默认包管理工具,允许开发者安装、共享、和管理项目所依赖的第三方库和工具。使用 npm,开发者可以方便地下载和更新各种开源的 JavaScript 库,简化项目的开发和维护。

npm 的基本功能

  1. 包管理:安装、卸载、更新和管理项目所需的各种包(库)。
  2. 包发布:发布和共享自己的包到 npm 仓库,供其他开发者使用。
  3. 依赖管理:自动处理项目的依赖关系,确保所有依赖包正确安装。

安装 Node.js 和 npm

npm 随 Node.js 一起安装。可以从 Node.js 官网 下载并安装 Node.js。

npm 的基本使用

1. 初始化项目

在项目根目录运行以下命令,创建一个 package.json 文件来管理项目的依赖和元数据:

npm init

按照提示填写项目名称、版本、描述等信息,完成后会生成一个 package.json 文件。

可以使用 -y 参数自动生成默认配置的 package.json 文件:

npm init -y

2. 安装包

安装指定的包并将其添加到 package.jsondependencies 列表中:

npm install <package-name>

例如,安装 express

npm install express

安装指定版本的包:

npm install <package-name>@<version>

例如,安装 express 的某个特定版本:

npm install express@4.17.1

安装开发依赖包,并将其添加到 package.jsondevDependencies 列表中:

npm install <package-name> --save-dev

例如,安装 nodemon 作为开发依赖:

npm install nodemon --save-dev

3. 卸载包

卸载指定的包,并从 package.json 中删除相关依赖:

npm uninstall <package-name>

4. 更新包

更新项目中所有包到最新版本:

npm update

更新指定的包到最新版本:

npm update <package-name>

5. 查看已安装的包

列出当前项目中安装的所有包及其版本:

npm list

查看全局安装的包:

npm list -g

6. 运行脚本

package.json 中,可以定义自定义脚本:

{
  "scripts": {
    "start": "node app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

运行定义的脚本:

npm run start
npm run test

7. 发布包

发布自己的包到 npm 仓库:

  1. 创建一个 npm 账户并登录:

    npm adduser
  2. 在项目根目录下运行以下命令,将包发布到 npm 仓库:

    npm publish

8. 使用 .npmrc 配置文件

可以在项目根目录或用户主目录下创建 .npmrc 文件来配置 npm 的行为。例如,配置默认的注册表地址:

registry=https://registry.npmjs.org/

示例项目

以下是一个简单的 Node.js 项目,使用 express 创建一个 Web 服务器:

  1. 初始化项目并安装 express

    mkdir myapp
    cd myapp
    npm init -y
    npm install express
  2. 创建一个 app.js 文件,内容如下:

    const express = require('express');
    const app = express();
    const port = 3000;
    
    app.get('/', (req, res) => {
      res.send('Hello World!');
    });
    
    app.listen(port, () => {
      console.log(`Example app listening at http://localhost:${port}`);
    });
  3. package.json 中添加 start 脚本:

    "scripts": {
      "start": "node app.js"
    }
  4. 运行项目:

    npm run start

    在浏览器中访问 http://localhost:3000,你会看到 "Hello World!"。

总结

npm 是 JavaScript 和 Node.js 项目中不可或缺的包管理工具。通过 npm,开发者可以方便地管理项目的依赖,发布和分享自己的代码库,以及自动化常见的开发任务。掌握 npm 的使用对于现代 JavaScript 开发者来说是非常重要的。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

Node.js 的事件循环是如何工作的?


Node.js 的事件循环(Event Loop)是其异步编程模型的核心,允许非阻塞 I/O 操作。了解事件循环的工作机制有助于更好地编写和优化 Node.js 应用程序。

事件循环的基本概念

Node.js 是基于事件驱动的异步运行时环境。事件循环是一个负责监听和处理事件的机制。它会不断检查是否有需要处理的事件,如果有就执行相应的回调函数。

事件循环的工作阶段

事件循环可以分为多个阶段,每个阶段都有特定的任务要处理。以下是事件循环的主要阶段:

  1. timers 阶段
  2. I/O callbacks 阶段
  3. idle, prepare 阶段
  4. poll 阶段
  5. check 阶段
  6. close callbacks 阶段

1. timers 阶段

这个阶段执行 setTimeoutsetInterval 的回调函数。如果有多个定时器到期,它们的回调会依次执行。

setTimeout(() => {
    console.log('Timeout callback executed');
}, 0);

2. I/O callbacks 阶段

这个阶段处理一些上个循环中延迟的 I/O 回调。例如,某些操作系统特定的 I/O 回调会在这个阶段执行。

3. idle, prepare 阶段

这个阶段仅供内部使用。Node.js 在这里执行系统级的操作。

4. poll 阶段

这是事件循环的核心阶段。在这个阶段,Node.js 会检查是否有新的 I/O 事件需要处理。如果没有待处理的 I/O 事件,事件循环可能会在这里阻塞并等待新的 I/O 事件。

5. check 阶段

这个阶段执行 setImmediate 的回调函数。setImmediate 被设计为立即执行的回调,优先级高于 setTimeout

setImmediate(() => {
    console.log('Immediate callback executed');
});

6. close callbacks 阶段

这个阶段执行一些关闭操作的回调函数,例如 socket.on('close', ...)

事件循环的运行顺序

以下是一个示例,展示不同阶段的执行顺序:

const fs = require('fs');

setTimeout(() => {
    console.log('Timeout callback executed');
}, 0);

setImmediate(() => {
    console.log('Immediate callback executed');
});

fs.readFile(__filename, () => {
    console.log('File read callback executed');
    
    setTimeout(() => {
        console.log('Timeout callback inside readFile');
    }, 0);

    setImmediate(() => {
        console.log('Immediate callback inside readFile');
    });
});

console.log('Main script executed');

执行结果如下(顺序可能略有不同):

Main script executed
File read callback executed
Immediate callback executed
Timeout callback executed
Immediate callback inside readFile
Timeout callback inside readFile

在这个示例中:

  1. Main script executed 立即执行。
  2. 文件读取操作异步进行,回调会在稍后执行。
  3. setImmediate 回调在 Immediate callback executed 阶段执行。
  4. setTimeout 回调在 Timeout callback executed 阶段执行。
  5. 文件读取完成后,回调 File read callback executed 执行。
  6. 在文件读取回调中,再次设置了 setImmediatesetTimeout,它们会在相应的阶段执行。

事件循环的优点

  1. 高效 I/O 处理:事件循环允许处理大量并发 I/O 操作,而不会阻塞主线程。
  2. 简洁的异步编程模型:通过回调、Promise 和 async/await,开发者可以简洁地编写异步代码。
  3. 资源节约:事件驱动模型节约资源,避免了传统多线程模型中的上下文切换开销。

总结

Node.js 的事件循环是其异步和非阻塞 I/O 操作的基础。通过理解事件循环的工作机制和各个阶段的运行顺序,开发者可以更有效地编写和优化 Node.js 应用程序。事件循环使得 Node.js 在处理高并发和 I/O 密集型任务时表现出色。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

什么是事件发射器(EventEmitter)?


在 Node.js 中,事件发射器(EventEmitter)是一个核心概念,用于处理事件驱动编程。EventEmitter 类是 Node.js 内置的一个模块,它提供了一种在对象之间发射(emit)和监听(listen)事件的机制。这使得开发者可以轻松地实现和管理异步事件。

事件发射器的基本用法

引入 EventEmitter

首先,需要从 events 模块中引入 EventEmitter 类:

const EventEmitter = require('events');

创建事件发射器实例

创建 EventEmitter 类的实例:

const myEmitter = new EventEmitter();

监听事件

使用 on 方法为特定事件注册监听器:

myEmitter.on('event', () => {
    console.log('An event occurred!');
});

发射事件

使用 emit 方法发射事件:

myEmitter.emit('event');

完整示例

以下是一个完整的示例,展示了如何使用 EventEmitter 来发射和监听事件:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

// 为 'event' 事件注册监听器
myEmitter.on('event', () => {
    console.log('An event occurred!');
});

// 发射 'event' 事件
myEmitter.emit('event');

运行上述代码时,输出如下:

An event occurred!

事件发射器的高级用法

1. 传递参数

可以在发射事件时传递参数,并在监听器中接收这些参数:

myEmitter.on('eventWithArgs', (arg1, arg2) => {
    console.log(`Event with arguments: ${arg1}, ${arg2}`);
});

myEmitter.emit('eventWithArgs', 'arg1Value', 'arg2Value');

2. 一次性监听器

使用 once 方法可以注册一次性监听器,监听器只会触发一次,然后自动移除:

myEmitter.once('onceEvent', () => {
    console.log('This event will only be logged once');
});

myEmitter.emit('onceEvent'); // 输出: This event will only be logged once
myEmitter.emit('onceEvent'); // 不会输出任何内容

3. 移除监听器

使用 removeListeneroff 方法移除监听器:

function logEvent() {
    console.log('Event logged');
}

myEmitter.on('removableEvent', logEvent);

// 移除监听器
myEmitter.removeListener('removableEvent', logEvent);

// 或使用
// myEmitter.off('removableEvent', logEvent);

myEmitter.emit('removableEvent'); // 不会输出任何内容

使用 removeAllListeners 方法可以移除所有监听器:

myEmitter.removeAllListeners('event');

4. 获取监听器

使用 listeners 方法获取指定事件的所有监听器:

const listeners = myEmitter.listeners('event');
console.log(listeners);

实际应用

事件发射器在 Node.js 中有广泛的应用,例如:

  1. 处理 HTTP 请求和响应:在 Node.js 内置的 HTTP 模块中,服务器和请求对象都继承自 EventEmitter,用于处理请求和响应事件。
  2. 流(Stream):流对象如 fs.createReadStreamfs.createWriteStream 都是 EventEmitter,用于处理数据读写事件。
  3. 进程事件process 对象也是 EventEmitter,用于处理进程生命周期中的事件,如 exituncaughtException

总结

EventEmitter 是 Node.js 中用于实现事件驱动编程的核心模块。通过 EventEmitter,开发者可以创建、发射和监听自定义事件,实现模块之间的解耦和异步事件处理。掌握 EventEmitter 的使用对构建高效、可维护的 Node.js 应用程序非常重要。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

如何使用 Node.js 操作文件和目录?


在 Node.js 中,操作文件和目录主要依赖于 fs(文件系统)模块。这个模块提供了一系列同步和异步的方法,用于创建、读取、写入、删除文件和目录。

引入 fs 模块

首先,需要引入 fs 模块:

const fs = require('fs');

1. 读取文件

1.1 异步读取文件

使用 fs.readFile 方法异步读取文件内容:

fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log(data);
});

1.2 同步读取文件

使用 fs.readFileSync 方法同步读取文件内容:

try {
    const data = fs.readFileSync('example.txt', 'utf8');
    console.log(data);
} catch (err) {
    console.error(err);
}

2. 写入文件

2.1 异步写入文件

使用 fs.writeFile 方法异步写入文件:

const content = 'This is some content to write into the file.';

fs.writeFile('example.txt', content, 'utf8', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('File has been written.');
});

2.2 同步写入文件

使用 fs.writeFileSync 方法同步写入文件:

const content = 'This is some content to write into the file.';

try {
    fs.writeFileSync('example.txt', content, 'utf8');
    console.log('File has been written.');
} catch (err) {
    console.error(err);
}

3. 追加内容到文件

3.1 异步追加内容

使用 fs.appendFile 方法异步追加内容:

const additionalContent = 'This content will be appended.';

fs.appendFile('example.txt', additionalContent, 'utf8', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('Content has been appended.');
});

3.2 同步追加内容

使用 fs.appendFileSync 方法同步追加内容:

const additionalContent = 'This content will be appended.';

try {
    fs.appendFileSync('example.txt', additionalContent, 'utf8');
    console.log('Content has been appended.');
} catch (err) {
    console.error(err);
}

4. 删除文件

4.1 异步删除文件

使用 fs.unlink 方法异步删除文件:

fs.unlink('example.txt', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('File has been deleted.');
});

4.2 同步删除文件

使用 fs.unlinkSync 方法同步删除文件:

try {
    fs.unlinkSync('example.txt');
    console.log('File has been deleted.');
} catch (err) {
    console.error(err);
}

5. 创建目录

5.1 异步创建目录

使用 fs.mkdir 方法异步创建目录:

fs.mkdir('exampleDir', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('Directory has been created.');
});

5.2 同步创建目录

使用 fs.mkdirSync 方法同步创建目录:

try {
    fs.mkdirSync('exampleDir');
    console.log('Directory has been created.');
} catch (err) {
    console.error(err);
}

6. 删除目录

6.1 异步删除目录

使用 fs.rmdir 方法异步删除目录:

fs.rmdir('exampleDir', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('Directory has been deleted.');
});

6.2 同步删除目录

使用 fs.rmdirSync 方法同步删除目录:

try {
    fs.rmdirSync('exampleDir');
    console.log('Directory has been deleted.');
} catch (err) {
    console.error(err);
}

7. 读取目录内容

7.1 异步读取目录内容

使用 fs.readdir 方法异步读取目录内容:

fs.readdir('exampleDir', (err, files) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log(files);
});

7.2 同步读取目录内容

使用 fs.readdirSync 方法同步读取目录内容:

try {
    const files = fs.readdirSync('exampleDir');
    console.log(files);
} catch (err) {
    console.error(err);
}

8. 重命名文件或目录

8.1 异步重命名

使用 fs.rename 方法异步重命名文件或目录:

fs.rename('oldName.txt', 'newName.txt', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('File has been renamed.');
});

8.2 同步重命名

使用 fs.renameSync 方法同步重命名文件或目录:

try {
    fs.renameSync('oldName.txt', 'newName.txt');
    console.log('File has been renamed.');
} catch (err) {
    console.error(err);
}

总结

通过 fs 模块,Node.js 提供了丰富的文件和目录操作方法,包括读取、写入、删除、重命名和遍历目录内容等。根据具体需求选择同步或异步方法,可以有效地管理和操作文件系统资源。熟练掌握这些方法有助于构建功能丰富且高效的 Node.js 应用程序。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

如何使用 Node.js 创建一个 HTTP 服务器?


在 Node.js 中,创建一个 HTTP 服务器是一个相对简单的任务。Node.js 提供了内置的 http 模块,可以用来处理 HTTP 请求和响应。下面是一个基本的示例,展示如何创建和运行一个 HTTP 服务器。

1. 引入 http 模块

首先,需要引入 Node.js 的 http 模块:

const http = require('http');

2. 创建服务器

使用 http.createServer 方法创建一个服务器实例,并定义处理请求的回调函数:

const server = http.createServer((req, res) => {
    // 设置响应头
    res.writeHead(200, {'Content-Type': 'text/plain'});

    // 设置响应内容
    res.write('Hello, World!');

    // 结束响应
    res.end();
});

在回调函数中,req 代表请求对象,res 代表响应对象。

3. 监听端口

使用 server.listen 方法指定服务器监听的端口号和主机名:

const port = 3000;
const hostname = '127.0.0.1';

server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

完整示例

将上述代码组合成一个完整的示例:

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('Hello, World!');
    res.end();
});

const port = 3000;
const hostname = '127.0.0.1';

server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

处理不同的请求和响应

在实际应用中,服务器通常需要处理不同的 URL 路径和请求方法。以下示例展示了如何根据请求的 URL 和方法做出不同的响应:

const http = require('http');

const server = http.createServer((req, res) => {
    if (req.method === 'GET') {
        if (req.url === '/') {
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end('<h1>Home Page</h1>');
        } else if (req.url === '/about') {
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.end('<h1>About Page</h1>');
        } else {
            res.writeHead(404, {'Content-Type': 'text/html'});
            res.end('<h1>404 Not Found</h1>');
        }
    } else {
        res.writeHead(405, {'Content-Type': 'text/html'});
        res.end('<h1>405 Method Not Allowed</h1>');
    }
});

const port = 3000;
const hostname = '127.0.0.1';

server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

处理 POST 请求

以下示例展示了如何处理 POST 请求并解析请求体中的数据:

const http = require('http');

const server = http.createServer((req, res) => {
    if (req.method === 'POST' && req.url === '/submit') {
        let body = '';

        req.on('data', chunk => {
            body += chunk.toString();
        });

        req.on('end', () => {
            res.writeHead(200, {'Content-Type': 'application/json'});
            res.end(JSON.stringify({ message: 'Data received', data: body }));
        });
    } else {
        res.writeHead(404, {'Content-Type': 'text/html'});
        res.end('<h1>404 Not Found</h1>');
    }
});

const port = 3000;
const hostname = '127.0.0.1';

server.listen(port, hostname, () => {
    console.log(`Server running at http://${hostname}:${port}/`);
});

在这个示例中,服务器监听 /submit 路径的 POST 请求,并将接收到的数据返回给客户端。

总结

通过 Node.js 的 http 模块,可以轻松创建和管理 HTTP 服务器。上述示例展示了如何创建一个简单的 HTTP 服务器,处理不同的请求路径和方法,以及处理 POST 请求中的数据。了解和掌握这些基本操作,是构建强大和高效的 Node.js 应用的基础。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 3, 2024

如何处理请求和响应?


在 Node.js 中,处理 HTTP 请求和响应是创建 Web 服务器的核心部分。你可以通过解析请求对象(req)来获取客户端发送的数据,并通过操作响应对象(res)来返回数据给客户端。以下是详细的步骤和示例,展示如何处理请求和响应。

1. 解析请求对象

1.1 获取请求方法和 URL

使用 req.methodreq.url 获取请求的方法和 URL 路径:

const http = require('http');

const server = http.createServer((req, res) => {
    const method = req.method;
    const url = req.url;
    console.log(`Request method: ${method}, URL: ${url}`);

    // 处理请求
    res.end();
});

server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

1.2 解析请求头

请求头可以通过 req.headers 获取,是一个包含请求头的对象:

const http = require('http');

const server = http.createServer((req, res) => {
    console.log('Request headers:', req.headers);

    // 处理请求
    res.end();
});

server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

1.3 解析请求体

对于 POST 请求,通常需要解析请求体的数据。可以使用 dataend 事件来获取请求体内容:

const http = require('http');

const server = http.createServer((req, res) => {
    if (req.method === 'POST') {
        let body = '';

        req.on('data', chunk => {
            body += chunk.toString();
        });

        req.on('end', () => {
            console.log('Request body:', body);
            res.end('Received POST data');
        });
    } else {
        res.end('Send a POST request to see data handling.');
    }
});

server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

2. 生成响应

2.1 设置响应状态码和头部

使用 res.writeHead 设置响应状态码和头部:

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello, World!');
});

server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

2.2 发送响应内容

使用 res.writeres.end 发送响应内容:

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('Hello, ');
    res.end('World!');
});

server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

2.3 发送 JSON 响应

通过设置 Content-Typeapplication/json,并使用 JSON.stringify 发送 JSON 响应:

const http = require('http');

const server = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    const response = { message: 'Hello, World!' };
    res.end(JSON.stringify(response));
});

server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

3. 路由处理

根据不同的 URL 路径和请求方法处理不同的逻辑:

const http = require('http');

const server = http.createServer((req, res) => {
    const method = req.method;
    const url = req.url;

    if (method === 'GET' && url === '/') {
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end('<h1>Home Page</h1>');
    } else if (method === 'GET' && url === '/about') {
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end('<h1>About Page</h1>');
    } else if (method === 'POST' && url === '/submit') {
        let body = '';

        req.on('data', chunk => {
            body += chunk.toString();
        });

        req.on('end', () => {
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ message: 'Data received', data: body }));
        });
    } else {
        res.writeHead(404, { 'Content-Type': 'text/html' });
        res.end('<h1>404 Not Found</h1>');
    }
});

server.listen(3000, () => {
    console.log('Server is listening on port 3000');
});

总结

通过上述方法,可以在 Node.js 中高效地处理 HTTP 请求和响应。关键在于正确解析请求对象并根据请求的方法和 URL 做出适当的响应。掌握这些基本操作是构建 Node.js Web 服务器的基础。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 4, 2024

如何使用 Express 创建路由?


在使用 Express 创建路由时,你需要按照以下步骤操作:

  1. 安装 Express:如果还没有安装 Express,需要先安装它。可以使用 npm 进行安装:

    npm install express
  2. 创建一个 Express 应用:新建一个 JavaScript 文件(例如 app.js),并在其中引入 Express 并创建应用:

    const express = require('express');
    const app = express();
    const port = 3000;
    
    app.listen(port, () => {
      console.log(`Server is running on http://localhost:${port}`);
    });
  3. 定义路由:在 Express 中,可以使用 app.get()app.post()app.put()app.delete() 等方法定义不同的路由。

    例如,定义一个 GET 路由:

    app.get('/', (req, res) => {
      res.send('Hello World!');
    });

    定义一个 POST 路由:

    app.post('/submit', (req, res) => {
      res.send('Form submitted!');
    });
  4. 使用路由器模块:为了更好地组织代码,可以使用 Express 的 Router 模块将路由分离到不同的文件中。例如,创建一个名为 routes.js 的文件:

    const express = require('express');
    const router = express.Router();
    
    // 定义一个 GET 路由
    router.get('/', (req, res) => {
      res.send('Welcome to the home page!');
    });
    
    // 定义一个 POST 路由
    router.post('/submit', (req, res) => {
      res.send('Form submitted!');
    });
    
    module.exports = router;

    然后在 app.js 中引入这个路由模块:

    const express = require('express');
    const app = express();
    const port = 3000;
    
    // 引入路由模块
    const routes = require('./routes');
    
    // 使用路由模块
    app.use('/', routes);
    
    app.listen(port, () => {
      console.log(`Server is running on http://localhost:${port}`);
    });
  5. 中间件和参数:在路由中可以使用中间件和参数。例如,定义一个带参数的路由:

    app.get('/user/:id', (req, res) => {
      const userId = req.params.id;
      res.send(`User ID: ${userId}`);
    });

    还可以添加中间件来处理请求:

    const express = require('express');
    const app = express();
    const port = 3000;
    
    // 中间件示例
    app.use((req, res, next) => {
      console.log('Time:', Date.now());
      next();
    });
    
    app.get('/', (req, res) => {
      res.send('Hello World!');
    });
    
    app.listen(port, () => {
      console.log(`Server is running on http://localhost:${port}`);
    });

通过这些步骤,你可以在 Express 中创建并管理路由,以构建灵活的 Web 应用程序。如果有需要进一步的详细说明或示例,请告诉我。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 4, 2024

中间件(Middleware)是什么?如何使用?


中间件(Middleware)是 Express 中一个核心概念,用于处理 HTTP 请求的各种方面。中间件是一个函数,可以访问请求对象(req)、响应对象(res)和应用程序的请求-响应周期中的下一个中间件函数(next)。

中间件的功能包括:

  • 执行任何代码。
  • 修改请求对象和响应对象。
  • 终止请求-响应循环。
  • 调用堆栈中的下一个中间件函数。

如果当前的中间件没有结束请求-响应循环,它必须调用 next() 函数来将控制传递给下一个中间件,否则请求将挂起。

使用中间件

  1. 应用级中间件

    应用级中间件绑定到应用对象 app,并使用 app.use()app.METHOD() 来绑定,其中 METHOD 是 HTTP 请求方法,例如 GETPOST

    const express = require('express');
    const app = express();
    
    // 应用级中间件
    app.use((req, res, next) => {
      console.log('Time:', Date.now());
      next();
    });
    
    app.get('/', (req, res) => {
      res.send('Hello World!');
    });
    
    app.listen(3000, () => {
      console.log('Server is running on http://localhost:3000');
    });
  2. 路由级中间件

    路由级中间件与应用级中间件类似,但它绑定到 express.Router() 实例。

    const express = require('express');
    const app = express();
    const router = express.Router();
    
    // 路由级中间件
    router.use((req, res, next) => {
      console.log('Request URL:', req.originalUrl);
      next();
    });
    
    router.get('/', (req, res) => {
      res.send('Home Page');
    });
    
    app.use('/', router);
    
    app.listen(3000, () => {
      console.log('Server is running on http://localhost:3000');
    });
  3. 错误处理中间件

    错误处理中间件与其他中间件类似,但它有四个参数:errreqresnext。你必须显式地定义四个参数,否则它不会被识别为错误处理中间件。

    const express = require('express');
    const app = express();
    
    // 普通中间件
    app.get('/', (req, res) => {
      throw new Error('Something went wrong!');
    });
    
    // 错误处理中间件
    app.use((err, req, res, next) => {
      console.error(err.stack);
      res.status(500).send('Something broke!');
    });
    
    app.listen(3000, () => {
      console.log('Server is running on http://localhost:3000');
    });
  4. 内置中间件

    Express 还提供了一些内置的中间件,比如 express.static 来提供静态文件。

    const express = require('express');
    const app = express();
    
    // 内置中间件
    app.use(express.static('public'));
    
    app.listen(3000, () => {
      console.log('Server is running on http://localhost:3000');
    });
  5. 第三方中间件

    你可以使用通过 npm 安装的第三方中间件。例如,body-parser 用于解析请求体。

    const express = require('express');
    const bodyParser = require('body-parser');
    const app = express();
    
    // 使用第三方中间件
    app.use(bodyParser.json());
    
    app.post('/data', (req, res) => {
      res.send(req.body);
    });
    
    app.listen(3000, () => {
      console.log('Server is running on http://localhost:3000');
    });

通过中间件,Express 提供了灵活且强大的方式来处理请求和响应。你可以根据需求组合和使用不同类型的中间件来实现复杂的功能。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 4, 2024

如何使用 MongoDB 连接并操作数据库?


使用 MongoDB 连接并操作数据库可以通过多种方式完成,其中最常见的是使用官方的 MongoDB Node.js 驱动。以下是一个详细的指南,展示了如何在 Node.js 应用程序中使用 MongoDB。

安装 MongoDB 驱动

首先,你需要安装 MongoDB 的 Node.js 驱动。可以通过 npm 安装:

npm install mongodb

连接到 MongoDB

在你的 Node.js 应用程序中,你需要引入 mongodb 模块并创建一个 MongoDB 客户端来连接到数据库。

const { MongoClient } = require('mongodb');

// MongoDB 连接 URI
const uri = 'mongodb://localhost:27017';

// 创建一个新的 MongoClient
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });

async function run() {
  try {
    // 连接到 MongoDB 服务器
    await client.connect();

    console.log('Connected to MongoDB');
    
    // 选择数据库
    const database = client.db('mydatabase');
    
    // 选择集合
    const collection = database.collection('mycollection');
    
    // 插入一个文档
    const doc = { name: 'Alice', age: 25, city: 'New York' };
    const result = await collection.insertOne(doc);
    console.log(`New document inserted with the following id: ${result.insertedId}`);

    // 查询文档
    const query = { name: 'Alice' };
    const user = await collection.findOne(query);
    console.log('Found document:', user);

    // 更新文档
    const update = { $set: { age: 26 } };
    const updateResult = await collection.updateOne(query, update);
    console.log(`Matched ${updateResult.matchedCount} document and modified ${updateResult.modifiedCount} document`);

    // 删除文档
    const deleteResult = await collection.deleteOne(query);
    console.log(`Deleted ${deleteResult.deletedCount} document`);

  } finally {
    // 关闭连接
    await client.close();
  }
}

run().catch(console.dir);

详细步骤说明

  1. 连接到 MongoDB

    • 使用 MongoClient.connect 方法连接到 MongoDB 服务器。连接 URI 包含了服务器的地址和端口号。
    • 使用 useNewUrlParseruseUnifiedTopology 选项来处理 MongoDB 驱动的新解析器和拓扑引擎。
  2. 选择数据库和集合

    • 使用 client.db('databaseName') 方法选择一个数据库。
    • 使用 database.collection('collectionName') 方法选择一个集合。
  3. 插入文档

    • 使用 collection.insertOne(document) 方法插入一个文档。document 是一个 JavaScript 对象,表示要插入的数据。
  4. 查询文档

    • 使用 collection.findOne(query) 方法查询一个文档。query 是一个 JavaScript 对象,表示查询条件。
  5. 更新文档

    • 使用 collection.updateOne(filter, update) 方法更新一个文档。filter 是一个 JavaScript 对象,表示查询条件;update 是一个 JavaScript 对象,表示更新操作。
  6. 删除文档

    • 使用 collection.deleteOne(filter) 方法删除一个文档。filter 是一个 JavaScript 对象,表示查询条件。

处理错误和关闭连接

在操作 MongoDB 时,应该始终包括错误处理逻辑,并在完成所有操作后关闭数据库连接:

async function run() {
  try {
    // 连接到 MongoDB 服务器
    await client.connect();

    console.log('Connected to MongoDB');

    // 选择数据库和集合等操作...
    
  } catch (err) {
    console.error(err);
  } finally {
    // 关闭连接
    await client.close();
  }
}

run().catch(console.dir);

通过以上步骤,你可以在 Node.js 应用程序中连接到 MongoDB 并执行基本的数据库操作。如果有更多需求,如复杂查询、聚合等,可以参考 MongoDB 官方文档 获取更多信息。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 4, 2024

如何使用 Sequelize 或 TypeORM 操作 SQL 数据库?


使用 Sequelize 或 TypeORM 操作 SQL 数据库非常方便。这两者都是流行的 ORM(对象关系映射)工具,可以帮助你在 Node.js 应用程序中更高效地与 SQL 数据库进行交互。以下是如何使用这两个工具的指南。

使用 Sequelize

1. 安装 Sequelize 和数据库驱动

首先,你需要安装 Sequelize 和相应的数据库驱动程序(例如 MySQL、PostgreSQL、SQLite 等)。

npm install sequelize
npm install mysql2  # For MySQL or MariaDB
npm install pg pg-hstore  # For PostgreSQL
npm install sqlite3  # For SQLite

2. 初始化 Sequelize

创建一个 Sequelize 实例,并配置数据库连接信息。

const { Sequelize, DataTypes } = require('sequelize');

const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql' // 'mysql' | 'mariadb' | 'postgres' | 'sqlite' | 'mssql'
});

(async () => {
  try {
    await sequelize.authenticate();
    console.log('Connection has been established successfully.');
  } catch (error) {
    console.error('Unable to connect to the database:', error);
  }
})();

3. 定义模型

定义一个模型以表示数据库中的表。

const User = sequelize.define('User', {
  username: {
    type: DataTypes.STRING,
    allowNull: false
  },
  birthday: {
    type: DataTypes.DATE,
    allowNull: false
  }
}, {
  // Other model options go here
});

// 同步模型到数据库
(async () => {
  await sequelize.sync({ force: true }); // `force: true` 会删除表并重新创建
  console.log("The table for the User model was just (re)created!");

  // 创建新用户
  const jane = await User.create({
    username: 'janedoe',
    birthday: new Date(1980, 6, 20)
  });

  console.log("Jane's auto-generated ID:", jane.id);
})();

4. 查询数据

使用 Sequelize 提供的查询方法来操作数据。

(async () => {
  const users = await User.findAll();
  console.log("All users:", JSON.stringify(users, null, 2));

  const jane = await User.findOne({ where: { username: 'janedoe' } });
  console.log("Found user:", jane);

  // 更新用户数据
  jane.birthday = new Date(1985, 6, 20);
  await jane.save();
  console.log("Updated user's birthday:", jane.birthday);

  // 删除用户
  await jane.destroy();
  console.log("Jane was deleted.");
})();

使用 TypeORM

1. 安装 TypeORM 和数据库驱动

首先,安装 TypeORM 和相应的数据库驱动程序。

npm install typeorm reflect-metadata
npm install mysql  # For MySQL or MariaDB
npm install pg  # For PostgreSQL
npm install sqlite3  # For SQLite

确保在 tsconfig.json 文件中启用 emitDecoratorMetadataexperimentalDecorators 选项。

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true
  }
}

2. 配置 TypeORM

创建 ormconfig.json 文件来配置数据库连接。

{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "test",
  "password": "test",
  "database": "test",
  "synchronize": true,
  "logging": false,
  "entities": [
    "dist/entity/**/*.js"
  ]
}

3. 定义实体

定义一个实体类来表示数据库中的表。

import { Entity, PrimaryGeneratedColumn, Column, createConnection } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  birthday: Date;
}

// 创建连接
createConnection().then(async connection => {
  console.log("Connected to the database.");

  const userRepository = connection.getRepository(User);

  // 创建新用户
  const user = new User();
  user.username = 'janedoe';
  user.birthday = new Date(1980, 6, 20);
  await userRepository.save(user);
  console.log("Saved a new user with id: " + user.id);

  // 查询所有用户
  const users = await userRepository.find();
  console.log("All users:", users);

  // 查询单个用户
  const jane = await userRepository.findOne({ username: 'janedoe' });
  console.log("Found user:", jane);

  // 更新用户数据
  jane.birthday = new Date(1985, 6, 20);
  await userRepository.save(jane);
  console.log("Updated user's birthday:", jane.birthday);

  // 删除用户
  await userRepository.remove(jane);
  console.log("Jane was deleted.");

}).catch(error => console.log(error));

通过上述步骤,你可以使用 Sequelize 或 TypeORM 在 Node.js 应用程序中连接和操作 SQL 数据库。这两个 ORM 工具提供了丰富的功能和灵活性,可以帮助你高效地管理数据库操作。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 4, 2024

如何实现用户登录和注册功能?


实现用户登录和注册功能是构建 web 应用程序的重要组成部分。以下是使用 Node.js 和 Express 的一个完整示例,结合 Sequelize 作为 ORM,来实现用户注册和登录功能。假设我们使用 MySQL 数据库。

准备工作

  1. 安装必要的包

    npm install express sequelize mysql2 bcryptjs jsonwebtoken
  2. 项目结构

    myapp/
    ├── app.js
    ├── config/
    │   └── database.js
    ├── models/
    │   └── user.js
    ├── routes/
    │   └── auth.js
    ├── controllers/
    │   └── authController.js
    └── middleware/
        └── auth.js
    

配置 Sequelize

config/database.js

const { Sequelize } = require('sequelize');

const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql'
});

module.exports = sequelize;

定义用户模型

models/user.js

const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');

const User = sequelize.define('User', {
  username: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  }
});

module.exports = User;

实现注册和登录功能

controllers/authController.js

const User = require('../models/user');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

exports.register = async (req, res) => {
  const { username, email, password } = req.body;

  try {
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = await User.create({
      username,
      email,
      password: hashedPassword
    });
    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

exports.login = async (req, res) => {
  const { email, password } = req.body;

  try {
    const user = await User.findOne({ where: { email } });

    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    const isMatch = await bcrypt.compare(password, user.password);

    if (!isMatch) {
      return res.status(400).json({ error: 'Invalid credentials' });
    }

    const token = jwt.sign({ userId: user.id }, 'your_jwt_secret', { expiresIn: '1h' });
    res.json({ token });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

定义路由

routes/auth.js

const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');

router.post('/register', authController.register);
router.post('/login', authController.login);

module.exports = router;

中间件验证 JWT

middleware/auth.js

const jwt = require('jsonwebtoken');

const auth = (req, res, next) => {
  const token = req.header('Authorization').replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ error: 'Access denied, no token provided' });
  }

  try {
    const decoded = jwt.verify(token, 'your_jwt_secret');
    req.user = decoded;
    next();
  } catch (error) {
    res.status(400).json({ error: 'Invalid token' });
  }
};

module.exports = auth;

配置 Express 应用

app.js

const express = require('express');
const sequelize = require('./config/database');
const userRoutes = require('./routes/auth');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());
app.use('/api/auth', userRoutes);

sequelize.sync().then(() => {
  app.listen(3000, () => {
    console.log('Server is running on http://localhost:3000');
  });
}).catch(error => {
  console.error('Unable to connect to the database:', error);
});

测试注册和登录功能

通过 Postman 或类似工具测试 API:

  1. 注册

    • URL: http://localhost:3000/api/auth/register
    • Method: POST
    • Body: JSON
      {
        "username": "testuser",
        "email": "testuser@example.com",
        "password": "password123"
      }
  2. 登录

    • URL: http://localhost:3000/api/auth/login
    • Method: POST
    • Body: JSON
      {
        "email": "testuser@example.com",
        "password": "password123"
      }

如果一切顺利,你应该能够成功注册和登录用户,并在登录时获得 JWT 令牌。你可以在受保护的路由中使用 JWT 验证中间件来验证用户身份。

⬆️ Back to Top

@llccing
Copy link
Owner Author

llccing commented Jul 4, 2024

JWT(JSON Web Token)是什么?如何使用?


JWT(JSON Web Token)是一种用于在各方之间作为 JSON 对象安全传输信息的紧凑、URL 安全的方式。该信息可以被验证和信任,因为它是经过数字签名的。JWT 通常用于身份验证和授权场景。

JWT 的组成

JWT 由三个部分组成:

  1. Header(头部)
  2. Payload(负载)
  3. Signature(签名)

这三个部分以点 (.) 作为分隔符连接起来。一个典型的 JWT 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. Header

Header 通常由两部分组成:令牌的类型(JWT)和使用的签名算法(如 HMAC SHA256 或 RSA)。

{
  "alg": "HS256",
  "typ": "JWT"
}

然后将这个 JSON 对象进行 Base64 编码,形成 JWT 的第一部分。

2. Payload

Payload 是一个 JSON 对象,包含需要传输的数据(声明,claims)。声明是关于实体(通常是用户)和其他数据的陈述。三种类型的声明:

  • Registered claims(注册声明):一组预定义的声明,推荐但不强制使用,如 iss(发行人),exp(到期时间),sub(主题),aud(受众)。
  • Public claims(公共声明):可以自由定义的声明,应该避免冲突。
  • Private claims(私有声明):通常在同意的各方之间使用。

一个示例 payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将这个 JSON 对象进行 Base64 编码,形成 JWT 的第二部分。

3. Signature

Signature 部分是对前两个部分的验证。首先需要指定一个秘密(可以是一个密钥):

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

使用 JWT

JWT 的主要使用场景包括用户认证和授权。以下是如何在 Node.js 应用中使用 JWT:

安装 JWT 库

npm install jsonwebtoken

创建 JWT

在用户登录时创建并返回一个 JWT:

const jwt = require('jsonwebtoken');

const user = { id: 1, username: 'john' }; // 模拟用户数据
const secret = 'your_jwt_secret';

const token = jwt.sign({ userId: user.id }, secret, { expiresIn: '1h' });

console.log(token);

验证 JWT

middleware/auth.js:

在每个需要保护的路由中验证 JWT:

const jwt = require('jsonwebtoken');

const secret = 'your_jwt_secret';

const auth = (req, res, next) => {
  const token = req.header('Authorization').replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ error: 'Access denied, no token provided' });
  }

  try {
    const decoded = jwt.verify(token, secret);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(400).json({ error: 'Invalid token' });
  }
};

module.exports = auth;

结合 Express 使用

下面是一个完整的示例,展示了如何在 Express 应用中实现用户注册、登录和基于 JWT 的认证。

app.js

const express = require('express');
const sequelize = require('./config/database');
const authRoutes = require('./routes/auth');
const bodyParser = require('body-parser');
const auth = require('./middleware/auth');

const app = express();

app.use(bodyParser.json());
app.use('/api/auth', authRoutes);

// 受保护的路由示例
app.get('/api/protected', auth, (req, res) => {
  res.json({ message: 'This is a protected route', user: req.user });
});

sequelize.sync().then(() => {
  app.listen(3000, () => {
    console.log('Server is running on http://localhost:3000');
  });
}).catch(error => {
  console.error('Unable to connect to the database:', error);
});

routes/auth.js

const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');

router.post('/register', authController.register);
router.post('/login', authController.login);

module.exports = router;

controllers/authController.js

const User = require('../models/user');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

exports.register = async (req, res) => {
  const { username, email, password } = req.body;

  try {
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = await User.create({
      username,
      email,
      password: hashedPassword
    });
    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

exports.login = async (req, res) => {
  const { email, password } = req.body;

  try {
    const user = await User.findOne({ where: { email } });

    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    const isMatch = await bcrypt.compare(password, user.password);

    if (!isMatch) {
      return res.status(400).json({ error: 'Invalid credentials' });
    }

    const token = jwt.sign({ userId: user.id }, 'your_jwt_secret', { expiresIn: '1h' });
    res.json({ token });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

通过上述步骤,你可以实现一个基本的用户注册和登录系统,并使用 JWT 进行认证和授权。这样,你可以确保只有经过身份验证的用户才能访问受保护的路由。

⬆️ Back to Top

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant