本教程介绍了如何使用 Closure 库监听和响应 JavaScript 事件。您将大致了解事件的工作原理,并学习使代码可扩展且可维护的具体 JavaScript 技术。
JavaScript 和 Closure 库中的事件
当用户在 JavaScript 程序中输入或使用鼠标时,JavaScript 会生成事件对象来代表用户的操作。这些事件会分发到 DOM 对象。要让 DOM 对象响应事件,您需要向其附加事件监听器,即 JavaScript 函数。在您的程序中,您要指定事件监听器以及应处理哪些事件。例如,当用户点击某个链接时,系统会将代表该事件的 DOM 对象的点击事件发送到点击监听器。
事件模型是 JavaScript 的一部分,但各种浏览器以不同方式实现事件。例如,大多数浏览器会将事件对象作为参数传递给监听器函数,但 Internet Explorer 会将事件对象存储在窗口属性中,而不是将其作为参数传递。
Closure 库提供一致的事件模型,所有模型的工作方式都相同。Closure 库实现了自己的事件模型,通过将不同浏览器的事件模型中的变体隐藏起来来提供统一的行为,所以您在编写程序时只需考虑一组行为。此外,Closure 库的事件模型也解决了浏览器的问题,例如 Internet Explorer 中嵌套函数的潜在内存泄漏。
事件的工作原理
当发生可生成事件的操作(例如用户点击链接)时,Closure 库事件处理过程就会开始。系统会创建一个代表该事件的点击事件对象,并且确定事件目标。在这种情况下,目标是用户点击的链接。
事件对象的属性包含与事件相关的信息。调用事件监听器时,事件对象会作为参数传递。Closure 库使用 goog.events.BrowserEvent
类的事件对象来表示浏览器事件。
事件会分为两个阶段进行调度:首先是捕获阶段,然后是气泡阶段。在捕获阶段,事件首先被分派到 DOM 的根元素,然后沿着 DOM 层次结构被分派,直到到达目标本身。在此阶段,任何监听捕获阶段事件的元素都将以 DOM 层次结构的顺序调用其监听器。
拍摄阶段完成后,气泡阶段开始。在气泡阶段,事件会被分派到事件目标,然后提升 DOM 层次结构,直到根元素。在此阶段,系统会调用监听气泡阶段事件的监听器和没有指定阶段的监听器。
在 Closure 库中,您可以使用 goog.events.listen()
为对象分配事件监听器。listen()
的完整签名为:
goog.events.listen(eventSource, eventType, listener, [capturePhase, [handler]]);
eventSource
是要附加到事件监听器的 DOM 对象。eventType
是一个字符串或字符串数组,用于定义将触发事件监听器的事件类型。listener
是事件监听器,即将指定事件分派给对象时将调用的函数。capturePhase
是一个可选参数,如果您希望仅在捕获阶段调用监听器,则应将其设置为true
。handler
是可选参数,可让您指定监听器函数中的this
表示哪个对象。
处理事件
在本部分中,我们将创建一个示例记事本应用,该应用可在用户点击文本时打开文本进行编辑。我们将在上一个教程中构建应用。您可以在此处找到本教程的源文件:
- notepad2.js(点击即可下载)
- notepad2.html(点击即可下载)
我们的记事本示例将文本显示为文档元素。为了使文本可编辑,我们希望显示 <textarea>
元素,以在用户点击文本时编辑文本。为此,我们将在创建元素时将一个事件监听器附加到文本内容元素。事件监听器只是在发生指定事件时调用的函数。
Closure 库拥有自己的事件框架,可以解决不同浏览器的事件模型中的不兼容问题。如需使用此框架,请调用 goog.events.listen()
将监听器函数附加到元素。
例如,以下对 goog.events.listen()
的调用可确保点击记事会触发对 openEditor()
的调用:
goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor);
第一个参数是我们希望监听事件的元素。第二个参数是我们监听的事件类型。第三个参数是指定事件发生时调用的函数。
下面是一个监听器内容示例,用于隐藏内容元素并显示编辑器元素:
tutorial.notepad.Note.prototype.openEditor = function(e) { var elt = e.target; // Get the current contents of the note text Element, so we can put it into // the editor field. var content = goog.dom.getTextContent(elt); // Given the way we've built our DOM structure, the editor div // will be the next Element after the note text Element. var editorContainer = goog.dom.getNextElementSibling(elt); var editor = goog.dom.getFirstElementChild(editorContainer); // Put the note contents into the editor field. editor.innerHTML = content; // Hide the note text Element and show the editor. elt.style.display = "none"; editorContainer.style.display = "inline"; };
文件 notepad2_1.js 包含更新后的示例版本。
使用实例方法
在 openEditor
函数中,每次调用函数时,我们都必须查找内容和编辑器元素。我们通过查看 DOM 结构找到这些元素。但是,在创建这些元素时,我们已经将对这些元素的引用存储为 Note
对象中的实例字段:
tutorial.notepad.Note.prototype.makeNoteDom = function() { // Create DOM structure to represent the note. this.headerElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.title); this.contentElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.content); // Create the editor text area and save button. this.editorElement = goog.dom.createDom(goog.dom.TagName.TEXTAREA, null, ''); ... }
为什么我们不能直接在 openEditor()
的正文中使用这些引用?遗憾的是,默认情况下,事件监听器在事件目标环境中执行。换句话说,当用户点击备注元素时,this
会引用 openEditor()
正文中的备注元素,即使 openEditor()
是 Note
的方法也是如此。
Closure 库提供了一个解决此问题的方法。为了能够将对象方法用作事件监听器,Closure 库允许将上下文对象作为 goog.events.listen()
的可选参数。例如,以下调用以如下方式附加 openEditor()
监听器,让 this
引用 openEditor()
正文中的 Note
对象:
goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor, false, this);
第五个参数用于提供要在监听器正文中充当 this
的对象。
我们可以将监听器附加到创建元素的同一 makeNoteDom()
方法中。在我们的示例中,将 tutorial.notepad.Note.prototype.makeNoteDom()
替换为以下方法:
tutorial.notepad.Note.prototype.makeNoteDom = function() { // Create DOM structure to represent the note. this.headerElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.title); this.contentElement = goog.dom.createDom(goog.dom.TagName.DIV, null, this.content); // Create the editor text area and save button. this.editorElement = goog.dom.createDom(goog.dom.TagName.TEXTAREA, null, ''); var saveBtn = goog.dom.createDom(goog.dom.TagName.INPUT, {'type': 'button', 'value': 'Save'}, ''); this.editorContainer = goog.dom.createDom(goog.dom.TagName.DIV, {'style': 'display:none;'}, this.editorElement, saveBtn); this.contentContainer = goog.dom.createDom(goog.dom.TagName.DIV, null, this.contentElement, this.editorContainer); // Wrap the editor and the content div in a single parent so they can // be toggled in unison. var newNote = goog.dom.createDom(goog.dom.TagName.DIV, null, this.headerElement, this.contentContainer); // Add the note's DOM structure to the document. this.parent.appendChild(newNote); // Attach the event listener that opens the editor. // CHANGED: We need to preserve the meaning of 'this' when the listener is called. goog.events.listen(this.contentElement, goog.events.EventType.CLICK, this.openEditor, false, this); // NEW: goog.events.listen(saveBtn, goog.events.EventType.CLICK, this.save, false, this); // Attach the Zippy behavior. this.zippy = new goog.ui.Zippy(this.headerElement, this.contentContainer); };
因为 Closure 库让我们能够在监听器中保留 this
的含义,所以可以简化它们。在 makeNoteDom() 下方添加以下方法:
tutorial.notepad.Note.prototype.save = function(e) { this.content = this.editorElement.value; this.closeEditor(); }; tutorial.notepad.Note.prototype.closeEditor = function() { this.contentElement.innerHTML = this.content; this.contentElement.style.display = "inline"; this.editorContainer.style.display = "none"; }; tutorial.notepad.Note.prototype.openEditor = function(e) { this.editorElement.value = this.content; this.contentElement.style.display = "none"; this.editorContainer.style.display = "inline"; };
我们的记事本示例现在可以修改和保存记事了!您的文件现在应如下所示:
- notepad2.2
- notepad2_2.html(请注意,我们并未修改本教程中的 .html 文件。为方便起见,我们在此处重新进行了关联。)