Ext JS 5初探(二) ——Bootstrap.js

在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的启动过程了。现在的问题是,我们怎么去加载本地化文件。



posted on 2014-04-11 01:05  木鱼哥  阅读(259)  评论(0编辑  收藏  举报

导航