深入理解 Promise,看看你会几道题

1. Promise A+规范的基本概念

Promise是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一

这套规范最早诞生于前端社区,规范名称为Promise A+

该规范出现后,立即得到了很多开发者的响应

Promise A+ 规定:

  1. 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在JS中应该表现为一个对象 ,该对象称之为Promise对象,也叫做任务对象

  2. 每个任务对象,都应该有两个阶段、三个状态

    根据常理,它们之间存在以下逻辑:

    • 任务总是从未决阶段变到已决阶段,无法逆行
    • 任务总是从挂起状态变到完成或失败状态,无法逆行
    • 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变
  3. 挂起->完成,称之为resolve挂起->失败称之为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。

  4. 可以针对任务进行后续处理,针对完成状态的后续处理称之为onFulfilled,针对失败的后续处理称之为onRejected

2. 创建Promise

js 复制代码
// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
  // 任务的具体执行流程,该函数会立即被执行
  // 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
  // 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});

pro.then(
  (data) => {
    // onFulfilled 函数,当任务完成后,会自动运行该函数,data为任务完成的相关数据
  },
  (reason) => {
    // onRejected 函数,当任务失败后,会自动运行该函数,reason为任务失败的相关原因
  }
);

3. 针对Promise进行后续处理

下面任务的最终状态是什么,相关数据或失败原因是什么,最终输出是什么?

js 复制代码
const pro1 = new Promise((resolve, reject) => {
    console.log('开始任务');
    resolve(1);
    reject(2);
    resolve(3);
    console.log('结束任务');
})
console.log(pro1);

const pro2 = new Promise((resolve, reject) => {
    console.log('开始任务');
    resolve(1);
    resolve(2);
    console.log('结束任务');
})
console.log(pro2);
  • 这里有一点需要注意,promise的状态一旦确定下来之后,是不会发生变化的,但后续的代码还会继续执行。

4.Promise链式调用

  1. then方法必须会返回一个新的promise, 可以理解为 后续处理也是一个任务

  2. 新任务的状态取决于后续处理:

    • 若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的状态。
    • 若有后续处理但未执行,新任务挂起。
    • 若后续处理执行了,则根据后续处理的情况确定新任务的状态。
      • 后续处理执行无错,新任务的状态未完成,数据为后续处理的返回值。
      • 后续处理执行有错,新任务的状态为失败,数据为异常数据。
      • 后续执行后返回时一个任务对象,新任务的状态和数据与该任务对象一致。

为了更直观的练习链式调用,来看几个题目:

js 复制代码
// 下面代码的输出结果是什么
const pro1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const pro2 = pro1.then((data) => {
  console.log(data);
  return data + 1;
});

const pro3 = pro2.then((data) => {
  console.log(data);
});

console.log(pro1, pro2, pro3);

setTimeout(() => {
  console.log(pro1, pro2, pro3);
}, 2000);
  • 结果是先输出三个padding状态的promise, 然后再打印 1 2 undefined,为啥?
  • 首先来看第一个起始任务pro1, 等待1秒之后成功,pro1处于padding状态,接着产生了第二个任务pro2,pro2是pro1的后续处理,所以毫无疑问也是padding状态,里面的任务先不会执行,同样的pro3也是padding挂起状态。
  • 接着继续执行pro1里面的代码,1秒钟到了后,pro1变成了fulfilled 数据是1;继续执行pro2里面的代码,打印了pro1传递过来的1,pro2变成了fulfilled data+1 返回一个2;pro3打印pro2传递过来的2,pro3状态变成fufilled, 没有返回结果,所以打印undefined;

再来一道,稍微改一下,把pro2的then改成catch:

js 复制代码
const pro1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const pro2 = pro1.catch((data) => {
  console.log(data);
  return data + 1;
});

const pro3 = pro2.then((data) => {
  console.log(data);
});

console.log(pro1, pro2, pro3);

setTimeout(() => {
  console.log(pro1, pro2, pro3);
}, 2000);
  • 结果是先输出三个padding状态的promise, 然后再打印 1 1 undefined
  • 起始任务pro1, 等待1秒之后成功,pro1处于padding状态,打印fulfilled 1;接着产生了第二个任务pro2,但是这里pro2并没有对pro1的成功做处理,而是用catch, pro2的跟pro1一样处于padding状态,打印fulfilled 1;pro3针对pro2的成功做了处理,输出完成的数据1,pro3的运行状态取决于pro2的运行结果,运行过程没有报错,padding状态,并没有返回结果,打印fulfilled undefined。
  • 最后打印 3个padding 1 fufilled 1 fufilled 1 fufilled undefined

再来改一下,给pro2抛一个错

js 复制代码
const pro1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const pro2 = pro1.then((data) => {
  throw 3;
  return data + 1;
});

const pro3 = pro2.then((data) => {
  console.log(data);
});

console.log(pro1, pro2, pro3);

setTimeout(() => {
  console.log(pro1, pro2, pro3);
}, 2000);
  • 默认pro1、pro2、pro3 都是pendding状态,pro1打印fufilled 1;运行pro2的过程中报了一个错,错误对象是3,pro2处于 rejected状态,下面的return不会运行,因为前面报错了;pro3 没有针对pro2的错误进行处理,pro3的状态跟pro2完全一致,rejected 3.
  • 最终输出 3个padding, fufilled 1 reject 3 reject 3

继续下一道题目:

js 复制代码
const pro = new Promise((resolve, reject) => {
    resolve(1)
}).then((res) => {
    console.log(res);
    return 2;
}).catch((err) => {
    return 3;
}).then((res) => {
    console.log(res);
})

cnosole.log(pro)
  • 你可能会以为是 1 3 undefined,为啥不是呢?
  • 首先一步一步来分析,第一个promise处于padding resolve一个1,在第二个promise打印,输出 fulfilled 1; 第三个promise只处理了失败的结果,并且return 3,问题就在这里,你可能以为会把这个3给到第四个 promise,结果确实由于第三个promise没有对第二个promise成功的状态进行处理,所以延续了第二个promise的fulfilled 2的结果;第4个promise的结果取决于上一个promise,第三个promise成功了,并且延续的第二个的promise,所以在第四个promise打印的时候还是2,并不是3,最后第4个promise没有结果可返回,打印了一个undefined。

再稍微改造一下:

js 复制代码
const pro = new Promise((resolve, reject) => {
    resolve()
}).then((res) => {
    console.log(res.toString());
    return 2;
}).catch((err) => {
    return 3;
}).then((res) => {
    console.log(res);
})
console.log(pro)
  • 第一个promise 这里简称pro1, pro1 resolve一个空,所以是fuilled undefined;pro2 将pro1延续的undefined进行toString(), 所以肯定报错,pro2变成了rejected状态;因为pro2报错,pro3的状态取决于pro2的处理,pro3捕获到了异常,并成功return了一个3,运行状态为fulfilled 返回一个3;pro4运行过程中没有错误,运行状态为 fufilled 打印pro3延续的结果3,pro4没有返回值,最后打印undefined

继续下一道:

js 复制代码
new Promise((resolve, reject) => {
  resolve(1)
})
  .then((res) => {
    console.log(res);
    return new Error('2');
  })
  .catch((err) => {
    throw err;
    return 3;
  })
  .then((res) => {
    console.log(res);
  });
  • 首先第一个pro1,resolve了一个1,fulfilled 1;pro2打印1,抛出异常,fulfilled error('2'); pro3捕获到了异常,同时抛出异常,后面的return 3 不会执行,fulfilled error('2');pro4 打印pro3的错误,fulfilled undefined;
js 复制代码
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject();
  }, 1000);
});
const promise2 = promise1.catch(() => {
  return 2;
});

console.log('promise1', promise1);
console.log('promise2', promise2);

setTimeout(() => {
  console.log('promise1', promise1);
  console.log('promise2', promise2);
}, 2000);
  • 一开始打印的两个promise 都处于padding挂起状态, 1秒后 promise1被拒绝了,rejected undefined;promise2对promise1的错误进行处理,并返回了一个2,promise2成功运行 fulfilled 2;

换一个类型的题目:

js 复制代码
const promise = new Promise((resolve, reject) => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
        resolve();
        console.log(3);
    });
}).then(() => {
    console.log(4);
})

console.log(5);
  • 运行第一个promise,输出1,setTimeout没有传第二个参数,你可能会以为会立即执行,但是因为setTimeout属于宏任务,就算没有传递第二个参数,被分配到宏任务队列也是处于等待执行状态,因为resolve在setTimeout里面,整个promise没有改变状态,依旧还是padding状态;
  • 第二个promise的then需要等待成功之后才会执行,但是因为上一个promise未成功,第二个promise不会进入微任务队列,所以不会执行;
  • 接着打印全局作用域的5,全局作用域的任务执行完了,就会把宏任务队列的setTimeout进行执行,打印2,返回第一个promise的resolve状态,第二个promise进入微任务队列,第一个promise继续打印3,第一个promise执行完了,轮到第二个promise执行,打印4;最终结果1 5 2 3 4
js 复制代码
setTimeout(() => {
    console.log(1);
});

const promise = new Promise((resolve, reject) => {
    console.log(2);
    resolve();
}).then(() => {
    console.log(3);
})
console.log(4);
  • 首先登场的是一个setTimeout,加入到宏队列;其次是一个promise,输出一个2,resolve将状态改为fulfilled;
  • .then开启了第二个promise,进入微任务队列,这个时候不会立即执行微任务队列,因为全局作用域还有代码没执行,全局作用域打印一个4
  • 接着执行微任务队列,打印3,最后执行宏任务,打印1

来个简单一点的:

js 复制代码
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject();
  }, 1000);
});
const promise2 = promise1.catch(() => {
  return 2;
});

console.log('promise1', promise1);
console.log('promise2', promise2);

setTimeout(() => {
  console.log('promise1', promise1);
  console.log('promise2', promise2);
}, 2000);
  • 有了前面的积累,充满的一肯定一眼就能看出输出啥了,没错,首先是两个padding,2s后打印undefined 2

5. async await 消除回调

  • async 关键字用于修饰函数,一般出现在函数前面,一定返回一个promise;
  • await 关键字表示等待某个Promise完成,它必须用于async函数中;
js 复制代码
async function m(){
    console.log(0);
    const n = await 1;
    console.log(n);
}
m();
console.log(2);
  • 为啥是 0 2 1,脑瓜子是不是嗡嗡的,听我细细道来,别被语法糖迷惑住了,async 后面的一定返回一个promise,m函数里面首先打印一个0;
  • const n = await 1;console.log(n) 相当于 Promise.resolve(1).then((e) => { console.log(n); }) , 给n赋值和输出n这段逻辑本质上是在微队列,打印完0之后不会卡住等待await执行,而是结束掉m函数,执行全局作用域的代码,打印2;最后执行微队列的代码,打印1,有意思吧;

再来一道:

js 复制代码
async function m() {
  console.log(0);
  const n = await 1;
  console.log(n);
}

(async () => {
  await m();
  console.log(2);
})();

console.log(3);
  • 看完不自信了吧,为啥?看了这么多题,还是会错?别急,听我一步一步分析
  • 首先看括号里面的立即执行函数,立即执行函数里面await调用m()函数,打印0,后面的await进入微队列等待,那么立即执行函数里面的await返回的promise也是处于padding状态,下面的代码无法继续执行;
  • 回到全局作用域,打印3,执行栈清空,开始执行微队列,打印1,m()函数执行完,可以执行立即执行函数了,打印2;最后打印 0 3 1 2

继续下一道:

js 复制代码
async function m1() {
  return 1;
}

async function m2() {
  const n = await m1();
  console.log(n);
  return 2;
}

async function m3() {
  const n = m2();
  console.log(n);
  return 3;
}

m3().then((n) => {
  console.log(n);
});

m3();

console.log(4);
  • 嘿,朋友,还好吗?别自闭了,这个确实有点绕,也不知道谁脑洞这么大出的这题,如果你有此困惑,说明还没彻底掌握,坚持住,听我逐一分析。
  • 首先直接看m3.then(), m3返回一个promise, m3里面调用m2,m2里面调用m1,m1里面 return 了一个1,状态为fulfilled 1;
  • m2的await m1()相当于 await 1,这个时候打印n,后续代码进入微队列,状态为padding。
  • m3 const n = m2() 注意这里没有使用 await,打印n,返回3,状态为fulfilled 3;
  • m3执行完了,调用 .then 函数,.then里面的n是m3 返回的 3,所以打印3;
  • 接着下面又调用了一次m3,继续m3调用m2,m2调用m1,m1返回1,状态为fufilled 1;m2 的await 进入微队列,后续代码进入微队列,状态为padding;m3没有等待m2,直接完成,返回3;
  • 最后输出全局作用域的4,微队列的一个个拿出来,先输出 1,接着是3 1,最后打印 4 1 3 1

累了没?那来一道看起来比较简单的开火车题目:

js 复制代码
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);
  • 你个老六,说好的比较简单,怎么火车上面还有雷,2和3去哪了,被炸了吗?
  • 别慌,事出反常必有妖,且听我慢慢分析,事情还得从 .then(2)开始,注意,这里不是一个函数,而是一个2,这不合常理,你只要记住一点,以后看到then里面传的不是函数,直接忽略这段代码,为什么?因为promise的then发现你传递的不是一个promise,也就是没有注册回调函数,那它的结果和状态和上一个promise是一致的,相当于 .then(null);
  • 继续看 .then(Promise.resolve(3)),.then里面传递的是一个promise对象,依旧不是函数,所以是无效的传递,也相当于 .then(null);
  • 最后一个不用解释了,.then(console.log)console.log是一个函数,打印undefined。
  • 奇怪的知识又增加了吧,记得关注我,我会提供更多查漏补缺的知识,来刷新你的认识。

来一个比较经典的问题,你可能会比较熟悉:

js 复制代码
var a;
var b = new Promise((resolve, reject) => {
  console.log('promise1');
  setTimeout(() => {
    resolve();
  }, 1000);
})
  .then(() => {
    console.log('promise2');
  })
  .then(() => {
    console.log('promise3');
  })
  .then(() => {
    console.log('promise4');
  });

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a;
  resolve(true);
  console.log('after2');
});

console.log('end');
  • 这个题有点复杂,但是别乱了阵脚,还是逐一分析。
  • 首先定义一个a,值为undefined,然后定义一个b,你可能会以为直接是一个promise,但在js里面变量的声明和赋值是有先后顺序的,出现b这种,得先把promise算出来,才能赋值给b,在没算出来之前,都是undefined。
  • 来看b的表达式,开了一个火车,从第一个promise开始,resolve放在一个定时器里面,也就是1s之后才能确定后面promise的状态,这里有个小知识,不管promise的火车有多长,.then接的有多少个,返回的都是最后一个 .then ,所以b是一个 padding状态的promise,打印 'promise1'。
  • 继续看下面代码,创建了一个新的promise赋值给a,new Promise 表达式里面首先打印a,因为a还没有成功赋值,所以打印上面的undefined;
  • 接着看await b 等待上面的代码,因此后面await b后面的代码不会执行,进入微队列,因此整个赋值给b的promise执行解释,状态为padding,赋值给a;
  • 执行打印全局作用域的 end ,等待1s后,定时器结束,赋值给b的promis状态为fufilled,执行后面的 .then 函数,开火车,依次打印 'promise2' 'promise3' 'promise4';
  • b的promise赋值完成之后,微任务队列中 await b后面的代码可以执行了,接着输出一个 'after1',又继续打印a,a这里前面执行过,为padding状态的promise;
  • 接着看 await a, 自己等待padding状态的自己之后再执行下面的代码?这事肯定没尽头,所以后面的代码不会再执行了。

继续下一道,我怕再玩下去,你要忍不住到要打我了:

js 复制代码
async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}

console.log('script start');

setTimeout(function () {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function (resolve) {
  console.log('promise1');
  resolve();
}).then(function () {
  console.log('promise2');
});
console.log('script end');
  • 这是一道promise async 混合的题目,但是不要慌,其实都一样。
  • 首先打印一个 'script start', 再看宏任务,有一个setTimeout,等待0秒,排队去,再看微任务,async1()被调用,打印 'async1 start',然后await 异步调用async2(),打印async2,返回一个promise,后续代码进入微队列;
  • 继续回到外层的 new Promise,打印 'promise1',resolve完成,后面的.then进入微队列;
  • 继续回到全局作用域的代码,打印 'script end',主线程执行完了,开始从微任务队列找代码执行,微任务首先打印async1里面等待的 'async1 end', async1这个promise状态完成,fufilled,然后再输出 new Promise 中挂起的 'promise2',微队列清空;
  • 再看宏队列,打印 'setTimeout',结束。

最后一道,绝对颠覆你对finally的认知:

js 复制代码
Promise.resolve('1')
  .then(res => {
    console.log(res)
  })
  .finally(() => {
    console.log('finally')
  })
Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
  	return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })

打印结果:

  • 你可能会疑惑,为什么在执行第一个promise的时候,打印完1,没有继续执行finally,而是转而执行第二个promise的finally?
  • 这个问题涉及到 JavaScript 中 Promise 的执行机制,特别是微任务(microtask)的调度顺序。让我为你详细解答,为什么在执行第一个 Promise 的 .then 并打印 '1' 后,没有立即执行它的 .finally,而是转而执行第二个 Promise 的 .finally

为了方便理解,先看看代码:

javascript 复制代码
Promise.resolve('1')
  .then(res => {
    console.log(res)  // 打印 '1'
  })
  .finally(() => {
    console.log('finally')  // 打印 'finally'
  })

Promise.resolve('2')
  .finally(() => {
    console.log('finally2')  // 打印 'finally2'
    return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)  // 打印 'finally2后面的then函数 2'
  })

运行结果是:

csharp 复制代码
1
finally2
finally
finally2后面的then函数 2

你好奇的是:为什么在打印 '1' 后,没有紧接着打印 'finally',而是先打印了 'finally2'

这与 JavaScript 的 微任务调度机制 有关。让我们一步步拆解:

1. Promise 的链式调用与微任务队列

  • 在 JavaScript 中,Promise 的 .then.catch.finally 都会返回一个新的 Promise 对象。
  • 当一个 Promise 完成(resolved 或 rejected)时,它注册的回调(如 .then.finally 中的函数)会被加入 微任务队列
  • 微任务队列是先进先出(FIFO)的,任务会按照加入队列的顺序依次执行。

2. 同步代码的执行

当代码运行时,同步部分会立即执行:

  • 第一个 PromisePromise.resolve('1') 创建一个立即 resolved 的 Promise,值为 '1'。它的 .then 回调(console.log(res))被加入微任务队列。
  • 第二个 PromisePromise.resolve('2') 也创建一个立即 resolved 的 Promise,值为 '2'。它的 .finally 回调(console.log('finally2'))也被加入微任务队列。

同步代码执行完后,微任务队列的初始状态是:

  • 队列:[第一个 Promise 的 .then, 第二个 Promise 的 .finally]

3. 微任务的逐步执行

JavaScript 的事件循环会在同步代码执行完后,清空微任务队列。让我们看看具体过程:

  • 执行第一个任务

    • 从队列中取出 第一个 Promise 的 .then,执行 console.log('1'),打印 '1'
    • .then 执行后,返回一个新的 Promise(默认 resolved),并将 .finally(() => { console.log('finally') }) 加入微任务队列。
    • 此时队列变为:[第二个 Promise 的 .finally, 第一个 Promise 的 .finally]
  • 执行第二个任务

    • 队列中下一个任务是 第二个 Promise 的 .finally,执行 console.log('finally2'),打印 'finally2'
    • .finally 执行后,返回一个新的 Promise(值为原始的 '2'),并将 .then(res => { console.log('finally2后面的then函数', res) }) 加入微任务队列。
    • 队列变为:[第一个 Promise 的 .finally, 第二个 Promise 的 .then]
  • 执行第三个任务

    • 执行 第一个 Promise 的 .finally,打印 'finally'
    • 队列变为:[第二个 Promise 的 .then]
  • 执行第四个任务

    • 执行 第二个 Promise 的 .then,打印 'finally2后面的then函数 2'
    • 队列清空,执行结束。

4. 关键点解答

为什么在打印 '1' 后没有立即执行 'finally'?原因在于:

  • 当第一个 Promise 的 .then 执行时,它的 .finally 回调并不是立即执行,而是被加入微任务队列的末尾。
  • 此时,第二个 Promise 的 .finally 已经在队列中(因为它在同步代码执行时就加入了),而且排在 第一个 Promise 的 .finally 前面。
  • 微任务队列严格遵循先进先出的原则,因此在执行完 第一个 Promise 的 .then 后,会先执行队列中已有的 第二个 Promise 的 .finally(打印 'finally2'),然后才轮到 第一个 Promise 的 .finally(打印 'finally')。

为了更直观地展示,以下是队列的演变过程:

  1. 初始状态(同步代码后):

    • 队列:[第一个 Promise 的 .then, 第二个 Promise 的 .finally]
  2. 执行 .then 后 (打印 '1'):

    • 队列:[第二个 Promise 的 .finally, 第一个 Promise 的 .finally]
  3. 执行第二个 .finally 后 (打印 'finally2'):

    • 队列:[第一个 Promise 的 .finally, 第二个 Promise 的 .then]
  4. 执行第一个 .finally 后 (打印 'finally'):

    • 队列:[第二个 Promise 的 .then]
  5. 执行第二个 .then 后 (打印 'finally2后面的then函数 2'):

    • 队列:空

在执行第一个 Promise 的 .then 并打印 '1' 后,它的 .finally 没有立即执行,是因为 .finally 的回调是在 .then 执行后才加入微任务队列的。而第二个 Promise 的 .finally 在同步代码阶段就已加入队列,排在前面。因此,微任务调度机制决定先执行 finally2,然后才执行第一个 Promise 的 finally。这种顺序是由微任务队列的 FIFO 特性决定的,确保了异步任务的执行有条不紊。

总结

  • 优先级:同步代码 > 微任务 > 宏任务;微任务队列必须清空后才会处理下一个宏任务。
  • 状态不可逆:一旦从 Pending 变为其他状态,不能再改变;
  • js代码永远不会卡住,后面有代码就会继续执行后面的;
  • 任何一个 .then() 抛出错误,会直接跳到最近的 .catch()
  • then里面如果传的不是函数,直接忽略这段代码,因为promise的then发现你传递的不是一个promise,也就是没有注册回调函数,那它的结果和状态和上一个promise是一致的,相当于 .then(null);
  • .finally()方法的回调函数不接受任何的参数,不管Promise对象最后的状态如何都会执行;
  • 微任务队列是先进先出(FIFO)的,任务会按照加入队列的顺序依次执行;
  • .then、.catch 和 .finally 会返回一个新的 Promise 对象,事件循环会在同步代码执行完后,清空微任务队列。
相关推荐
小蜜蜂嗡嗡6 分钟前
flutter封装vlcplayer的控制器
前端·javascript·flutter
一tiao咸鱼9 分钟前
如何简单使用 prompt
前端·aigc
cdbqss114 分钟前
VB.net编写的身份证类
前端·.net
骑自行车的码农31 分钟前
React短文系列 遍历fiber树 App的创建
前端·react.js
AskSky35 分钟前
为了搞一个完美的健身APP,我真是费尽心机
前端
斯~内克40 分钟前
基于Vue.js和PDF-Lib的条形码生成与批量打印方案
前端·vue.js·pdf
阴阳怪气乌托邦42 分钟前
别再啃OA代码了!低代码"搭积木"式搞数智化,我直接少写500行
前端·低代码
__NK43 分钟前
【字节跳动高频面试题】不超过 N 的最大数拼接
面试·大厂·字节跳动·手撕
beelan1 小时前
v-on的思考
前端
山河木马1 小时前
前端学习C++之:.h(.hpp)与.cpp文件
前端·javascript·c++