前端学习 node 快速入门 系列 —— 事件循环

这篇具有很好参考价值的文章主要介绍了前端学习 node 快速入门 系列 —— 事件循环。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

事件循环

本篇将对以下问题进行讨论:

  • 浏览器有事件循环,node 也有事件循环,两者有什么异同?
  • node 核心特性(事件驱动和非阻塞 I/O )和事件循环有什么关系?
  • node 中的高并发和高性能和事件循环有关系吗?
  • node 不适合什么场景?
  • 有人说 Node 是单线程,有人又说 node 存在多线程,哪个正确?
  • 如果一个请求需要2秒,用 pm2 能将其优化吗?

浏览器中的事件循环

有关事件循环前文已经略作介绍,这里进一步补充。

请在浏览器中运行这段代码:

console.log('1');

setTimeout(() => {
    console.log('2');
    Promise.resolve().then(() => console.log('3'));
    Promise.resolve().then(() => console.log('4'));
}, 100);

setTimeout(() => {
    console.log('5');
    Promise.resolve().then(() => console.log('6'));
}, 150);

Promise.resolve().then(() => console.log('7'));

setTimeout(() => console.log('8'), 200);

console.log('9');

/*
结果:
1
9
7
2
3
4
5
6
8
*/

分析这段代码的事件循环的详细过程之前,有几点需要说一下:

  • 在一次事件循环中,只会执行一个宏任务所有的微任务,而且宏任务和微任务的处理顺序是固定的:每次执行完一个宏任务后,首先会立即处理所有的微任务,然后才会执行下一个宏任务。如果在执行微任务时又产生了新的微任务,那么这些新的微任务也会被添加到队列中,直到全部微任务都执行完成,才会执行宏任务。
  • 宏任务执行期间产生的微任务都会在当前宏任务执行完毕之后立即执行,不会延迟到下一个宏任务或事件循环中执行
  • 当一个宏任务执行的过程中产生了微任务,那么这些微任务会被推入微任务队列中等待处理。而只有当当前宏任务执行结束之后,主线程才会去处理微任务队列中的所有微任务。因此,所有的微任务都会在下一个宏任务执行之前被处理完毕。
  • 在浏览器中,主线程使用轮询方式来实现事件循环机制。在执行完当前的任务之后,如果宏任务队列为空,主线程会等待一段时间,这个时间间隔是由浏览器厂商自行决定的,然后再次查询宏任务队列是否有任务需要执行。
  • setTimeout 是宏任务,比如执行 setTimeout(() => console.log('8'), 200),浏览器会创建一个定时器(200ms),并将回调函数和指定的时间保存在一个任务中。当指定的时间到达时,定时器才会将这个任务推入宏任务队列中等待处理

这段代码大概有四次事件循环,执行过程如下:

  • 第一次事件循环:
首先将 console.log('1') 加入执行栈中,输出 1,然后将其从执行栈中弹出。

第一个 setTimeout 函数被调用时,浏览器会创建一个定时器(100ms),并将回调函数和指定的时间保存在一个任务中。当指定的时间到达时,定时器会将这个任务推入宏任务队列中等待处理

第二个 setTimeout 与第一 setTimeout 类似,等待 150ms 后会被放入宏任务队列中

Promise.resolve().then(() => console.log('7')) 放入微任务队列

第三个 setTimeout 与第一 setTimeout 类似,等待 200ms 后会被放入宏任务队列中

执行 console.log('9')

取出微任务队列中的所有任务,输出 7
  • 第二次事件循环:
执行栈为空,主线程轮询查看宏任务队列(微任务队列刚才已经清空了),此时宏任务队列为空

100ms后,第一个setTimeout 宏任务推入宏任务队列中,取出这个宏任务放入执行栈中

输出 2

执行 `Promise.resolve().then(() => console.log('3'));`、`Promise.resolve().then(() => console.log('4'));`,放入微任务队列

这个宏任务执行完毕之后,主线程会转而执行当前微任务队列中的所有任务,输出 3 和 4
  • 第三次事件循环:
执行栈为空,主线程轮询宏任务队列发现其为空

150ms后,第二个setTimeout 宏任务推入宏任务队列中,取出这个宏任务放入执行栈中

输出 5

执行 `Promise.resolve().then(() => console.log('6'));` 放入微任务队列

这个宏任务执行完毕之后,主线程会转而执行当前微任务队列中的所有任务,输出 6
  • 第四次事件循环:
执行栈为空,主线程轮询宏任务队列发现其为空

200ms后,第三个setTimeout 宏任务推入宏任务队列中,取出这个宏任务放入执行栈中

输出 8

宏任务优先级

宏任务之间其实存在优先级。比如 click > requestAnimationFrame > setTimeout

  • 用户交互相关的任务具有最高的优先级。在用户交互(例如点击)后,会将与该事件相关的任务添加到宏任务队列中并标记为紧急,从而使它们具有比其他任务更高的优先级。这确保了与用户直接交互相关的操作具有更快的响应时间。
  • requestAnimationFrame函数,这个函数也有较高的优先级,因为它需要在下一次屏幕刷新之前进行处理以提供平滑的动画效果
  • setTimeout 或 setInterval 添加的回调函数。通常情况下,先添加到队列中的回调函数会优先得到处理。它们只能保证至少在指定的时间后才开始执行

请看示例:

function log(message) {
    const now = new Date();
    console.log(`[${now.getSeconds()}:${now.getMilliseconds()}] ${message}`);
}

setTimeout(() => {
    log('setTimeout callback');
}, 0);

requestAnimationFrame(() => {
    log('requestAnimationFrame callback');
});

document.addEventListener('click', () => {
    log('click event');
});

// 手动触发 click 事件
const event = new Event('click');
document.dispatchEvent(event);

/*
[46:280] click event
[46:299] setTimeout callback
[5:646] requestAnimationFrame callback
*/

无论测试多少次,click 总是最先输出。但是 requestAnimationFrame 就不一定先 setTimeout 输出,因为 requestAnimationFrame 有自己的节奏,只要不影响平滑的动画效果,即使在 setTimeout 后面也可能。

核心特性

Node.js 核心的特性是事件驱动(Event-driven)和非阻塞 I/O(Non-blocking I/O):

  • 事件驱动 - nodejs 中的异步操作基于事件,也就是说,当某个操作完成时,Node.js 会发出一个事件来通知你,然后你就可以通过注册事件的方式来执行回调函数。
  • 非阻塞 I/O - nodejs 执行一个 I/O 操作时,它不会像传统的同步阻塞 I/O 一样等待操作完成,而是会在操作的同时继续处理其他请求。这种方式可以避免 I/O 导致的阻塞,提高系统的吞吐量和响应能力。

Tip:两个特性有关系,但不是一个概念。比如可以说:基于事件驱动的非阻塞 I/O

Node.js 中的事件驱动和非阻塞 I/O 是基于事件循环实现的。

在 node 中,事件循环是一个持续不断的循环过程,不断地从事件队列中取出事件并处理,直到事件队列为空。具体来说,当 Node.js 遇到一个需要异步处理的 I/O 操作时,它不会等待操作完成后再执行下一步操作,而是将该操作放到事件队列中,并继续执行下一步。当操作完成后,Node.js 会将相应的回调函数也放到事件队列中,等待事件循环来处理。这样一来,Node.js 就可以同时处理多个请求,而且不会因为某一个操作的阻塞而影响整个应用程序的性能。

除了 I/O 操作之外,事件循环还可以用于处理定时器HTTP 请求数据库访问等各种类型的事件

Tip: 事件队列不仅包含宏任务队列微任务队列,还有维护着几个其他的队列,这些队列通过事件循环机制来实现异步非阻塞。其他队列有:

  • check 队列。check 队列用于存放 setImmediate() 的回调函数
  • I/O 观察器队列(watcher queue)
  • 关闭事件队列(close queue)

高并发和高性能

在 Node.js 中,高并发指的是系统能够处理高并发请求的能力。不会因为一个请求的处理而阻塞其他请求的执行,系统能够同时处理众多请求。高性能通常指的是它在处理大量并发请求时表现出的优异性能。

事件循环是 Node.js 实现高并发和高性能的核心机制之一。通过将计算密集型任务和 I/O 任务分离并采用异步执行,Node.js 能够充分利用 CPU 和内存资源,从而实现高性能和高并发。

没有事件循环,Node.js 就无法实现异步 I/O 和非阻塞式编程模型。在传统的阻塞式 I/O 模型中,一个 I/O 操作会一直等待数据返回,导致应用程序被阻塞,无法进行其他操作。而通过事件循环机制,Node.js 实现了异步 I/O,当一个 I/O 操作被触发后,Node.js 将其放入事件循环队列中,然后立即执行下一个任务,不必等待当前的 I/O 操作结束。当 I/O 操作完成时,Node.js 会将相应的回调函数添加到事件队列中等待执行。

node 中的事件循环

vs 浏览器中的事件循环

相同点:单个主线程、单个执行栈、有宏任务队列和微任务队列

不同点:

  • 实现不同。Node.js 是一款服务端运行时,而浏览器则用于页面和交互等,场景不同,所以实现方式不同。Node.js 中的事件循环机制是通过 libuv 库来实现,因为它具有跨平台性、高效性、多功能性(除了事件循环机制外,libuv 还提供了很多其他的系统功能和服务,能够满足 Node.js 在服务器端编程上的需要)等。
  • 一次事件循环不同。浏览器中的一次事件循环包括一个宏任务和相关所有微任务。在 node 中,一次事件循环包含6个阶段(下文会详细介绍)

虽然两者有不同,但它们有相同的设计目标:高效而可靠的方式处理异步任务(或者说:解决 JavaScript 异步编程问题)。

原理

一次事件循环包含以下 6 个阶段:

+--------------------------+
|                          |
|   timers                 | 计时器阶段:处理 setTimeout() 和 setInterval() 定时器的回调函数。
|                          |
+--------------------------+
|                          |
|   pending callbacks      | 待定回调阶段:用于处理系统级别的错误信息,例如 TCP 错误或者 DNS 解析异常。
|                          |
+--------------------------+
|                          |
|   idle, prepare          | 仅在内部使用,可以忽略不计。
|                          |
+--------------------------+
|                          |
|   poll                   | 轮询阶段:等待 I/O 事件(如网络请求或文件 I/O 等)的发生,然后执行对应的回调函数,并且会处理定时器相关的回调函数。
|                          |          如果没有任何 I/O 事件发生,此阶段可能会使事件循环阻塞。
+--------------------------+
|                          |
|   check                  | 检查阶段:处理 setImmediate() 的回调函数。check 的回调优先级比 setTimeout 高,比微任务要低
|                          |
+--------------------------+
|                          |
|   close callbacks        | 关闭回调阶段:处理一些关闭的回调函数,比如 socket.on('close')。
|                          |
+--------------------------+

这 6 个阶段执行顺序:

  1. 事件循环首先会进入 timers 阶段,执行所有超时时间到达的定时器相关的回调函数。
  2. 当 Node.js 执行完 timers 阶段后,就会进入到 pending callbacks 阶段。在这个阶段, Node.js 会执行一些系统级别的回调函数,这些回调函数一般都是由 Node.js 的内部模块触发的,而不是由 JavaScript 代码直接触发的。
  3. 然后进入 poll 阶段,等待 I/O 事件的发生,处理相关的回调函数。如果在此阶段确定没有任何 I/O 事件需要处理,那么事件循环会等待一定的时间,以防止 CPU 空转,这个时间会由系统自动设置或者手动在代码中指定。如果有定时器在此阶段需要处理,那么事件循环会回到 timers 阶段继续执行相应的回调函数。
  4. 接着进入 check 阶段,处理 setImmediate() 注册的回调函数。setImmediate() 的优先级比 timers 阶段要高。当事件循环进入 check 阶段时,如果发现事件队列中存在 setImmediate() 的回调函数,则会立即执行该回调函数而不是继续等待 timers 阶段的到来。
  5. 最后进入 close callbacks 阶段,处理一些关闭的回调函数。

事件循环的每个阶段都有对应的宏任务队列微任务队列。当一个阶段中的所有宏任务都执行完之后,事件循环会进入下一个阶段。在该阶段结束时,如果存在微任务,事件循环将会在开始下一个阶段之前执行所有的微任务。这样一来,无论在何时添加微任务,都能确保先执行所有的微任务,避免了某些任务的并发问题。如果我们在某个阶段中添加了多个微任务,那么它们会在该阶段结束时依次执行,直到所有微任务都被处理完成,才会进入下一个阶段的宏任务队列。

一次事件循环周期以清空6个阶段的宏任务队列和微任务队列来结束。

一次事件循环周期内,每个阶段是否可以执行多次。例如此时在 poll 阶段,这时 timers 阶段任务队列中有了回调函数,由于 timers 的优先级高于 poll,所以又回到 timers 阶段,执行完该阶段的宏任务和微任务后,在回到 poll 阶段。

总之,这 6 个阶段构成了 Node.js 的事件循环机制,确保了所有被注册的回调函数都能得到及时、准确的执行

Tip:当调用 setTimeout 方法时,如果超时时间还没到,则生成的定时器宏任务也不会立刻放入宏任务队列中,而是会被放入计时器队列中。计时器队列和延迟队列类似,都是由定时器宏任务组成的小根堆结构,每个定时器宏任务也对应着其到期时间以及对应的回调函数。当超时时间到达后,Node.js 会将该定时器宏任务从计时器队列中取出并放入宏任务队列中,等待事件循环去执行。

尽管事件循环的机制比较明确,但由于各种因素的影响,具体的执行顺序仍然难以精确预测。其顺序取决于当前事件队列中各个回调函数的执行情况、耗时以及系统各种资源的利用情况等多种因素。每次事件循环的顺序都不一定相同:

  • 例如,在事件循环的 poll 阶段中,如果存在大量耗时较长的 I/O 回调函数,则事件循环可能会在 poll 阶段中花费较长的时间。此时,即使定时器的超时时间到达了,事件循环也不会立即进入 timers 阶段,而是要先处理 poll 阶段中还未完成的任务。

Tip: setTimeout 在node 中最小是 1ms,在浏览器中是4ms

示例

console.log("start");

setTimeout(() => {
  console.log("first timeout callback");
}, 1);

setImmediate(() => {
  console.log("immediate callback");
});

process.nextTick(() => {
  console.log("next tick callback");
});

console.log("end");

运行10次node 输出如下:

start
end
next tick callback
first timeout callback
immediate callback

执行分析:

  • 先执行同步代码,输出 startend
  • setTimeout和setImmediate属于宏任务
  • process.nextTick 是微任务,输出 next tick callback

现在的难点是 setImmediate 和 setTimeout 的回调哪个先执行!

:在某些特殊情况下,timers 阶段和 check 阶段的任务可能会交错执行。这通常发生在以下两种情况下:

  • 当 timers 阶段中存在长时间运行的回调函数时(如一个耗时很长的 for 循环),会导致该阶段阻塞,影响事件循环的正常执行。在这种情况下,如果 check 阶段中有一些较短的回调函数需要执行,Node.js 可能会在 timers 阶段中间中断执行,并立即进入 check 阶段处理已经准备好的回调函数,然后再返回 timers 阶段继续执行剩余的回调函数。
  • 当注册了 setImmediate() 和 setTimeout() 回调函数并且它们被分别安排到不同的事件循环周期中执行时,这时候 setImmediate() 的回调函数可能会在 timers 阶段的回调函数之前被执行。这是因为 check 阶段的任务队列优先级比 timers 阶段的任务队列要高,所以在下一个循环周期的 check 阶段中,setImmediate() 的回调函数会被优先处理。

根据结果,我们推测:setImmediate 和 setTimeout 都进入了下一个循环周期,先执行 timers 阶段,在执行 check 阶段的回调。

Tip: 尽管 setImmediate 被称为 "immediate",但它并不保证会立刻执行。在 Node.js 的事件循环中,setImmediate() 的回调函数会被加入到 check 阶段的任务队列中,等到轮到 check 阶段时才会执行。

CPU 密集型场景

Node.js 不适合CPU 密集型场景。比如大量数学计算,可能会阻塞 Node.js 主线程。

比如一个 1 到 10亿求和的请求:

const http = require('http');

http.createServer((req, res) => {
  console.log('start');
  let sum = 0;
  for (let i = 1; i <= 1000000000; i++) {
    sum += i;
  }
  console.log('end');

  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end(sum.toString());
}).listen(3000);

console.log('server running at http://localhost:3000/');

通过curl 检测访问 http://localhost:3000/ 的时间,分别是 1.754s1.072s2.821s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    18    0    18    0     0     15      0 --:--:--  0:00:01 --:--:--    15500000000067109000

real    0m1.754s
user    0m0.000s
sys     0m0.078s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    18    0    18    0     0     20      0 --:--:-- --:--:-- --:--:--    21500000000067109000

real    0m1.072s
user    0m0.015s
sys     0m0.093s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    18    0    18    0     0      6      0 --:--:--  0:00:02 --:--:--     6500000000067109000

real    0m2.821s
user    0m0.031s
sys     0m0.077s

接着用node 内置的 cluster 模块将计算工作分配到4个子进程中,访问速度大幅度提升。

const http = require('http');
const cluster = require('cluster');

if (cluster.isMaster) {
  // 计算工作分配到4个子进程中
  const numCPUs = require('os').cpus().length;
  const range = 1000000000;
  const rangePerCore = Math.ceil(range / numCPUs);
  let endIndex = 0;
  let sum = 0;

  for (let i = 0; i < numCPUs; i++) {
    const worker = cluster.fork();
    worker.on('message', function({ endIndex, result }) {
      sum += result;
      if (endIndex === range) {
        console.log(sum);
        // 启动 Web 服务器,在主进程中处理请求
        http.createServer((req, res) => {
          res.statusCode = 200;
          res.setHeader('Content-Type', 'text/plain');
          res.end(`The sum is ${sum}\n`);
        }).listen(3000, () => {
          console.log(`Server running at http://localhost:3000/`);
        });
      }
    });
    worker.send({ startIndex: endIndex + 1, endIndex: endIndex + rangePerCore });
    endIndex += rangePerCore;
  }
} else {
  process.on('message', function({ startIndex, endIndex }) {
    let sum = 0;
    for (let i = startIndex; i <= endIndex; i++) {
      sum += i;
    }
    process.send({ endIndex, result: sum });
  });
}

访问时长分别是:0.230s0.216s0.205s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30  100    30    0     0   2354      0 --:--:-- --:--:-- --:--:--  4285The sum is 500000000098792260


real    0m0.230s
user    0m0.000s
sys     0m0.109s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30  100    30    0     0   2212      0 --:--:-- --:--:-- --:--:--  3750The sum is 500000000098792260


real    0m0.216s
user    0m0.000s
sys     0m0.078s

Administrator@ MINGW64 /e/ (master)
$ time curl http://localhost:3000/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    30  100    30    0     0   2545      0 --:--:-- --:--:-- --:--:--  6000The sum is 500000000098792260


real    0m0.205s
user    0m0.000s
sys     0m0.078s

其他

pm2 的一个局限性

假如一个请求得花费2秒(1 到 10亿之和),使用 pm2 也不能减小请求时间。

pm2能做的是:比如一个 node 应用单核(1个cpu内核)可以支持一千个并发请求,现在并发四千个请求,由于超出能力,请求响应会变慢。现在通过 Pm2 在四核服务器中启动4个node应用,之前还存在负载均衡,这样就可以支持四千个并发请求。

Tip:pm2的介绍请看这里

单线程

Node.js 是单线程的,这意味着所有事件循环(Event Loop)和 I/O 操作都在一个主线程中运行。所以说,Node.js 中只存在一个事件循环和一个执行上下文栈。

不过,Node.js 的实现并不简单粗暴。它通过使用非阻塞 I/O、异步编程以及事件驱动机制,让单线程可以支持高并发处理大量的 I/O 操作。Node.js 底层采用的是 libuv 库来实现异步 I/O 模型,该库在底层会使用 libev 和 libeio 等多种事件驱动框架来实现对底层 I/O 系统调用的封装,从而让单线程可以同时处理多个 I/O 任务,避免了线程切换的开销,提高了应用程序的性能。

此外,在 Node.js 版本 10.5.0 之后,Node.js 引入了 worker_threads 模块,支持通过创建子线程的方式来实现多线程。worker_threads 模块提供了一套 API,使得开发者可以方便地创建和管理多个子线程,并利用多线程来加速处理计算密集型任务等场景。

总之,Node.js 是单线程的,但同时也通过采用异步 I/O 模型、事件驱动机制和多线程等技术手段,来支持高并发、高性能的应用程序开发。文章来源地址https://www.toymoban.com/news/detail-455087.html

到了这里,关于前端学习 node 快速入门 系列 —— 事件循环的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 谈一谈浏览器与Node.js中的JavaScript事件循环,宏任务与微任务机制

    JavaScript是一个单线程非阻塞的脚本语言。这代表代码是执行在一个主线程上面的。但是JavaScript中有很多耗时的异步操作,例如AJAX,setTimeout等等;也有很多事件,例如用户触发的点击事件,鼠标事件等等。这些异步操作并不会阻塞我们代码的执行。例如: 可以看到,上述代

    2024年02月12日
    浏览(49)
  • AJAX入门到实战,学习前端框架前必会的(ajax+node.js+webpack+git)(七)

    实操: server.js utils/lib/index.js utils/package.json 从别处(网上、其他人手中)拿到写好的项目,一般不携带node_modules文件夹(所占存储空间大) 但有package.json文件,里面记载了当前项目下载过的包 还有package-lock.json文件,固定软件包的版本 导入模块/包,除了自己创建的模块、包

    2024年01月22日
    浏览(67)
  • [前端系列第3弹]JS入门教程:从零开始学习JavaScript

    本文将带领大家,从零开始学习JavaScript,fighting~ 目录 一、JavaScript简介 二、变量和数据类型 三、注释和分号 四、算术运算符 五、表达式和语句 六、代码块和作用域 七、函数(最重要)          JavaScript(简称JS)是一种运行在浏览器中的脚本语言,它可以让网页变得

    2024年02月13日
    浏览(65)
  • AJAX入门到实战,学习前端框架前必会的(ajax+node.js+webpack+git)(五)—— 项目-新闻头条-数据管理平台-ajax综合案例前端

    愿许秋风知我意,解我心中意难平。 推荐使用, 每个程序员都有自己的管理方式。 HTML结构: 1.为什么要提取公共前缀地址(基地址),因为公司业务可能会更换服务器,如果你不想一条一条地修改请求地址的话。 后续使用axios时,url不需要再写前缀。 2.请求成功与失败 成

    2024年01月25日
    浏览(52)
  • [前端系列第2弹]CSS入门教程:从零开始学习Web页面的样式和布局

    在这篇文章中,我将介绍CSS的基本概念、语法、选择器、属性和值,以及如何使用它们来定义Web页面的外观和布局。还将给一些简单而实用的例子,可以跟着我一步一步地编写自己的CSS样式表。 目录 一、什么是CSS 二、CSS的语法 三、CSS的选择器 四、CSS的属性和值 (一)颜色

    2024年02月13日
    浏览(59)
  • Redis系列学习文章分享---第一篇(Redis快速入门之初始Redis--NoSql+安装redis+客户端+常用命令)

    欢迎小伙伴交流学习,这是第一篇(Redis快速入门之初始Redis–NoSql+安装redis+客户端+常用命令),后续持续更新 Redis是一种键值型的NoSql数据库,这里有两个: 键值型 NoSql 其中 键值型 ,是指Redis中存储的数据都是以key、value对的形式存储,而value的形式多种多样,可以

    2024年02月20日
    浏览(57)
  • vue快速入门(十六)事件修饰符

    注释很详细,直接上代码 上一篇 新增内容 事件修饰符之阻止冒泡 事件修饰符之阻止默认行为 源码 效果演示 下一篇

    2024年04月13日
    浏览(47)
  • Python Qt6快速入门-事件处理

    Qt GUI应用程序的核心是 QApplication 类。 每个GUI应用程序都需要一个并且只有一个 QApplication 对象才能运行。 该对象包含应用程序的事件循环——控制所有用户与 GUI 交互的核心循环。

    2024年02月15日
    浏览(43)
  • 前端node.js入门

    (创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 Node.js 入门  什么是 Node.js? 什么是前端工程化?   Node.js 为何能执行 JS?  fs 模块 - 读写文件  path 模块 - 路径处理 URL 中的端口号  常见的服务程序  Node.js 模块化 什

    2024年02月17日
    浏览(48)
  • 【前端面试手撕题】事件委托、数组去重、合法URL、快速排序、全排列

    描述 请补全JavaScript代码,要求如下: 给\\\"ul\\\"标签添加点击事件 当点击某\\\"li\\\"标签时,该标签内容拼接\\\".“符号。如:某\\\"li\\\"标签被点击时,该标签内容为”…\\\" 注意: 必须使用DOM0级标准事件(onclick) 描述 请补全JavaScript代码,要求去除数组参数中的重复数字项并返回该数组。

    2024年02月14日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包