【JS设计模式】机器学习——惰性模式

189 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

很多公司招聘要求上会写着 “熟悉各主流平台差异、兼容性优化解决方案等”,今天的这个惰性模式就是可以用来进行兼容性优化的。

什么是兼容性问题呢?通俗的说就是,某个方法A,在这个浏览器上存在,在另一个浏览器上就不存在或者说是不叫这个名字而是叫方法B,这样就会导致同样一段代码,在这个浏览器上可以运行,到另一个浏览器上就报错,这就是兼容性问题。

那我们再来思考一下,换做是你,你会如何解决兼容性问题呢?

假设现在有一个方法M,存在document对象上,然后有3种浏览器A、B、C,然后方法M在A上就叫M,在浏览器B中叫BM,在浏览器C中叫CM,接着让你写一段代码,保证能在3种浏览器上都实现方法M的功能。

按照平时的思路,大家首先想到的肯定是使用 if-else 来解决这个兼容性问题吧,就像下面的代码一样:

function comp() {
    // 如果document对象上存在M方法,说明是在A浏览器中
    if (document.M) {
        document.M() // 直接执行对应方法
    } else if (document.BM) {
        document.BM() // B浏览器中
    } else {
        document.CM() // C浏览器中
    }
}

上面这段代码确实实现了我们的需求,保证了ABC3种浏览器都可以实现方法M的功能,但是有什么不足的地方呢? 我们来分析一下,每次执行 comp 函数的时候,都会对当前浏览器环境进行判断,然后再根据判断结果去执行对应的方法,那么有没有可能,让 comp 函数记住当前浏览器环境,下次我们再执行 comp 函数的时候它无需再对浏览器环境进行判断,而是直接执行符合当前浏览器环境的方法呢?

这种方法就是 惰性模式,也叫机器学习。惰性模式的精髓在于下面这句话

惰性模式精髓
既然第一次执行的时候已经判断过了,而且以后再执行是不必要的,那么就在第一次执行后重新定义它吧。

惰性模式有 两种 实现方式

  1. 使用闭包,在文件加载进来 的时候通过闭包执行该方法,并对其重新定义;
  2. 在1的基础上,做一次 延迟执行,该为在函数第一次调用时对其重定义;

第一种方法的不足之处在于 会使得页面在加载的时候就占用一定的资源。 而第二种方式则是将这种资源消耗进行 惰性推移,在第一次执行函数的时候才进行,进而减少了文件加载时的资源消耗。

就像吃个云吞,要花7块钱,首先你要明确的一点就是,这7块钱你是无论如何都要给的了,然后现在有两种交易方式,一种是吃完就给钱,一种是下个月再给钱。 不要小看第二种方式,这种惰性推移有时候是很有必要的,当我们重定义会花费的资源比较多而又要不影响页面显示的时候,惰性推移的必要性就显示出来了。

加载时执行重定义

// 添加绑定事件on
A.on = function (dom, type, fn) {
    // 如果支持addEventListener
    if (document.addEventListener) {
        // 返回重新定义的方法
        return function (dom, type, fn) {
            dom.addEventListener(type, fn, false)
        }
    }
    // 如果支持attachEvent (IE浏览器)
    else if (document.attachEvent) {
        return function (dom, type, fn) {
            dom.attachEvent('on' + type, fn)
        }
    } else {
        // 返回新定义方法
        return function (dom, type, fn) {
            dom['on' + type] = fn
        }
    }
}(); // 加载时执行

假设上面的代码在一个 demo.js 中,然后将 A.on 方法重定义需要花费8秒钟时间,那么使用第一种方法带来的副作用就是:当页面加载到 demo.js 的时候,因为加载js文件是会阻塞页面的,而重定义 A.on 方法需要花费8秒钟,所以页面被阻塞了8秒,有可能会白屏8秒。但是好处是,加载完毕之后,你再执行A.on事件,将不再损失资源了。

惰性执行

// 添加绑定事件on
A.on = function (dom, type, fn) {
    // 如果支持addEventListener
    if (document.addEventListener) {
        A.on = function (dom, type, fn) {
            dom.addEventListener(type, fn)
        }
    }
    // 如果支持attachEvent (IE浏览器)
    else if (document.attachEvent) {
        A.on = function (dom, type, fn) {
            dom.attachEvent('on' + type, fn)
        }
        // 如果支持DOM0级的事件绑定
    } else {
        A.on = function (dom, type, fn) {
            dom['on' + type] = fn
        }
    }
    // 执行重定义on方法
    A.on(dom, type, fn)
}

假设上面的代码在一个 demo.js 中,然后将 A.on 方法重定义需要花费8秒钟时间,那么使用第二种方法带来的副作用就是:当页面加载到 demo.js 的时候,页面不会阻塞,但是当我们第一次需要调用 A.on 方法的时候,会花费8秒时间对 A.on 方法进行重定义,然后再真正执行。也就是把资源的损失推迟到了第一次调用时。

以上就是两种实现惰性模式方法了,各有优劣,根据使用场景进行取舍。

会遇到兼容性问题的API有

  1. document.addEventListener 事件
  2. 创建XHR对象 XMLHttpRequest
  3. 获取区域元素的CSS样式 IE中是currentStyle,标准浏览器、国内浏览器则各有各的方法

上面是常见的兼容性问题,大家可以试试用惰性模式来优化一下解决方法。