JavaScript语法

JavaScript语法

变量

变量的定义:

  • 变量是存储值容器
  • 使用关键字 let 或 var。
  • 结尾加分号。
  • 当需要输出的变量从未声明过,或声明过但没有赋值,最终显示出来的都是undefined字样。

变量的数据类型:

变量 解释 示例
String 字符串:使用引号括起来。 let myName = ‘冷冷’;
Number 数字:无需引号。 let myAge = 25;
Boolean 布尔值:无需引号。 let myCar = true;
Array 数组:用于在单一引用中存储多个值的结构。 let myHouse = [1,’Jeep’,3];元素引用方法:myHouse[0],myHouse[1]…
Object 对象:一切皆对象,一切皆可存储在变量里。 let a = document.querySelector(‘h1’);以及上面所有示例都是对象。

let 与 var的区别:

let 是 es6 中新增命令
let 和 var 的区别体现在作用域上。var 的作用域被规定为一个函数作用域,而 let 则被规定为块作用域,块作用域要比函数作用域小一些,但是如果两者既没在函数中,也没在块作用域中定义,那么两者都属于全局作用域。

全局作用域

var 和 let 声明的变量在全局作用域中被定义时,两者非常相似

1
2
let bar = 'hehe'; 
var baz = 'lala';

但是,被let声明的变量不会作为全局对象window的属性,而被var声明的变量却可以

1
2
console.log(window.bar);  //undefined 
console.log(window.baz); // 'able'

函数作用域

var 和 let 在函数作用域中声明一个变量,两个变量的意义是相同的。

1
2
3
function  aFun(){     
let bar = 'hehe'; // 函数作用域中的变量
var baz = 'lala'; // 函数作用域中的变量 }

块作用域

在块作用域中两者的区别较为明显, let只在for()循环中可用,而 var是对于包围for循环的整个函数可用

1
2
3
4
5
6
7
8
9
10
11
12
13
function  aFun1(){     
// i 对于for循环外的范围是不可见的(i is not defined)
for(let i = 1; i<5; i++){
// i只有在这里是可见的
}
// i 对于for循环外的范围是不可见的(i is not defined)
}
function aFun2(){ // i 对于for循环外的范围是可见的
for(var i = 1;i<5; i++){
// i 在for 在整个函数体内都是可见的
}
// i 对于for循环外的范围是可见的
}

let 和 var 重复声明

var允许在同一作用域中声明同名的变量,而let不可以。

1
2
3
4
5
let me  = 'foo';
let me = 'bar'; //SyntaxError: Identifier 'me' has already been declared

var me = 'foo';
var me = 'bar'; //这里me被替代了,是可以重复声明的

es6 中还有一个声明变量的命令 const,const 和 let 都是在声明的块作用域中有效,但是 let 声明的变量可变,值和类型都可以改变,没有限制。const 声明额变量不能改变,所以,const 一旦声明一个变量,就必须马上初始化,不能留到以后赋值

1
2
3
4
const hehe; //报错,Missing initializer in const declaration

const a = 3;
a = 5; //报错,Uncaught TypeError: Assignment to constant variable.

以上就是let和var在不同作用域下的区别

那么在什么情况下要用到 let 呢?

let 在块作用域中有效,有的时候,我们为了降低变量污染的风险,在块作用域中使用let来代替var,这样不会污染块作用域的外部作用域,降低 bug率,使代码更安全。

在代码中尽可能多地使用 let,而不是 var。因为没有理由使用 var,除非你需要用代码支持旧版本的 Internet Explorer (它直到第 11 版才支持 let ,现代的 Windows Edge 浏览器支持的很好)。

变量提升:

JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。

1
2
console.log(a);
var a = 1;

上面代码首先使用console.log方法,在控制台(console)显示变量a的值。这时变量a还没有声明和赋值,所以这是一种错误的做法,但是实际上不会报错。因为存在变量提升,真正运行的是下面的代码。

1
2
3
var a;
console.log(a);
a = 1;

最后的结果是显示undefined,表示变量a已声明,但还未赋值。

但提升操作适用于 let 。如果将上面例子中的 var 替换成 let 将不起作用并引起一个错误。

变量作用域:

一个 JavaScript 标识符必须以字母、下划线(_)或者美元符号($)开头;后续的字符也可以是数字(0-9)。因为 JavaScript 语言是区分大小写的,所以字母可以是从“A”到“Z”的大写字母和从“a”到“z”的小写字母。合法的标识符示例:Number_hits,temp99,$credit 和_name。

var:声明一个局部变量和全局变量,可选初始化一个值。

let:声明一个块作用域的局部变量,可选初始化一个值。

const:声明一个块作用域只读常量

  • 用 var 或 let 语句声明的变量,如果没有赋初始值,则其值为 undefined。
  • 如果访问一个未声明的变量会导致抛出一个引用错误异常。
  • 数值类型环境中 undefined 值会被转换为 NaN
  • 当对一个 null 变量求值时,空值 null 在数值类型环境中会被当作 0 来对待,而布尔类型环境中会被当作 false
  • 对象属性被赋值为常量是不受保护的,数组的被定义为常量也是不受保护的。

在函数之外声明的变量,叫做***全局**变量,因为它可被当前文档中的任何其他代码所访问。在函数内部声明的变量,叫做**局部***变量,因为它只能在当前函数的内部访问。

  • ES6 前没有语句块,使用的是 var,作用域是全局范围。
  • ES6 中:
1
2
3
4
if (true) {
let y = 5;
}
console.log(y); // ReferenceError: y 没有被声明

变量提升:

  • JavaScript 变量的另一个不同寻常的地方是,你可以先使用变量稍后再声明变量而不会引发异常。这一概念称为变量提升。
  • JavaScript 变量感觉上是被“提升”或移到了函数或语句的最前面。但是,提升后的变量将返回 undefined 值。因此在使用或引用某个变量之后进行声明和初始化操作,这个被提升的变量仍将返回 undefined 值。

关于变量命名的规则:

可以给变量赋任何你喜欢的名字,但有一些限制。一般你应当坚持使用拉丁字符 (0-9,a-z,A-Z) 和下划线字符。

  • 不应当使用规则之外的其他字符,因为它们可能引发错误,或对国际用户来说难以理解。
  • 变量名不要以下划线开头——以下划线开头的被某些 JavaScript 设计为特殊的含义,因此可能让人迷惑。
  • 变量名不要以数字开头。这种行为是不被允许的,并且将引发一个错误。
  • 一个可靠的命名约定叫做 “小写驼峰命名法”,用来将多个单词组在一起,小写整个命名的第一个字母然后大写剩下单词的首字符。
  • 让变量名直观,它们描述了所包含的数据。不要只使用单一的字母/数字,或者长句。
  • 变量名大小写敏感——因此myage与myAge是 2 个不同的变量。
  • 最后也是最重要的一点——应当避免使用 JavaScript 的保留字给变量命名。保留字,即是组成 JavaScript 的实际语法的单词!因此诸如 var、function、let 和 for 等,都不能被作为变量名使用。浏览器将把它们识别为不同的代码项,因此将得到错误。

数据类型

字符串

在 JavaScript 中,你可以选择单引号(’)、双引号(”)或反引号(`)来包裹字符串。

1
2
3
const single = "单引号";
const double = "双引号";
const backtick = `反引号`;

使用反引号声明的字符串是一种特殊字符串,被称为模板字面量。在大多数情况下,模板字面量与普通字符串类似,但它具有一些特殊的属性:

  • 你可以在其中嵌入 JavaScript

    • 使用 ${ } 中包装 JavaScript 变量表达式,其结果将被包含在字符串中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const name = "克里斯";
const greeting = `你好,${name}`;
console.log(greeting); // "你好,克里斯"

const one = "你好,";
const two = "请问最近如何?";
const joined = `${one}${two}`;
console.log(joined); // "你好,请问最近如何?"

const song = "青花瓷";
const score = 9;
const highestScore = 10;
const output = `我喜欢歌曲《${song}》。我给它打了 ${
(score / highestScore) * 100
} 分。`;
console.log(output); // "我喜欢歌曲《青花瓷》。我给它打了 90 分。"
  • 你可以声明多行的模板字面量
1
2
3
4
5
6
7
8
const newline = `终于有一天,
你知道了必须做的事情,而且开始……`;
console.log(newline);

/*
终于有一天,
你知道了必须做的事情,而且开始……
*/

数字与字符串

如果有一个数字变量,你想将其转换为字符串,或者你想将一个字符串变量转换为数字,可以使用以下两个结构:

  • 如果可以的话,Number() 函数会将其参数转换为数字。
1
2
3
4
const myString = "123";
const myNum = Number(myString);
console.log(typeof myNum);
// number
  • 相反,String() 函数将其参数转换为字符串。
1
2
3
4
const myNum2 = 123;
const myString2 = String(myNum2);
console.log(typeof myString2);
// string

这些结构在某些情况下非常有用。例如,如果用户在表单的文本字段中输入一个数字,那么它就是一个字符串。然而,如果你想将这个数字与某个值相加,则需要它是一个数字,因此你可以通过 Number() 来处理它。

比较字符串

一、大于(>),小于(<)运算符

javascript字符串在进行大于(小于)比较时,会根据第一个不同的字符的ascii值码进行比较,当数字(number)与字符串(string)进行比较大小时,会强制的将数字(number)转换成字符串(string)然后再进行比较。

1
2
3
4
5
6
7
(function(){
console.log('13'>'3'); // 输出:false
console.log('5>'6'); // 输出: false
console.log('d'>'ABDC') // 输出: true
console.log('19>'ssf') // 输出 false
console.log('A'>'abcdef') // 输出 false
})()

二、相等(==),严格相等(===)运算符

在进行相等(==)运算比较时,如果一边是字符,一边是数字,会先将字符串转换成数字再进行比较;严格相等(===)则不会进行类型转换,会比较类型是否相等。

注:NaN与任何值比较时都是false

1
2
3
4
5
6
7
8
(function(){
console.log('6'==6) // true
console.log('6'===6) // false
console.log(6===6) // true
console.log('abc'==2) // false
console.log('abc'=='abc') // true
console.log('abc'==='abc') // true
})()

一些特殊值的相等与严格相等比较:

1
2
3
4
5
6
7
8
9
10
(function(){
console.log(null==undefined) // 输出:true
console.log(null===undefined) // 输出:false
console.log(null===null) // 输出:true
console.log(undefined===undefined) // 输出:true
console.log(NaN==undefined) // 输出:false
console.log(NaN==null) // 输出:false
console.log(NaN==NaN) // 输出:false
console.log(NaN===NaN) // 输出:false
})()
  • 字符串比大小时会左对齐然后从左往右依次比较,只要不相同能分出胜负就停下。在字母相同的情况下JavaScript是区分大小写的,同一个字母的小写大于大写。

数组

数组是一个包含了多个值的对象。

数组对象可以存储在变量中,并且能用和其他任何类型的值完全相同的方式处理,区别在于我们可以单独访问列表中的每个值,并使用列表执行一些有用和高效的操作,如循环——它对数组中的每个元素都执行相同的操作。

创建数组

数组由方括号构成,其中包含用逗号分隔的元素列表。

  • 假设想在一个数组中存储一个购物清单
1
let shopping = ["bread", "milk", "cheese", "hummus", "noodles"];
  • 在这种情况下,数组中的每个项目都是一个字符串,但请记住,可以将任何类型的元素存储在数组中(字符串,数字,对象,另一个变量,甚至另一个数组)。也可以混合和匹配项目类型,它们并不都是数字,字符串等。
1
2
let sequence = [1, 1, 2, 3, 5, 8, 13];
let random = ["tree", 795, [0, 1, 2]];

字符串和数组之间的转换

join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串,用逗号或指定的分隔符字符串分隔。如果数组只有一个元素,那么将返回该元素而不使用分隔符。使用 join() 可以指定不同的分隔符。

1
2
3
4
const elements = ['Fire', 'Air', 'Water'];

console.log(elements.join('-'));
// Expected output: "Fire-Air-Water"

toString() 方法返回一个字符串,表示指定的数组及其元素。toString() 可以比 join() 更简单,因为它不需要一个参数,但更有限制。

1
2
let dogNames = ["Rocket", "Flash", "Bella", "Slugger"];
dogNames.toString(); //Rocket,Flash,Bella,Slugger

添加和删除数组项

使用 push() 添加一个或多个要添加到数组末尾的元素。

从数组中删除最后一个元素的话直接使用 pop() 就可以。

unshift() 和 shift() 从功能上与 push() 和 pop() 完全相同,只是它们分别作用于数组的开始,而不是结尾。

条件语句

if…else 语句

1
2
3
4
5
if (condition) {
/* 条件为真时运行的代码 */
} else {
/* 否则,运行其他的代码 */
}
  • 如果(if)条件(condition)返回 true,运行代码 A,否则(else)运行代码 B
  • 在这种情况下,第二段代码不被条件语句控制,所以它总会运行,不管条件返回的是 true 还是 false
1
2
3
4
5
if (condition) {
/* 条件为真时运行的代码 */
}

/* 运行其他的代码 */
  • 任何不是 false、undefined、null、0、NaN、或一个空字符串(’’)在作为条件语句进行测试时实际返回 true,因此可以简单地使用变量名称来测试它是否为真,甚至是否存在(即它不是 undefined 的)。
1
2
3
4
5
6
7
8
let cheese = "Cheddar";

if (cheese) {
console.log("耶!这里有一些制作奶酪吐司的奶酪。");
} else {
console.log("今天你的吐司上没有奶酪了。");
}
//耶!这里有一些制作奶酪吐司的奶酪。

switch 语句

switch语句以单个表达式/值作为输入,然后查看多个选项,直到找到与该值相匹配的选项,执行与之相关的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch (表达式) {
case 选择1:
运行这段代码
break;

case 选择2:
否则,运行这段代码
break;

// 包含尽可能多的情况

default:
实际上,仅仅运行这段代码
}

default 部分不是必须的——如果表达式不可能存在未知值,则可以安全地省略它。然而,如果有这样的机会,你需要包括它来处理未知的情况。

三元运算符

三元运算符用于测试一个条件,并返回一个值/表达式,如果它是 true 则返回其中一个,否则(false)返回另外一个。

1
condition ? 运行这段代码 : 否则,运行这段代码

循环

一段循环通常需要一个或多个条件:

  • 一个开始条件,它被初始化为一个特定的值,这是循环的起点 。
  • 一个结束条件,这是循环停止的标准,通常计数器达到一定值。
  • 一个迭代器,这通常在每个连续循环上递增少量的计数器,直到达到退出条件。
1
2
3
4
5
6
7
8
9
loop(food = 0; foodNeeded = 10) {
if (food = foodNeeded) {
exit loop;
// 我们有足够的食物了,回家吧。
} else {
food += 2; // 花一个小时多收集两个食物。
// 循环将会继续执行。
}
}

for 循环

1
2
3
for (initializer; exit-condition; final-expression) {
// code to run
}
  1. 关键字for,后跟一些括号。

  2. 在括号内,我们有三个项目,以分号分隔:

    1. 一个初始化器initializer - 这通常是一个设置为一个数字的变量,它被递增来计算循环运行的次数。它也有时被称为计数变量
    2. 一个退出条件exit-condition - 如前面提到的,这个定义循环何时停止循环。这通常是一个表现为比较运算符的表达式,用于查看退出条件是否已满足的测试。
    3. 一个最终条件final-expression - 这总是被判断(或运行),每个循环已经通过一个完整的迭代消失时间。它通常用于增加(或在某些情况下递减)计数器变量,使其更接近退出条件值。
  3. 一些包含代码块的花括号 - 每次循环迭代时都会运行这个代码。

使用 break 退出循环

如果要在所有迭代完成之前退出循环,可以使用 break 语句。

使用 continue 跳过迭代

continue 语句以类似的方式工作,而不是完全跳出循环,而是跳过当前循环执行下一个循环

while 语句和 do … while 语句

1
2
3
4
5
6
7
8
9
10
11
12
初始化器initializer
while (退出条件exit-condition) {
// code to run

最终条件final-expression
}
初始化器initializer
do {
// code to run

最终条件final-expression
} while (退出条件exit-condition)

这里的区别在于退出条件是一切都包含在括号中,而后面是一个 while 关键字。在 do … while 循环中,花括号中的代码是在检查之前运行一次,以查看是否应该再次执行(在 while 和 for 中,检查首先出现,因此代码可能永远不会执行)。

运算符

运算符的定义:

  • 运算符是一类数学符号,可以根据两个值(或变量)产生结果。
  • 注: 这里说“根据两个值(或变量)产生结果”是不严谨的,计算两个变量的运算符称为“二元运算符”,还有一元运算符和三元运算符,“取非”就是一元运算符。

运算符种类:

算数运算符:

运算符 解释 符号 示例
将两个数字相加,或拼接两个字符串。 + 6 + 9;
“Hello “ + “world!”;
减、乘、除 这些运算符操作与基础算术一致。只是乘法写作星号,除法写作斜杠。 -, *, / 9 - 3;
8 * 2; //乘法在 JS 中是一个星号
9 / 3;
求余 (有时候也叫取模) 在你将左边的数分成同右边数字相同的若干整数部分后,返回剩下的余数 % 8 % 3 (返回 2,8 除以 3 的倍数,余下 2。)
取底数的指数次方,即指数所指定的底数相乘。它在 EcmaScript 2016 中首次引入。 ** 5 ** 5 (返回 3125,相当于 5 * 5 * 5 * 5 * 5 。)

比较运算符:

运算符 解释 符号 示例
全等 测试两个值是否相等,并返回一个 true/false (布尔)值。 === 5 === 2 + 4 // false
‘Chris’ === ‘Bob’ // false
5 === 2 + 3 // true
2 === ‘2’ // false;数字与字符串不相等
不等于 和等于运算符相反,测试两个值是否不相等,并返回一个 true/false (布尔)值。 !== 5 !== 2 + 4 // true
‘Chris’ !== ‘Bob’ // true
5 !== 2 + 3 // false
2 !== ‘2’ // true;数字与字符串不相等

赋值运算符:

运算符 解释 符号 示例
赋值运算符 为变量赋值 = let myVariable = ‘冷冷’;

逻辑运算符:

运算符 解释 符号 示例
逻辑与 允许你把两个或多个表达式连在一起,这样所有的表达式都必须单独评估为 true,整个表达式才能返回 true。 && var a1 = true && true; // t && t returns true
var a2 = true && false; // t && f returns false
var a3 = false && true; // f && t returns false
var a4 = false && 3 == 4; // f && f returns false
var a5 = “Cat” && “Dog”; // t && t returns Dog
var a6 = false && “Cat”; // f && t returns false
var a7 = “Cat” && false; // t && f returns false
逻辑或 允许你把两个或多个表达式连在一起,其中一个或多个表达式必须单独评估为 true,整个表达式才能返回 true。 || var o1 = true || true; // t || t returns true
var o2 = false || true; // f || t returns true
var o3 = true || false; // t || f returns true
var o4 = false || 3 == 4; // f || f returns false
var o5 = “Cat” || “Dog”; // t|| t returns Cat
var o6 = false || “Cat”; // f || t returns Cat var o7 = “Cat” || false; // t || f returns Cat
取非 返回逻辑相反的值,比如当前值为真,则返回 false。 ! var n1 = !true; // !t returns false
var n2 = !false; // !f returns true
var n3 = !”Cat”; // !t returns false

运算符优先级

JavaScript 中的运算符优先级与学校的数学课程相同——乘法和除法总是先完成,然后是加法和减法(总是从左到右进行计算)。

如果想要改变计算优先级,可以把想要优先计算的部分用括号围住。

函数

函数:函数用来封装可复用的功能。如果没有函数,一段特定的操作过程用几次就要重复写几次,而使用函数则只需写下函数名和一些简短的信息。

注: return 语句告诉浏览器当前函数返回 result 变量。这是一点很有必要,因为函数内定义的变量只能在函数内使用。这叫做变量的作用域。

匿名函数

主要使用匿名函数来运行负载的代码以响应事件触发(如点击按钮)——使用事件处理程序。

创建一个没有名称的函数:

1
2
3
function() {
alert('hello');
}

这个函数叫做匿名函数——它没有函数名!它也不会自己做任何事情。你通常将匿名函数与事件处理程序一起使用,例如,如果单击相关按钮,以下操作将在函数内运行代码:

1
2
3
4
5
var myButton = document.querySelector("button");

myButton.onclick = function () {
alert("hello");
};

还可以将匿名函数分配为变量的值,还可以将该函数分配为多个变量的值,但这只会令人费解,所以不要这样做!例如:

1
2
3
4
5
6
7
8
9
10
11
var myGreeting = function () {
alert("hello");
};

var anotherGreeting = function () {
alert("hello");
};

//可以使用以下任一方法调用此函数
myGreeting();
anotherGreeting();

注:匿名函数也称为函数表达式。函数表达式与函数声明有一些区别。函数声明会进行声明提升(declaration hoisting),而函数表达式不会。

闭包

闭包是一个函数中包含内层函数+引用的外层函数变量组成的。

1
2
3
4
5
6
7
8
function fn() {
let count = 1
return function() {
count++
console.log(count)
}
}
const f = fn()
  • 闭包实现了数据的私有,例如:节流防抖、Vue3、React 中都使用了闭包。
  • 闭包不一定要 return,需要使用内部变量时再return。
  • 闭包不一定会引起内存泄漏,只有在外部使用了内存变量时才有可能内存泄漏。

事件

事件是在浏览器窗口内触发的,并倾向于附加到驻留在其中的特定项目。这可能是一个单一的元素,一组元素,当前标签中加载的 HTML 文档,或整个浏览器窗口。有许多不同类型的事件可以发生。

例如:

  • 用户选择、点击或将光标悬停在某一元素上。
  • 用户在键盘中按下某个按键。
  • 用户调整浏览器窗口的大小或者关闭浏览器窗口。
  • 网页结束加载。
  • 表单提交。
  • 视频播放、暂停或结束。
  • 发生错误。

为了对一个事件做出反应,你要给它附加一个事件处理器。事件处理器有时候被叫做事件监听器——从我们的用意来看这两个名字是相同的,尽管严格地来说这块代码既监听也处理事件。监听器留意事件是否发生,处理器对事件发生做出回应。

事件监听器

事件能为网页添加真实的交互能力。它可以捕捉浏览器操作并运行一些代码做为响应。最简单的事件是点击事件,鼠标的点击操作会触发该事件,定义了一个 addEventListener() 函数

要传入两个参数:

  • 字符串 “click”,表示我们要监听点击事件。按钮可以触发很多其他的事件,比如当用户将鼠标移到按钮上时(“mouseover” 事件),或者当用户按下一个键并且按钮被聚焦时(“keydown” 事件)。
  • 当事件发生时所调用的函数。
1
2
3
document.querySelector("button").addEventListener("click", function () {
alert("别戳我,我怕疼。");
});
  • 传递给 addEventListener() 的函数被称为匿名函数,因为它没有名字。匿名函数还有另一种我们称之为箭头函数的写法,箭头函数使用 () => 代替 function ():
1
2
3
document.querySelector('button').addEventListener('click', () => {
alert('别戳我,我怕疼。');
});

如果你使用 addEventListener() 添加了一个事件处理器,你可以使用 removeEventListener() 方法再次删除它。例如,这将删除 changeBackground() 事件处理器

1
btn.removeEventListener("click", changeBackground);

事件处理器也可以通过传递 AbortSignal 到 addEventListener(),然后在拥有 AbortSignal 的控制器上调用abort(),从而删除事件处理器。例如,要添加一个可以使用 AbortSignal 来删除的事件处理器,可以这样做:

1
2
3
4
5
6
7
8
9
const controller = new AbortController();

btn.addEventListener("click",
() => {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
},
{ signal: controller.signal } // 向该处理器传递 AbortSignal
);

然后可以像这样删除上面代码创建的事件处理器

1
controller.abort(); // 移除任何/所有与该控制器相关的事件处理器

其他事件监听器机制

推荐使用 addEventListener() 来注册事件处理器。这是最强大的方法,在更复杂的程序中,它的扩展性最好。然而,还有两种注册事件处理器的方法:事件处理器属性内联事件处理器

事件处理器属性

可以触发事件的对象(如按钮)通常也有属性,其名称是 on,后面是事件的名称。例如,元素有一个属性 onclick。这被称为事件处理器属性。为了监听事件,你可以将处理函数分配给该属性。

以像这样重写随机颜色示例:

1
2
3
4
5
6
7
8
9
10
const btn = document.querySelector("button");

function random(number) {
return Math.floor(Math.random() * (number + 1));
}

btn.onclick = () => {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
};

也可以将处理器属性分配给具名函数:

1
2
3
4
5
6
7
8
9
10
11
12
const btn = document.querySelector("button");

function random(number) {
return Math.floor(Math.random() * (number + 1));
}

function bgChange() {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
}

btn.onclick = bgChange;

对于事件处理器属性不能为一个事件添加一个以上的处理程序。

例如,你可以在一个元素上多次调用 addEventListener(‘click’, handler),并在第二个参数中指定不同的函数:

1
2
element.addEventListener("click", function1);
element.addEventListener("click", function2);

对于事件处理器属性来说,这是不可能的,因为任何后续尝试都会覆写较早设置的属性

1
2
element.onclick = function1;
element.onclick = function2;

内联事件处理器——不要使用

**永远不应该使用 HTML 事件处理器属性,**使用它们是不好的做法。

1
2
3
4
5
6
<button onclick="bgChange()">按下我</button>

function bgChange() {
const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`;
document.body.style.backgroundColor = rndCol;
}

事件对象

有时候在事件处理函数内部,你可能会看到一个固定指定名称的参数,例如 event、evt 或 e。这被称为事件对象,它被自动传递给事件处理函数,以提供额外的功能和信息。

阻止默认行为

有时,会遇到一些情况,希望事件不执行它的默认行为。最常见的例子是 Web 表单,例如自定义注册表单。当你填写详细信息并按提交按钮时,自然行为是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种“成功消息”页面。

当用户没有正确提交数据时,麻烦就来了——作为开发人员,你希望停止提交信息给服务器,并给他们一个错误提示,告诉他们什么做错了,以及需要做些什么来修正错误。

在 submit 事件(表单提交时触发提交事件)的处理程序中实现一个非常简单的检查,测试文本字段是否为空。如果是这样,我们就在事件对象上调用 preventDefault() 函数,停止表单提交,然后在我们的表单下面的段落中显示错误信息,告诉用户出了什么问题:

1
2
3
4
5
6
7
8
9
10
11
const form = document.querySelector("form");
const fname = document.getElementById("fname");
const lname = document.getElementById("lname");
const para = document.querySelector("p");

form.addEventListener("submit", (e) => {
if (fname.value === "" || lname.value === "") {
e.preventDefault();
para.textContent = "You need to fill in both names!";
}
});

事件冒泡

事件冒泡描述了浏览器如何处理针对嵌套元素的事件。

在父元素上设置监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="container">
<button>点我!</button>
</div>
<pre id="output"></pre>

const output = document.querySelector("#output");
function handleClick(e) {
output.textContent += `你在 ${e.currentTarget.tagName} 元素上进行了点击\n`;
}

const container = document.querySelector("#container");
container.addEventListener("click", handleClick);
//你在 DIV 元素上进行了点击

这里有一个在其他元素(<div>)内部的按钮,可以说这里的<div>元素是其中包含元素的父元素

按钮在 <div> 里面,所以当你点击按钮的时候,你也隐含地点击了它所在的元素。

  • 如果在按钮及其父元素上同时添加事件处理器,如果给按钮、它的父元素(<div>)以及包含它们的<body>元素添加点击事件处理器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<div id="container">
<button>点我!</button>
</div>
<pre id="output"></pre>
</body>

const output = document.querySelector("#output");
function handleClick(e) {
output.textContent += `你在 ${e.currentTarget.tagName} 元素上进行了点击\n`;
}

const container = document.querySelector("#container");
const button = document.querySelector("button");

document.body.addEventListener("click", handleClick);
container.addEventListener("click", handleClick);
button.addEventListener("click", handleClick);
你在 BUTTON 元素上进行了点击
你在 DIV 元素上进行了点击
你在 BODY 元素上进行了点击
    • 在这种情况下:
      • 最先触发按钮上的单击事件
      • 然后是按钮的父元素( <div> 元素)
      • 然后是 <div>的父元素(<body>元素)
    • 可以这样描述:事件从被点击的最里面的元素冒泡而出。
    • 事件冒泡有时会产生问题,但有一种方法可以防止这些问题。Event对象有一个可用的函数,叫做 stopPropagation(),当在一个事件处理器中调用时,可以防止事件向任何其他元素传递

事件捕获

事件传播的另一种形式是事件捕获。这就像事件冒泡,但顺序是相反的:事件不是先在最内层的目标元素上发生,然后在连续较少的嵌套元素上发生,而是先在最小嵌套元素上发生,然后在连续更多的嵌套元素上发生,直到达到目标。

事件捕获默认是禁用的,你需要在 addEventListener() 的 capture 选项中启用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<div id="container">
<button>点我!</button>
</div>
<pre id="output"></pre>
</body>

const output = document.querySelector("#output");
function handleClick(e) {
output.textContent += `你在 ${e.currentTarget.tagName} 元素上进行了点击\n`;
}

const container = document.querySelector("#container");
const button = document.querySelector("button");

document.body.addEventListener("click", handleClick, { capture: true });
container.addEventListener("click", handleClick, { capture: true });
button.addEventListener("click", handleClick);
你在 BODY 元素上进行了点击
你在 DIV 元素上进行了点击
你在 BUTTON 元素上进行了点击
  • 在这种情况下,消息出现的顺序发生了颠倒:<body> 事件处理器首先触发,然后是 <div> 的,最后是 <button> 的。
  • 默认情况下,几乎所有的事件处理程序都是在冒泡阶段注册的,这在大多数情况下更有意义。

事件委托

事件冒泡可以实现事件委托。在这种做法中,当我们想在用户与大量的子元素中的任何一个互动时运行一些代码时,我们在它们的父元素上设置事件监听器,让发生在它们身上的事件冒泡到它们的父元素上,而不必在每个子元素上单独设置事件监听器。

作者

冷冷

发布于

2020-09-05

更新于

2021-10-01

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×