在Bootstrap.js文件中,总共有1500行(包含注释和空行),使用编辑器的代码折叠功能就如下图可以一窥全貌了。
从代码可以看到,这里主要定义了Ext.Boot、Ext.globalEval、Ext.Microloader和Ext.manifest这4个对象或属性。关键代码是最后一句的调用Ext.Microloader的load方法,下面来研究一下这个load方法,代码如下:
load: function (manifestDef) { var manifest = Microloader.initManifest(manifestDef), loadOrder = manifest.loadOrder, loadOrderMap = (loadOrder) ? Boot.createLoadOrderMap(loadOrder) : null, urls = [], js = manifest.js || [], css = manifest.css || [], resources = js.concat(css), resource, i, len, include, loadedFn = function () { _loaded = true; Microloader.notify(); }; for (len = resources.length, i = 0; i < len; i++) { resource = resources[i]; include = true; if (resource.platform && !Microloader.filterPlatform(resource.platform)) { include = false; } if (include) { urls.push(resource.path); } } if (loadOrder) { manifest.loadOrderMap = loadOrderMap; } Boot.load({ url: urls, loadOrder: loadOrder, loadOrderMap: loadOrderMap, sequential: true, success: loadedFn, failure: loadedFn }); },
代码第一句执行了Microloader的initManifest方法,代码如下:
initManifest: function (manifest) { Microloader.init(); var tmpManifest = manifest || Ext.manifest; if (typeof tmpManifest === "string") { var url = Boot.baseUrl + tmpManifest + ".json", content = Boot.fetchSync(url); tmpManifest = JSON.parse(content.content); } Ext.manifest = tmpManifest; return tmpManifest; },
根据load方法的调用,可以知道manifest为null,不过这里第一句又先调用了Microloader的init方法,代码如下:
init: function () { Microloader.initPlatformTags(); Ext.filterPlatform = Microloader.filterPlatform; },
又要跳到initPlatformTags方法,快给转晕了,代码如下:
initPlatformTags: function () { Microloader.platformTags = Microloader.detectPlatformTags(Microloader.platformTags); },
还跳,这里省去n字,继续去看detectPlatformTags方法,代码如下:
detectPlatformTags: function (tags) { var ua = navigator.userAgent, isMobile = tags.isMobile = /Mobile(\/|\s)/.test(ua), isPhone, isDesktop, isTablet, touchSupported, isIE10, isBlackberry, element = document.createElement(‘div‘), uaTagChecks = [ ‘iPhone‘, ‘iPod‘, ‘Android‘, ‘Silk‘, ‘Android 2‘, ‘BlackBerry‘, ‘BB‘, ‘iPad‘, ‘RIM Tablet OS‘, ‘MSIE 10‘, ‘Trident‘, ‘Chrome‘, ‘Tizen‘, ‘Firefox‘, ‘Safari‘, ‘Windows Phone‘ ], isEventSupported = function(name, tag) { if (tag === undefined) { tag = window; } var eventName = ‘on‘ + name.toLowerCase(), isSupported = (eventName in element); if (!isSupported) { if (element.setAttribute && element.removeAttribute) { element.setAttribute(eventName, ‘‘); isSupported = typeof element[eventName] === ‘function‘; if (typeof element[eventName] !== ‘undefined‘) { element[eventName] = undefined; } element.removeAttribute(eventName); } } return isSupported; }, uaTags = {}, len = uaTagChecks.length, check, c; for (c = 0; c < len; c++) { check = uaTagChecks[c]; uaTags[check] = new RegExp(check).test(ua); } isPhone = (uaTags.iPhone || uaTags.iPod) || (!uaTags.Silk && (uaTags.Android && (uaTags[‘Android 2‘] || isMobile))) || ((uaTags.BlackBerry || uaTags.BB) && uaTags.isMobile) || (uaTags[‘Windows Phone‘]); isTablet = (!tags.isPhone) && ( uaTags.iPad || uaTags.Android || uaTags.Silk || uaTags[‘RIM Tablet OS‘] || (uaTags[‘MSIE 10‘] && /; Touch/.test(ua)) ); touchSupported = // if the browser has touch events we can be reasonably sure the device has // a touch screen isEventSupported(‘touchend‘) || // browsers that use pointer event have maxTouchPoints > 0 if the // device supports touch input // http://www.w3.org/TR/pointerevents/#widl-Navigator-maxTouchPoints navigator.maxTouchPoints || // IE10 uses a vendor-prefixed maxTouchPoints property navigator.msMaxTouchPoints; isDesktop = !isPhone && !isTablet; isIE10 = uaTags[‘MSIE 10‘]; isBlackberry = uaTags.Blackberry || uaTags.BB; apply(tags, Microloader.loadPlatformsParam(), { phone: isPhone, tablet: isTablet, desktop: isDesktop, touch: touchSupported, ios: (uaTags.iPad || uaTags.iPhone || uaTags.iPod), android: uaTags.Android || uaTags.Silk, blackberry: isBlackberry, safari: uaTags.Safari && isBlackberry, chrome: uaTags.Chrome, ie10: isIE10, windows: isIE10 || uaTags.Trident, tizen: uaTags.Tizen, firefox: uaTags.Firefox }); if (Ext.beforeLoad) { tags = Ext.beforeLoad(tags); } return tags; },
好了,这次不用再跳了。代码先调用navigator.userAgent返回了浏览器用于 HTTP 请求的用户代理头的值,这个值可用来检查浏览器和版本号。如果值包含了字符串Mobile,说明是移动设备,这时候isMobile为true。在定义了一堆变量后,在页面中添加了一个div元素。接下来的uaTagChecks根据变量名可以知道,这是要检测的标记了。
接下来定义了isEventSupported函数,看名字就知道是用来检测是否支持事件的。根据函数内容,可以看到检测方式有两种,第一种就是检测事件名是否在刚才创建的元素div内,如果在,说明支持。第二种方法就是div元素上添加事件属性,然后判断元素对象内的事件属性是否为function,如果是,说明支持,否则就是不支持了。
定义结束后,就开始使用循环来检测平台属性了,检测结果将保存在uaTags对象中,对象中的属性名称就是uaTagChecks中的字符串,值就是检测值。
检测完之后就要给几个变量赋值了,赋值完成后,会调用apply方法将对象的成员复制到tags中。在调用apply方法时,还调用了loadPlatformsParam方法,该方法我就不列了,它的主要作用就是可通过访问地址的platformTags参数来自定义平台参数,这样做的目的是可以通过浏览器做一些模拟效果,如桌面pc模拟平板的效果。
下一句判断Ext.beforeLoad是否存在,在当前情况是不存在的,所以,这段代码可以忽略。最后是将平台检测结果返回了。
返回initPlatformTags方法,可以知道Microloader.platformTags现在指向的平台检测结果。再返回init方法,在计算出平台检测结果后,会将Ext.filterPlatform属性指向Microloader.filterPlatform方法,也就是说,在调用Ext的filterPlatform方法时,会执行Microloader.的filterPlatform方法,该方法的主要作用就是把不需要的平台过滤掉。
好了,现在返回initManifest方法,在执行完init方法后,会给tmpManifest赋值,由于在当前情况下,manifest为null,所以tmpManifes的值将会是Ext.manifest的值,而从图中可以知道,Ext.manifes的值是bootstrap,也就是说,现在tmpManifes的值是bootstrap。接下来判断tmpManifes是否为字符串,当前情况下,tmpManifes是字符串,所以要执行判断语句内的代码。先给url赋值,这个由Boot.baseUrl、tmpManifest和“.json”三部分构成,先不管Boot.baseUrl,可以知道,这里要找的是bootstrap.json文件。接下来会调用Boot.fetchSync方法,代码如下:
fetchSync: function(url) { var exception, xhr, status, content; exception = false; xhr = new XMLHttpRequest(); try { xhr.open(‘GET‘, url, false); xhr.send(null); } catch (e) { exception = true; } status = (xhr.status === 1223) ? 204 : (xhr.status === 0 && ((self.location || {}).protocol === ‘file:‘ || (self.location || {}).protocol === ‘ionp:‘)) ? 200 : xhr.status; content = xhr.responseText; xhr = null; // Prevent potential IE memory leak return { content: content, exception: exception, status: status }; },
从代码中的new XMLHttpRequest这语句就知道,这段代码的主要作用就是使用Ajax去加载bootstrap.json文件了。现在假定能正确加载bootstrap.json文件并返回initManifest方法。
在initManifest方法内,接下来要做的是调用JSON.parse将返回的数据解析为JSON对象,并Ext.manifest属性指向该对象。最后将JSON对象返回laod方法。
在load方法的第二句,会先从返回的对象中取出loadOrder的值。在bootstrap.json文件中,loadOrder是一个由对象组成的数组,而每一个对象包含path、requires、uses和idx这4个成员。如果对于Ext JS有一定理解,那么要理解这4个成员不难。成员paths的值就是Ext JS类的脚本的路径,requires和uese指的是这个类所需要的类和使用到的类,而idx则是这个类的唯一标识。在requires和uese中就是使用这个唯一标识来指定所需或使用到的类文件的。
把这个loadOrder取出后,会调用Boot.createLoadOrderMap方法进行处理,代码如下:
createLoadOrderMap: function(loadOrder) { var len = loadOrder.length, loadOrderMap = {}, i, element; for(i = 0; i < len; i++) { element = loadOrder[i]; loadOrderMap[element.path] = element; } return loadOrderMap; },
代码的作用只是把loadOrder数组转换为对象,对象的属性名称就是类文件的路径,值就是类对象本身。
返回到load方法,在处理完loadOrder数组后,会继续从bootstrap.json文件中把js和css的值取出来,然后合并到resources数组中。在当前项目中,bootstrap.json文件中的js和css的定义如下:
"js":[{"path":"app.js"}], "css":[{"path":"bootstrap.css"}
这样对于理解后面的循环就容易多了,由于在定义中,没有platform这个成员,所以循环中的第一个判断就会被跳过,直接执行第二个判断了,也就是把路径信息推人urls数组中。
处理完这个,就开始调用Boot.load方法了,代码如下:
load: function (request) { if (request.sync || _syncMode) { return this.loadSync(request); } // Allow a raw array of paths to be passed. if (!request.url) { request = { url: request }; } // If there is a request in progress, we must // queue this new request to be fired when the current request completes. if (_currentRequest) { _suspendedQueue.push(request); } else { Boot.expandLoadOrder(request); var url = request.url, urls = url.charAt ? [ url ] : url, length = urls.length, i; // Start the counter here. This is reduced as we notify this fellow of script // loads. request.urls = urls; request.loaded = 0; request.loading = length; request.charset = request.charset || _config.charset; request.buster = ((‘cache‘ in request) ? !request.cache : _config.disableCaching) && (_config.disableCachingParam + ‘=‘ + (+new Date())); _currentRequest = request; request.sequential = false; for (i = 0; i < length; ++i) { Boot.loadUrl(urls[i], request); } } return this; },
在这段代码中,前面的代码都是与处理请求地址有关,而当这些都准备好了以后,就会调用Boot.loadUrl方法去加载文件了。而在Boot.loadUrl方法内,会调用Boot.create方法去创建加载标记,代码如下:
create: function (url, key) { var css = url && cssRe.test(url), el = doc.createElement(css ? ‘link‘ : ‘script‘), prop; if (css) { el.rel = ‘stylesheet‘; prop = ‘href‘; } else { el.type = ‘text/javascript‘; if (!url) { return el; } prop = ‘src‘; if(Boot.hasAsync) { el.async = false; } } key = key || url; return _items[key] = { key: key, url: url, css: css, done: false, el: el, prop: prop, loaded: false, evaluated: false }; },
从代码doc.createElement这句就可以看到,在这里会创建SCRIPT或LINK标记去加载脚本或样式。
这么简单的东西搞得那么复杂的一个原因是要确保类的加载顺序,以确保不会出现类初始化时找不到依赖类的情况。因而,在整个加载过程中,需要监控每个脚本的加载情况,在依赖类没有加载完成之前,不去加载该类。
在bootstrap.json文件中,已经把app.js、bootstrap.css等文件加进去了,所以,在index.html文件中,只需要加载bootstrap.js文件就行了。
至此,我们已经基本了解了Ext JS 5的启动过程了。现在的问题是,我们怎么去加载本地化文件。