资讯专栏INFORMATION COLUMN

Fruit-Ninja 源码结构简要解析

scwang90 / 919人阅读

摘要:源码结构简要解析文件结构图片脚本音频入口文件直接依赖查看代码可以直接发现依赖样式脚本依赖分析脚本首先第一段代码定义了三个主要的全局方法定义模块,由存储,模块本质是函数,这里参照模块规范。

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.jsscripts/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 ) );
    }
};

接下来监听了两个自定义事件,sliceslice.at

最后对运行环境进行判断并给出相关提示

相关链接

https://ucren.com/blog/archiv...

https://github.com/ChineseDro...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/98631.html

相关文章

  • 【Vue原理】从模板到DOM的简要流程

    摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理从模板到的简要流程今天的计划是, 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基...

    wenzi 评论0 收藏0
  • webpack源码分析之一:文件打包

    摘要:当发现该路径为文件夹时则,则依次查找如下文件字段扩展名文件解析文件可以定位之后,则是解析定位下来的文件了,本文用的是,文档如规范文档解析文件返回一个语法树。对语法树进行遍历对遇到为且其为为的节点将该节点的以及下标包装成对象储存起来。 前言 自动化打包工具webpack,相信很多人和我一样尝试着研究下它,但是繁杂的功能以及高度抽象的代码实在是很难理解,所以笔者只能通过github的web...

    Richard_Gao 评论0 收藏0
  • 【Nginx源码分析】Nginx配置文件解析(一)

    摘要:本文将从源码从此深入分析配置文件的解析,配置存储,与配置查找。在学习配置文件的解析过程之前,需要先了解一下模块与指令的一些基本知识。 运营研发团队 李乐 配置文件是nginx的基础,对于学习nginx源码甚至开发nginx模块的同学来说更是必须深究。本文将从源码从此深入分析nginx配置文件的解析,配置存储,与配置查找。 看本文之前读者可以先思考两个问题: 1.nginx源码中随处可以...

    JasonZhang 评论0 收藏0
  • 【Vue原理】Component - 源码版 之 挂载组件DOM

    摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理源码版之挂载组件由这篇文章从模 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于...

    lbool 评论0 收藏0

发表评论

0条评论

scwang90

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<