博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ES6-Promise 源码阅读
阅读量:7083 次
发布时间:2019-06-28

本文共 19847 字,大约阅读时间需要 66 分钟。

前言

此次阅读的 promise 实现是 ES6-Promise ,是 promise 的众多实现中较为完善的一个例子,从入口文件 lib/es6-promise/promise.js 开始阅读,可以看到 Promise 定义有如下的静态方法

Promise.all

Promise.race

Promise.resolve

Promise.reject

以及挂载在 Promise.protoype 上的方法

Promise.prototype.then

Promise.prototype.catch

Promise.prototype.finally

接下来会对其核心代码进行解读。

Promise的三种状态

Promise规范中定义了promise的三种状态,而且promise的状态更新只能是以下两种情况,一旦状态发生改变之后就不能再修改:

  • PENDING -> FULFILLED
  • PENDING -> REJECTED

ES6-Promise 内部中的定义如下:

// void 运算符 对表达式进行求值,结果都返回 undefined// 可以防止undefined被重写,如:const undefined = xxxconst PENDING = void 0; // undefinedconst FULFILLED = 1;const REJECTED = 2;复制代码

Promise的使用例子

我们来看一个Promise的使用例子,传入一个resolver函数,初始化一个promise实例。resolver函数会被立即执行,并且可以使用promise内部传入的两个参数 resolve 和 reject ,resolve 和 reject都是函数,接收一个参数, value 或者 reason,在resolver函数内部调用 resolve 或者 reject ,都会更新promise的状态 。

具体调用例子如:

const promise = new Promise(function(resolve, reject) {  if (/* 异步操作成功 */){    // 更新状态为 fulfilled 并缓存 value    resolve(value);  } else {    // 更新状态为 rejected  并缓存 reason    reject(error);  }});复制代码

为了响应promise不同的状态(fulfilled 或 rejected),可以通过实例的 then 方法绑定回调函数:

promise.then(     // resolver 中 resolve 的 value    value => { /* pending -> fulfilled 时调用 */    },    // resolver 中 reject 的 resaon    reason => { /* pending -> rejected 时调用 */    })复制代码

这样就完成了一个promise的基本使用。

Promise的构造函数

由上面的基本调用开始,先看看promise的构造方法:

class Promise {  constructor(resolver) {    // export const PROMISE_ID = Math.random().toString(36).substring(2);     // nextId() 返回一个自增的闭包变量    // Promise实例的唯一标识    this[PROMISE_ID] = nextId();    // 初始化变量    // _result 缓存 resolve 时的 value 或者 reject 时的 reason    // _state  缓存 promise 的 当前状态 初始化为 pending    this._result = this._state = undefined;    // 订阅序列    this._subscribers = [];    // noop = ()=>{}  内部定义的一个空函数    // 内部调用时: new Promise(noop) 不会经过下面的初始化过程    // 外部调用时: 对传入参数进行检查    if (noop !== resolver) {      // 条件1:resolver 必须要是一个函数      typeof resolver !== 'function' && needsResolver();      // 条件2: 必须通过 new Promise() 初始化实例      this instanceof Promise ? initializePromise(this, resolver) : needsNew();      // 符合以上条件的进行初始化      // initializePromise() 函数    }  }复制代码

可以看到promise的构造方法完成了以下的工作:

  • 初始化变量

    [PROMISE_ID] : 该实例独一无二的ID

    result: 缓存value或者reason

    subscribers: 订阅promise状态变化的序列(后面会讲到)

  • 对入参 resolver 进行判断

    resolver === noop noop是内部定义的一个空函数,此时 promise 会初始化结束。

    resolver !==  noop resolver 必须是函数,同时必须是通过 new Promise() 初始化实例。满足以上两个条件的,就会调用 initializePromise ,对实例进一步初始化,其代码如下:

function initializePromise(promise, resolver) {  try {    // 立即执行 resolver函数     // 传入包装好的参数    resolver(function resolvePromise(value) {      resolve(promise, value);    }, function rejectPromise(reason) {      reject(promise, reason);    });  } catch (e) {    // 代码发生异常     // 则直接更新 promise 的状态为 rejected    reject(promise, e);  }}复制代码

可以看到,resolver函数被立即执行了,而且在 resolver 函数中调用的 resolve 和 reject 就是 resolvePromise 和 rejectPromise 函数,这也是promise提供给resolver的两个入参,它们包装了内部实现的 resolve 和 reject 函数。

再来看看内部的 resolve和 reject 是如何对promise状态进行更新的。

内部的 reject 函数

reject函数较为简单:

/**  * pending => rejected */function reject(promise, reason) {  // 状态一旦改变就不可逆转  if (promise._state !== PENDING) {    return;  }  // 缓存处理结果 更新promise状态  promise._state = REJECTED;  promise._result = reason;  // 注:asap 函数暂不详细解释 后面源码中多次用到   // asap(func,param) 可以理解为立即执行 func(param)  // promise 的状态发生了改变 通知订阅者  asap(publishRejection, promise);}复制代码

promise的状态更新为rejected后,会通过publishRejection向订阅序列发出通知,publishRejection函数代码如下,具体的publish函数封装了如何向订阅序列发出通知的逻辑,后续会说到:

function publishRejection(promise) {  // ???  // onError 好像一直都是null啊  if (promise._onerror) {    promise._onerror(promise._result);  }  // promise的状态发生了改变  // 向订阅序列发出通知  publish(promise);}复制代码

内部的 resolve 函数

resolve 则会对 value 的类型进行判断,情况有以下:

  • value是实例本身,更新promise的状态为 rejected,不允许resolve本身
  • value是对象或者函数,调用 handleMaybeThenable()方法进行特殊处理
  • 其他情况,调用 fulfill 方法更新状态

具体代码逻辑如下:

function resolve(promise, value) {  if (promise === value) {    // 1. 不允许 value 是 promise实例本身    // 更新为 rejected 状态     reject(promise, selfFulfillment());  } else if (objectOrFunction(value)) {    // 2. value是对象或者函数     handleMaybeThenable(promise, value, getThen(value));  } else {    // 3. 其他数据类型    fulfill(promise, value);  }}复制代码

先看看 fulfill 方法,方法不难理解,就是更新promise的状态为 fulfilled 同时向订阅序列发出通知。

/** * pedding => fulfilled */function fulfill(promise, value) {  // promise状态不可逆  if (promise._state !== PENDING) {    return;  }  // 缓存处理结果 修改promise状态  promise._result = value;  promise._state = FULFILLED;  // promise的状态发生了改变  // 向订阅序列发出通知  if (promise._subscribers.length !== 0) {     asap(publish, promise);  }}复制代码

再来看看当 resolve 函数传入的value是对象或者函数,调用特殊处理的 handleMaybeThenable(promise,value,getThen(value))方法。其中getThen方法接收一个参数promise,用于尝试获取promise的then属性,会捕获错误,返回包装的错误对象。函数内部又对value进行了进一步划分。

handleMaybeThenable的具体代码如下:

function handleMaybeThenable(promise, maybeThenable, then) {  if (    maybeThenable.constructor === promise.constructor &&    then === originalThen &&    maybeThenable.constructor.resolve === originalResolve) {    // 1. resolve的值是一个 promise 实例     // 即通过 new Promise() 新建的实例    handleOwnThenable(promise, maybeThenable);  } else {    // 2. 获取 value 的 then 属性时发生错误    // 更新 promise 的 状态为 rejected    if (then === TRY_CATCH_ERROR) {      reject(promise, TRY_CATCH_ERROR.error);      TRY_CATCH_ERROR.error = null;    } else if (then === undefined) {      // 3. 虽然 resolve的值是 函数或者对象 但不存在 then 属性      // 按照普通数据类型进行处理      fulfill(promise, maybeThenable);    } else if (isFunction(then)) {            // 4. resolve的值是一个 thenable 对象, 具有then属性,并且为函数      handleForeignThenable(promise, maybeThenable, then);    } else {      // 5. then 是其他数据类型 直接进行处理      fulfill(promise, maybeThenable);    }  }}复制代码

重新总结一下 resolve 的值分为以下情况:

  1. value为 promise 实例,调用handleOwnThenable方法进行处理
  2. value为 thenable 实例(一个具有then方法的对象),调用handleForeThenable方法进行处理
  3. 其他情况,直接调用 fulfill 方法处理值

第一种情况中,handleOwnThenable 具体代码如下:

/** * resolve 的值 value 是一个 promise 实例  * @param promise 当前的promise * @param thenable value */function handleOwnThenable(promise, thenable) {  // 检查 thenable 状态  if (thenable._state === FULFILLED) {    // FULFILLED 状态     // 使用 thenable 内部缓存的处理结果     // 作为 value 调用 fulfill 更新状态    fulfill(promise, thenable._result);  } else if (thenable._state === REJECTED) {    // REJECTED  状态     // 使用 thenable 内部缓存的处理结果     // 作为 reason 调用 reject 更新状态    reject(promise, thenable._result);  } else {    // pending 状态, 对 thenable 增加监听    // 当 thenable 的状态改变时     // 用其结果缓存 更新 promise 的状态    subscribe(thenable, undefined,      value => resolve(promise, value),      reason => reject(promise, reason))  }}复制代码

也就是说,当resolve的value是一个promise时,原封不动地返回这个promise。

第二种情况,当为一个具有then属性并且then为函数的对象时:

// let thenable = {
// then: function(resolve, reject) {
// resolve(42);// }// };function handleForeignThenable(promise, thenable, then) { asap(promise => { // 用一个 sealed 保证只能调用一次 var sealed = false; // tryThen 方法 // 1. 以 thenable 作为执行上下文 // 2. 传入 (value=>{} , reason=>{}) 参数 // 3. 执行 then 函数 // var error = tryThen(then, thenable, value => { if (sealed) { return; } sealed = true; if (thenable !== value) { // 如果 resolve 的 value 不是原来的thenale对象 // 进一步对value进行处理(有可能又是一个promise实例或者thenable对象,或者其他情况...) resolve(promise, value); } else { // 如果 resolve 的 value 还是原来的thenale对象 // 则直接 fulfill 这个对象 // 不再进一步通过 resolve 避免死循环 fulfill(promise, value); } }, reason => { if (sealed) { return; } sealed = true; // then 方法 reject(‘xx') reject(promise, reason); }, 'Settle: ' + (promise._label || ' unknown promise')); // if error // rejected 状态 if (!sealed && error) { sealed = true; reject(promise, error); } }, promise);}复制代码

也就是说,当resolve的值是一个thenable对象时,会执行其then方法,根据then方法中调用入参resolve或者reject时传入的value或reason来对promise的状态进行更新。

subscribe 订阅机制

之前有提到promise的订阅,在Promise初始化的时候,声明了一个内部变量,用来存储对promise的订阅序列。

// 订阅序列this._subscribers = [];复制代码

然后可以调用 subscribe 函数注册监听:

/** * @param {promise} parent         被订阅状态变化的Promise * @param {promise} child          订阅状态变化的Promise(可为undefined) * @param {function} onFulfillment 状态变化为 fulfilled 的订阅函数  * @param {function} onRejection   状态变化为 rejected  的订阅函数 */function subscribe(parent, child, onFulfillment, onRejection) {  let { _subscribers  } = parent;  let { length } = _subscribers;  parent._onerror = null;  // 每调用一次subscribe函数 订阅序列会增加三个元素  _subscribers[length] = child;  _subscribers[length + FULFILLED] = onFulfillment;  _subscribers[length + REJECTED] = onRejection;  // 有可能是在 parent 状态更新之后增加的订阅  // 此时的订阅序列被清空 状态不再发生变化 则直接发出通知  if (length === 0 && parent._state) {    asap(publish, parent);  }}复制代码

可以看到,对一个promise订阅其状态更新时,会在其订阅序列增加三个参数,子promise(可为空),状态变为fulfilled时的回调,变为rejected时的回调。当一个promise状态更新之后,就会触发publish函数,发布修改,publish的具体代码逻辑如下:

/** * Promise状态更新 发出通知 * @param {Promise} promise  */function publish(promise) {  // 所有订阅序列  let subscribers = promise._subscribers;  // promise的状态  let settled = promise._state;  // 订阅序列为空  if (subscribers.length === 0) {    return;  }   /**   * child:    订阅状态变化的promise   * callback: 回调函数   * detail:   value或者reason缓存   */  let child, callback,    detail = promise._result;   // 每三个元素为一个订阅单位  // 格式如: child + fufilled + rejected  for (let i = 0; i < subscribers.length; i += 3) {    // child === [object Promise]    child = subscribers[i];    // 根据 promsie 的状态获取相应的回调函数    callback = subscribers[i + settled];    // 情况1: 存在promise实例订阅    if (child) {      invokeCallback(settled, child, callback, detail);    } else {      // 情况2: 触发响应的回调函数      callback(detail);    }  }  // 清空监听数组     promise._subscribers.length = 0;}复制代码

每三个元素为一个单位,publish函数会依次判断订阅者:

  1. 订阅存在子promise,存在则再调用 invokeCallback 函数
  2. 订阅不存在,直接传入结果缓存value或者reason 调用相应的回调函数

第一种情况下:

/** * settled  :  被监听promise的状态 * promise  :  订阅监听的promise * callback :  fullfled | rejected  * detial   :  value | reason */function invokeCallback(settled, promise, callback, detail) {  let hasCallback = isFunction(callback),    value, error, succeeded, failed;  if (hasCallback) {    // 尝试执行回调函数    // 发生错误时 则返回 TRY_CATCH_ERROR 对象    value = tryCatch(callback, detail);    // 执行cb函数时出错    if (value === TRY_CATCH_ERROR) {      // 获取error的值      failed = true;      error = value.error;      value.error = null;    } else {      // 设置状态      succeeded = true;    }    // 不允许回调函数返回了原有的promise实例    if (promise === value) {      reject(promise, cannotReturnOwn());      return;    }  } else {    // 不存在回调函数    value = detail;    succeeded = true;  }  // 更新 promise 的状态  if (promise._state !== PENDING) {    // noop  } else if (hasCallback && succeeded) {   // 回调函数存在的情况    // value 是回调函数的返回值    resolve(promise, value);  } else if (failed) {   // error 是调用回调函数的错误时的原因    reject(promise, error);  } else if (settled === FULFILLED) {   // 回调函数不存在的情况    // 直接使用父promise的value    fulfill(promise, value);  } else if (settled === REJECTED) {       reject(promise, value);  }}复制代码

总结一下,订阅一个promise 的状态变化,主要有两种情况:

  1. 一种是仅传入了回调函数,不存在子promise订阅,此时会promise的状态一旦变化,就会传入value或者reason,调用相应的回调函数。
  2. 另一种是订阅存在子promise,此时promise的状态一旦变化,又会分成回调函数有无两种情况, 有回调函数的话,传入value或者reason到相应的回调函数执行,回调函数的返回值作为结果缓存到子promise。 无回调函数的话,就直接使用value或者reason作为结果缓存。

Promise.prototype.then

方法传入两个函数参数,当promise的状态更新后,调用相应的回调函数,同时返回一个新的promise。

export default function then(onFulfillment, onRejection) {  // 当前的 promise  const parent = this;  // 新建一个 promise  // new pormise(()=>{})  // [!] 通过此方式创建的 promise 不会进行 initializePromise 方法初始化  // initializePromise 会执行传入的函数 空函数不需要执行 只需要初始化相关变量  const child = new this.constructor(noop);  // 仅初始化状态变量  // promise[PROMISE_ID] = id++;  // promise._state = undefined;  // promise._result = undefined;  // promise._subscribers = [];  if (child[PROMISE_ID] === undefined) {    makePromise(child);  }  // 获取当前 promise 的状态  const { _state } = parent;  // 当前的 promise 为 fullfied / rejected   // 直接处理  if (_state) {    // state = fullfied   ===> onFulfillment    // state = rejected   ===> onRejection    // 获取与状态对应的处理函数    const callback = arguments[_state - 1];    // 传入结果立即执行    // 更新新建promise的状态    asap(() => invokeCallback(_state, child, callback, parent._result));  } else {    // 当前的 promise 为 pending     // 将新建的 promise 绑定在 当前 promise 上    // 当前的 pormise 状态改变之后 同步新建的 promise 状态    subscribe(parent, child, onFulfillment, onRejection);  }    // 返回新建的 promise  return child;}复制代码

总结一下,首先 then 方法会返回一个新的promise,其状态和原来的 promise 同步,结果缓存则有以下的两种情况。

  1. 如果设置了回调函数(onFulfillment或onRejection),会将原有promise的value或者reason传入回调函数,回调函数的返回结果缓存到新的promise实例中。
  2. 如果没有设置回调函数,会将原有的promise的value或者reason直接缓存给新的promise实例。

也就是下面的两种例子:

// 1. 第一种情况let promise = new Promise((resolve,reject)=>{    resolve('OK')})let child1 = promise.then((value)=>{    return value + 'child1'},()=>{})// 此时child1的结果缓存是 'OKchild1'// 2. 第二种情况let child2 = promise.then().then().then().then().then( value =>{    console.log(value) // 结果缓存还是最开始那个promise 也就是 'OK'})复制代码

Promise.protoype.catch

catch方法是then方法的语法糖,通常我们会这样设置回调:

let promise = new Promise((resolve,reject)=>{    if(/* 异步OK*/){        resolve('OK')    }else{        reject(new Error('something wrong'))    }}).then(value=>{    // xxx}).catch(err=>{    // xxx})// 但实际上等同于promise.then( value=>{},err=>{})复制代码

其代码也很简单:

/**   * Promise.prototype.catch   */  catch (onRejection) {    return this.then(null, onRejection);  }复制代码

Promise.prototype.finally

finally方法用于无论状态发生了什么改变(fulfilled或者rejected)都要执行的操作,来自ES2018的标准。 注意:代码中的Promise.resolve 可先看看下面关于此方法的解读。

/**   * Promise.prototype.finally   */  finally(callback) {    // 当前 promise    let promise = this;    // Promise    let constructor = promise.constructor;    if (isFunction(callback)) {      // Promise.resolve 包裹成一个promise,再调用其 then 方法返回一个新的promise      return promise.then(        // pending -> fulfilled        value => constructor.resolve(callback()).then(() => value),        // pending -> rejected        reason => constructor.resolve(callback()).then(() => {          throw reason;        }));    }    // callback 不是函数    return promise.then(callback, callback);  }复制代码

Promise.resolve

方法接收一个参数,返回一个Promise,可接受的输入情况有:

// 1. 传入的是一个 promise实例 // 直接返回这个实例Promise.resolve(promise);// 2. 传入的是一个 thenable实例// 执行 thenable  的 then 方法 // 根据 方法内部 resolve 或者 reject 得到的 value 或者 resaon// 设置为 promise 的状态和结果缓存let thenable = {  then: function(resolve, reject) {    resolve(42);  }};Promise.resolve(theanable);// 3. 普通数据值 返回一个 fulfilled 状态的 promise// 结果缓存为其 valuePromise.resolve(value);复制代码

具体代码实现如下

export default function resolve(object) {  // Promise.resolve = Resolve;  // [ Promise ]   let Constructor = this;  // 1. 传入一个 promise 实例  // 则直接返回这个promise  if (object     && typeof object === 'object'     && object.constructor === Constructor) {    return object;  }  // 新建一个空的 promise  // 不会经过 initialPromise() 方法的初始化  let promise = new Constructor(noop);  // 2. 可能是 thenable 对象  //    或者是其他普通值  _resolve(promise, object);  // 返回 promise  return promise;}复制代码

Promise.reject

方法较为简单,接收一个参数reason,直接返回一个状态为rejected的promise实例。

export default function reject(reason) {  // Promise.reject  // Promise  let Constructor = this;  // new Promise(()=>{})  // 不会经过 initialPromise() 方法的初始化  let promise = new Constructor(noop);  // pending -> rejected  _reject(promise, reason);  // 返回promise  return promise;}复制代码

Promise.race

方法传入一个 数组,一般传入多个promise,包装成一个promise返回,这个promise的状态和传入的多个promise中最先更新状态那一个promise同步。

export default function race(entries) {  // Promise  let Constructor = this;  if (!isArray(entries)) {    // 不是数组 返回一个状态为rejected的Promise实例    return new Constructor((_, reject) => reject(new TypeError('You must pass an array to race.')));  } else {    // 返回一个新的 Promise 实例     return new Constructor((resolve, reject) => {      let length = entries.length;      for (let i = 0; i < length; i++) {        // Promise.resolve(entries[i]).then(resolve,reject)        // 遍历所有元素 用 Promise.resolve() 包装成 Promise实例        // 最先的一个 Promise 更新了状态后 都会同步外层 Promise         Constructor.resolve(entries[i]).then(resolve, reject);      }    });  }}复制代码

Promise.all

方法也是传入一个数组,一般传入多个promise,包装成一个promise返回,其状态有以下两种情况: 1 : 所有promise的状态都变成fulfilled时,返回的promise状态为fulfilled,结果是所有promise返回值的数组 2 : 有一个promise的状态率先变为rejected,返回的promise状态为rejected,结果是这个promise rejected的返回值

代码如下:

// Promise.allexport default function all(entries) {  // 初始化一个 Enumerator 实例 返回其属性 promise  return new Enumerator(this, entries).promise;}复制代码

Enumerator的具体代码如下:

export default class Enumerator {  constructor(Constructor, input) {    // Promise    this._instanceConstructor = Constructor;    // 新建一个空的promise 不会经过 initialPromise() 方法的初始化    // Promise.all() 最后返回这个值    this.promise = new Constructor(noop);    // 初始化    // promise[PROMISE_ID] = id++;    // promise._state = undefined;    // promise._result = undefined;    // promise._subscribers = [];    if (!this.promise[PROMISE_ID]) {      makePromise(this.promise);    }    if (isArray(input)) {      // 总长度      this.length = input.length;      // 剩余长度      this._remaining = input.length;      // 存放结果      this._result = new Array(this.length);      if (this.length === 0) {        // 空数组        // 返回一个状态为 fulfilled 的promise        fulfill(this.promise, this._result);      } else {        this.length = this.length || 0;        // 执行 _enumerate        this._enumerate(input);        if (this._remaining === 0) {          // 剩余长度为0          // 返回一个状态为 fulfilled 的promise          fulfill(this.promise, this._result);        }      }    } else {      // 入参不是数组      // 返回一个状态为r ejected 的promise      reject(this.promise, validationError());    }  }  _enumerate(input) {    // 遍历执行所有状态为 pending 的 promise    for (let i = 0; this._state === PENDING && i < input.length; i++) {      this._eachEntry(input[i], i);    }  }  _eachEntry(entry, i) {    // Promise.all() 方法 传入的是 Promise     let c = this._instanceConstructor;    //                     Promise.resolve    let { resolve } = c;    // Promise.resolve = Promise.resolve    if (resolve === originalResolve) {           // 尝试获取 entry 的 then 属性      let then = getThen(entry);      if (then === originalThen         && entry._state !== PENDING) {        // then  === Promise.prototype.then        // 情况1: entry是promise实例 同时 状态不为 pending                this._settledAt(entry._state, i, entry._result);      } else if (typeof then !== 'function') {        // then 不为函数 为普通值        // 直接设置        this._remaining--;        this._result[i] = entry;      } else if (c === Promise) {
// true 在Promise.all() 中此处一直成立 let promise = new c(noop); // then 是一个函数 // 对 then 进行进一步检查 如果是一个 thenable 对象 // let thenable = {
// then: function(resolve,reject)=>{ resolve('66')} // } // 则会执行 then 方法 获取相应的值后设置 promise 的状态 handleMaybeThenable(promise, entry, then); // 设置监听 this._willSettleAt(promise, i); } else { // 情况2: entry是promise实例 状态为 pending // 绑定监听 待entry状态改变之后 再设置相应的值 this._willSettleAt(new c(resolve => resolve(entry)), i); } } else { // 其他情况 这里不作考虑 // 因为Promise.all 传入的是Promise this._willSettleAt(resolve(entry), i); } } _settledAt(state, i, value) { let { promise } = this; if (promise._state === PENDING) { // 又完成了一个 Promise this._remaining--; if (state === REJECTED) { // 一旦有一个promise实例状态变为rejected // 更新状态为rejected reject(promise, value); } else {
// fulfilled // 更新值 this._result[i] = value; } } if (this._remaining === 0) { // 所有的元素都已经变为 fulfilled // 同步返回promise的状态 fulfill(promise, this._result); } } _willSettleAt(promise, i) { let enumerator = this; // 绑定监听 // promise的状态改变后 调用相应的函数 subscribe( promise, undefined, value => enumerator._settledAt(FULFILLED, i, value), reason => enumerator._settledAt(REJECTED, i, reason) ); }};复制代码

以上便是es6-promise核心代码的阅读。

最后

原文地址:

ES6-Promise仓库地址:

转载于:https://juejin.im/post/5bac9e7ce51d450e827b5005

你可能感兴趣的文章
洛谷P4459/loj#2511 [BJOI2018]双人猜数游戏(博弈论)
查看>>
html5杂谈1
查看>>
阿里云 azkaban 发邮件的坑
查看>>
socket协议和http协议性能对比
查看>>
看《东陵大盗》有感
查看>>
解释:C++虚函数
查看>>
C++ #define,typedef,using用法区别
查看>>
IDEA软件安装详解,
查看>>
three.js-002
查看>>
网络知识===《图解TCP/IP》学习笔记——网络的构成要素
查看>>
SQL Server2005配置同步复制
查看>>
烂泥:haproxy与nginx、zabbix集成
查看>>
c#MD5珍藏
查看>>
自定义App首次启动引导页
查看>>
如何清除全部的NSUserDefaults储存的数据。
查看>>
HTML光标样式
查看>>
窗体传值
查看>>
基于函数的索引+创建基于函数的索引
查看>>
企业信息管理软件 OA、CRM、PM、HR 财务、ERP等
查看>>
[开源]KJFramework.Message 智能二进制消息框架 -- 对于数组的极致性优化
查看>>