JavaScript框架设计读书笔记 · 系列 第一章 种子模块

119 阅读1分钟

种子模块

种子模块是核心模块,是框架最先执行的部分(initial)

  1. 它可以方便的将其他方法或属性添加进来(mixins)---拓展性
  2. 内部方法是及其常用的---高可用
  3. 不能轻易在版本升级中删除,要有向后兼容---稳定性

对象拓展

通常来说,将新功能添加到全局变量(如jq的$)上,可以直接按照obj[pro] = xxx来实现,但是在遍历添加的时候,旧版本IE浏览器不支持遍历Object的原型方法,最后ES6支持了Object.assgin,低端浏览器可以使用pollyfill进行支持。

数组化

  • 类数组对象

类数组对象包括function的args,节点集合,以及类似以下结构的自定义对象:

// 注意必须要有length属性,否则不能处理
let arrayLike = {
    0: 'a',
    1: '1',
    2: '2',
    length: 3
} 

转换类数组对象到数组,我们可以使用Array.prototype.slice.call、Array.from等实现转换,但IE浏览器下的HTMLCollection和NodeList不是Object的子类,使用上述方法会发生执行异常,因此需要判断当前浏览器环境是否IE,如果是,则使用转换函数(见p7),如果不是,直接使用Array.prototype.slice.call进行转换。

还可以使用pollyfill来进行转换(作者建议放入补丁模块便于后期升级舍弃)。

类型判定

JavaScript类型包括基本类型string,number,boolean,null,undefined,symbol以及引用类型,其中typeof可以识别string,number,boolean,function,undefined。如果使用typeof判断null,argument,RegExp等会发生问题。

  • typeof的局限
// typeof的错误情况
typeof null // object
typeof /\d/i  // function
typeof document.childNodes // function

另外我们经常使用instanceof来判断数组对象等,但是instanceof在部分情况下也会有判断问题。

  • instanceof的局限
// instanceof的错误情况
var iframe = document.createElement('iframe)
document.body.appendChild(iframe)
// 获取iframe里的Array
xArray = window.frames[window.frames.length - 1].Array
var arr = new xArray(1,2,3)
arr instanceof Array // false
arr.constructor === Array // false

可见,iframe的数组实例并不是父窗口的Array实例,判断中会返回false,但他确实是数组。 还有其他的一些constructor错误,也一并列举:

  • 其他constructor错误
window.constructor // IE67 undefined
document.constructor // IE67 undefined
document.body.constructor // IE67 undefined

如上,相当多的坑等着我们,但是类型判断在底层需要特别准确,怎么办呢?于是Object.prototype.toString被发掘出来,此方法直接输出对象内部的[[Class]],可以跳过大部分陷阱,但是此方法只针对原生数据类型,比如检测对象是否window以及是否纯净的JavaScript对象时就需要另觅他法了。

  • solution方案
Object.prototype.toString.call(2) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(Math) // "[object Math]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"

接下来说一说判断window对象及纯净对象的方法:
对于普通浏览器,仍然使用toString方法获取并用正则匹配,对于IE浏览器,则使用obj及obj.document的对比判断:

var rwindow = /^\[object (?:Window|DOMWindow|global)\]$/
function isWindow = (obj) {
    return rwindow.test(toString.call(obj))
}
if (isWindow(window)) {
    $.isWindow = isWindow
} else {
    $.isWindow = (obj) => {
        if (!obj) return false
        return obj === obj.document && obj.document != obj
    }
}

isNumeric及isArrayLike

这个就暂时不展开了,有兴趣可以翻书看看。

domReady

domReady是DOMContentLoaded的别称但又不同于DOMContentLoaded。
在框架中,越早介入对DOM的干涉就越好,比如要进行特征侦测等;另外domReady还可以满足用户提前绑定事件的需求,比如网页请求的资源过多,window.onload迟迟不能触发,就需要尽快绑定事件处理,使得用户可以尽早进行页面操作。
如果我们的种子模块是动态加载的

  • 情况一: dom树构建完之后才插入加载,无法根据DOMContentLoaded来触发ready回调。那么连onload一起监听
  • 情况二: 如果是onload之后加载呢?只能通过document.readyState来判断是否等于complete,如果是,则已经domReady,执行回调
  • 情况三: firefox没有document.readyState属性,需要使用document.body来判断状态

以上,需要综合hack一下,具体代码可以参考书中的p19-p20。

无冲突处理

当引入的多个库命名空间冲突时,需要在库里设置无冲突处理方法,让调用者可以无需为此烦恼,这是个很有用且必须的功能。具体代码可参考书页p20,这里就不再赘述了。