源码速读!一看就会、一写就废的 Promise 实现

点击上方“前端号关注,点亮你的前端技能树

Promise 对于前端来说,是个老生常谈的话题,Promise 的出现解决了 js 回调地狱(Callback Hell)的问题。

Promise 非常好用,不过要自己去理解它的源码实现可以说是非常蛋疼,本文尝试换个角度,从 Promise 的 使用角度 v.s 源码角度 来剖析源码具体实现,给你一个直观清晰的解释,到时候面试管问起来的时候你也可以从容应对

目前市面上有很多 Promise 库,但其最终实现都要遵从 Promise/A+ 规范,这里对规范不做解读,有兴趣的可以查看链接内容。Promise/A+规范链接Promise/A+规范中文链接

方便讲解找了一个极其轻量级的 Promise polyfill 实现解析, 源码地址 promise-polyfill,本文就从它开始分析源码。

0. API 列表

先看一下 API 列表,相信这些方法你都了解过,如若没有请自行面壁思过(不了解的话前端面试一般就 over 了...):

Promise  // 构造函数
Promise.prototype.then
Promise.prototype.catch
Promise.prototype.finally

// 静态方法
Promise.resolve
Promise.reject
Promise.race
Promise.all

1. 源码解析

1.1 构造函数

咱们先看一下构造函数的样子。

从使用角度:Promise 的第一步当然是构造实例了,传入 Function 形参,形参接收两个 Function 类型参数 resolve, reject

const asyncTask = () => {};
const pro = new Promise((resolve, reject) => {
  asyncTask((err, data) => {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
});

从源码角度:对比这个使用,我们看一下对应的 源码部分,内容有点儿长,请耐心阅读(关键部分我都给了中文注释)

/** 这玩意就是构造函数???? **/
function Promise(fn) {
  /** 常规操作,如果不是 Promise 实例,就先甩锅(报错)**/
  if (!(this instanceof Promise))
    throw new TypeError('Promises must be constructed via new');
    
  /** 如果入参 `fn` 不是函数,也报错,“我不管,就是你的错” **/
  if (typeof fn !== 'function') throw new TypeError('not a function');
  
  /** 初始化一系列状态变量,想象成各种监视 Promise 内部运转行为的各种监视器 **/
  this._state = 0;
  this._handled = false;
  this._value = undefined;
  this._deferreds = [];
  
  /** 开始调用传入 `fn` 函数,doResolve 方法解释在下方 ????  **/
  doResolve(fn, this);
}

/** 大伙儿咱们继续 **/
function doResolve(fn, self) {

  /** 先初始化 `done` 变量为 false,这个 `done` 变量挺有用的,确保 `resolve` 和 `reject` 只执行一次 **/
  var done = false;
  
  try {
    /** 立即执行传入的 fn(resolve,reject),注意这个 “立即执行”,敲黑板,这是考试重点!**/
    fn(
      /** 这里是 fn 的 resolve 回调**/
      function(value) {
        /** done 变量发挥了它的作用 **/
        if (done) return;
        done = true;
        
        /** 这里是 Promise 内部 resove 方法的调用,注意区分!!这个函数待会儿会讲**/
        resolve(self, value);
      },
      /** 这里是 fn 的 reject 回调**/
      function(reason) {
        /** done 变量再一次发挥了它的作用 **/
        if (done) return;
        done = true;
        /** 同样这里是 Promise 内部 reject 方法的调用**/
        reject(self, reason);
      }
    );
  } catch (ex) {
    if (done) return;
    done = true;
    reject(self, ex);
  }
}

总结一下:

  • Promise 必须通过构造函数实例化来使用

  • 传入 Promise 构造函数的形参 fndoResolve 方法内是 立即调用执行 的,并没有异步(指放入事件循环队列)处理

  • doResolve 内部针对 fn 函数的回调参数做了封装处理,done 变量 保证了 resolve reject 方法只执行一次,这在后面说到的 Promise.race()函数实现有很大用处。

一言以蔽之:Promise 的构造函数是比较 “鸡贼” 的,如果你不用 Promiser 而直接调 fn(resolve,reject) 方法就是普通的函数执行,但如果你将这个 fn 函数传入 Promise 构造函数,Promise 会帮你立即执行你的 fn,但是!它会 “狸猫换太子”,把你传给 fnresolvereject 更换成它自己内部的!—— 这样就方便操控 resolve 和 reject 的执行时机,顺带注入很多状态变量来监控运行状态;

1.1.1 内部变量介绍

Promise 实例的内部变量介绍,就是刚说的想象成各种监视 Promise 内部运转行为的各种 “监视器”:

名称类型默认值描述
_stateNumber0Promise 内部状态码,枚举值,可能值 0-3
_handledBooleanfalseonFulfilled,onRejected是否被处理过
_valueAnyundefinedPromise 内部值,resolve 或者 reject返回的值
_deferredsArray[]存放 Handle 实例对象的数组,缓存 then 方法传入的回调

1.1.2 _state 枚举值

_state 枚举值类型及其解释如下:

_state === 0  // pending,当前 Promise 正在执行中
_state === 1  // fulfilled, 表示执行了 `resolve` 函数,并且 `_value` instanceof Promise === true
_state === 2  // rejected, 表示执行了`reject` 函数
_state === 3  // fulfilled, 执行了 `resolve 函数,并且_value instanceof Promise === false

注意:这里 _state 区分了 1 和 3 两种状态,下面会解释原因,这里留个悬念。

这个 _state 枚举值和其所代表的的含义,最好记住,下面源码讲解中会反复查询这个变量值的含义。(我估计你后续还是会反复跳到这一小节查看的 ????)

1.1.3 Handler 构造函数

/**
 * Handle 构造函数
 * @param onFulfilled resolve 回调函数
 * @param onRejected reject 回调函数
 * @param promise 下一个 promise 实例对象
 * @constructor
 */
function Handler(onFulfilled, onRejected, promise) {
  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  this.promise = promise;
}

这个构造函数没有多少内容,作用也很直接,就是将 onFulfilledonRejectedpromise 三个内容 “打包起来” 作为一个整体方便后面调用 —— 这就是面向对象编程中“封装” 特性的体现。(额...那个...编程我会了,请问对象在哪儿领?)

1.1.4 _deferreds 数组

想象你中午去热门餐厅吃饭,等位的人很多,此时服务员会让你取个号,等叫到你的号了再入馆点餐。

这个_deferreds 数组的用法和餐厅的叫号等位功能是一模一样的:

  • 当在 Promise 内部调用了异步处理任务时,pro.then(onFulfilled, onRejected) 传入的两个函数不会立即执行;类比排位等号场景,当还没叫你号时,服务员并不关心你是否真的要点餐吃饭(onFulfilled),还是你因为前方排队人数太多而放弃点餐(onRejected),你的号码只是作为一个冰冷的数字放在等号系统里

  • 待到当前 Promise 的 resolve 或者 reject 触发调用后,才会去 forEach 这个 _deferreds 数组中的每个 Handle 实例去处理对应的 onFulfilled, onRejected 方法。类比排位等号场景,只有当服务员喊到你的号的时候,她才会关心这个号码的主人是还在坚持等位(onFulfilled)呢,还是已经放弃了(onRejected)

看到这里你还没放弃,那么恭喜你,距离吊打面试官的境界又进一步了!

1.2、 Promise 内部 resolve / reject / finale 方法

上面说到,doResolve 内部做了 fn立即执行(再强调一次),并保证 resolvereject 方法只执行一次。

1.2.1 resolvereject

那么,我们接下来说说 resolvereject 内部具体做了什么:

function resolve(self, newValue) {
  try {
  
    /** resolve 的值不能为本身 this 对象,不然要死循环了;虽说你可以狠起来连自己都打,但在 Promise 里不行 **/
    if (newValue === self)
      throw new TypeError('A promise cannot be resolved with itself.');
      
    /** 如果被 resolve 值为 Promise 对象的情况,特殊处理 **/
    if (
      newValue &&
      (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
      var then = newValue.then;
      if (newValue instanceof Promise) {
        /** 如果是 promise 对象,_state = 3 **/
        self._state = 3;
        self._value = newValue;
        finale(self);
        return;
      } else if (typeof then === 'function') {
        /** 兼容类 Promise 对象的处理方式,对其 then 方法继续执行 doResolve **/
        doResolve(bind(then, newValue), self);
        return;
      }
    }
    /** 被 resolve 的为正常值时的流程,_state = 1 **/
    self._state = 1;
    self._value = newValue;
    finale(self);
  } catch (e) {
    reject(self, e);
  }
}

function reject(self, newValue) {
  self._state = 2;
  self._value = newValue;
  finale(self);
}

虽说 resolve 看上去比较长,但你会发现它函数核心就三句代码,和 reject 是一致的:

  1. 设置 self._state

  2. newValue 赋值给 self._value

  3. 调用 finale 方法

毕竟 resolvereject 这两个函数的作用是等同的,所以操作的内容本质也必然等同,都是 三步走 —— 将变量转换成内部变量,方便 Promise 内部不同函数之间消费。

其实本文的读者内涵和这两个方法的 三步走 也有着异曲同工之妙:各位小哥哥小姐姐在读完本篇内容记得一键三连:评论转发再看!!

1.2.2 finale

我们回来继续分析,由于 resolvereject 这两个方法最终都会调用 finale 方法(只是_state 状态会有所不同),所以让我们探究一下 finale 方法:

/** finale 函数核心就是调用 Promise 内部的 handle 消费 self._deferreds 队列 **/
function finale(self) {

  /** Promise reject 时,如果此时 then 方法没提供 reject 回调函数参数 或者 未实现 catch 函数,就给警告 —— 你想想,作为一个 Promise 如果不知错就改,那还不得给个警告?**/ 
  if (self._state === 2 && self._deferreds.length === 0) {
    Promise._immediateFn(function() {
      if (!self._handled) {
        /** 这里给警告 **/
        Promise._unhandledRejectionFn(self._value);
      }
    });
  }

  /** 还记得上面等号点餐的例子不?这里相当于服务员执行 “叫号” 的操作 **/
  for (var i = 0, len = self._deferreds.length; i < len; i++) {
    /** self._deferreds[i] 存放的是 then 方法传入的 onFulfilled, onRejected 函数,类比于每个手持等位号的顾客 **/
    /** 这个 handle 方法是核心中的核心,待会儿专门有一节来讲 **/
    handle(self, self._deferreds[i]);
  }
  
  /** 处理完就废弃掉这个队列,类比于当点餐等位号都叫完了,喊号的服务员也就可以下班休息了 **/
  self._deferreds = null;
}

以刚才的排队点餐为例,这个 finale 函数相当于让服务员执行 “叫号” 操作,每个顾客需要看一下自己手里的号,从而做出不同的选择,要么沉默,要么喊“这是我的号~”

1.2.3 注意事项

resolvereject 是由用户在异步任务里面触发的回调函数 ,在调用 resolvereject 方法有以下几点注意事项。

注意事项 1:

newValue 不能为当前的 this 对象,即下面的这样写法是错误的,分分钟给你抛出一个错误:

const pro = new Promise((resolve)=>{
    setTimeout(function () {
    resolve(pro);
  },1000)}
);

pro.then(data => console.log(data)).catch(err => {console.log(err)});

因为 resolve 做了 try catch 的操作,直接会进入 reject 流程。

注意事项 2:

newValue 可以为另一个 Promise 对象类型实例resolve 的值返回的是另一个 Promise 对象实例的内部的 _value,而不是其本身 Promise 对象。即可以这样写:

const pro1 = new Promise((resolve)=>{
  setTimeout(function () {
  resolve(100);
},2000)});

const pro = new Promise((resolve)=>{
  setTimeout(function () {
  /** 调用上个 promise 实例对象 **/
  resolve(pro1);
},1000)});

pro.then(data => console.log('resolve' + data)).catch(err => {console.log('reject' + err)});

// 输出结果:resolve 100
// data 并不是 pro1 对象

具体原因就在 resolve 方法体内部做了 newValue instanceof Promise 的判断,并将当前的 _state=3self._value = newValue,然后进入 finale 方法体;最后在 handle 方法做了核心处理,这个下面介绍 handle 方法会说到;

注意事项 3:

这里有一个注意点,resolvevalue 可能是其他框架的 Promise (比如:global.Promise,nodejs 内部的 Promise 实现)构造实例,所以在 typeof then === 'function' 条件下做了 doResolve(bind(then, newValue), self); 的重新调用,继续执行当前类型的 Promise then 方法,即又重新回到了 doResolve 流程。

如果这里的实现方式稍微调整下,即不管newValue是自身的 Promise 实例还是其他框架实现的 Promise 实例,都执行 doResolve(bind(then, newValue), self) 也能行得通,只不过会多执行 then 方式一次,从代码性能上说,上面的实现方式会更好。参照代码如下

function resolve(self, newValue) {
  try {
    ...
    if (
      newValue &&
      (typeof newValue === 'object' || typeof newValue === 'function')
    ) {
      /** 这里简单粗暴处理,无论是 Promise 还是 global.Promise 都直接调用doResolve **/
      var then = newValue.then;
      if (typeof then === 'function') {
        doResolve(bind(then, newValue), self);
        return;
      }
    }
    ...
}

注意事项 4:当 Promise 出现reject的情况时,而没有提供 onRejected 函数时,内部会打印一个错误出来,提示要捕获错误。看一下两种作死现场:

const pro = new Promise((resolve,reject)=>{
  setTimeout(function () {
  reject(100);
},1000)});

pro.then(data => console.log(data));  // ???? 会报错
pro.then(data => console.log(data)).catch();  // ???? 会报错

再看一下正确打开方式:

pro.then(data => console.log(data)).catch(()=>{});  // ???? 不会报错
pro.then(data => console.log(data),()=>{})  // ???? 不会报错

上面源码解读中,有个 handle 内部方法,是核心中的核心,待会儿专门有一节会讲。

接下来咱们先看一下其他 API 方法的源码。

1.3、thencatchfinally 方法

理解上面两小节的内容,那么理解 thencatchfinally 方法方法就水到渠成,小菜一碟~

Promise 实例对象支持 then 方法来处理回调,支持无限链式调用;then 方法第一个参数成功回调,第二个参数失败或者异常回调:

function noop() {}

Promise.prototype.then = function(onFulfilled, onRejected) {
  var prom = new this.constructor(noop);
  handle(this, new Handler(onFulfilled, onRejected, prom));
  return prom;
};

Promise.prototype['catch'] = function(onRejected) {
  return this.then(null, onRejected);
};

Promise.prototype['finally'] = function(callback) {
  var constructor = this.constructor;
  return this.then(
    function(value) {
      return constructor.resolve(callback()).then(function() {
        return value;
      });
    },
    function(reason) {
      return constructor.resolve(callback()).then(function() {
        return constructor.reject(reason);
      });
    }
  );
};
  1. Promise.prototype.then方法内部构造了一个新的 Promsie 实例并返回,这样从 api 角度解决了 Promise 链式调用的问题,而且值得注意的是,每个 then 方法返回的都是一个新的 Promise 对象,并不是当前的 this 链接调用方式。最终的处理都会调用内部 handle 方法。

  2. catch方法在 then 方法上做了一个简单的封装,所以从这里也可以看出,then 方法的形参并不是必传的,catch 只接收 onRejected

  3. finally 方法不管是调用了 then 还是 catch,最终都会执行到 finally 的 callback

1.4、源码 C 位:handle 方法

上面说了这么多,最终的 resolvereject 回调处理都会进入到 handle 方法中,来处理 onFulfilledonRejected,毫无疑问这个 handle 方法是 Promise 源码中的 C 位。

接下来我们来解读一下这名默默在后台发挥作用的函数,走进它那波澜不惊的内心世界。

前方高能警告,虽说该 handle 源码不长,但阅读该 handle 源码将消耗你不少脑力,请及时补充能量~

先看源码:

/** 该方法你就认为是 setImmediate 的别名 **/
Promise._immediateFn =
  (typeof setImmediate === 'function' &&
    function(fn) {
      setImmediate(fn);
    }) ||
  function(fn) {
    setTimeoutFunc(fn, 0);
  };
  
/** 类比排队等号场景,这个 deferred 就是一个排位号子,该 handle 的操作过程就相当于:服务员检查当前拿号子的人是否可以进入餐厅点菜吃饭 **/
function handle(self, deferred) {
  /** 如果当前的self._value instanceof Promise,则将 self._value => self,接下来处理新 Promise,将执行权交给新的 Promise  **/
  while (self._state === 3) {
    self = self._value;
  }
  
  /** self._state=== 0 说明还没有执行 resolve || reject 方法,让 deferred 放入等候队列 —— 相当于新来了一个人想就餐,服务员让他取号等位,不能让他插队 **/
  if (self._state === 0) {
    self._deferreds.push(deferred);
    return;
  }
  
  /** 如果不是上述情况,标记当前进行的 promise._handled 状态量为 true **/
  self._handled = true;
  
  /** 通过事件循环异步来做回调的处理 **/
  Promise._immediateFn(function() {
 
    /** 如果自己有onFulfilled||onRejected方法,则执行自己的方法;如果没有,则调用下一个 Promise 对象的onFulfilled||onRejected **/
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
  
    /** 自己没有回调函数,进入下一个 Promise 对象的回调 **/
    if (cb === null) {
      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
      return;
    }
    
    /** 自己有回调函数,进入自己的回调函数 **/
    var ret;
    try {
      ret = cb(self._value);
    } catch (e) {
      reject(deferred.promise, e);
      return;
    }
    
    /** 处理下一个 Promise 的 then 回调方法,ret 作为上一个Promise then 回调 return的值 => 返回给下一个Promise then 作为输入值 **/
    resolve(deferred.promise, ret);
  });
}

情景 1self._state === 3,说明当前 resolve(promise) 方法回传的值类型为 Promise 对象, 即 self._value instanceOf Promise === true将 self=self._value, 即当前处理变更到了新的 Promise 对象上

情景 2:如果当前 promise 对象内部状态是 fulfilled 或者 rejected,则直接处理 onFulfilled 或者 onRejected 回调;如果仍然是 pendding 状态,则继续等待

这就很好的解释了下面代码中为什么 resolve(pro1), pro.then 的回调取的值却是 pro1._value

const pro1 = new Promise(resolve=>{
  setTimeout(()=>{resolve(100)}, 1000);
})  // 执行耗时1s 的异步任务
  
pro.then(()=>pro1)
    .then(data => console.log(data))
    .catch(err => {});
// 输出结果: 正常打印了100,data 并不是当前的 pro1 对象

pro1 内部是耗时1s 的异步任务,此时 self._state === 0,即内部是 pendding 状态,则将 deferred 对象 push 到 _deferreds 数组里面,然后继续等待 pro1 内部调用 resolve(100) 后,这才继续上面 resolve 方法体执行:

const pro1 = new Promise(resolve=>resolve(100)}) // 执行同步任务
pro.then(()=>pro1)
  .then(data => console.log(data))
  .catch(err => {});
// 输出结果: 正常打印了 100,data 并不是当前的 pro1 对象

但是如果 pro1 内部是同步任务,立即执行的话,当前的 self._state === 1,即调过 push 到 _deferreds 数组的操作,执行最后的 onFulfilled , onRejected 回调,onFulfilled, onRejected  会被放入到事件循环队列里面执行,即执行到了 Promise._immediateFn

Promise._immediateFn 回调函数放到了事件循环队列里面来执行 这里的 deferred 对象存放了当前的 onFulfilledonRejected 回调函数和下一个 promise 对象。

情景 3:当前对象的 onFulfilledonRejected,如果存在时,则执行自己的回调;

pro.then(data => data).then(data => data).catch(err => {});
// 正确写法: 输出两次  data

注意:then 方法一定要做 return 下一个值的操作,因为当前的 ret 值会被带入到下一个 Promise 对象,即 resolve(deferred.promise, ret)。如果不提供返回值,则第二个 then 的 data 会变成 undefined,即这样的错误写法:

pro.then(data => {}}).then(data => data).catch(err => {});
// 错误写法: 第二个 then 方法的 data 为 undefined

如果 onFulfilledonRejected 回调不存在,则执行下一个 promise 的回调并携带当前的 _value 值。即可以这样写:

pro.then().then().then().then(data => {}).catch(err => {});
// 正确写法: 第四个 then 方法仍然能取到第一个pro 的内部_value 值
// 当然前面的三个 then 写起来毫无用处

所以针对下面的情况:当第一个 then 提供了 reject 回调,后面又跟了个 catch 方法。当 reject 时,会优先执行第一个 Promise 的 onRejected 回调函数,catch 是在下一个 Promise 对象上的捕获错误方法:

pro.then(data => data,err => err).catch(err => err);

最终总结: resolve 要么提供带返回值的回调,要么不提供回调函数

核心的 handle 方法已经到这里已经讲完了,好好消化。。。

最后接下来还剩下两个静态方法,理解起来稍微轻松一些。

1.5、静态方法:race

Promise.race = function(values) {
  return new Promise(function(resolve, reject) {
    for (var i = 0, len = values.length; i < len; i++) {
    
      /** 因为doResolve方法内部 done 变量控制了对 resolve reject 方法只执行一次的处理 **/
      /** 所以这里实现很简单,清晰明了,最快的 Promise 执行了  resolve||reject,后面相对慢的 Promise 都不执行 **/
      values[i].then(resolve, reject);
    }
  });
};

用法

Promise.race([pro1,pro2,pro3]).then()

race 的实现非常巧妙,对当前的 values(必须是 Promise 数组) for 循环执行每个 Promise 的 then 方法,resolve, reject 方法对于所有 race 中 promise 对象都是公用的,从而利用  doResolve 内部的 done 变量,保证了 最快执行的 Promise 能做 resolve reject 的回调,从而达到了多个Promise race 竞赛的机制,谁跑的快执行谁

1.6、静态方法:all

Promise.all = function(arr) {
  return new Promise(function(resolve, reject) {
    if (!arr || typeof arr.length === 'undefined')
      throw new TypeError('Promise.all accepts an array');
    var args = Array.prototype.slice.call(arr);
    if (args.length === 0) return resolve([]);
    var remaining = args.length;

    function res(i, val) {
      try {
        /** 如果 val 是 Promise 对象的话,则执行 Promise,直到 resolve 了一个非 Promise 对象 **/
        if (val && (typeof val === 'object' || typeof val === 'function')) {
          var then = val.then;
          if (typeof then === 'function') {
            then.call(
              val,
              function(val) {
                res(i, val);
              },
              reject
            );
            return;
          }
        }
        
        /** 用当前resolve||reject 的值重写 args[i]{Promise} 对象 **/
        args[i] = val;
        
        /** 直到所有的 Promise 都执行完毕,则 resolve all 的 Promise 对象,返回args数组结果 **/
        if (--remaining === 0) {
          resolve(args);
        }
      } catch (ex) {
        /** 只要其中一个 Promise 出现异常,则全部的 Promise 执行退出,进入 catch异常处理 **/
        /** 因为 resolve 和 reject 回调有 done 变量的保证只能执行一次,所以其他的 Promise 都不执行 **/
        reject(ex);
      }
    }

    for (var i = 0; i < args.length; i++) {
      res(i, args[i]);
    }
  });
};

用法

Promise.all([pro1,pro2,pro3]).then()

all 等待所有的 Promise 都执行完毕,才会执行 Promise.all().then() 回调,只要其中一个出错,则直接进入错误回调,因为对于所有 all 中 promise 对象 reject 回调是公用的,利用 doResolve 内部的 done 变量,保证一次错误终止所有操作。

但是对于 resolve 则不一样, resolve 回调函数通过 res 递归调用自己,从而保证其值 _value 不为 Promise 类型才结束,并将 _value 赋值到 args 数组,最后直到所有的数组 Promise 都处理完毕由统一的 resolve 方法结束当前的 all 操作,进入 then 处理流程。

3、结束语

上面针对 Promise 的所有 api 做了详细的代码解释和使用场景。

或许你可能因为篇幅可能过长,看起来比较费力;也或者你是前端大佬,对此文的讲解因为太过于浅显而不屑一顾~

但是,这些都不重要...

重要的是求大侠们一键三连:评论转发再看 !!

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  • 点个【在看】,或者分享转发,让更多的人也能看到这篇内容

  • 关注公众号【全栈前端精选】,不定期分享原创&精品技术文章。

  • 公众号内回复:【 1 】。加入全栈前端精选公众号交流群。

欢迎评论区留下你的精彩评论~

觉得文章不错可以分享到朋友圈让更多的小伙伴看到哦~

客官!在看一下呗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现在Unity2D中点击某个物体后,物体改变速度,并在一段时间后恢复原始速度,可以按照以下步骤进行: 1. 创建物体:首先创建一个物体,可以使用Unity的模型编辑器或导入自定义模型。 2. 添加组件:给物体添加刚体组件和碰撞器组件,以便进行物理模拟。 3. 编写脚本:创建一个脚本来控制物体的速度变化和复原。在脚本中,可以使用协程来延迟一段时间后恢复原始速度。 ```csharp using UnityEngine; public class ObjectController : MonoBehaviour { public float originalSpeed = 5f; // 物体的原始速度 public float changedSpeed = 10f; // 物体改变后的速度 public float restoreDelay = 2f; // 物体恢复原始速度的延迟时间 private Rigidbody2D objectRigidbody; private float currentSpeed; void Start() { objectRigidbody = GetComponent<Rigidbody2D>(); currentSpeed = originalSpeed; } void Update() { // 点击鼠标左键时改变物体速度 if (Input.GetMouseButtonDown(0)) { Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); Collider2D collider = Physics2D.OverlapPoint(mousePosition); if (collider != null && collider.gameObject == gameObject) { ChangeSpeed(changedSpeed); StartCoroutine(RestoreSpeed(restoreDelay)); } } // 应用物体速度 Vector2 velocity = transform.up * currentSpeed; objectRigidbody.velocity = velocity; } void ChangeSpeed(float newSpeed) { currentSpeed = newSpeed; } System.Collections.IEnumerator RestoreSpeed(float delay) { yield return new WaitForSeconds(delay); currentSpeed = originalSpeed; } } ``` 4. 在场景中放置物体实例:在场景中放置一个物体的实例,并将物体控制脚本(ObjectController)添加到物体的GameObject上。 通过以上步骤,当点击该物体时,物体的速度将改变为指定的速度,并在一段时间后恢复原始速度。你可以根据需要调整原始速度、改变后的速度和恢复延迟时间。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值