作用:设置一个定时器,在指定时间(毫秒)后触发,调用传入的回调函数。
参数类型:(function/code, delayTime, args…)
function: 回调函数
code: 字符串形式的代码,会使用eval()执行,因此不推荐使用
delayTime: (可选) 延迟的时间(毫秒),默认0,尽快执行。
args: (可选) 不定长参数列表,会传递给回调函数
作用:设置一个定时器,每隔一段时间触发一次,调用传入的回调函数。
参数类型:(function/code, delayTime, args…)
和sertTimeout一致,区别是setInterval会一直执行回调函数,但setTimeout**仅执行一次。**delayTime就是每次间隔的时间。
// UserInfoView.js
cc.Class({
extends: cc.Component,
ctor () {
// 定义测试变量
this.testInfo = 222;
},
start () {
// 1. 箭头函数
setTimeout(() => {
// do something
}, 10000);
// 2. bind方式
setTimeout(function() {
// do something
cc.log(this); // 指向当前组件 UserInfoView{xxxxx}
cc.log(this.testInfo); // 222
}.bind(this), 1000);
// 3. 直接传递
setTimeout(function() {
// do something
cc.log(this); // 指向Window对象
cc.log(this.testInfo); // undefined
}, 1000);
}
作用:结合了setTimeout和setInterval,如官方文档所说,提供了更大的灵活性,可以设置执行次数、第一次的延迟以及触发间隔时间。注:该方法定义于cc.Component,故只能在继承cc.Component的对象上调用(从编辑器创建的js类会默认继承cc.Component)。
参数列表:(callback, interval, repeat, delay)
callback: 回调函数
interval: 触发间隔(秒)
repeat: 重复次数,可设置cc.macro.REPEAT_FOREVER让它一直重复。注:实际执行次数是重复次数+1
delay: 延迟时间(秒)
// CCComponent.js cocos2d\core\components\CCComponent.js
schedule (callback, interval, repeat, delay) {
// 1619: callback function must be non-null 回调函数不能为空
cc.assertID(callback, 1619);
// 默认触发间隔为0
interval = interval || 0;
// 1620:interval must be positive 间隔必须是有效的(>=0)
cc.assertID(interval >= 0, 1620);
// repeat不是数字时, 默认为无限重复
repeat = isNaN(repeat) ? cc.macro.REPEAT_FOREVER : repeat;
// 默认延迟时间为0
delay = delay || 0;
// 获得获取系统定时器(是唯一的,cc.director是单例的,在init方法中创建了一个Scheduler定时器)
// scheduler: cc.Scheduler类型 定义于engine/cocos2d/core/CCScheduler.js
var scheduler = cc.director.getScheduler();
// scheduler内部维护了一个叫_hashForTimers的键值对,通过传入的this可以获得一个HashTimerEntry对象(定义于CCScheduler)
// HashTimerEntry中保存了paused属性(boolean),当组件_onDisabled/_onEnabled时,paused会被设置为true/false
// 也有可能是_hashForUpdates。_hashForTimers保存的是自定义定时器的信息,_hashForUpdates保存的是update定时器的信息
var paused = scheduler.isTargetPaused(this);
// 调用CCScheduler中的方法,这里传递的target为this,所以回调函数不需要bind/使用箭头函数
scheduler.schedule(callback, this, interval, repeat, delay, paused);
}
// CCScheduler.js cocos2d\core\CCScheduler.js
schedule: function (callback, target, interval, repeat, delay, paused) {
'use strict';
if (typeof callback !== 'function') {
// 交换callback和target的值
var tmp = callback;
callback = target;
target = tmp;
}
// 适配不同参数长度
//selector, target, interval, repeat, delay, paused
//selector, target, interval, paused
if (arguments.length === 4 || arguments.length === 5) {
paused = !!repeat;
repeat = cc.macro.REPEAT_FOREVER;
delay = 0;
}
// 1502:cc.scheduler.scheduleCallbackForTarget(): target should be non-null. target不能为空
cc.assertID(target, 1502);
// 获得组件的id
var targetId = target._id;
if (!targetId) {
if (target.__instanceId) {
// 1513:cc.Scheduler: scheduler stopped using `__instanceId` as id since v2.0, you should do scheduler.enableForTarget(target) before all scheduler API usage on target
// 提示2.0版本后不再使用__instanceId属性 这里应该是为了保持兼容
cc.warnID(1513);
targetId = target._id = target.__instanceId;
}
else {
// 1510:cc.Scheduler: Illegal target which doesn't have uuid or instanceId.
// target非法,没有uuid和instanceId
cc.errorID(1510);
}
}
// 通过targetId获得对应的定时器实例(HashTimerEntry),有点拗口的感觉,里面保存了timers target paused等属性
var element = this._hashForTimers[targetId];
if (!element) {
// 没有取到实例 使用get方法从对象池(_hashTimerEntries)中获得一个实例
// Is this the 1st element ? Then set the pause level to all the callback_fns of this target
element = HashTimerEntry.get(null, target, 0, null, null, paused);
this._arrayForTimers.push(element);
this._hashForTimers[targetId] = element;
} else if (element.paused !== paused) {
// 1511:cc.Scheduler: pause state of the scheduled task doesn't match the element pause state in Scheduler, the given paused state will be ignored.
// 定时器实例中的paused属性和传入的paused不匹配,传入的值会被忽略(可能在创建的过程中,组件disable/enable状态改变了)
cc.warnID(1511);
}
var timer, i;
// 定时器列表(timers)为空 初始化为空数组
if (element.timers == null) {
element.timers = [];
}
else {
// 遍历列表 判断是否有相同的回调函数
for (i = 0; i < element.timers.length; ++i) {
timer = element.timers[i];
if (timer && callback === timer._callback) {
// 1507:CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %s to %s"
// 回调已存在,将更新interval属性
cc.logID(1507, timer.getInterval(), interval);
timer._interval = interval;
return;
}
}
}
// 获得一个定时器实例(CallbackTimer),这里也有一个对象池(_timers)
timer = CallbackTimer.get();
// 初始化 里面的代码基本类似 _callback = callback 这样的赋值
timer.initWithCallback(this, callback, target, interval, repeat, delay);
// 加到定时器列表中
element.timers.push(timer);
// 修改_currentTargetSalvaged防止当前的HashTimerEntry被删除 这个在update函数中会有相关解释
if (this._currentTarget === element && this._currentTargetSalvaged) {
this._currentTargetSalvaged = false;
}
}
以上就是创建计时器的代码,感觉许多代码量都在做一些验证、兼容之类的操作,最后创建计时器相关对象储存起来,而触发回调的代码则是在update函数中
// scheduler的update函数在cc.director.mainLoop()中触发
// CCDirector.js cocos2d\core\CCDirector.js
this._scheduler.update(this._deltaTime);
// 而director的mainLoop函数在cc.game._runMainLoop()中触发
// CCGame.js cocos2d\core\CCGame.js
_runMainLoop: function () {
// 省略部分代码
callback = function (now) {
if (!self._paused) {
self._intervalId = window.requestAnimFrame(callback);
if (!CC_JSB && !CC_RUNTIME && frameRate === 30) {
if (skip = !skip) {
return;
}
}
// 在这~
director.mainLoop(now);
}
};
// 使用requestAnimFrame设置一个每帧触发的回调,会在下一次的重绘之前被调用,一般来说60次/秒。
// 详情可参考MDN文档https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
self._intervalId = window.requestAnimFrame(callback);
self._paused = false;
}
// CCScheduler.js cocos2d\core\CCScheduler.js
/**
* !#en 'update' the scheduler. (You should NEVER call this method, unless you know what you are doing.)
* !#zh update 调度函数。(不应该直接调用这个方法,除非完全了解这么做的结果)
* @method update
* @param {Number} dt delta time
*/
update: function (dt) {
// boolean属性 加锁 避免执行update定时器过程中出现删除操作
this._updateHashLocked = true;
// 时间缩放 可以实现慢/快动作
if(this._timeScale !== 1)
dt *= this._timeScale;
var i, list, len, entry;
// 此处省略触发update定时器的代码
// Iterate over all the custom selectors
// 遍历自定义定时器
var elt, arr = this._arrayForTimers;
for(i=0; i<arr.length; i++){
elt = arr[i];
this._currentTarget = elt;
this._currentTargetSalvaged = false;
// 目标没有被停止则执行
if (!elt.paused){
// The 'timers' array may change while inside this loop
// 遍历定时器列表。列表有可能在循环过程中被改变(repeat次数执行完了)
for (elt.timerIndex = 0; elt.timerIndex < elt.timers.length; ++(elt.timerIndex)){
elt.currentTimer = elt.timers[elt.timerIndex];
elt.currentTimerSalvaged = false;
// 调用定时器的update函数
elt.currentTimer.update(dt);
elt.currentTimer = null;
}
}
// only delete currentTarget if no actions were scheduled during the cycle (issue #481)
// _currentTargetSalvaged在上面被设为false,但仍有可能在update函数执行后被改变(repeat次数执行完了)
// 但是不会直接回收,而是通过设置_currentTargetSalvaged,在这里统一回收。(避免了重复回收?)
if (this._currentTargetSalvaged && this._currentTarget.timers.length === 0) {
this._removeHashElement(this._currentTarget);
--i;
}
}
// 此处省略清理update定时器的代码
// 解除锁
this._updateHashLocked = false;
this._currentTarget = null;
}
// CallbackTimer 定义于cocos2d\core\CCScheduler.js
/**
* triggers the timer
* @param {Number} dt delta time
*/
proto.update = function (dt) {
// _elapsed: boolean 离上一次触发的事件 调用CallbackTimer.initWithCallback()的时候会被设为-1
if (this._elapsed === -1) {
// 第一次触发的时候 重置_elapsed和_timesExecuted,(所以定时器是在下一帧开始才正式生效的?)
this._elapsed = 0;
this._timesExecuted = 0;
} else {
// 累加_elapsed 实现delay和interval的效果
this._elapsed += dt;
// 当前计时器是一直循环的且不需要延迟
if (this._runForever && !this._useDelay) {//standard timer usage
// 标准触发流程
// _elapsed达到interval间隔时间,触发事件,重置_elapsed
if (this._elapsed >= this._interval) {
this.trigger();
this._elapsed = 0;
}
} else {//advanced usage
// 高级用法(?)
if (this._useDelay) {
// 需要延迟 达到延迟时间的时候触发
if (this._elapsed >= this._delay) {
this.trigger();
// 扣除延迟的时间、已执行次数+1、重置延迟状态为false
this._elapsed -= this._delay;
this._timesExecuted += 1;
this._useDelay = false;
}
} else {
// 不需要延迟(或延迟已经触发过了) 达到间隔时间触发
if (this._elapsed >= this._interval) {
this.trigger();
// 重置_elapsed、已执行次数+1
this._elapsed = 0;
this._timesExecuted += 1;
}
}
// 设置了repeat次数的定时器,执行次数大于_repeat次数的时候 取消这个定时器
// 这就是上面的update中提到的定时器列表有可能被改变
if (this._callback && !this._runForever && this._timesExecuted > this._repeat)
this.cancel();
}
}
};
// 触发回调
proto.trigger = function () {
if (this._target && this._callback) {
// 加锁 放回对象池中的时候会判断_lock是否为true
this._lock = true;
// 调用回调函数
this._callback.call(this._target, this._elapsed);
this._lock = false;
}
};
start () {
this.testInfo = 222;
this.schedule((dt)=>{
// 每0.1s执行一次
cc.log("回调1", dt);
}, 0.1);
this.schedule((dt)=>{
// 每0.1s执行一次,重复一次(总共执行两次)
cc.log("回调2", dt);
}, 0.1, 1);
this.schedule((dt)=>{
// 每0.1s执行一次,不重复,延迟0.2秒后触发
cc.log("回调3", dt);
cc.log("测试this", this.testInfo);
}, 0.1, 0, 0.2);
this.schedule((dt)=>{
// 0.5s后触发,测试active对计时器的影响
cc.log("回调4", dt);
this.node.active = false;
}, 0.5);
}
// 输出1
// 回调1 0.10002300000000003
// 回调2 0.10002300000000003
// 回调1 0.10004099999999994
// 回调2 0.10004099999999994
// 回调3 0.200064
// 测试this 222
// 回调1 0.11671000000000005
// 回调1 0.10003199999999993
// 回调4 0.5000120000000001
// 输出2
// 回调1 0.10002899999999999
// 回调2 0.10002899999999999
// 回调3 0.20002500000000004
// 测试this 222
// 回调1 0.11670800000000003
// 回调2 0.11670800000000003
// 回调1 0.116745
// 回调1 0.11657899999999996
// 回调4 0.5001940000000001
秋招挺凄惨的,想去的都挂了。主要原因还是自己以前对基础知识不够重视,这个结局算是还债吧。
因为一些原因,开始复习的也比较晚。复(yu)习的时候就想顺便写点东西记录一下。
文章是真的难写啊淦(x 很怕写错。
应该是第一次相对深入的读cocos creator的源码,还挺有意思的。但是毕竟还是小菜鸡,有纰漏还望大家多多包涵2333。