Fruit-Ninja 源码结构简要解析
文件结构
- images 图片
- scripts 脚本
- sound 音频
- index.html 入口 html 文件
直接依赖
查看 index.html 代码可以直接发现依赖:
样式
- images/index.css
脚本
- scripts/all.js
依赖分析
脚本
void function(global){
var mapping = {}, cache = {};
global.startModule = function(m){
require(m).start();
};
global.define = function(id, func){
mapping[id] = func;
};
global.require = function(id){
if(!/\.js$/.test(id))
id += '.js';
if(cache[id])
return cache[id];
else
return cache[id] = mapping[id]({});
};
}(this);
all.js
首先第一段代码定义了三个主要的全局方法
- define: 定义模块,由
mapping
存储,模块本质是函数,这里参照 AMD 模块规范。 - require: 加载指定模块,模块名后缀.js 处理、
cache
缓存模块。 - startModule: 加载并启动模块,通过 start()函数可以确定模块内部必须实现 start 函数用以初始化模块。
剩下的全是 define 函数,把所有脚本注册到 mapping 里面,最后加载 main 模块 并启动。
main模块
all.js
内scripts/main.js
模块
var timeline = require( "timeline" );
var tools = require( "tools" );
var sence = require( "sence" );
var Ucren = require( "lib/ucren" );
var buzz = require( "lib/buzz" );
var control = require( "control" );
var csl = require( "object/console" );
var message = require( "message" );
var state = require( "state" );
先加载依赖,这里 require 是首次开始运行,它首先会补全该模块路径后缀,然后尝试读取 cache 缓存,如果之前有加载过就不会重复加载,如果是第一次加载则会从 mapping 模块中心找到指定 id 的模块函数并直接执行出结果,并在返回该结果前将之缓存。
我们接着看第一个依赖timeline
到底返回的是什么结果,找到它的 define 函数
define("scripts/timeline.js", function(exports){
/**
* a easy timeline manager
* @version 0.9
* @author dron
*/
var Ucren = require("scripts/lib/ucren");
/**
* initialize timeline
*/
exports.init = function(){
var me = this;
me.startTime = now();
me.count = 0;
// var interval = function(){
// me.count ++;
// update( now() );
// requestAnimationFrame( interval );
// };
// interval();
var time = 1;
// if( Ucren.isSafari )
// time = 10;
setInterval( function(){
me.count ++;
update( now() );
}, time );
};
/**
* create a task
* @param {Object} conf the config
* @return {Task} a task instance
*/
exports.createTask = function( conf ){
/* e.g. createTask({
start: 500, duration: 2000, data: [a, b, c,..],
object: module, onTimeUpdate: fn(time, a, b, c,..), onTimeStart: fn(a, b, c,..), onTimeEnd: fn(a, b, c,..),
recycle: []
}); */
var task = createTask(conf);
addingTasks.unshift( task );
adding = 1;
if( conf.recycle )
this.taskList( conf.recycle, task );
return task;
};
/**
* use a array to recycle the task
* @param {Array} queue be use for recycling task
* @param {Task} task a task instance
* @return {Array} this queue
*/
exports.taskList = function( queue, task ){
if( !queue.clear )
queue.clear = function(){
for(var task, i = this.length - 1; i >= 0; i --)
task = this[i],
task.stop(),
this.splice( i, 1 );
return this;
};
if( task )
queue.unshift( task );
return queue;
};
/**
* create a timer for once callback
* @param {Function} fn callback function
* @param {Number} time time, unit: ms
*/
exports.setTimeout = function( fn, time ){
// e.g. setTimeout(fn, time);
return this.createTask({ start: time, duration: 0, onTimeStart: fn });
};
/**
* create a timer for ongoing callback
* @param {Function} fn callback function
* @param {Number} time time, unit: ms
*/
exports.setInterval = function( fn, time ){
// e.g. setInterval(fn, time);
var timer = setInterval( fn, time );
return {
stop: function(){
clearInterval( timer );
}
};
};
/**
* get the current fps
* @return {Number} fps number
*/
exports.getFPS = function(){
var t = now(), fps = this.count / (t - this.startTime) * 1e3;
if(this.count > 1e3)
this.count = 0,
this.startTime = t;
return fps;
};
/**
* @private
*/
var Ucren = require("scripts/lib/ucren");
var tasks = [], addingTasks = [], adding = 0;
var now = function(){
return new Date().getTime();
};
var createTask = function( conf ){
var object = conf.object || {};
conf.start = conf.start || 0;
return {
start: conf.start + now(),
duration: conf.duration == -1 ? 86400000 : conf.duration,
data: conf.data ? [0].concat( conf.data ) : [0],
started: 0,
object: object,
onTimeStart: conf.onTimeStart || object.onTimeStart || Ucren.nul,
onTimeUpdate: conf.onTimeUpdate || object.onTimeUpdate || Ucren.nul,
onTimeEnd: conf.onTimeEnd || object.onTimeEnd || Ucren.nul,
stop: function(){
this.stopped = 1;
}
}
};
var updateTask = function( task, time ){
var data = task.data;
data[0] = time;
task.onTimeUpdate.apply( task.object, data );
};
var checkStartTask = function( task ){
if( !task.started ){
task.started = 1;
task.onTimeStart.apply( task.object, task.data.slice(1) );
updateTask( task, 0 );
}
};
var update = function(time){
var i = tasks.length, t, task, start, duration, data;
// TODO: 三八五时检查一下 tasks 有没有释放完成
// document.title = i;
while( i -- ){
task = tasks[i];
start = task.start;
duration = task.duration;
if( time >= start ){
if( task.stopped ){
tasks.splice( i, 1 );
continue;
}
checkStartTask( task );
if( ( t = time - start ) < duration )
updateTask( task, t );
else
updateTask( task, duration ),
task.onTimeEnd.apply( task.object, task.data.slice(1) ),
tasks.splice( i, 1 );
}
}
if( adding ){
tasks.unshift.apply( tasks, addingTasks );
addingTasks.length = adding = 0;
}
};;
return exports;
});
timeline 字面意思时间轴,先是引入了lib/ucren
依赖,发现这个依赖是烂代码没有用,接着为形参 exports(传入的实际参数是一个空对象)定义了一系列方法,包括:
- init: 初始化时间轴
- createTask: 创建任务
...
具体逻辑不作赘述,最后将 exports 作为结果返回了,于是 timeline 就有了 setTimeout 这个属性,
var setTimeout = timeline.setTimeout.bind( timeline );
bind 是继承自 Function 基类的一个属性,用于改变当前函数内部 this 指向,可以确定timeline.setTimeout
内有使用 this,
exports.setTimeout = function( fn, time ){
// e.g. setTimeout(fn, time);
return this.createTask({ start: time, duration: 0, onTimeStart: fn });
};
果不其然,this.createTask
同样是 timeline 才有的属性,才要绑定 timeline,不然运行时无法判断 this 指向的对象是否有这么一个属性去调用。
start 启动函数里面使用 invoke 分别调用了timeline, sence, control
的 init 方法
Array.prototype.invoke = function( method ){
var args = slice.call( arguments, 1 );
this.forEach(function( item ){
if(item instanceof Array)
item[0][method].apply( item[0], item.slice(1) );
else
item[method].apply( item, args );
});
return this;
};
打印日志,3 秒后执行 sence.switchSence.saturate,saturate同样是由Ucren库提供的Function静态方法,作用是返回一个函数,这个函数是原函数对象 this 忽略自己接受的参数,仅接受指定参数的版本,目前来看除了动了参数列表意外看不懂到底是干嘛的
Function.prototype.saturate = function(scope){
var fn = this, afters = slice.call( arguments, 1 );
return function(){
return fn.apply( scope, slice.call( arguments, 0 ).concat( afters ) );
}
};
接下来监听了两个自定义事件,slice
和slice.at
最后对运行环境进行判断并给出相关提示
相关链接
https://ucren.com/blog/archiv...
https://github.com/ChineseDro...