eslint简单使用
rules中 “extends”: “eslint:recommended”
Use eslint with Typescript, today! 666
tslint
暂时使用 tslint + tslint-microsoft-contrib 这个稳定点, 碰到要强制返回函数类型器的bug, 因为react的render和生命周期的函数
this is subtitle
rules中 “extends”: “eslint:recommended”
Use eslint with Typescript, today! 666
暂时使用 tslint + tslint-microsoft-contrib 这个稳定点, 碰到要强制返回函数类型器的bug, 因为react的render和生命周期的函数
1 | var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象 |
创建 XMLHttpRequest 对象的语法:
1 | variable = new XMLHttpRequest(); |
老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象:
1 | variable = new ActiveXObject('Microsoft.XMLHTTP'); |
如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open()
和 send()
方法:
1 | xmlhttp.open('GET', 'ajax_info.txt', true); |
1 | xmlhttp.open('POST', '/try/ajax/demo_post2.php', true); |
如需获得来自服务器的响应,请使用 XMLHttpRequest 对象的 responseText 或 responseXML 属性。
属性 | 描述 |
---|---|
responseText | 获得字符串形式的响应数据。 |
responseXML | 获得 XML 形式的响应数据。 |
当请求被发送到服务器时,我们需要执行一些基于响应的任务。
每当 readyState
改变时,就会触发 onreadystatechange
事件。
readyState
属性存有 XMLHttpRequest
的状态信息。
下面是 XMLHttpRequest 对象的三个重要的属性:
存有 XMLHttpRequest
的状态。从 0 到 4 发生变化。
XMLHttpRequest
对象还没有完成初始化XMLHttpRequest
对象开始发送请求XMLHttpRequest
对象的请求发送完成, 开始接受XMLHttpRequest
对象开始读取服务器的响应, 解析中XMLHttpRequest
对象读取服务器响应结束onreadystatechange 事件被触发 4 次(0 - 4), 分别是: 0-1、1-2、2-3、3-4,对应着 readyState 的每个变化。
立即执行函数表达式(IIFE) 是在创建函数后立即执行函数的方法。
立即执行函数表达式(IIFE) 非常有用,因为它们不会污染全局对象,它们是隔离变量声明的简单方法。
这是定义 IIFE 的语法:
JavaScript 代码:
1 | ;(function() { |
立即执行函数也可以使用箭头函数定义:
JavaScript 代码:
1 | ;(() => { |
基本上,我们在括号内定义了一个函数,然后在后面加上一个括号 ()
来执行该函数:(/* function */)()
。
包裹函数的括号实际上是使我们的函数在内部被视为表达式。 否则,函数声明将无效,因为我们没有指定任何名称:
函数声明需要一个名称
,而函数表达式不需要它。
你也可以将调用括号放在表达式括号内,没有区别,只是写法不同:
JavaScript 代码:
1 | (function() { |
你可以使用一些更奇怪的语法来创建 IIFE ,但它在现实世界中很少使用,并且它依赖于使用任何一元运算符:
JavaScript 代码:
1 | -(function() { |
但是这种方式不适用于箭头函数。
IIFE
也可以是命名的常规函数(不是箭头函数)。 这不会导致函数“泄漏”到全局作用域,并且在执行后也不能再次调用它:
JavaScript 代码:
1 | (function doSomething() { |
你可能已经注意到 IIFE
前的分号了:
JavaScript 代码:
1 | (function() { |
这可以防止在盲目合并两个 JavaScript
文件时出现问题。 由于 JavaScript
不需要分号,因此你可能会在最后一行中使用某些语句连接一个文件,从而导致语法错误。
这个问题基本上是通过像 webpack 这样的“智能”打包工具来解决的。
this
值根据使用位置的不同具有不同的值。不了解 JavaScript
这个小细节会导致很多头痛的问题,所以花 5 分钟学习所有技巧是值得的。
严格模式下,在任何对象之外的 this
始终是 undefined
(未定义的)。
注意我提到了严格模式。 如果禁用严格模式(如果没有在文件顶部明确添加 'use strict'
,则默认为禁用),即草率模式(sloppy mode),也有人称为非严格模式,在这种模式下 , 如果在本文下面某有提及的这些情况下,this
都指向全局对象。
浏览器的全局对象为 window
。
方法是附加到对象属性的函数。
你可以看到各种形式。
这是其中一种形式:
JavaScript 代码:
1 | const car = { |
在这种情况下,使用常规函数,this
会自动绑定到对象。
注意:上面的方法声明是 drive: function(){ …
的简写形式,ES6 语法,等价于:
JavaScript 代码:
1 | const car = { |
在这个例子中同样有效:
JavaScript 代码:
1 | const car = { |
箭头函数不能以相同的方式工作,因为它是词法绑定的:
JavaScript 代码:
1 | const car = { |
你不能像使用普通函数那样将值绑定到箭头函数。
由于他们的工作方式不同。箭头函数中 this
是词法(lexically)绑定,这意味着 this
的值来自定义它们的上下文。
了解更多详情请阅读:JavaScript 箭头函数(Arrow Function)
JavaScript 提供了一些方法来将 this
映射到你想要的任何对象上。
在 函数声明 阶段使用 bind()
:
JavaScript 代码:
1 | const car = { |
你还可以绑定现有的对象方法来重新映射 this
值:
JavaScript 代码:
1 | const car = { |
在 函数调用 阶段使用 call()
或 apply()
:
JavaScript 代码:
1 | const car = { |
传入 call()
或者 apply()
的第一个参数始终绑定 this
。call()
和 apply()
之间的区别在于, apply()
需要一个数组作为参数列表,而 call()
者可以接受多个参数。
apply array
在事件处理程序回调中,this
指向接收事件的 DOM
元素:
JavaScript 代码:
1 | document.querySelector('#button').addEventListener('click', function(e) { |
你可以这样绑定:
JavaScript 代码:
1 | document.querySelector('#button').addEventListener( |
Promises
是一种 JavaScript
中处理异步代码的方法,无需在代码中编写太多回调。
Promises
通常定义为最终可用的值的代理(a proxy for a value that will eventually become available)。
Promises
是处理异步代码的一种方法,无需在代码中编写太多回调。
其实 Promises
已经存在多年,但是直到 ES2015 才被标准化和引入,现在它们已经在 ES2017 中被 async
(异步) 函数所取代。
Async functions
(异步函数) 使用 promises API
作为构建块,因此理解 Promises
是必须的,即使在较新的代码中,你可能会使用 async
(异步) 函数而不是 promises
。
Promsie
是如何工作的一旦调用了一个 promise
,它就会以 pending(挂起) 状态开始。 这意味着调用者函数继续执行,同时等待 promise
执行自己的处理,并为调用者函数提供一些反馈。
此时,调用者函数等待它以 resolved 状态或者 rejected 状态 返回 promise
,但是如果你知道 JavaScript 是异步的,那么 函数会在 promise
完成其工作时继续执行 。
除了你自己的代码和库代码之外,Promises 还被用于标准的现代 Web API,例如:
在现代 JavaScript 中你不太可能发现自己没有使用 Promises ,所以让我们开始深入了解它们。
Promise API
公开了一个 Promise
构造函数,你可以使用 new Promise()
进行初始化:
JavaScript 代码:
1 | let done = true; |
高阶函数, 函数当参数
!注意, 这个executor
不是用return
, 而是返回并执行resolve, reject
回调函数
!注意, 这个executor
不是用return
, 而是返回并执行resolve, reject
回调函数
!注意, 这个executor
不是用return
, 而是返回并执行resolve, reject
回调函数
正如你所看到的,promise
会检查 done
这个全局常量,如果 done
为 true
,我们将返回 resolved
状态的 promise
,否则将返回 rejected
状态的 promise
。
使用 resolve
和 reject
时,我们可以回传一个值,在上面的例子中我们只是回传了一个字符串,但它也可以是一个对象。
在上一节中,我们介绍了如何创建 promise
。
现在让我们看看如何使用 promise
。
JavaScript 代码:
1 | const isItDoneYet = new Promise(); |
运行 checkIfItsDone()
将执行 isItDoneYet()
promise 并使用 then
回调等待该 promise
的 resolve
状态。如果有错误,它将在 catch
回调中处理这个错误。
promise
可以返回另一个 promise
,形成一个链式 promise
。
链式调用 promises
的一个很好的例子是 Fetch API
,它是 XMLHttpRequest API
上层 API,我们可以使用它来获取资源,并在获取资源时对 Promise 链进行排列。
promise
和ajax
没有关系, 而ajax
是fetch
的基础
Fetch API
基于 promise
机制,调用 fetch()
等同于我们通过 new promise()
定义一个我们自己的 promise
。
JavaScript 代码:
1 | const status = (response) => { |
在这个例子中,我们调用 fetch()
从根域名中的 todos.json
文件获取一个 TODO 清单,我们创建了一个 promises 链。
运行 fetch()
返回一个包含很多个属性的 response
,我们引用了其中的:
status
,数字值表示的 HTTP 状态代码statusText
,状态消息,请求成功时为 OKresponse
还有一个 json()
方法,该方法返回一个 resolve
状态的 promise
,并且将响应内容转化为 JSON 作为该 promise 的回传值。
在这些前提下,会发生这样的情况:链中的第一个 promise 是我们定义的函数 status()
,它检查响应状态,如果响应不成功(在 200 到 299 之间),则 reject 该 promise 。
此操作将导致 promise 链跳过队列中的所有的 promise ,并将直接跳到底部的 catch()
语句,打印 Request failed 文本以及错误消息。
如果成功,它会调用我们定义的 json()
函数。 前一个 promise 返回 response 对象,作为第二个 promise 的输入。
在这种情况下,我们返回处理过的 JSON 数据 ,因此第三个 promise 直接接收 JSON:
JavaScript 代码:
1 | .then((data) => { |
在控制台会打印出这些内容。
在上面的例子中,promises
链后面有一个额外的 catch
块。当 promises
链有任何错误发生,将 promise 置为 reject
,控制会转到链中最近的 catch()
语句。
JavaScript 代码:
1 | new Promise((resolve, reject) => { |
如果在 catch()
内部又抛出一个错误,你可以添加第二个 catch()
处理它,以此类推。
JavaScript 代码:
1 | new Promise((resolve, reject) => { |
这是 ES2018(ES9)的新特性.
当一个 promise
得到满足(fulfilled)时,它会一个接一个地调用 then()
方法。如果在此期间发生错误 reject
,则跳过 then() 方法并执行 catch() 方法。
finally()
允许您运行一些代码,无论 promise 的执行成功或失败:
JavaScript 代码:
1 | fetch('file.json') |
如果你需要同步处理多个 promises,Promise.all()
可以帮助你定义一组 promises 列表,等待它们全部 resolved
之后再执行某些操作。比如:
JavaScript 代码:
1 | const f1 = fetch('/something.json') |
ES2015 的解构语法也允许你这么做:
JavaScript 代码:
1 | Promise.all([f1, f2]).then(([res1, res2]) => { // 这里解构数组 |
这不仅限于 fetch
,任何 promise
都可以处理。
Promise.race()
会在你传递给它的第一个 promise resolves 时运行,并且它只运行一次附加的回调,并传入首先 resolved 的 promise 返回的结果。
示例:
JavaScript 代码:
1 | const first = new Promise((resolve, reject) => { |
如果你控制台中看到 Uncaught TypeError: undefined is not a promise
错误,请确保使用的是 new Promise()
而不是 Promise()
。
如果你还有什么疑问,可以查看 ES6 Promise 指南 666666
1 | const myPromise = new Promise((resolve, reject) => { |
在上面的例子中还有一些需要注意的 重要事项
。
我们创建了一个 promise 实例 myPromise。我们分别在第 13 行和第 16 行附加了两个 .then 的处理程序。尽管它们在功能上是相同的,但它们还是被被视为不同的处理程序。但是 ——
promise
只能成功(resolved)或失败(reject)一次。它不能成功或失败两次,也不能从成功切换到失败,反之亦然。promise
在你添加成功/失败回调(即 .then)之前就已经成功或者失败,则 promise
还是会正确地调用回调函数,即使事件发生地比添加回调函数要早。这意味着一旦 promise
达到最终状态,即使你多次附加 .then
处理程序,状态也不会改变(即不会再重新开始计算)。
这说的就是第二个
.then
只会打印Hello, Promises!
为了验证这一点,你可以在第 3 行看到一个 console.log
语句。当你用 .then
处理程序运行上述代码时,需要输出的语句只会被打印一次。 它表明 promise
缓存了结果,并且下次也会得到相同的结果。
另一个要注意的是,promise 的特点是及早求值(evaluated eagerly)。 只要声明并将其绑定到变量,就立即开始执行。没有 .start
或 .begin
方法。就像在上面的例子中那样。
为了确保 promise 不是立即开始而是惰性求值(evaluates lazily), 我们将它们包装在函数中。稍后会看到一个例子。
一个
.catch
本身总是被解析为promise
,并且不会被拒绝(除非你故意抛出错误)。这就是为什么.then
后面的.catch
会被执行的原因。
这里建议使用 .catch
而不是带有 onResolved
和 onRejected
参数的 .then
去处理。下面有一个案例解释了为什么最好这样做
1 | const promiseThatResolves = () => |
理解 JavaScript 中的 Promises – JavaScript 完全手册(2018 版)
ES6 Promise 指南 666
ES2017 新特性:Async Functions (异步函数) 66 讲了和 generator 关系
用 async 和 await 编写现代 JavaScript 异步代码 – JavaScript 完全手册(2018 版)
译】Async-Await≈Generators+Promises
JavaScript 默认是同步的,并且是单线程的。这意味着代码无法创建新线程并且并行运行。让我们了解一下什么是异步代码及其样子。
计算机在设计上是异步的。
异步意味着某些事情可以独立于主程序流而发生。
在当前的消费者计算机中,每个程序都运行在一个特定的时间段,然后它停止执行,以让另一个程序继续执行。这种循环运行如此之快以至于我们无法注意到,我们认为我们的计算机同时运行许多程序,但这是一种幻觉(除了多处理器机器)。
程序内部使用 中断 ,这是一个发送到处理器的信号,以引起系统的注意。
我不会深入讨论它的内部细节,但请记住,程序是异步的是很正常的,并在需要注意之前停止它们的执行,并且计算机可以在此期间执行其他操作。当程序正在等待来自网络的响应时,它不能在请求完成之前停止处理器。
通常,编程语言是同步的,有些语言提供了一种在语言或库中管理异步方法。 C,Java,C#,PHP,Go,Ruby,Swift,Python,默认情况下它们都是同步的。其中一些通过使用线程处理异步,产生一个新进程。
JavaScript 默认是同步的,并且是单线程的。这意味着代码无法创建新线程并且并行运行。
代码行是一个接一个地串行执行的,例如:
JavaScript 代码:
1 | const a = 1; |
但 JavaScript 诞生于浏览器中,一开始其主要工作就是响应用户操作,如 onClick
,onMouseOver
,onChange
,onSubmit等
。 怎么用同步编程模型呢?
答案是在它的环境中。 浏览器通过提供一组可以处理这种功能的 API
来解决这个问题。
最近,Node.js 引入了一个非阻塞 I/O 环境,将这个概念扩展到文件访问,网络调用等。
你无法知道用户何时要单击按钮,因此你 要为 click 事件定义事件处理程序。 此事件处理程序接受一个函数,该函数将在事件触发时调用:
JavaScript 代码:
1 | document.getElementById('button').addEventListener('click', () => { |
这就是所谓的 回调函数
。
回调是一个简单的函数,它作为值传递给另一个函数,并且只在事件发生时执行。 我们可以这样做,是因为函数是 JavaScript 的 “一等公民” ,可以分配给变量并传递给其他函数(称为 javascript-高阶函数 )
将所有客户端代码包装在 window
对象上的 load
事件侦听器中是很常见的,这样仅在该页面准备就绪时才运行回调函数:
JavaScript 代码:
1 | window.addEventListener('load', () => { |
回调在任何地方都使用,而不仅仅在 DOM
事件中使用。
一个常见的例子是使用定时器:
JavaScript 代码:
1 | setTimeout(() => { |
XHR
请求也接受回调。在本示例中,通过将函数分配给属性,该函数在特定事件发生时将被调用(在本例中,请求状态发生了变化):
JavaScript 代码:
1 | const xhr = new XMLHttpRequest(); |
你如何处理回调错误? 一个非常常见的策略是使用 Node.js
采用的办法:任何回调函数中的第一个参数是错误对象:error-first callbacks
(错误优先的回调)
如果没有错误,则该对象为 null
。 如果有错误,则包含错误的描述和其他信息。
JavaScript 代码:
1 | fs.readFile('/file.json', (err, data) => { |
对于简单的情况,回调非常有用!
但是每个回调都会增加嵌套层级,当你有很多回调时,代码开始变得非常复杂,也就大家经常会听到的 “回调地狱” :
JavaScript 代码:
1 | window.addEventListener('load', () => { |
这只是一个简单的 4 层嵌套代码,但我已经看到了更多层级的嵌套,这并不好玩。
我们如何解决这个问题呢?
从 ES6 开始,JavaScript
引入了一些新功能,可以帮助我们不用回调就能处理异步代码:
这些将在后面的章节中单独介绍。
从输入URL到页面加载发生了什么 > JavaScript-的-async-await
事件循环是 JavaScript 最重要的内容之一。
本节旨在解释 JavaScript 如何处理单个线程的内部细节,以及如何处理异步函数。
您的 JavaScript 代码运行是单线程的。一次只发生一件事。
这是一个实际上非常有用的限制,因为它简化了很多程序,而不必担心并发问题。
您只需要注意编写代码的方式,避免任何可能阻塞线程的内容,如同步网络请求或无限循环。
通常,在大多数浏览器中,每个浏览器标签都有一个独立的事件循环机制,以使每个进程隔离,并避免 web 页面具有无限循环或繁重的处理,从而阻塞整个浏览器。
浏览器环境管理多个并发事件循环,例如处理 API 调用。 Web Workers 也在自己的事件循环中运行。
您只要明白您的代码将在单个事件循环上运行,并在编写代码时考虑到这一点,以避免阻塞它。
任何执行时间过长不能将控制权返回给事件循环的 JavaScript
代码都会阻塞页面内其它代码的执行,甚至阻塞 UI 线程,用户不能单击、滚动页面,等等。
几乎所有 JavaScript 中的 I/O 原语都是非阻塞的,比如网络请求,Node.js 文件系统操作等。 阻塞是个例外,这就是为什么 JavaScript 基于回调,以及最近的 promises
和 async/await
。
调用堆栈是 LIFO 队列(Last In, First Out)。 就是个栈 先进后出, 后出先进.
事件循环不断检查调用堆栈里是否仍有函数需要运行。
在这样做的同时,它将它找到的任何函数调用添加到调用堆栈,并按顺序执行每个调用。
您可以在调试器或浏览器控制台中了解您可能熟悉的错误堆栈跟踪吗? 浏览器在调用堆栈中查找函数名称,以通知您哪个函数发起当前调用:
内到外, 栈
我们来举一个例子:
JavaScript 代码:
1 | const bar = () => console.log('bar'); |
此代码打印:
JavaScript 代码:
1 | foo; |
正如大家所料,和预期一样。
当此代码运行时,首先调用 foo()
。在 foo()
里面我们先调用 bar()
,然后调用 baz()
。
此时调用堆栈如下所示:
每次迭代的事件循环都会查看调用栈中是否存在某些内容并执行它:
直到调用堆栈为空。
上面的例子看起来很正常,没有什么特别之处:JavaScript
找到要执行的东西,按顺序运行它们。
让我们看看如何推迟一个函数,直到堆栈清空。
setTimeout(() => {}), 0)
用例是调用一个函数,会在代码中其他每个函数都已执行后再执行它。
举个例子:
JavaScript 代码:
1 | const bar = () => console.log('bar'); |
这段代码打印出来,结果是:
JavaScript 代码:
1 | foo; |
当此代码运行时,首先调用 foo()
。 在 foo()
里面我们首先调用 setTimeout
,将 bar
作为参数传递,然后将 0
作为计时器传递,意思是让它尽可能快地运行。 然后调用 baz()
。
此时调用堆栈如下所示:
注意看是 foo()
执行完后 再执行 bar()
以下是我们程序中所有函数的执行顺序:
为什么会这样?
当调用 setTimeout()
时,浏览器或者 Node.js 就会启动计时器。 一旦计时器到期,在这个例子中,当我们将 0
作为超时时间,回调函数立即被放入 消息队列 中。
消息队列也包含用户发起的事件,如单击事件、键盘事件,或者在代码有机会对 fetch
响应作出响应之前,fetch
响应就已经进入队列了。再或者像 onLoad
这样的 DOM
事件。
循环优先于调用堆栈,它首先处理它在调用堆栈中找到的所有内容,一旦没有任何东西,它就会在事件队列中查找要执行的内容。
我们不必等待 setTimeout
,fetch
或其他函数来完成它们自己的工作,因为它们是由浏览器提供的,并且它们依赖于自己的线程。例如,如果您将 setTimeout
超时设置为 2 秒,那么您不必等待 2 秒 — 等待在其他地方发生。
ECMAScript 2015 引入了 Promises
(也在 ES6 / ES2015 中引入) ,使用了作业队列(Job Queue
)概念。 这是一种尽快执行异步函数的方法,而不是放在调用堆栈的末尾。
在当前函数结束之前 resolve
状态的 Promises
将在当前函数之后立即执行。
我发现在游乐园里乘坐过山车的情况可以很好地解释 ES6 作业队列(Job Queue):消息队列让你排队等候队列,等队列中所有其他人坐完之后乘坐,而作业队列(Job Queue)相当于是快速通行票,可以让您在乘坐完之后立即再乘一次。
例如:
JavaScript 代码:
1 | const bar = () => console.log('bar'); |
此代码打印:
JavaScript 代码:
1 | foo |
这是 Promises
(包括基于 promises
的 Async/await
)和普通的旧异步函数(通过 setTimeout()
或其他平台 API )之间的巨大差异。
浏览器中的 JavaScript 使用事件驱动的编程模型。 一切都始于事件。 本节介绍 JavaScript 事件以及事件处理的工作原理。
浏览器中的 JavaScript 使用事件驱动的编程模型。
一切都始于事件。
事件可能是 DOM
已加载,或者是异步请求完成,或用户单击元素或滚动页面,或用户按下键盘。
有很多不同类型的事件。
你可以使用事件处理程序响应任何事件,事件处理程序只是在事件发生时调用的函数。
你可以为同一事件注册多个处理程序,并在事件发生时调用它们。
JavaScript 提供了三种注册事件处理程序的方法:
由于他自身的限制,这种类型的事件处理程序今天很少使用,但这是 JavaScript 早期的唯一方法:
HTML 代码:
1 | <a href='site.com' onclick='dosomething();'> |
当一个对象只有一个事件处理器时这种方法很常用,因为在这种情况下无法添加多个处理程序:
JavaScript 代码:
1 | window.onload = () => { |
它在处理XHR
请求时最常用:
JavaScript 代码:
1 | const xhr = new XMLHttpRequest(); |
你可以使用 if ('onsomething' in window) {}
检查是否已将处理程序分配给某个属性。
这是 现代 方式。这种方法允许我们根据需求注册多个处理程序,你会发现它是绑定处理程序最受欢迎的方式:
JavaScript 代码:
1 | window.addEventListener('load', () => { |
注意:IE8 及以下版本不支持这个方法,可以使用
attachEvent
() 代替。如果你需要支持旧浏览器,请记住这一点。
你可以监听 window
来拦截“全局”事件,比如键盘的使用,你也可以监听特定元素上发生的事件,比如鼠标点击了某个按钮。
这也是为什么 addEventListener
有时候在 window
上调用,有时间在某个 DOM
元素上。
事件处理器会获得一个 Event
对象作为第一个参数:
JavaScript 代码:
1 | const link = document.getElementById('my-link'); |
这个对象包含很多有用的属性和方法,比如:
target
,事件发生的目标 DOM
元素type
,事件类型stopPropagation()
,调用以阻止 DOM
事件传播其它属性提供给特定的事件,Event
只是不同事件的一个接口:
…等等
上面的每一个都链接到了 MDN 页面,你可以在那查看它们所有的属性。
例如,当一个键盘事件发生时,你可以检查哪个键被按下,通过 key
属性值得到一个可读格式的值( Escape
, Enter
等等):
JavaScript 代码:
1 | window.addEventListener('keydown', (event) => { |
在鼠标事件中,我们可以检查按下了哪个鼠标按钮:
JavaScript 代码:
1 | const link = document.getElementById('my-link'); |
事件冒泡和事件捕捉是事件传播的两个模型。
假设你的 DOM 结构是这样的:
HTML 代码:
1 | <div id="container"> |
你希望跟踪用户何时单击该按钮,并且你有两个事件侦听器,一个在 button
上,另一个在 #container
上。 请记住,单击子元素将始终传播到其父元素,除非你停止事件传播(我们稍后会看到)。
这些事件侦听器会按照顺序调用,这个顺序通过事件冒泡/事件捕捉模型决定。
冒泡 意味着事件从被点击的元素(子元素)一直向上传播到所有祖先元素,从最近的一个开始。从子开始处理相应程序
在我们的例子中,点击 button
的话, button
上的处理器会在 #container
之前发生。
捕捉 恰恰相反:最外部的事件会在特定处理器之前发生,比如还是点击 button
, 但先冒泡到最外层的 #container
, #container
上的事件处理先相应, 然后是 button
上的事件处理程序响应.
默认采用事件冒泡
模型。从子开始处理
你也可以选择使用事件捕捉,通过将 addEventListener
的第三个参数设为 true:
JavaScript 代码:
1 | document.getElementById('container').addEventListener( |
注意:首先运行所有捕获
事件处理程序。
然后是所有冒泡
的事件处理程序。
这个顺序遵循这个原则:DOM
遍历从 Window
对象开始的所有元素,直到找到被点击的元素项。执行此操作时,调用与事件关联的任何事件处理程序(捕获阶段)。
一旦找到目标元素,它会重复这个过程直到回到 Window
对象,此时调用相应的事件处理器(冒泡阶段)。
这样图可以帮助你理解这个过程:
注意咯, 如果你设置了多个事件处理, 比如一个
window
上设置了 2 个 一个用 冒泡, 一个用 捕获, 那么 都会触发的, 然后触发顺序是 下面的 1 2 3 4 先 处理 捕获的 在处理 冒泡 的事件处理程序
DOM 元素上的事件将传播到其所有父元素上,除非手动停止传播:
HTML 代码:
1 | <html> |
a
上的 click
事件会传播到 section
然后是 body
。
你可以调用 Event
的 stopPropagation()
方法来停止事件传播,通常放在事件处理程序的末尾(注:我个人喜好放在事件处理程序的开始处):
JavaScript 代码:
1 | const link = document.getElementById('my-link'); |
以下可能是你会处理的最常见事件的列表。
页面加载完成后,在 window
和 body
元素上触发 load
事件。
单击鼠标按钮时 click
事件触发。 单击鼠标两次时触发 dbclick
事件。 当然,在这种情况下,click
事件会在此事件之前触发。mousedown
,mousemove
和 mouseup
可以和拖动事件结合在一起。小心使用 mousemove
,因为它会在鼠标移动过程中触发很多次(稍后会看到节流)。
当按下键盘键时 keydown
事件就会触发(当按下按钮时,任何时候重复键)。当键被释放时,将触发 keyup
事件。
每次滚动页面时都会在 window
上触发 scroll
事件。在事件处理程序中,你可以通过检查 window.scrollY
(Y 轴)(注:我个人喜好用 document.documentElement.scrollTop)
来检查当前的滚动位置。
请记住,此事件不是一次性的事件。它在滚动期间会发生很多次,而不仅仅是在滚动的结尾或开始时,所以不要在处理程序中进行任何频繁的计算或操作 – 而是使用节流代替。
如上所述,mousemove
和 scroll
这两个事件都不是一次性事件,而是在持续操作的时间内连续调用它们的事件处理函数。
这是因为它们提供坐标,因此你可以跟踪正在发生的事件。
如果你在这些事件处理器中进行复杂的操作,则会影响性能并导致站点用户体验不佳。
像 Lodash throttle 这样的库提供了 100 行代码实现的节流函数来处理这个问题。一个简单易懂的实现是使用 setTimeout
每隔 100ms
缓存一次滚动事件:
JavaScript 代码:
1 | let cached = null; |
两者区别
理解 Debouncing 与 Throttling 的区别 666
白话 debounce 和 throttle
debounce 的主要区别是 推迟这次调用。 电梯例子
throttle 保证方法每 Xms 有规律的执行,
debounce
ajax
请求的情况,例如当页面下拉超过一定返回就通过 ajax
请求新的页面内容,这时候可以通过 debounce 合并 ajax 请求事件throttle
JavaScript 提供了许多迭代循环的方法。本节通过一个小例子和主要属性解释现代 JavaScript 中的所有各种循环方法。
JavaScript 代码:
1 | const list = ['a', 'b', 'c']; |
您可以使用 break
中断 for
循环
您可以使用 continue
快速进入到 for
循环的下一次迭代
在 ES5 中引入。给定一个数组,您可以使用 list.forEach()
迭代其属性:
JavaScript 代码:
1 | const list = ['a', 'b', 'c']; |
不幸的是,你无法中断 forEach
循环。
JavaScript 代码:
1 | const list = ['a', 'b', 'c']; |
可以通过 break
中断 do...while
循环:
JavaScript 代码:
1 | do { |
你可以使用 continue
跳转到下一个迭代:
JavaScript 代码:
1 | do { |
JavaScript 代码:
1 | const list = ['a', 'b', 'c']; |
您可以使用 break
中断 while
循环:
JavaScript 代码:
1 | while (true) { |
你可以使用 continue
跳转到下一个迭代:
JavaScript 代码:
1 | while (true) { |
while
与 do...while
的区别在于 do...while
至少执行一次循环。
迭代对象所有可枚举属性。 会遍历原型链的, 结合 Object.prototype.hasOwnProperty()
JavaScript 代码:
1 | for (let property in object) { |
ES2015 中引入了 for...of
循环,它结合了 forEach
的简洁性,并且for...of
循环具有可以中断循环特性:
JavaScript 代码:
1 | // 迭代值 |
注意使用 const
。这个循环在每次迭代都创建了一个新的作用域,所以我们可以安全的使用它替代 let
。
和 for...in
不同的是:
for...in
迭代属性名 keyfor...of
迭代属性值 value