Foundation

基础库是所有脚本和控件的核心基础。这个库的目标是:

这对于用户花费大量时间与页面交互的页面尤其重要,例如 CMS 和页面部分,但所有后端控件都应遵循这些规则,因为我们永远不知道它们何时被使用。

为什么释放内存、DOM 引用和事件处理程序很重要

一个典型的 JavaScript 控件类实例由以下部分组成:

  1. 表示控件的 JavaScript 对象。
  2. 对相应 DOM 元素的引用。通常它是控件的根元素,包含带有控件 HTML 标记的树。
  3. 一些事件处理程序来处理用户与控件的交互。

如果其中任何一个组件没有发布,我们就会遇到这些问题:

  1. 未发布的 JavaScript 对象会增加内存占用。应用程序使用的内存越多,运行速度就越慢。最终它可能会导致选项卡崩溃或整个浏览器崩溃。
  2. 对 DOM 元素的未发布引用可能会导致分离的 DOM 树。反过来,这可能会导致页面中存在数千个不可见的 DOM 元素,从而增加内存占用并降低应用程序的响应速度。
  3. 未绑定的事件处理程序通常会导致未释放的 DOM 元素,这本身就是不好的,还会导致在用户与应用程序交互时执行的代码不应执行。这会影响性能。

这是处理这些问题的方法:

  1. 删除 JavaScript 对象 - 通常通过从控​​件的根元素中删除数据:this.$el.removeData('oc.myControl') 清除对 DOM 元素的所有引用。通常它是通过将 NULL 分配给相应的对象属性来完成的。
  2. 注意任何被闭包捕获的引用(或者 - 最好不要使用闭包,见下文)。
  3. 取消绑定事件处理程序。

Winter Storm UI 提供了我们实现目标所需的一切。请继续阅读以了解更多信息!

如何写出高质量的代码

OOP 方法和原型应该在所有地方使用。这种方法自动处理可能保留对范围变量的引用的闭包。典型类代码模板:

function ($) { "use strict";
    var SomeClass = function() {
        this.init()
    }

    SomeClass.prototype.init = function (){
        ...
    }
}

编写一次性课程的基础知识

如果一个类应该是一次性的(所有 UI 控件都应该是一次性的),则该类应该扩展$.wn.foundation.base 班级。该类有两个有用的方法:proxy(method)dispose().

proxy() 方法是 jQuery 的替代方法$.proxy, 但作为$.wn.foundation.base 实现 OOP 方法,不需要将此参数传递给方法。由于三个原因,这种方法很好。

  1. 它的代码非常简单,易于控制和调试。
  2. 它缓存绑定函数并且不创建新函数$.proxy 做。
  3. 当使用 dispose() 方法处理对象时,它会自动删除所有缓存的绑定函数。

dispose() 基类中的方法清除缓存的绑定方法proxy() 方法并提供用于处理对象的通用 API。所有应该进行清理工作的类都应该重写该方法,进行自己的清理并调用基类dispose() 方法。

一次性课程的例子:

+function ($) { "use strict";
    var Base = $.wn.foundation.base,
        BaseProto = Base.prototype

    var SomeDisposableClass = function(element) {
        this.$el = $(element)

        Base.call(this)
        this.init()
    }

    SomeDisposableClass.prototype = Object.create(BaseProto)
    SomeDisposableClass.prototype.constructor = SomeDisposableClass

    SomeDisposableClass.prototype.init = function () {
    }

    SomeDisposableClass.prototype.dispose = function () {
        this.$el = null
        BaseProto.dispose.call(this)
    }
}

需要注意几件重要的事情:

  1. 类构造函数应该调用 Base.call(this)。
  2. 类原型应替换为基类原型的副本,其构造函数引用应还原回类构造函数。它应该在类构造函数之后和类原型中定义任何方法之前完成。

绑定和解除绑定事件

绑定事件时,使用 this.proxy() 来引用事件处理程序。始终在 dispose() 方法中取消绑定事件:

+function ($) { "use strict";
    var Base = $.wn.foundation.base,
        BaseProto = Base.prototype

    var SomeDisposableClass = function(element) {
        this.$el = $(element)

        Base.call(this)
        this.init()
    }

    [...]

    SomeDisposableClass.prototype.init = function () {
        this.$el.on('click', this.proxy(this.onClick))
    }

    SomeDisposableClass.prototype.dispose = function () {
        this.$el.off('click', this.proxy(this.onClick))
        this.$el = null
        BaseProto.dispose.call(this)
    }
}

制作一次性控件

UI 控件应该支持两种处理方式——调用它们的dispose() 方法并调用处置控制处理程序。此外,一次性控件应使用 Winter Foundation API 将其相应的 DOM 元素标记为一次性。例子:

+function ($) { "use strict";
    var Base = $.wn.foundation.base,
        BaseProto = Base.prototype

    var SomeDisposableControl = function(element) {
        this.$el = $(element)

        $.wn.foundation.controlUtils.markDisposable(element)
        Base.call(this)
        this.init()
    }

    ...

    SomeDisposableControl.prototype.init = function () {
        this.$el.one('dispose-control', this.proxy(this.dispose))
    }

    SomeDisposableControl.prototype.dispose = function () {
        this.$el.off('dispose-control', this.proxy(this.dispose))
        this.$el = null
        BaseProto.dispose.call(this)
    }
}

$.wn.foundation.controlUtils.markDisposable(element) 在构造函数中调用添加data-disposable 属性添加到 DOM 元素,允许框架找到容器中的所有一次性元素,并在需要时调用它们的处置控制处理程序来处置它们。

创建一次性控件的 jQuery 插件的完整示例

我们已经有了 jQuery 代码的样板代码。一次性控制方法只是对其进行了扩展。不要忘记从控件的 DOM 元素中删除与控件关联的数据。

+function ($) { "use strict";
    var Base = $.wn.foundation.base,
        BaseProto = Base.prototype

    var SomeDisposableControl = function (element, options) {
        this.$el = $(element)
        this.options = options || {}

        $.wn.foundation.controlUtils.markDisposable(element)
        Base.call(this)
        this.init()
    }

    SomeDisposableControl.prototype = Object.create(BaseProto)
    SomeDisposableControl.prototype.constructor = SomeDisposableControl

    SomeDisposableControl.prototype.init = function() {
        this.$el.on('click', this.proxy(this.onClick))
        this.$el.one('dispose-control', this.proxy(this.dispose))
    }

    SomeDisposableControl.prototype.dispose = function() {
        this.$el.off('click', this.proxy(this.onClick))
        this.$el.off('dispose-control', this.proxy(this.dispose))
        this.$el.removeData('oc.someDisposableControl')

        this.$el = null

        // In some cases options could contain callbacks,
        // so it's better to clean them up too.
        this.options = null

        BaseProto.dispose.call(this)
    }

    SomeDisposableControl.DEFAULTS = {
        someParam: null
    }

    // PLUGIN DEFINITION
    // ============================

    var old = $.fn.someDisposableControl

    $.fn.someDisposableControl = function (option) {
        var args = Array.prototype.slice.call(arguments, 1), items, result

        items = this.each(function () {
            var $this   = $(this)
            var data    = $this.data('oc.someDisposableControl')
            var options = $.extend({}, SomeDisposableControl.DEFAULTS, $this.data(), typeof option == 'object' && option)
            if (!data) $this.data('oc.someDisposableControl', (data = new SomeDisposableControl(this, options)))
            if (typeof option == 'string') result = data[option].apply(data, args)
            if (typeof result != 'undefined') return false
        })

        return result ? result : items
    }

    $.fn.someDisposableControl.Constructor = SomeDisposableControl

    $.fn.someDisposableControl.noConflict = function () {
        $.fn.someDisposableControl = old
        return this
    }

    // Add this only if required
    $(document).render(function (){
        $('[data-some-disposable-control]').someDisposableControl()
    })

}(window.jQuery);
豫ICP备18041297号-2