Foundation
基础库是所有脚本和控件的核心基础。这个库的目标是:
- 结构良好且可读的代码。
- 不要留下对 DOM 元素的引用。
- 取消绑定所有事件处理程序。
- 编写高性能代码(在需要的情况下)。
这对于用户花费大量时间与页面交互的页面尤其重要,例如 CMS 和页面部分,但所有后端控件都应遵循这些规则,因为我们永远不知道它们何时被使用。
为什么释放内存、DOM 引用和事件处理程序很重要
一个典型的 JavaScript 控件类实例由以下部分组成:
- 表示控件的 JavaScript 对象。
- 对相应 DOM 元素的引用。通常它是控件的根元素,包含带有控件 HTML 标记的树。
- 一些事件处理程序来处理用户与控件的交互。
如果其中任何一个组件没有发布,我们就会遇到这些问题:
- 未发布的 JavaScript 对象会增加内存占用。应用程序使用的内存越多,运行速度就越慢。最终它可能会导致选项卡崩溃或整个浏览器崩溃。
- 对 DOM 元素的未发布引用可能会导致分离的 DOM 树。反过来,这可能会导致页面中存在数千个不可见的 DOM 元素,从而增加内存占用并降低应用程序的响应速度。
- 未绑定的事件处理程序通常会导致未释放的 DOM 元素,这本身就是不好的,还会导致在用户与应用程序交互时执行的代码不应执行。这会影响性能。
这是处理这些问题的方法:
- 删除 JavaScript 对象 - 通常通过从控件的根元素中删除数据:
this.$el.removeData('oc.myControl')
清除对 DOM 元素的所有引用。通常它是通过将 NULL 分配给相应的对象属性来完成的。 - 注意任何被闭包捕获的引用(或者 - 最好不要使用闭包,见下文)。
- 取消绑定事件处理程序。
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 方法,不需要将此参数传递给方法。由于三个原因,这种方法很好。
- 它的代码非常简单,易于控制和调试。
- 它缓存绑定函数并且不创建新函数
$.proxy
做。 - 当使用 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)
}
}
需要注意几件重要的事情:
- 类构造函数应该调用 Base.call(this)。
- 类原型应替换为基类原型的副本,其构造函数引用应还原回类构造函数。它应该在类构造函数之后和类原型中定义任何方法之前完成。
绑定和解除绑定事件
绑定事件时,使用 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);