> For the complete documentation index, see [llms.txt](https://lizh.gitbook.io/knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lizh.gitbook.io/knowledge/javascript-xi-lie/02-jin-jie-pian-03-you-te-xing-de-yi-xie-shi-jian.md).

# 进阶篇 03 有特性的一些事件

## onbeforeunload事件

当浏览器窗口关闭或者刷新时，会触发 beforeunload 事件。事件使网页能够触发一个确认对话框，询问用户是否真的要离开该页面。如果用户确认，浏览器将导航到新页面，否则导航将会取消。

根据规范，要显示确认对话框，事件处理程序需要在事件上调用 `preventDefault()`。

但是请注意，并非所有浏览器都支持此方法，而有些浏览器需要事件处理程序实现两个遗留方法中的一个作为代替：

* 将字符串分配给事件的 returnValue 属性
* 从事件处理程序返回一个字符串。

```js
window.addEventListener('beforeunload', (event) => {
    // 规范：调用 preventDefault()
    event.preventDefault()
    // 有些浏览器需要赋值一个returnValue。如：Chrome\edge
    event.returnValue = '' // 任何字符串 | true | false
    // 注意：return 对这个事件没有影响 
})
```

某些浏览器过去在确认对话框中显示返回的字符串，从而使事件处理程序能够向用户显示自定义消息。但是，此方法已被弃用，并且在大多数浏览器中不再支持。

为避免意外弹出窗口，**除非页面已与之交互，否则浏览器可能不会显示在 `beforeunload` 事件中创建的提示，甚至根本不会显示它们**。

将事件处理程序/监听器加到 `window`或 `document` 的 `beforeunload` 事件后，将阻止浏览器使用内存中的页面导航缓存。

HTML 规范指出在此事件中调用 `window.alert()、window.confirm()、window.prompt()`方法，可能会失效。

而通常情况，我们希望在关闭页面，弹出一个自己定义的弹层，询问是否关闭页面。

```js
history.pushState(null, null, document.URL)
window.addEventListener('popstate', function () {
    if (true) {
        // 回退
        history.go(-1)
    	return
    }
    // 阻止回退
	history.pushState(null, null, document.URL)
})
```

## onsubmit事件

React、Vue 等框架流行后，Web 开发者越来越少直接操作 form 表单了。这些框架一般都有状态管理，form 表单元素值的变化，可以实时反应在对应的数据模型中，开发者可以绕过 `onsubmit` 事件，通过 AJAX 将数据发送到服务器。

这种操作，在逻辑上来说，没有问题。但从体验上来说，并不友好。它忽略了 `onsubmit` 本身的一些特性。

```js
<form>
    <label for="name">姓名:</label>
    <input type="text" name="name" id="name">
    <input type="submit">
</form>
```

[查看 onsubmit事件 DEMO](https://1927344728.github.io/demo-lizh/html/others_01.html?type=6)

如上代码，如果您在文本框中获得焦点并按回车键，表单将自动提交。

基本上，如果用户在文本字段**获得焦点**时**按下回车键**，浏览器应该找到表单中的**第一个提交按钮**并单击它。这种行为在所有浏览器中都是一致的，称为**隐式提交**。**隐式提交对于辅助技术和根本无法使用鼠标的受损用户至关重要。**

**隐式提交的一些交互逻辑：**

* 如果表单中，没有提交按钮，并且只有一个表单元素，则将进行隐式提交。
* 如果表单中，没有提交按钮，但是有多个表单元素，则不会进行隐式提交。
* 如果表单中，有一个或多提交按钮，则第一个按钮会进行隐式提交。

这里说的提交按钮，可以是 `<input type="submit">`、`<button>提交</button>`、`<input type="image">` 中的任意一个。（注意：IE7 有一个错误， button 元素默认的 type 值为 `button` 而不是 `submit`。因此，为了 IE7 兼容性，您需要 `<button type="submit">`。）

**在 JavaScript 中防止隐式提交也很容易：**

```js
document.getElementById('name').addEventListener('keypress', function(event) {
    if (event.keyCode == 13) {
        event.preventDefault()
    }
})
```

**请注意：**

* 提交按钮的 `onclick` 事件与表单的 `onsubmit` 事件是不一样的，并且在不同的浏览器中它们的执行顺序不一。
* form 元素可以用 `submit()` 方法执行提交表单，但是，该方法并不会触发 `onsubmit` 事件。

隐式表单提交对于辅助技术和重要的用户便利性至关重要，建议提交表单时，尽量采用 `onsubmit` 事件，它将有助于使网络成为对每个人都更好的地方。

## touchmove 事件

touchmove 事件的目标元素和触发 touchstart 事件的目标元素相同，即使当 touchmove 事件触发时，触点已经移出了该元素 。如果要实时获取触摸点所在位置的元素，普通方法已不奏效。

**解决方案一(推荐)：**

```js
// 获取存储当前触摸点位置的对象，取 clientX、clientY，再通过 elementFromPoint 获取当前触摸位置的元素
const myLocation = e.originalEvent.changedTouches[0];
const realTarget = document.elementFromPoint(myLocation.clientX, myLocation.clientY);
```

**注意：** elementFromPoint() 方法返回的是最顶层的元素，所以要将需要的元素的 z-index 设置为最高。

**注意：** 由于手机兼容性问题。touchmove 事件的监听函数需加上 e.stopPropagation() 和 e.preventDefault()。

**解决方案二：** 在父元素上定义 touchmove 事件。

## touchend事件

部分安卓机中，touchend 事件无效。

\*\*解决方案一：\*\*在 touchstart 或者 touchmove 事件的监听函数中，阻止事件的默认行为 `event.preventDefault()`，那么到 touchend 事件就能正常触发。

**注意：** 该方案解决了 touchend 事件触发问题，但页面默认滚动页面被阻止掉了。

\*\*解决方案二：\*\*同时绑定 touchcancel 和 touchend 事件。

可以解决 touchend 事件的不触发问题，但其实还不是最优解，touchend 事件 还是比较难触发。最根本的还是寄希望于谷歌尽早解决这个历史遗留bug。

## 相关问题

### 如何移除一个匿名函数的绑定事件？

Javascript 用 addEventListener 函数给 DOM 绑定事件，用 removeEventListner 移除绑定事件。

```javascript
const ckHandler = function () {}
element.addEventListener('click', ckHandler, false)
element.removeEventListner('click', ckHandler)
```

由上可知，移除绑定事件的函数，需要提供函数名，如果是匿名函数，该如何移除呢？

```javascript
element.addEventListener('click', function () {
	console.log('如何移除事件绑定的匿名函数？')
}, false)
```

**方法一：** 在绑定的匿名函数中添加移除函数。

```javascript
let count = 0
element.addEventListener('click', function () {
    console.log('如何移除事件绑定的匿名函数？')
    count ++
    if (count > 2) {
        element.removeEventListener(e.type,arguments.callee,false);        
    }
}, false)
```

**方法二：** 缓存匿名函数。

```javascript
window.pro_elt_addEventListener=Element.prototype.addEventListener;
Element.prototype.addEventListener=function(){
    if(!this.eventList) {
        this.eventList={};
    }
    if(!this.eventList[arguments[0]]) {
        this.eventList[arguments[0]]=[];
    }
    this.eventList[arguments[0]].push(arguments[1]);
    window.pro_elt_addEventListener.apply(this,arguments);
};
```

## 参考资料

[The Enter Key should Submit Forms, Stop Suppressing it](https://www.tjvantoll.com/2013/01/01/enter-should-submit-forms-stop-messing-with-that/)
