因为工作需求我也开始接触这个框架,前面开始可能会有一些废话。我是希望分享我分析学习的一个过程。
假设是一个不太懂JS开发者,看到这些代码?应该会头晕脑胀吧,但是别怕,我逐步逐步来分析它。
我想说的是,在面对一大堆,我们看不懂的代码的时候,该怎么入手?
从基础入手,看这段代码,哪些是我们懂的?
cc 是什么?大家想想什么类型可以用点来访问?是不是只有对象才拥有属性?那我们知道cc是一个对象,虽然我们不知道它有什么用。
cc.game 是什么? cc是对象,通过点来访问它的game属性,game是cc的属性,那它又是个什么东西呢?是不是又通过点来访问一个属性?虽然我们不知道cc的game属性有什么用处?但是我们知道它是一个对象。
cc.game.onStart = funct 是什么?onStart是game通过点来访问的,它是game的属性,然后给赋值为一个函数,我们虽然不知道game的onStart方法有什么用处?但是我们知道它是一个方法。
不知道大家有没有感觉,它其实也没有那么ko怕,就一个对象内的属性或方法而已。
接下去该分析哪里呢?onStart方法?不是的,想想,其实写JS是不是,总归就是 执行 与 赋值,还会有其他?可能我考虑的很浅,但我们暂且把一个js的文件分为 赋值 和 执行,那么在想想赋值和执行。在这个main.js,就一个赋值,和一个执行,哪个才是入口点呢?(当我们分清楚了这个页面的无非是给对象赋值,和执行对象的方法。)
想想cc.game.onStart赋值是为了什么,为了接下来可以执行它,main.js并没有执行cc.game.onStart方法。但会发现在给onStart赋值以后,执行了cc.game对象的run方法。
不难想到,cc.game.run方法必定执行了cc.game.onStart()方法。
所以我们先来定位cc.game.run方法,定位方法有很多,我这边是直接用谷歌浏览器console.log(cc.game.run);,在右边会看到。
来看一下cc.game.run方法,它做了3件事:
1.定义了个self变量,赋值this,这里的this指向哪里?cc对象?不是的!This指向的是cc.game对象,run方法是隶属于cc.game对象而不是cc对象,简单的断点一下, this === cc.game 观看返回值即可。
2.定义了个_run函数,至于执行了什么,待我们执行该函数,在来分析。
3.document.body? Func1():Func2(),这里的是一个三元表达式,有没有这个document.body对象,决定了接下去执行的方向,(这里呢?因为源码太多了,不可能就是全部都分析,只能跟着正常情况走),那么正常情况下document.body是body元素,那什么情况不会,document.body为空false呢?试着在head标签的尾部添加一个script标签,然后断点一下,审查元素,打印一下。
这时发现,在这种情况下,document.body为false 执行 cc._addEventListener()方法,其实看到这个方法名,大家应该都很熟悉,直接来定位或者打印一下。
就是给window对象绑定load事件,当Dom加载完毕,解绑this所指向的对象(window)的load事件,解绑的时候必第二个参数必须是绑定的方法,arguments.callee其实就是
解绑完毕后,执行 _run方法,这样做是为了防止Dom没加载完毕,就执行_run方法。
来到_run方法,这里面有3个if语句
1.第一个if语句是判断id,但我们在执行cc.game.run方法的时候并没有值,所以这里铁定不是走的,但是,我们可以试着打印里面的值来挖掘一下这东西,到底有什么用,
看到这2个字符串有何感想,gameCanvas 是不是和index.html的canvas元素id值,self在_run方法的作用域下面并没有声明,那往上走在cc.game.run方法的作用域下找到self。
现在知道self其实就是cc.game对象,
如果我们要改变要改变canvas的id的话,那么执行cc.game.run方法,时候,传参我们改变id的name就可以了。
2.第二个if语句是取反cc.game._prepareCalled 属性为false的时即执行cc.game.prepare方法,默认的cc.game._prepareCalled 属性为false,所以这个if语句是必定执行的。
那来走一下cc.game.prepare方法:
(在分析这个方法之前我看了一下,发现这个方法,不想之前遇到那些方法一样简短,在这个方法定义的几个变量,都是保存了对象或者是属性,所以从表面来看,除非你对cocos2d-js这个框架很熟悉,不然的话单纯从变量名用意来得知它的用意)
所以这里的变量我先通过打印它的值来罗列一下,待要用到的时候,才来分析它们。这张图算是prepare这个方法的上半段吧。
2.1.这里是先罗列打印了5个变量
2.2接下来是一个if语句,cc._supportRender属性,这里我打印了一下是true,取反的话这个if语句是不会被执行的,执行内容是自定义了错误。反过来想想这东西既然可能是true那么也可能是false,那不然它写这个有什么意义,我这不是废话呢,那么这个cc._supportRender属性应该是判断是否支持某些功能给它赋值。看一下翻译了错误信息,得知是:渲染器不支持rendermode。所以这里来Ctrl+F一下设置cc._supportRender属性位置。往上查找,会找到一个匿名自执行函数,尝试断点一下,会发现,该匿名函数执行在ccBoot.js内,在main.js之前引入,初始化的时候,这个匿名自执行函数会在我们执行cc.game.run之前执行。
来分析一下,还是老思路,变量太多了,罗列打印一下。
看到这列值,其实没什么好说的,抓几个来说,sys形参的OS_ANDROID属性是字符串,加了括号,赋值给shideldOs就成数组了。cc.newElement方法,return document.createElement,这里是创建了个canvas元素。我们的主角cc._supportRender这里被设置成为false。
还有就是win.WebGLRenderingContext,会发现自执行函数并没有win变量,我打印了一下是window对象,估计是上级作用域定义了,有这个win.WebGLRenderingContext方法的浏览器便支持canvas3D。
接下来是2个if语句:
其实不看判断语句,单从执行的语句来看,不难发现第一条if语句是执行3D的,第二条是支持2D的,在第一条if语句会看到逻辑与supportWebGL,supportWebGL保存了window.WebGLRenderingContext,如果浏览器不支持,第一个if语句是不会通过的。我这里用的是谷歌浏览器所以走的是第一条if语句。
这里再次做了个if语句,必须cc.create3DContext方法执行完毕后,返回值不为false,才把主角cc._supportRender设置为true。来打印一下cc.create3DContext方法:
定义一串数组,这是启动canvas3D的各个浏览器名字,应该是canvas3D各个浏览器还未统一吧,for循环,创建成功即break跳出循环,返回context,如果4个都不行的话,返回就是null。
cc._supportRender的初始化设置就到一段落了。
好,继续回到cc.game.prepare方法(上半段的最后2个赋值语句)
设置了cc.game._prepareCalled属性的值为true,jsList是一个数组,有什么用呢? 留一个疑问
3.cc.game.prepare方法(下半段):下半段是一个 if else语句:
我打印了一下cc.Class,结果是undefined,我试着检索了一下,CCBoot.js,并没有设置cc.Class属性的,所以这里试着来看一下loader.loadJsWithImg方法。loader保存的是cc.loader。定位一下。
断点,强制设置cc.Class为true。(注意,这里的cc.Class,应该是哪里出现问题cc.Class才会为true,从而走这条路线呢?我们这里没有遇到这个问题,但是还是分析一下,最后分析完这条路线,应该总结一下,什么情况才会走这条路,所以这一段会比较枯燥,也可能不准确,因为我们是强制执行它的,参数什么的也可能不对,导致我们曲解它最终要实现的效果)
3.1 Self保存的是 cc.loader
3.2 jsLoadingImg是保存了执行cc.loader._loadJsImg方法的 返回值。定位一下这个方法:
d.getElementById(cocos2D_loadJsImg)这个是什么,打开index.html的时候,会发现一开始有一个load动画,断点一下会发现:
If语句是取反,没有这个元素的即,重新创建一个img元素,设置src,添加到画布内等等等。。。,最后都是返回这个元素。
3.3 agrs是保存了执行cc.loader._getArgs4Js方法的 返回值。定位一下这个方法:
这里最终返回results,初始化results数组,反正这里就是根据args的长度,来保存设置results的值得。
3.4 执行cc.loader.loadJs方法。定位一下这个方法:
3.4.1 self保存的是cc.loader
3.4.2 args 同 3.3
3.4.3 preDir,list,callback
这个一直是我们执行cc.loader.loadJsWithImg的传参
3.4.4 (navigator.userAgent.indexOf("Trident/5") > -1)判断浏览器是否包含Trident/5,网上检索一下这是来判断win7的ie9,即执行cc.loader._loadJs4Dependency,
3.4.4.1 jsList:["src/resource.js", "src/app.js"] 长度 小于等于0 执行 cb(异常方法)
3.4.4.2 self保存的是cc.loader
3.4.4.3 执行 cc.loader._createScript 来定位一下这个方法:
看到这里整个头晕,(这里不是用ie9)抓重点讲,创建 script元素,然后来判断,判断传参jsPath["src/resource.js", "src/app.js"],设置异步,给script 绑定load/error事件,加载完毕删除元素,解除绑定事件。添加到body元素。Js就会进行加载了,走到这里可以判断,cc.Class为true的情况,可能是处理没加载到 没加载 到project.json文件内jsList设置好的链接。
3.4.5 因为我用的是谷歌浏览器,不是ie9,继续往下走,执行cc.async.map方法,来定位一下这个方法:
3.4.5.1 locTterator 保存了 形参iterator(一个函数)
3.4.5.2 形参iterator是一个function 所以这个if语句不会走。。。
3.4.5.3 构造了一个asyncPool对象,并且执行asyncPool对象下的方法flow,最后是返回asyncPool,所以这里来看创建这个对象后执行flow方法,有什么用。。。定位一下:
这里要说的是不直接分析这个构造函数,而且直接来看flow的方法实现了什么功能,因为一个构造函数,构造一个对象,一个对象可能会有很多属性,很多方法,在不知道这个对象到底的干什么的用的时候来分析它,感觉会有点枯燥。在这之前我是打算直接,分析这个构造函数的,但是发现特别。。,并且中文api文档并没有这个的解释,英文的有解释,也就2句。。所以这里就直接看它的方法是实现什么功能。
3.4.5.3.1 Self 指向 构造出来的对象,指向 cc.AsyncPool.prototype
3.4.5.3.2 if语句,条件this._pool属性的长度不等于0,所以这里是不会执行。
3.4.5.3.3 for循环 执行 cc.AsyncPool.limit次数的 cc.AsyncPool._handleeItem方法,到这这里,在来看cc.AsyncPool构造函数的属性就不那么枯燥了,次数是怎么得来的呢?
如果一开始就来分析这些属性,估计也说不出个之所以然来,来看一下 构造对象的时候传入的参数
Cc.each,的方法实现相信大家不陌生的,这里就不说了,遍历数组srcObj以对象的形式{index:num,value:string}push进this._pool数组,所以这里的this.size的保存了this._pool数组的长度,最后,this._limit 用逻辑或来赋值,当然不会是本身,本身是0;
好,现在知道执行次数,this._handleItem来看一下这个方法实现的了什么功能?
3.4.5.3.4 Self 指向 构造出来的对象,指向 cc.AsyncPool.prototype
3.4.5.3.5 item保存了self._pool数组删除掉的第一个元素值。要执行2次,下次在执行的话,保存的就是当前的第二个元素,self._pool数组也为空。
3.4.5.3.6 value保存了item对象的value属性,index保存了item对象的index属性。
3.4.5.3.7 改变用self-_iterator 方法的对象为 self._iteratorTarget(这里是undefined),
这里的self._iteratorTarget是undefined,然后我再试着在 self._iterator方法内断点,打印this,或者是模拟一个函数,把对象伪装成undefined,结果this都指向window对象。所以也就这样。
3.4.5.3.8 执行cc.path.join方法,接受2个值,返回赋值给 jsPath,来定位一下方法。
遍历传参,返回去除指定字符后拼接成链接。
3.4.5.3.9 if语句内localJsCache对象为空,也就是说这个语句不会触发。
3.4.5.3.10 self._createScript这个方法,是不是很熟悉,在前面 ie9的情况
所以最后大胆的猜测。来总结cc.Class,可能是project设置了的文件没加载到,所以它们会重复请求,还是没有的话就会抛出异常。但是经过测试,如果就加载project预设的文件走的不是cc.Class这个if语句。所以大体通过给cc.Class赋值为true,也算是分析了它干了写什么。
这一章就到这里,希望下面会解决这个疑问。最后就是关于暴力分析backbone.js的事情,因为工作原因,我又得投入其他学习,所以有空尽量更新,把它写好点。