什么是事件循环?什么是信息队列?什么是任务队列?

前言 一、事件循环(Event Loop):JavaScript 采用单线程模型,即同一时间只能执行一个任务。为了处理多个任务,JavaScript 使用了事件循环机制。事件循环会不断地从任务队列中取出任务来执行,执行完一个任务后再取出下一个任务,如此循环往复。当所有任务都执行完毕后,事件循环会等待新的任务加入队列。
JavaScipt 中的事件循环(event loop),以及微任务 和宏任务的概念

说事件循环(event loop)之前先要搞清楚几个问题。

1. js为什么是单线程的?

试想一下,如果js不是单线程的,同时有两个方法作用dom,一个删除,一个修改,那么这时候浏览器该听谁的?由此js同一时间只能执行一个功能

js的script标签会导致渲染的过程阻塞吗?

答案:会的,因为js是单线程只能一个一个的去执行。

如何解决js在渲染的过程的阻塞问题?

答:

1.可以使用内联样式

js 复制代码
<body>
    <div class="a">
        <p>11</p>
        <span>22</span>
        <h2></h2>
    </div>
    <script>
        var oDiv = document.querySelector('.a');
        var oSpan = document.querySelector('span');

        var oStrong = document.createElement('strong');
        oStrong.innerHTML = '888'

        oDiv.replaceChild(oStrong, oSpan) //用oStrong替换oSpan
    </script>
</body>

2.外联样式引入js要加async或者defer,否则会导致js的阻塞

js 复制代码
<!-- 使用async属性 -->  
<script async src="index.js"></script>  
  
<!-- 使用defer属性 -->  
<script defer src="index.js"></script>

async和defer这两者有什么区别?

asyncdefer属性在HTML的<script>标签中都用于异步加载JavaScript,但它们之间存在一些重要的区别。

  1. 执行时机async属性的脚本会立即下载,并在下载完成后立即执行。这意味着脚本的执行可能会在主线程上阻塞,从而影响页面的渲染。而defer属性的脚本会等到整个HTML文档解析完成后才执行。因此,使用defer可以确保脚本在执行前,DOM已经完全加载和解析完成。
  2. 加载顺序 :对于async属性的脚本,没有明确的加载顺序,可能会并行加载多个脚本。而defer属性的脚本会按照它们在HTML文档中出现的顺序进行加载和执行。
  3. 兼容性async属性是HTML5中引入的,因此在一些较旧的浏览器中可能不被支持。而defer属性在所有主流浏览器中都有良好的兼容性。

总结:根据这些区别,你可以根据需要选择使用asyncdefer属性。如果你希望脚本尽快下载并执行,并且不关心脚本的加载顺序,可以选择使用async属性。如果你希望脚本在DOM完全加载和解析完成后执行,并且需要按照一定的顺序加载脚本,那么使用defer属性是更好的选择。

想要执行快一点就用async,慢一点就用defer

事件循环也是遵循的宏任务与微任务的,那什么是宏任务?什么是微任务?

直接举例子:

js 复制代码
<script>
    console.log('同步任务1'); // 宏任务  
    setTimeout(function () {
        console.log('异步任务1'); // 宏任务  
    }, 0);

    Promise.resolve().then(function () {
        console.log('同步任务2'); // 微任务  
    }).then(function () {
        console.log('同步任务3'); // 微任务  
    });

    console.log('同步任务4'); // 宏任务
</script>

一、宏任务(Macro Task)

宏任务是指由 JavaScript 主线程执行的任务,它包括但不限于以下情况:

  • 包括script整个脚本

  • 浏览器事件(如 click、mouseover 等)

  • 定时器任务(如 setTimeout 和 setInterval)

  • 页面渲染(如 回流或重绘)

  • 事件回调(如 I/O、点击事件等)

  • 网络请求 (如 XMLHttpRequest 和 fetch 等)

宏任务通常独立于当前任务,并按顺序排队执行。以下是一些常见的代码示例来说明宏任务的概念和用法:

示例 1: 使用事件监听器创建宏任务

javascript 复制代码
javascript
复制代码
// 事件监听器创建宏任务
const button = document.querySelector("button");

button.addEventListener("click", () => {
  console.log("Button clicked");
});

console.log("Waiting for button click...");

解释:在这个示例中,等待按钮点击的语句是同步任务,而当按钮被点击时,事件回调函数会作为宏任务被执行。

输出结果为

示例 2: 使用定时器创建宏任务

javascript 复制代码
javascript
复制代码
// 定时器任务
console.log("Start");
setTimeout(() => {
  console.log("In Timeout");
}, 2000);
console.log("End");

// 事件监听器创建宏任务
const button = document.querySelector("button");

button.addEventListener("click", () => {
  console.log("Button clicked");
});

console.log("Waiting for button click...");

解释:在这个示例中,打印 "Start" 和 "End" 的语句是同步任务,而通过 setTimeout 创建的回调函数被作为宏任务,在 2000 毫秒后才执行,所以在执行宏任务之前会先输出同步任务的结果。

输出结果为

示例 3: 页面渲染

javascript 复制代码
javascript
复制代码
console.log("Start");

// 修改页面样式
document.body.style.backgroundColor = "red";

console.log("End");

解释:在这个示例中,修改页面样式是一个宏任务,当样式被修改后,浏览器会执行重新渲染页面的操作。

输出结果为

示例 4: 使用 XMLHttpRequest 发起网络请求

javascript 复制代码
javascript
复制代码
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data");
xhr.addEventListener("load", () => {
  console.log("Request completed");
});
xhr.send();

console.log("Waiting for request to complete...");

解释:在这个示例中,使用 XMLHttpRequest 发起网络请求是一个宏任务。当请求完成后,load 事件回调函数会作为宏任务被执行。

输出结果为

总结: 宏任务的使用广泛,包括定时器任务、网络请求、事件监听器等。理解宏任务的概念和用法可以帮助我们正确处理 JavaScript 中的异步操作,并合理安排任务的执行顺序,以提高应用的性能和用户体验。

二、 微任务(Micro Task)

微任务是指由 JavaScript 引擎执行的任务,它在宏任务之后执行,但在下一次渲染之前执行。微任务通常是由宏任务中的某个特定任务触发的,并立即执行。常见的微任务有:

  • Promise 的回调函数
  • Async/Await 函数
  • MutationObserver 的回调函数
  • process.nextTick(Node.js 环境下)

示例 1:微任务的执行顺序

还是沿用本文章开头所使用的代码示例,说明微任务的执行顺序:

javascript 复制代码
javascript
复制代码
console.log("1");

setTimeout(() => {
  console.log("2");
  Promise.resolve().then(() => console.log("3"));
});

Promise.resolve().then(() => console.log("4"));

console.log("5");

解释

  • 在第一个宏任务中,同步的打印语句 15 首先执行。
  • 然后,第一个宏任务中使用 setTimeout 创建了一个回调函数,它被添加到宏任务队列中等待执行。
  • 在第一个宏任务执行结束前,微任务队列中的回调函数执行。Promise.resolve().then(() => console.log('4')) 的回调函数首先被添加到微任务队列中,因此会在 2 之前执行,打印 4
  • 当第一个宏任务任务队列为空时,开始执行第二个宏任务,打印 2
  • 然后,Promise 的回调函数 Promise.resolve().then(() => console.log('3')) 会被添加到微任务队列中等待执行。
  • 在本轮事件循环中,微任务队列中的任务会按序执行,因此打印 3

输出结果为

结论

微任务是 JavaScript 中处理异步操作的一种机制,它通过及时响应并在当前任务结束后立即执行,有助于编写更高效和灵活的异步代码。了解微任务的概念和用法能够帮助我们更好地利用异步特性,提升代码的性能和用户体验。

三、宏任务与微任务的区别

宏任务和微任务主要在两个方面有所区别:执行时机调度机制

1. 执行时机

  • 宏任务:宏任务是由 JavaScript 引擎在执行栈(执行同步任务)和任务队列中的任务之间切换时执行的。宏任务在下一个宏任务之前执行,并按照宏任务队列的顺序执行。
  • 微任务:微任务是在宏任务执行结束,下一个宏任务开始之前执行的任务。微任务在当前宏任务中执行完后立即执行,并按照微任务队列的顺序执行。

宏任务在主线程上执行,而微任务在宏任务执行完毕之后执行,即在下一轮事件循环的步骤中执行,这也是为什么微任务会在宏任务之前执行的原因。

2. 调度机制

  • 宏任务:宏任务由 JavaScript 引擎的任务调度器调度执行。当主线程执行完当前宏任务后,会检查是否存在微任务,如果存在,则会执行所有微任务,然后选择下一个宏任务执行。
  • 微任务:微任务同样由 JavaScript 引擎的任务调度器调度执行。当微任务队列中存在微任务时,会依次执行微任务,直到微任务队列为空。

宏任务使用先进先出的调度机制,即它们按照任务的顺序排列,并按顺序执行。

而微任务则使用一个任务队列(microtask queue)进行调度,当某个宏任务执行完毕后,会立即将所有的微任务添加到任务队列中,并按照先进先出的顺序依次执行。

宏任务和微任务的区别在于它们的执行机制和调度机制。宏任务在下一个宏任务执行之前执行,而微任务在当前宏任务结束后立即执行。微任务优先级高于宏任务,因此在同一轮事件循环中,微任务会优先执行。了解宏任务和微任务的区别对于编写高效的异步 JavaScript 代码非常重要。

仅凭文字描述要理解这两个机制并不容易,因此通过下面的事件循环机制的的说明消化一下这两个机制。

四、事件循环机制(Event Loop)

如上图,当同步任务执行完毕后,就会执行所有的宏任务,宏任务执行完成后,会判断是否有可执行的微任务;如果有,则执行微任务,完成后,执行宏任务;如果没有,则执行新的宏任务,形成事件循环。

事件循环机制的整体执行流程如下

  1. 执行同步任务:JavaScript 代码从上到下逐行执行同步任务,直到遇到第一个异步任务。
  2. 处理微任务:请注意,当遇到一个微任务时,将其添加到微任务队列中,而不是立即执行。
  3. 执行宏任务:当同步任务执行完毕或遇到第一个微任务时,执行宏任务队列中的第一个任务。执行宏任务时,如果遇到嵌套的微任务,也会将其添加到微任务队列中等待执行。
  4. 执行微任务:执行完一个宏任务后,立即处理微任务队列中的所有任务,按照顺序依次执行。
  5. 重复上述步骤:不断地循环执行上述步骤,直到任务队列为空。

需要注意的是:微任务比宏任务优先级要高。

在同一个任务中,如果既有微任务又有宏任务,那么微任务会先执行完毕。

在不同的任务中,宏任务的执行优先级要高于微任务,因此在一个宏任务执行完毕后,它才会执行下一个宏任务和微任务队列中的任务。

总结:事件循环的执行顺序:先执行同步任务,再执行宏任务,下一步询问是否执行完毕宏任务了?如果没有就继续执行完宏任务,再执行微任务,微任务执行完后就退出。

--------------------------------------------------------

什么是信息队列?

信息队列是在消息的传输过程中保存消息的容器。

消息队列简介

消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。

消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。

"消息"是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象。

消息被发送到队列中。"消息队列"是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。

js发布订阅原理,代码解析

发布订阅

1.发布订阅原理

2.js代码实现

3.js代码实现原理

发布订阅原理

1.将要处理的时间放入时间队列中存储(订阅)

2.将事件队列中存储的事件,按照要求进行统一的执行(发布)

js代码实现

js 复制代码
var dep= {};
    dep.list= [];     //此数组用来存放订阅事件 
    //定义listen监听函数 将事件根据 key值来进行区分,存储 (将一类的key的事件存储在一起)
    dep.listen = function(key , fn){
        if(!this.list[key]){
            this.list[key]=[]
        }
        //将一类的key的事件存储在一起 通过key的值进行分类,将相同key的事件存储在一个单独的数组中
        this.list[key].push(fn);
    }
    //发布事件
    dep.trigger = function(){
        var key = Array.prototype.shift.call(arguments) //取出实际参数的第一个参数 (key值)
        var fns = this.list[key];                       //根据key值,获取队列中对应的事件数组
        if(! fns || fns.length==0){
            return
        }
        for(var i=0,fn;fn = fns[i++];){
            fn.apply(this,arguments)                    //循环执行数组中的事件
        }
    }
    //测试数据  进行订阅
    dep.listen('red',function(size){
        console.log('小红颜色的尺寸'+size);
    })
    dep.listen('red',function(size){
        console.log('小红颜色的尺寸'+size);
    })
    dep.listen('block',function(size){
        console.log('小清颜色的尺寸'+size);
    })
    //进行发布 仅发布 key值为red的队列
    dep.trigger('red',37)

js代码实现原理

1.队列通过数组dep实现,

2.每个key对应的一类js事件,每一个key都对应着单独的队列(数组实现)

3.通过key值的判断,取出对应的事件队列,循环执行队列中的事件

发布订阅模式的基本原理

JavaScript 发布订阅模式的基本原理是:有一个主题对象,该对象维护一个订阅者列表,当主题对象发生变化时,主题对象会遍历订阅者列表,调用每个订阅者的更新方法,通知订阅者进行相应的处理。

在 JavaScript 中,可以通过自定义事件和回调函数实现发布订阅模式。主题对象维护一个事件列表,每个事件对应一个或多个回调函数。当主题对象发生变化时,主题对象会触发相应的事件,调用该事件对应的所有回调函数,通知订阅者进行相应的处理。

以下是一个发布订阅模式的简单代码示例:

kotlin 复制代码
kotlin
复制代码
// 消息代理
class MessageBroker {
  constructor() {
    this.subscriptions = {};
  }

  subscribe(topic, callback) {
    if (!this.subscriptions[topic]) {
      this.subscriptions[topic] = [];
    }
    this.subscriptions[topic].push(callback);
  }

  publish(topic, data) {
    if (!this.subscriptions[topic]) {
      return;
    }
    this.subscriptions[topic].forEach((callback) => {
      callback(data);
    });
  }
}

// 发布者
class Publisher {
  constructor(broker) {
    this.broker = broker;
  }

  publishMessage(topic, message) {
    this.broker.publish(topic, message);
  }
}

// 订阅者
class Subscriber {
  constructor(broker, name) {
    this.broker = broker;
    this.name = name;
  }

  subscribeToTopic(topic) {
    this.broker.subscribe(topic, (data) => {
      console.log(`Subscriber ${this.name} received message: ${data}`);
    });
  }
}

// 使用示例
const broker = new MessageBroker();
const publisher = new Publisher(broker);

const subscriber1 = new Subscriber(broker, 'Alice');
const subscriber2 = new Subscriber(broker, 'Bob');

subscriber1.subscribeToTopic('news');
subscriber2.subscribeToTopic('weather');

publisher.publishMessage('news', 'Breaking news: the sky is blue');
publisher.publishMessage('weather', 'It will be sunny tomorrow');

发布订阅模式的应用场景

下面我们来举几个常见的发布订阅模式的应用场景和代码示例。

生产者 & 消费者关系

发布订阅模式适用于需要解耦生产者和消费者之间关系的场景,生产者只需要发布消息,而不需要关心哪些消费者会收到消息。消费者可以订阅自己感兴趣的主题,只有在该主题上有新的消息时才会收到通知。这样可以提高代码的灵活性和可维护性。

以下是一个基于发布订阅模式的具体场景和代码示例:

假设我们正在开发一个在线商城,需要实时更新商品价格和库存信息。我们可以使用发布订阅模式,在商品库存和价格发生变化时,自动向所有关注该商品的客户端推送更新。

kotlin 复制代码
kotlin
复制代码
// 消息代理
class MessageBroker {
  constructor() {
    this.subscriptions = {};
  }

  subscribe(topic, callback) {
    if (!this.subscriptions[topic]) {
      this.subscriptions[topic] = [];
    }
    this.subscriptions[topic].push(callback);
  }

  publish(topic, data) {
    if (!this.subscriptions[topic]) {
      return;
    }
    this.subscriptions[topic].forEach((callback) => {
      callback(data);
    });
  }
}

// 商品类
class Product {
  constructor(name, price, stock) {
    this.name = name;
    this.price = price;
    this.stock = stock;
  }

  setPrice(newPrice) {
    this.price = newPrice;
    this.broker.publish(`product/${this.name}/price`, this.price);
  }

  setStock(newStock) {
    this.stock = newStock;
    this.broker.publish(`product/${this.name}/stock`, this.stock);
  }

  setBroker(broker) {
    this.broker = broker;
  }
}

// 客户端类
class Client {
  constructor(name) {
    this.name = name;
  }

  subscribeToProduct(product) {
    product.broker.subscribe(`product/${product.name}/price`, (data) => {
      console.log(`Client ${this.name} received new price for ${product.name}: ${data}`);
    });
    product.broker.subscribe(`product/${product.name}/stock`, (data) => {
      console.log(`Client ${this.name} received new stock for ${product.name}: ${data}`);
    });
  }
}

// 使用示例
const broker = new MessageBroker();

const product1 = new Product('Product 1', 100, 10);
const product2 = new Product('Product 2', 200, 20);

product1.setBroker(broker);
product2.setBroker(broker);

const client1 = new Client('Alice');
const client2 = new Client('Bob');

client1.subscribeToProduct(product1);
client2.subscribeToProduct(product2);

product1.setPrice(120);
product1.setStock(5);

product2.setPrice(180);
product2.setStock(10);

在上面的示例中,我们创建了一个消息代理 MessageBroker,以及两个商品 Product 和两个客户端 Client。商品类中的 setPrice 和 setStock 方法会在价格和库存发生变化时向代理发送消息,客户端类中的 subscribeToProduct 方法会订阅指定商品的价格和库存主题,并在收到消息时打印出来。在这个示例中,我们使用 console.log 来模拟消息的输出。

消息队列

以下是一个简单的消息队列场景的代码示例,实现了消息的生产和消费:

javascript 复制代码
javascript
复制代码
class MessageQueue {
  constructor() {
    this.subscriptions = {};
    this.queue = [];
  }

  subscribe(topic, callback) {
    if (!this.subscriptions[topic]) {
      this.subscriptions[topic] = [];
    }
    this.subscriptions[topic].push(callback);
  }

  publish(topic, data) {
    if (!this.subscriptions[topic]) {
      return;
    }
    this.subscriptions[topic].forEach((callback) => {
      callback(data);
    });
  }

  enqueue(message) {
    this.queue.push(message);
  }

  dequeue() {
    return this.queue.shift();
  }

  process() {
    const message = this.dequeue();
    if (message) {
      this.publish(message.topic, message.data);
    }
  }
}

// 生产者
const producer = (queue) => {
  setInterval(() => {
    const message = { topic: 'test', data: new Date().toISOString() };
    queue.enqueue(message);
    console.log(`Produced message: ${JSON.stringify(message)}`);
  }, 1000);
};

// 消费者
const consumer = (queue) => {
  setInterval(() => {
    queue.process();
  }, 500);
};

// 使用示例
const queue = new MessageQueue();

queue.subscribe('test', (data) => {
  console.log(`Consumed message: ${data}`);
});

producer(queue);
consumer(queue);

在上面的代码示例中,我们定义了一个 MessageQueue 类,实现了基本的消息队列功能,包括订阅、发布、入队、出队和处理。生产者通过调用 enqueue 方法将消息入队,消费者通过调用 process 方法从队列中取出消息并进行处理。在使用示例中,我们创建了一个消息队列,生产者每隔一秒钟向队列中添加一个消息,消息的内容是当前时间。消费者每隔半秒钟从队列中取出一个消息并输出到控制台。

当我们运行上面的代码示例时,可以看到生产者不断地向队列中添加消息,消费者不断地从队列中取出消息并输出到控制台,实现了一个基本的消息队列。

自定义事件系统

在一些大型的 Web 应用中,可能需要实现自定义的事件系统,以便进行组件间通信和数据交互。这时可以使用 JavaScript 发布订阅模式,将「发布-订阅中心」作为主题对象,将事件监听器作为订阅者,实现自定义事件系统。

示例代码:

javascript 复制代码
javascript
复制代码
class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  off(event, listener) {
    if (!this.events[event]) {
      return;
    }
    const index = this.events[event].indexOf(listener);
    if (index >= 0) {
      this.events[event].splice(index, 1);
    }
  }

  emit(event, ...args) {
    if (!this.events[event]) {
      return;
    }
    this.events[event].forEach((listener) => {
      listener.apply(this, args);
    });
  }
}

// 使用示例
const emitter = new EventEmitter();

const listener1 = (msg) => {
  console.log(`Listener 1 received: ${msg}`);
};

const listener2 = (msg) => {
  console.log(`Listener 2 received: ${msg}`);
};

emitter.on('test', listener1);
emitter.on('test', listener2);

emitter.emit('test', 'test message 1');
// Output:
// Listener 1 received: test message 1
// Listener 2 received: test message 1

emitter.off('test', listener1);

emitter.emit('test', 'test message 2');
// Output:
// Listener 2 received: test message 2

结语

本文介绍了 JavaScript 发布订阅模式的基本原理、应用场景以及各场景的代码示例。在实际开发中,发布订阅模式可以用于解耦对象之间的依赖关系,提高代码的可维护性和可扩展性。不同的实现方式适用于不同的场景和框架,开发者可以根据需要选择合适的实现方式。同时,使用发布订阅模式也需要注意防止事件泄漏和内存泄漏等问题,保证代码的性能和稳定性。希望本文能够帮助读者更深入地了解 JavaScript 发布订阅模式,提高代码的质量和效率。

-------------------------------------------------------

什么是任务队列?

在前端开发中,任务队列是一个用于管理和执行异步任务的机制。任务队列是一种先进先出的数据结构,用于存储待执行的任务。这些任务可以是异步操作,如网络请求、定时器回调等。

任务队列中的任务按照它们被添加到队列的顺序排列。当主线程执行完一个任务后,它会查看任务队列是否有待执行的任务。如果有,它会从队列中取出一个任务并开始执行。这个过程会不断重复,直到任务队列为空。

在前端开发中,异步操作是常见的需求,因为这样可以避免阻塞主线程并提高应用程序的性能和响应性。通过使用任务队列,我们可以将异步操作组织起来,按照一定的顺序执行,并在适当的时候处理结果。

常见的任务队列实现包括Web Workers、WebSockets、Fetch API等。这些实现提供了不同的功能和特性,可以满足不同的异步操作需求。

如何使用任务队列来管理异步操作,以实现商品添加到购物车的功能?以下举例:

js 复制代码
<script>
    // 假设你已经获取到了商品数据,并将其存储在变量中  
    const product = {
        id: 1,
        name: "Product 1",
        price: 100
    };

    // 创建一个任务队列  
    const taskQueue = [];

    // 添加商品到购物车的函数  
    function addToCart(product) {
        // 将任务添加到任务队列中  
        taskQueue.push(product);

        // 执行任务队列中的任务  
        processQueue();
    }

    // 处理任务队列中的任务的函数  
    function processQueue() {
        if (taskQueue.length === 0) {
            // 任务队列为空,停止处理  
            return;
        }

        // 从任务队列中取出一个任务  
        const currentTask = taskQueue.shift();

        // 发送网络请求将商品添加到购物车中  
        fetch('/api/cart', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ productId: currentTask.id })
        })
            .then(response => response.json())
            .then(data => {
                // 处理服务器返回的数据,更新UI等操作...  
                console.log('商品添加成功!');
            })
            .catch(error => {
                // 处理网络请求错误等操作...  
                console.error('添加商品失败:', error);
            });
    }

    // 调用函数将商品添加到购物车中  
    addToCart(product);
</script>

总结:任务队列就是用于存储待执行的任务。这些任务可以是异步操作,如网络请求、定时器回调等。

相关推荐
passerby606115 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅16 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment16 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅16 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端
爱敲代码的小鱼16 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte16 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc