# 事件
因為JavaScript在瀏覽器中以單線程模式運行,頁面加載后,一旦頁面上所有的JavaScript代碼被執行完后,就只能依賴觸發事件來執行JavaScript代碼。
瀏覽器在接收到用戶的鼠標或鍵盤輸入后,會自動在對應的DOM節點上觸發相應的事件。如果該節點已經綁定了對應的JavaScript處理函數,該函數就會自動調用。
由于不同的瀏覽器綁定事件的代碼都不太一樣,所以用jQuery來寫代碼,就屏蔽了不同瀏覽器的差異,我們總是編寫相同的代碼。
舉個例子,假設要在用戶點擊了超鏈接時彈出提示框,我們用jQuery這樣綁定一個`click`事件:
<script>$(function () { $('#test-link').click(function () { alert('Hello!'); }); });</script>
```
/* HTML:
*
* <a id="test-link" href="#0">點我試試</a>
*
*/
// 獲取超鏈接的jQuery對象:
var a = $('#test-link');
a.on('click', function () {
alert('Hello!');
});
```
實測:[點我試試](#0)
`on`方法用來綁定一個事件,我們需要傳入事件名稱和對應的處理函數。
另一種更簡化的寫法是直接調用`click()`方法:
```
a.click(function () {
alert('Hello!');
});
```
兩者完全等價。我們通常用后面的寫法。
jQuery能夠綁定的事件主要包括:
## 鼠標事件
click: 鼠標單擊時觸發; dblclick:鼠標雙擊時觸發; mouseenter:鼠標進入時觸發; mouseleave:鼠標移出時觸發; mousemove:鼠標在DOM內部移動時觸發; hover:鼠標進入和退出時觸發兩個函數,相當于mouseenter加上mouseleave。
## 鍵盤事件
鍵盤事件僅作用在當前焦點的DOM上,通常是`<input>`和`<textarea>`。
keydown:鍵盤按下時觸發; keyup:鍵盤松開時觸發; keypress:按一次鍵后觸發。
## 其他事件
focus:當DOM獲得焦點時觸發; blur:當DOM失去焦點時觸發; change:當`<input>`、`<select>`或`<textarea>`的內容改變時觸發; submit:當`<form>`提交時觸發; ready:當頁面被載入并且DOM樹完成初始化后觸發。
其中,`ready`僅作用于`document`對象。由于`ready`事件在DOM完成初始化后觸發,且只觸發一次,所以非常適合用來寫其他的初始化代碼。假設我們想給一個`<form>`表單綁定`submit`事件,下面的代碼沒有預期的效果:
```
<html>
<head>
<script>
// 代碼有誤:
$('#testForm).on('submit', function () {
alert('submit!');
});
</script>
</head>
<body>
<form id="testForm">
...
</form>
</body>
```
因為JavaScript在此執行的時候,`<form>`尚未載入瀏覽器,所以`$('#testForm)`返回`[]`,并沒有綁定事件到任何DOM上。
所以我們自己的初始化代碼必須放到`document`對象的`ready`事件中,保證DOM已完成初始化:
```
<html>
<head>
<script>
$(document).on('ready', function () {
$('#testForm).on('submit', function () {
alert('submit!');
});
});
</script>
</head>
<body>
<form id="testForm">
...
</form>
</body>
```
這樣寫就沒有問題了。因為相關代碼會在DOM樹初始化后再執行。
由于`ready`事件使用非常普遍,所以可以這樣簡化:
```
$(document).ready(function () {
// on('submit', function)也可以簡化:
$('#testForm).submit(function () {
alert('submit!');
});
});
```
甚至還可以再簡化為:
```
$(function () {
// init...
});
```
上面的這種寫法最為常見。如果你遇到`$(function () {...})`的形式,牢記這是`document`對象的`ready`事件處理函數。
完全可以反復綁定事件處理函數,它們會依次執行:
```
$(function () {
console.log('init A...');
});
$(function () {
console.log('init B...');
});
$(function () {
console.log('init C...');
});
```
## 事件參數
有些事件,如`mousemove`和`keypress`,我們需要獲取鼠標位置和按鍵的值,否則監聽這些事件就沒什么意義了。所有事件都會傳入`Event`對象作為參數,可以從`Event`對象上獲取到更多的信息:
```
$(function () {
$('#testMouseMoveDiv').mousemove(function (e) {
$('#testMouseMoveSpan').text('pageX = ' + e.pageX + ', pageY = ' + e.pageY);
});
});
```
<script>$(function () { $('#testMouseMoveDiv').mousemove(function (e) { $('#testMouseMoveSpan').text('pageX = ' + e.pageX + ', pageY = ' + e.pageY); }); });</script>
## 取消綁定
一個已被綁定的事件可以解除綁定,通過`off('click', function)`實現:
```
function hello() {
alert('hello!');
}
a.click(hello); // 綁定事件
// 10秒鐘后解除綁定:
setTimeout(function () {
a.off('click', hello);
}, 10000);
```
需要特別注意的是,下面這種寫法是無效的:
```
// 綁定事件:
a.click(function () {
alert('hello!');
});
// 解除綁定:
a.off('click', function () {
alert('hello!');
});
```
這是因為兩個匿名函數雖然長得一模一樣,但是它們是兩個_不同_的函數對象,`off('click', function () {...})`無法移除已綁定的第一個匿名函數。
為了實現移除效果,可以使用`off('click')`一次性移除已綁定的`click`事件的所有處理函數。
同理,無參數調用`off()`一次性移除已綁定的所有類型的事件處理函數。
## 事件觸發條件
一個需要注意的問題是,事件的觸發總是由用戶操作引發的。例如,我們監控文本框的內容改動:
```
var input = $('#test-input');
input.change(function () {
console.log('changed...');
});
```
當用戶在文本框中輸入時,就會觸發`change`事件。但是,如果用JavaScript代碼去改動文本框的值,將**_不會_**觸發`change`事件:
```
var input = $('#test-input');
input.val('change it!'); // 無法觸發change事件
```
有些時候,我們希望用代碼觸發`change`事件,可以直接調用無參數的`change()`方法來觸發該事件:
```
var input = $('#test-input');
input.val('change it!');
input.change(); // 觸發change事件
```
`input.change()`相當于`input.trigger('change')`,它是`trigger()`方法的簡寫。
為什么我們希望手動觸發一個事件呢?如果不這么做,很多時候,我們就得寫兩份一模一樣的代碼。
## 瀏覽器安全限制
在瀏覽器中,有些JavaScript代碼只有在用戶觸發下才能執行,例如,`window.open()`函數:
```
// 無法彈出新窗口,將被瀏覽器屏蔽:
$(function () {
window.open('/');
});
```
這些“敏感代碼”只能由用戶操作來觸發:
```
var button1 = $('#testPopupButton1');
var button2 = $('#testPopupButton2');
function popupTestWindow() {
window.open('/');
}
button1.click(function () {
popupTestWindow();
});
button2.click(function () {
// 不立刻執行popupTestWindow(),100毫秒后執行:
setTimeout(popupTestWindow, 100);
});
```
當用戶點擊`button1`時,`click`事件被觸發,由于`popupTestWindow()`在`click`事件處理函數內執行,這是瀏覽器允許的,而`button2`的`click`事件并未立刻執行`popupTestWindow()`,延遲執行的`popupTestWindow()`將被瀏覽器攔截。
## 練習
對如下的Form表單:
```
<!-- HTML結構 -->
<form id="test-form" action="test">
<legend>請選擇想要學習的編程語言:</legend>
<fieldset>
<p><label class="selectAll"><input type="checkbox"> <span class="selectAll">全選</span><span class="deselectAll">全不選</span></label> <a href="#0" class="invertSelect">反選</a></p>
<p><label><input type="checkbox" name="lang" value="javascript"> JavaScript</label></p>
<p><label><input type="checkbox" name="lang" value="python"> Python</label></p>
<p><label><input type="checkbox" name="lang" value="ruby"> Ruby</label></p>
<p><label><input type="checkbox" name="lang" value="haskell"> Haskell</label></p>
<p><label><input type="checkbox" name="lang" value="scheme"> Scheme</label></p>
<p><button type="submit">Submit</button></p>
</fieldset>
</form>
```
綁定合適的事件處理函數,實現以下邏輯:
當用戶勾上“全選”時,自動選中所有語言,并把“全選”變成“全不選”;
當用戶去掉“全不選”時,自動不選中所有語言;
當用戶點擊“反選”時,自動把所有語言狀態反轉(選中的變為未選,未選的變為選中);
當用戶把所有語言都手動勾上時,“全選”被自動勾上,并變為“全不選”;
當用戶手動去掉選中至少一種語言時,“全不選”自動被去掉選中,并變為“全選”。
```
'use strict';
var
form = $('#test-form'),
langs = form.find('[name=lang]'),
selectAll = form.find('label.selectAll :checkbox'),
selectAllLabel = form.find('label.selectAll span.selectAll'),
deselectAllLabel = form.find('label.selectAll span.deselectAll'),
invertSelect = form.find('a.invertSelect');
// 重置初始化狀態:
form.find('*').show().off();
form.find(':checkbox').prop('checked', false).off();
deselectAllLabel.hide();
// 攔截form提交事件:
form.off().submit(function (e) {
e.preventDefault();
alert(form.serialize());
});
// TODO:綁定事件
// 測試:
alert('請測試功能是否正常。');
```
- JavaScript教程
- JavaScript簡介
- 快速入門
- 基本語法
- 數據類型和變量
- 字符串
- 數組
- 對象
- 條件判斷
- 循環
- Map和Set
- iterable
- 函數
- 函數定義和調用
- 變量作用域
- 方法
- 高階函數
- map/reduce
- filter
- sort
- 閉包
- 箭頭函數
- generator
- 標準對象
- Date
- RegExp
- JSON
- 面向對象編程
- 創建對象
- 原型繼承
- 瀏覽器
- 瀏覽器對象
- 操作DOM
- 更新DOM
- 插入DOM
- 刪除DOM
- 操作表單
- 操作文件
- AJAX
- Promise
- Canvas
- jQuery
- 選擇器
- 層級選擇器
- 查找和過濾
- 操作DOM
- 修改DOM結構
- 事件
- 動畫
- 擴展
- underscore
- Collections
- Arrays
- Functions
- Objects
- Chaining
- Node.js
- 安裝Node.js和npm
- 第一個Node程序
- 模塊
- 基本模塊
- fs
- stream
- http
- buffer
- Web開發
- koa
- mysql
- swig
- 自動化工具
- 期末總結
- Python 2.7教程
- Python簡介
- 安裝Python
- Python解釋器
- 第一個Python程序
- 使用文本編輯器
- 輸入和輸出
- Python基礎
- 數據類型和變量
- 字符串和編碼
- 使用list和tuple
- 條件判斷和循環
- 使用dict和set
- 函數
- 調用函數
- 定義函數
- 函數的參數
- 遞歸函數
- 高級特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 函數式編程
- 高階函數
- map/reduce
- filter
- sorted
- 返回函數
- 匿名函數
- 裝飾器
- 偏函數
- 模塊
- 使用模塊
- 安裝第三方模塊
- 使用__future__
- 面向對象編程
- 類和實例
- 訪問限制
- 繼承和多態
- 獲取對象信息
- 面向對象高級編程
- 使用__slots__
- 使用@property
- 多重繼承
- 定制類
- 使用元類
- 錯誤、調試和測試
- 錯誤處理
- 調試
- 單元測試
- 文檔測試
- IO編程
- 文件讀寫
- 操作文件和目錄
- 序列化
- 進程和線程
- 多進程
- 多線程
- ThreadLocal
- 進程 vs. 線程
- 分布式進程
- 正則表達式
- 常用內建模塊
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- 常用第三方模塊
- PIL
- 圖形界面
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 電子郵件
- SMTP發送郵件
- POP3收取郵件
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web開發
- HTTP協議簡介
- HTML簡介
- WSGI接口
- 使用Web框架
- 使用模板
- 協程
- gevent
- 實戰
- Day 1 - 搭建開發環境
- Day 2 - 編寫數據庫模塊
- Day 3 - 編寫ORM
- Day 4 - 編寫Model
- Day 5 - 編寫Web框架
- Day 6 - 添加配置文件
- Day 7 - 編寫MVC
- Day 8 - 構建前端
- Day 9 - 編寫API
- Day 10 - 用戶注冊和登錄
- Day 11 - 編寫日志創建頁
- Day 12 - 編寫日志列表頁
- Day 13 - 提升開發效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 編寫移動App
- 期末總結
- Python3教程
- Python簡介
- 安裝Python
- Python解釋器
- 第一個Python程序
- 使用文本編輯器
- Python代碼運行助手
- 輸入和輸出
- Python基礎
- 數據類型和變量
- 字符串和編碼
- 使用list和tuple
- 條件判斷
- 循環
- 使用dict和set
- 函數
- 調用函數
- 定義函數
- 函數的參數
- 遞歸函數
- 高級特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 迭代器
- 函數式編程
- 高階函數
- map/reduce
- filter
- sorted
- 返回函數
- 匿名函數
- 裝飾器
- 偏函數
- 模塊
- 使用模塊
- 安裝第三方模塊
- 面向對象編程
- 類和實例
- 訪問限制
- 繼承和多態
- 獲取對象信息
- 實例屬性和類屬性
- 面向對象高級編程
- 使用__slots__
- 使用@property
- 多重繼承
- 定制類
- 使用枚舉類
- 使用元類
- 錯誤、調試和測試
- 錯誤處理
- 調試
- 單元測試
- 文檔測試
- IO編程
- 文件讀寫
- StringIO和BytesIO
- 操作文件和目錄
- 序列化
- 進程和線程
- 多進程
- 多線程
- ThreadLocal
- 進程 vs. 線程
- 分布式進程
- 正則表達式
- 常用內建模塊
- datetime
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- urllib
- 常用第三方模塊
- PIL
- virtualenv
- 圖形界面
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 電子郵件
- SMTP發送郵件
- POP3收取郵件
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web開發
- HTTP協議簡介
- HTML簡介
- WSGI接口
- 使用Web框架
- 使用模板
- 異步IO
- 協程
- asyncio
- async/await
- aiohttp
- 實戰
- Day 1 - 搭建開發環境
- Day 2 - 編寫Web App骨架
- Day 3 - 編寫ORM
- Day 4 - 編寫Model
- Day 5 - 編寫Web框架
- Day 6 - 編寫配置文件
- Day 7 - 編寫MVC
- Day 8 - 構建前端
- Day 9 - 編寫API
- Day 10 - 用戶注冊和登錄
- Day 11 - 編寫日志創建頁
- Day 12 - 編寫日志列表頁
- Day 13 - 提升開發效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 編寫移動App
- FAQ
- 期末總結
- Git教程
- Git簡介
- Git的誕生
- 集中式vs分布式
- 安裝Git
- 創建版本庫
- 時光機穿梭
- 版本回退
- 工作區和暫存區
- 管理修改
- 撤銷修改
- 刪除文件
- 遠程倉庫
- 添加遠程庫
- 從遠程庫克隆
- 分支管理
- 創建與合并分支
- 解決沖突
- 分支管理策略
- Bug分支
- Feature分支
- 多人協作
- 標簽管理
- 創建標簽
- 操作標簽
- 使用GitHub
- 自定義Git
- 忽略特殊文件
- 配置別名
- 搭建Git服務器
- 期末總結