# 事件
因为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服务器
- 期末总结