简介 script标签 使用 <script> 标签将 javascript 程序插入到 html 文档的任何位置 1 2 3 <script > console .info(11111 ); </script >
现代的标记(markup) <script> 标签有一些现在很少用到的特性 (attribute),但是我们可以在老代码中找到它们type 特性: <script type=...></script>,在老的 html4 标准中,要求 script 标签有 type 属性,通常是 type="text/javascript"。这样的特性声明现在已经不再需要。而且,现代 HTML 标准已经完全改变了此特性的含义。现在,它可以用于 JavaScript 模块language 特性: <script language=...></script>,这个特性是为了显示脚本使用的语言。这个特性现在已经没有任何意义,因为语言默认就是 JavaScript。不再需要使用它了脚本前后的注释,在非常古老的书籍和指南中,你可能会在 script 标签里面找到注释,现代 JavaScript 中已经不这样使用了。这些注释是用于不支持 <script> 标签的古老的浏览器隐藏 JavaScript 代码的。由于最近 15 年内发布的浏览器都没有这样的问题,因此这种注释能帮你辨认出一些老掉牙的代码1 2 3 <script type="text/javascript" ><!-- ...
外部脚本 如果有大量的 JavaScript 代码,可以将它放入一个单独的文件,通过 src 特性添加到 html 文件中1 <script scr ="/path/to/script.js" > </script >
一般来说,只有最简单的脚本才嵌入到 HTML 中。更复杂的脚本存放在单独的文件中。 使用独立文件的好处是浏览器会下载它,然后将它保存到浏览器的 缓存 中。 之后,其他页面想要相同的脚本就会从缓存中获取,而不是下载它。所以文件实际上只会下载一次。 这可以节省流量,并使得页面(加载)更快。
如果设置了 src 属性,script 标签内容将会被忽略 一个单独的 script 标签不能同时有 src 特性和内部包裹的代码,这将不会工作
1 2 3 <script src ="file.js" > alert(1 ); </script >
我们必须进行选择,要么使用外部的 <script src="..."></script>,要么使用正常包裹代码的 script 标签
1 2 3 4 <script src ="file.js" > </script > <script > alert(1); </script >
代码结构 语句 语句是执行行为的语法结构和命令,通常每条语句独占一行,以提高代码的可读性 分号 当存在换行符时,在大多数情况下可以省略分号,下面这个例子中 js 将换行符理解成隐式的分号(自动分号插入)1 2 alert('hello' ) alert('world' )
在大多数情况下,换行意味着一个分号,但是大多数情况并不意味着总是 存在JavaScript无法确定是否真的需要自动插入分号的情况 一个错误的例子
1 2 alert('hello' ); [1 , 2 ].forEach(alert);
上面的例子会正确输出
1 2 alert('hello' ) [1 , 2 ].forEach(alert);
上面的例子会发生报错,这是因为 JavaScript 引擎并没有假设在方括号 [...] 前有一个分号,因此这段代码被视为了一个语句
1 alert('hello' )[1 , 2 ].forEach(alert);
即使语句被换行符分隔了,我们依然建议在它们之间加分号
注释 随着时间推移,程序变得越来越复杂。为代码添加 注释 来描述它做了什么和为什么要这样做,变得非常有必要了。可以在脚本的任何地方添加注释,它们并不会影响代码的执行,因为引擎会直接忽略它们 单行注释以两个正斜杠字符 // 开始1 2 3 4 alert('hello' ); alert('world' );
多行注释以一个正斜杠和星号开始 “/*” 并以一个星号和正斜杠结束 “*/”1 2 3 4 5 6 alert('hello' ); alert('world' );
不支持注释嵌套,不要在 /*...*/ 内嵌另一个 /*...*/
注释会增加代码总量,但这一点也不是什么问题。有很多工具可以帮你在把代码部署到服务器之前缩减代码。这些工具会移除注释,这样注释就不会出现在发布的脚本中。所以,注释对我们的生产没有任何负面影响。
现代模式 长久以来,JavaScript 不断向前发展且并未带来任何兼容性问题。新的特性被加入,旧的功能也没有改变。这么做有利于兼容旧代码,但缺点是 JavaScript 创造者的任何错误或不完善的决定也将永远被保留在 JavaScript 语言中。这种情况一直持续到 2009 年 ECMAScript 5 (ES5) 的出现。ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。为了保证旧的功能能够使用,大部分的修改是默认不生效的。你需要一个特殊的指令 —— "use strict" 来明确地激活这些特性
use strict这个指令看上去像一个字符串 "use strict" 或者 'use strict'。当它处于脚本文件的顶部时,则整个脚本文件都将以“现代”模式进行工作 "use strict" 可以被放在函数体的开头。这样则可以只在该函数中启用严格模式1 2 3 4 function sky ( ) { "use strict" }
确保 use strict 出现在最顶部,否则严格模式可能无法启用 没有办法取消 use strict,一旦进入了严格模式,就没有回头路 现代浏览器支持 class 和 module 一旦使用了它们就默认开启了严格模式 变量 变量是数据的命名存储,使用 let 关键字进行定义变量 let message 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let message;message = "hello" ; let message = 'hello' ;let user = 'John' , age = 25 , message = 'hello' ;
变量声明两次会触发 error,一个变量应该只被声明一次,对同一变量进行重复声明会触发 error。因此我们对同一个变量应该只声明一次,之后在不使用 let 的情况下对其引用 变量命名 变量名称必须仅包含字母、数字、符号$和_ 首字符必须非数字 推荐驼峰命名法 变量区分大小写,apple 和 APPLE 是两个不同的变量 允许非英文字符,但不推荐 保留字无法作为变量名使用 1 2 let userName;let test123;
常量 声明一个不变的变量,可以使用 const 而非 let 使用 const 声明的变量称为常量,它们不能被修改 1 2 const myBBB = '18.2423' ;myBBB = 1234 ;
大写形式的常数 一个普遍的做法是将常量用作别名,以便记住那些在执行之前就已知的难以记住的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const COLOR_RED = "#F00" ;const COLOR_GREEN = "#0F0" ;const COLOR_BLUE = "#00F" ;const COLOR_ORANGE = "#FF7F00" ;let color = COLOR_ORANGE;alert(color);
什么时候该为常量使用大写命名,什么时候进行常规命名?作为一个“常数”,意味着值永远不变。但是有些常量在执行之前就已知了(比如红色的十六进制值),还有些在执行期间被“计算”出来,但初始赋值之后就不会改变 pageLoadTime 的值在页面加载之前是未知的,所以采用常规命名。但是它仍然是个常量,因为赋值之后不会改变,换句话说,大写命名的常量仅用作“硬编码(hard-coded)”值的别名
数据类型 在 JavaScript 中有 8 种基本的数据类型(译注:7 种原始类型和 1 种引用类型) 我们可以将任何类型的值存入变量。例如,一个变量可以在前一刻是个字符串,下一刻就存储一个数字。允许这种操作的编程语言,例如 JavaScript,被称为“动态类型”(dynamically typed)的编程语言,意思是虽然编程语言中有不同的数据类型,但是你定义的变量并不会在定义后,被限制为某一数据类型 1 2 let message = 'hello' ;message = 1234 ;
Number 类型1 2 let n = 123 ;n = 12.345 ;
number 类型代表整数和浮点数,数值运算 * / + -除了常规的数字,还包括特殊数值,也属于 number 类型:Infinity、-Infinity、NaNInfinity 表示正无穷 console.log(1 / 0)NaN 代表计算错误,它是一个不正确的或者一个未定义的数学操作所得到的结果 alert('not' / 2)NaN 是粘性的,任何对 NaN 的进一步数学运算都会返回 NaN所以,如果在数学表达式中有一个 NaN,会被传播到最终结果(只有一个例外:NaN ** 0 结果为 1) 数学运算是安全的在 JavaScript 中做数学运算是安全的。我们可以做任何事:除以 0,将非数字字符串视为数字,等等。脚本永远不会因为一个致命的错误(“死亡”)而停止。最坏的情况下,我们会得到 NaN 的结果 特殊的数值属于 number 类型 1 2 alert(NaN + 1 ); alert(3 * NaN );
BigInt 类型 在 JavaScript 中 number 类型无法安全地表示大于 2^53 - 1 即 9007199254740991,或小于 -(2^53 - 1) 的整数 更准确的说 number 类型可以存储更大的整数(最多 1.7976931348623157 * 10^308),但超过安全整数范围 ±(2^53 - 1) 会出现精度问题,因为并非所有数字都适合固定的 64 位存储。因此,可能存储的是近似值 1 2 3 console .log(9007199254740991 + 1 ); console .log(9007199254740991 + 2 );
也就是说,所有大于 2^53 - 1 的奇数都不能用 number 类型存储。在大多数情况下,±(2^53 - 1) 范围就足够了,但有时候需要整个范围非常大的整数,例如用于密码学或微秒精度的事件戳 BigInt 类型用来表示任意长度的整数,可以将 n 附加到整数字段的末尾来创建 BigInt 值1 2 const bigInt = 1234567890123456789012345678901234567890n ;
String 类型JavaScript 中的字符串都必须被括在引号中三种书写字符串的方式双引号: "hello" 单引号: 'hello' 反引号: hello 双引号和单引号都是简单的引用,没有什么区别,反引号是功能拓展引号,它允许通过变量和表达式包装在 ${...} 中,来将它们嵌入到字符串中 JavaScript 中没有 character 类型1 2 3 let name = "John" ;console .info(`hello ${name} ` );
Boolean 类型(逻辑类型)boolean 类型仅包含两个值 true 和 falsenull 值特殊的 null 值不属于上述任何一个类型,它构成了一个独立的类型,只包含 null 值 JavaScript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值undefined 值特殊值 undefined 和 null 一样自成类型,undefined 的含义是 未被赋值,如果一个变量已被声明,但未被赋值,那么它的值就是 undefined 1 2 3 4 let age;console .log(age); age = undefined ;
Object 类型和 Symbol 类型object 类型是一个特殊的类型其他所有的数据类型都被称为“原始类型”,因为它们的值只包含一个单独的内容(字符串、数字或者其他)。相反,object 则用于储存数据集合和更复杂的实体 symbol 类型用于创建对象的唯一标识符typeof 运算符typeof 运算符返回参数的类型1 2 3 4 5 6 7 8 9 typeof undefined typeof 0 typeof 10n typeof true typeof "foo" typeof Symbol ("id" ) typeof Math typeof null typeof alert
最后三行额外说明Math 是一个提供数学运算的内建 objecttypeof null 的结果为 "object"。这是官方承认的 typeof 的错误,这个问题来自于 JavaScript 语言的早期阶段,并为了兼容性而保留了下来。null 绝对不是一个 object。null 有自己的类型,它是一个特殊值。typeof 的行为在这里是错误的typeof alert 的结果是 "function" 在 JavaScript 语言中没有一个特别的 “function” 类型。函数隶属于 object 类型。但是 typeof 会对函数区分对待,并返回 "function"。这也是来自于 JavaScript 语言早期的问题。从技术上讲,这种行为是不正确的,但在实际编程中却非常方便 typeof(x) 语法typeof(x) 和 typeof x 相同简单点说:typeof 是一个操作符,不是一个函数。这里的括号不是 typeof 的一部分。它是数学运算分组的括号 通常,这样的括号里包含的是一个数学表达式,例如 (2 + 2),但这里它只包含一个参数 (x)。从语法上讲,它们允许在 typeof 运算符和其参数之间不打空格 交互:alert、prompt和confirm alert弹出带有信息的小窗口,“modal” 意味着用户不能与页面的其他部分(例如点击其他按钮等)进行交互,直到他们处理完窗口 promptprompt 函数接收两个参数 prompt(title [, default]);title 显示给用户的文本default 可选的第二个参数,指定 input 框的初始值浏览器显示一个带有文本消息的模态窗口,还有 input 框和确定/取消按钮 返回用户输入的文本,如果用户取消了输入,则返回 null confirmconfirm 函数显示带有 question 以及确定和取消两个按钮的模态窗口,语法 confirm(question)点击确认返回 true 点击取消返回 false 类型转换 大多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型 在某些情况下,我们需要将值显式地转换为我们期望的类型 字符串转换 当我们需要一个字符串形式的值时,就会进行字符串转换 显式调用 String(value) 将 value 转换为字符串类型 数字型转换 在算术函数和表达式中,会自动进行 number 类型转换 "6" / "2" 显式调用 Number(value) 将 value 转换为 number 类型 如果不是一个有效的数字,转换的结果会是 NaN 值 结果 undefinedNaNnull0true 和 false1 和 0string去掉首尾空白字符(空格、换行符 \n、制表符 \t 等)后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN
1 2 3 4 alert( Number (" 123 " ) ); alert( Number ("123z" ) ); alert( Number (true ) ); alert( Number (false ) );
注意 null 和 undefined 是不同的,null 转换为 0 而 undefined 转为为 NaN 布尔类型转换 直观上为空的值(如 0、空字符串、null、undefined 和 NaN)将变为 false,其他值变成 true 包含 0 的字符串 "0" 是 true 1 2 3 4 alert( Boolean (1 ) ); alert( Boolean (0 ) ); alert( Boolean ("hello" ) ); alert( Boolean ("" ) );
基础运算符 术语 运算元 — 运算符应用的对象。比如说乘法运算 5 * 2,有两个运算元:左运算元 5 和右运算元 2。有时候人们也称其为“参数”而不是“运算元” 一元运算符 — 如果一个运算符对应的只有一个运算元,那么它是 一元运算符。比如说一元负号运算符(unary negation)-,它的作用是对数字进行正负转换 二元运算符 — 如果一个运算符拥有两个运算元,那么它是 二元运算符。减号还存在二元运算符形式 1 2 3 4 5 6 let x = 1 ;x = -x; let y = 3 ;console .log(x - y);
数据运算 加法 + 减法 - 乘法 * 除法 / 取余 % a % b => a 整除 b 的余数 求幂 ** a ** b => a^b => Math.pow(a, b) 用二元运算符 + 连接字符串 通常 + 号用于求和,但是如果 + 被用于字符串,它将合并(连接)各个字符串 二元 + 是唯一一个以这种方式支持字符串的运算符。其他算术运算符只对数字起作用,并且总是将其运算元转换为数字 1 2 3 4 5 6 7 8 9 10 let s = "my" + "string" ;console .log("1" + 2 ); console .log(2 + '1' ); console .log(2 + 2 + '1' ); console .log(6 - '2' ); console .log('6' / '2' );
数字转化,一元运算符 + 加号 + 有两种形式。一种是二元运算符,一种是一元运算符 加号 + 可以将其他类型转换为数字类型,它的效果和 Number(...) 相同,但是更加简短 1 2 3 4 5 6 7 8 9 10 11 12 13 let x = 1 ;console .log(+x); let y = -2 ;console .log(+y); console .log(+true ); console .log(+"" ); let app = "1" ;let xpp = "2" ;console .log(+app + +xpp);
运算符优先级 |优先级|名称|符号| |…|…|…| |...|...|...| |15|一元加号|+| |15|一元负号|-| |14|求幂|**| |13|乘号|*| |13|除号|/| |12|加号|+| |12|减号|-| |...|...|...| |2|赋值符|=| |...|...|...|
赋值运算符 赋值运算符的优先级只有 2 赋值 = 返回一个值 = 是一个运算符,而不是一个有着魔法作用的语言结构在 JavaScript 中,所有运算符都会返回一个值,这对于 + - 来说是显而易见的,但对于 =来说也是如此 语句 x = value 将值 value 写入 x 然后返回 value 1 2 3 4 5 6 7 8 let a = 1 ;let b = 2 ;let c = 3 - (a = b + 1 ); console .log(a); console .log(c);
链式赋值 1 2 3 4 5 6 7 let a, b, c;a = b = c = 2 + 2 ; console .log(a, b, c);
原地修改 1 2 3 let n = 2 ;n = n + 5 ; n = n * 2 ;
上面的写法可以使用运算符 += 和 *= 缩写来表示 所有算数和位运算都有简单的 修改并赋值 的运算符 /= 和 -=,这类运算符的优先级和普通的赋值运算符的优先级相同,所以它们在大多数其他运算之后执行 自增/自减 对一个数进行加一、减一是最常见的数学运算符之一 自增 ++ 将变量和 1 相加,自减 -- 将变量与一相减 自增/自减只能用于变量 ,`5++` 常量是不可以这样使用的++ -- 是可以置于变量前也可以放在变量后,它们都做同一件事,将变量 +1放在变量前,被称为前置形式 ++count 放在变量后,被称为后置形式 count++ 前置和后置的区别在于返回值,上面我们说所有的运算符都有返回值,自增/自减也不例外。前置形式返回一个新的值,但后置返回原来的值(做加法/减法之前的值)如果自增/自减的值不会被使用,那么两者形式没有区别 如果我们想要对变量进行自增操作,并且 需要立刻使用自增后的值,那么我们需要使用前置形式 如果我们想要将一个数加一,但是我们想使用其自增之前的值,那么我们需要使用后置形式 运算符同样可以在表达式内部使用,它的优先级比绝大部份的算数运算符要高 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let count = 1 ;let a = ++count;console .log(a); let count1 = 1 ;let a1 = count1++;console .log(a1); let count2 = 1 ;console .log(2 * ++count2); let count3 = 1 ;console .log(2 * count3++);
位运算符 位运算符把运算元当作 32 位整数,并在它们的二进制表现形式上操作 位运算符按位与 & 按位或 | 按位异或 ^ 按位非 ~ 左移 << 右移 >> 无符号右移 >>> 逗号运算符 逗号运算符 , 是最少见最不常用的运算符之一 逗号运算符能让我们处理多个表达式,使用 , 将它们分开,每个表达式都运行了,但是只有最后一个结果会被返回 逗号运算符的优先级非常低 ,请注意逗号的优先级非常低,比 `=` 还要低,因此下面的例子中圆括号非常重要1 2 3 4 5 6 7 let a = (1 + 2 , 3 + 4 ); for (a = 1 , b = 3 , c = a * b; a < 10 ; a++) { }
值的比较 大于 / 小于: a > b a < b 大于等于 / 小于等于 a >= b a <= b 检查两个值的相等 a == b 检查两个值的不相等 a != b 比较结果为布尔类型 所有的运算符均返回布尔值 1 2 3 alert(2 > 1 ); alert(2 == 1 ); alert(2 != 1 );
比较的值可以赋值给任意值 字符串比较 比较字符串的大小时,JavaScript 会使用字典(unicode 编码)顺序进行判定,换言之,字符串是按字符逐个进行比较的首先比较两个字符的首位字符大小 如果一方字符较大(或较小),则该字符串大于(或小于)另一个字符串,算法结束 否则,如果两个字符串的首位字符相等,则继续取出两个字符串各自的后一位字符进行比较 重复上述步骤进行比较,直到比较完成某个字符串的所有字符为止 如果两个字符串的字符同时用完,那么判定他们相等,否则未结束(还有未比较的字符)的字符串更大 1 2 3 alert('Z' > 'A' ); console .log('Glow' > 'Glee' ); console .log('Bee' > 'Be' );
不同类型间的比较 当不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小 对于布尔类型值,true 会转化为 1 false 转化为 0 1 2 3 4 5 console .log('2' > 1 ); console .log('01' == 1 ); console .log(true == 1 ); console .log(false == 0 );
一个有趣的现象若直接比较两个值,其结果是相等的 若把两个值转为布尔值,它们可能得出完全相反的结果,即一个是 true 一个是 false 因为 JavaScript 会把待比较的值转化为数字后再做比较(因此 "0" 变成了 0)。若只是将一个变量转化为 Boolean 值,则会使用其他的类型转换规则 1 2 3 4 5 6 7 let a = 0 ;console .log(Boolean (a)); let b = '0' ;console .log(Boolean (b)); console .log(a == b);
严格相等 普通的相等性检查 == 存在一个问题,不能区分出 0 和 false,也无法区分出空字符串和 false 1 2 console .log(0 == false ); console .log('' == false );
在比较不同类型的值时,处于相等判断符号 == 两侧的值会先被转化为数字。空字符串和 false 也是如此,转化后它们都为数字 0 严格相等运算符 === 在进行比较时不会做任何的类型转换 同样的与不相等类似,严格不相等可以表示为 !== 1 console .log(0 === false );
对 null 和 undefined 进行比较 当使用严格相等 === 比较二者时它们不相等 当使用非严格相等 == 比较二者时它们相等 1 2 console .log(null === undefined ); console .log(null == undefined );
当使用数学式或其他比较方法 < > <= >= 时null / undefined 会被转化为数字:null 被转化为 0,undefined 被转化为 NaN 奇怪的结果 null vs 0 1 2 3 console .log(null > 0 ); console .log(null == 0 ); console .log(null >= 0 );
为什么会出现这种反常结果,这是因为相等性检查 == 和普通比较符 > < >= <= 的代码逻辑是相互独立的。进行值的比较时,null 会被转化为数字,因此它被转化为了 0。这就是为什么(3)中 null >= 0 返回值是 true,(1)中 null > 0 返回值是 false 另一方面,undefined 和 null 在相等性检查 == 中不会进行任何的类型转换,它们有自己独立的比较规则,所以除了它们之间互等外,不会等于任何其他的值。这就解释了为什么(2)中 null == 0 会返回 false 特立独行的 undefined undefined 不应该被与其他值进行比较1 2 3 console .log(undefined > 0 ); console .log(undefined < 0 ); console .log(undefined == 0 );
(1) 和 (2) 都返回 false 是因为 undefined 在比较中被转换为了 NaN,而 NaN 是一个特殊的数值型值,它与任何值进行比较都会返回 false(3) 返回 false 是因为这是一个相等性检查,而 undefined 只与 null 相等,不会与其他值相等避免问题 除了严格相等 === 外,其他但凡是有 undefined/null 参与的比较,我们都需要格外小心 除非你非常清楚自己在做什么,否则永远不要使用 >= > < <= 去比较一个可能为 null/undefined 的变量。对于取值可能是 null/undefined 的变量,请按需要分别检查它的取值情况 条件分支: if 和 ? if 语句if(...) 语句计算括号里的条件表达式,如果计算结果是 true 就会执行对应的代码块布尔转换 if(...) 语句会计算圆括号内的表达式,并将计算结果转化为布尔值类型转换的规则数字 0、空字符串 ""、null、undefined 和 NaN 都会被转换成 false 其他值被转换为 true else 语句if 语句有时会包含一个可选的 “else” 块。如果判断条件不成立,就会执行它内部的代码多个条件 else if 可以通过使用 else if 子句实现一个条件的几个变体 条件运算符 ? 这个运算符通过问号 ? 表示。有时它被称为三元运算符,被称为“三元”是因为该运算符中有三个操作数。实际上它是 JavaScript 中唯一一个有这么多操作数的运算符 1 let result = condition ? value1 : value2;
多个 ? 使用一系列问号 ? 运算符可以返回一个取决于多个条件的值 1 2 3 4 5 6 7 8 let age = prompt('age?' , 18 );let message = (age < 3 ) ? 'Hi, baby!' : (age < 18 ) ? 'Hello!' : (age < 100 ) ? 'Greetings!' : 'What an unusual age!' ; console .log(message);
? 的非常规使用1 (company == 'Netscape' ) ? alert('Right!' ) : alert('Wrong.' );
逻辑运算符 JavaScript 中的四个逻辑运算 || 、&&、!、??(空值合并运算符)|| 或运算符在传统的编程中,逻辑或仅能够操作布尔值。如果参与运算的任意一个参数为 true,返回的结果就为 true,否则返回 false 在 JavaScript 中,逻辑运算符更加灵活强大 1 2 3 4 console .log(true || true ); console .log(false || true ); console .log(true || false ); console .log(false || false );
大多数情况下,逻辑或 || 会被用在 if 语句中,用来测试是否有任何给定的条件为 true 或运算寻找第一个真值 1 let result = value1 || value2 || value3;
上面的或运算符做了如下过程:从左到右依次计算操作数 处理每一个操作数时,都将其转化为布尔值,如果结果是 true 就停止计算,返回这个操作数的初始值 如果所有的操作数都被计算过(也就是,转化结果都是 false),则返回最后一个操作数 返回的值是操作数的初始形式,不会做布尔转换 换句话说,一个或运算 || 的链,将返回第一个真值,如果不存在真值,就返回该链的最后一个值 1 2 3 4 console .log(1 || 0 ); console .log(null || 1 ); console .log(null || 0 || 1 ); console .log(undefined || null || 0 );
有趣的用法 1 2 3 4 5 6 7 8 9 10 let firstName = "" ;let lastName = "" ;let nickName = "SuperCoder" ;alert( firstName || lastName || nickName || "Anonymous" ); true || alert("not printed" );false || alert("printed" );
&& 与运算符在传统的编程中,当两个操作数都是真值时,与运算返回 true,否则返回 false 就像或运算一样,与运算的操作数可以是任意类型的值 1 2 3 4 console .log(true && true ); console .log(false && true ); console .log(true && false ); console .log(false && false );
与运算寻找第一个假值 1 result = value1 && value2 && value3
&& 做了如下事从左到右依次计算操作数 在处理每一个操作数时,都将其转化为布尔值,如果结果是 false 就停止,并返回这个操作数的初始值 如果所有操作数都被计算过 ,则返回最后一个操作数 与运算 && 在或运算 || 之前进行 ! 非逻辑非运算符接收一个参数,并按如下运作:将操作数转化为布尔类型: true / false 返回相反的值 两个非运算 !! 有时候用来将某个值转化为布尔类型 !!0 非运算符 ! 的优先级在所有逻辑运算符里面最高,所以它总是在 && 和 || 之前执行 空值合并运算符 ?? 空值运算符 ??, a ?? b 的结果是,如果 a 是已定义的,则结果是 a 否则结果是 b 换言之,如果第一个参数不是 null/undefined,则 ?? 返回第一个参数,否则返回第二个参数,也可以使用 result = a || b => result = a ?? b 还可以使用 ?? 从一系列值中选择第一个非 null/undefined 的值 result = a ?? b ?? c ?? 'dd' 或运算符 || 可以与 ?? 运算符相同的方式使用,但是它们之间最重要的区别在于|| 返回第一个 真值 ?? 返回第一个 已定义的值 || 无法区别 false 0 空字符串 "" 和 null / undefined 它们都 一样 。如果其中任何一个是 || 的第一个参数,那么都将得到第二个参数作为结果?? 运算符的优先级与 || 相同,他们的优先级都是 4出于安全原因,JavaScript 禁止将 ?? 运算符 && 和 || 一起使用,除非使用括号明确指定了优先级 1 2 3 let x = 1 && 2 ?? 3 ; let x = (1 && 2 ) ?? 3 ;
循环 while 和 for while 循环1 2 3 4 5 6 7 while (condition) { } let i = 3 ;while (i) console .info(i--);
循环体的单次执行叫作 一次迭代 单行循环体不需要大括号,可以省略 do...while 循环1 2 3 do { } while (condition);
do...while 循环不管条件是否为真,循环体 至少执行一次 for 循环1 2 3 4 5 6 7 8 for (begin; condition; step) { }
1 2 3 4 5 6 for (let i = 0 ; i < 3 ; i++) { alert(i); } alert(i);
for 循环的任何语句段都可以被省略,例如,如果我们在循环开始时不需要做任何事,我们就可以省略 begin 语句段1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let i = 0 ; for (; i < 3 ; i++) { alert( i ); } let i = 0 ;for (; i < 3 ;) { alert( i++ ); } for (;;) { }
跳出循环 通常条件为假时,循环会终止 可以使用 break 指令强制退出 1 2 3 4 5 6 7 8 let sum = 0 ;while (true ) { let value = +prompt("Enter a number" , '' ); if (!value) break ; sum += value; } alert( 'Sum: ' + sum );
继续下一次迭代 continue 指令是 break 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环 1 2 3 4 5 for (let i = 0 ; i < 10 ; i++) { if (i % 2 == 0 ) continue ; alert(i); }
continue 指令利于减少嵌套1 2 3 4 5 6 for (let i = 0 ; i < 10 ; i++) { if (i % 2 ) { alert( i ); } }
禁止 break / continue 在 ? 的右边请注意非表达式的语法结构不能与三元运算符 `?` 一起使用。特别是 `break/continue` 这样的指令是不允许这样使用的 1 2 3 4 5 6 7 8 9 if (i > 5 ) { alert(i); } else { continue ; } (i > 5 ) ? alert(i) : continue ;
break / continue 标签有时候需要一次从多层嵌套的循环中跳出来 1 2 3 4 5 6 7 8 for (let i = 0 ; i < 3 ; i++) { for (let j = 0 ; j < 3 ; j++) { let input = prompt(`Value at coords (${i} ,${j} )` , '' ); } } alert('Done!' );
在 input 之后的普通 break 只会打破内部循环,标签可以实现这个功能 标签是在循环之前带有冒号的标识符 1 2 3 labelName: for (...) { ... }
break <labelName> 语句跳出循环至标签处1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 outer: for (let i = 0 ; i < 3 ; i++) { for (let j = 0 ; j < 3 ; j++) { let input = prompt(`Value at coords (${i} ,${j} )` , '' ); if (!input) { break outer; } } } outer: for (let i = 0 ; i < 3 ; i++) { for (let j = 0 ; j < 3 ; j++) { let input = prompt(`Value at coords (${i} ,${j} )` , '' ); if (!input) { break outer; } } }
continue 指令也可以与标签一起使用,这种情况下,执行跳转到标记循环的下一次迭代标签并不允许跳到所有位置 1 2 3 4 5 6 7 8 9 10 break label; label: for (...); label: { break label; }
switch 语句switch 语句可以替代多个 if 判断switch 语句为多分枝选择的情况提供了一个更具描述性的方式switch 语法1 2 3 4 5 6 7 8 9 10 11 switch (x) { case "value1" : [break ] case "value2" : [break ] default : [break ] }
比较 x 值和第一个 case 是否相等,然后比较第二个 case 以此类推(强调一下,这里的相等是严格相等。被比较的值必须是相同的类型才能进行匹配 ) 如果相等,switch 语句就执行相应 case 下的代码块,直到遇到最靠近的 break 语句 (或者直到 switch 语句末尾) 如果没有符合的 case 则执行 default 代码块 (如果 default 存在) 如果没有 break 程序将不经过任何检查就会继续执行下一个 case 1 2 3 4 5 6 7 8 9 10 11 12 let a = 2 + 2 ;switch (a) { case 3 : console .log(1 ); case 4 : console .log(2 ); case 5 : console .log(3 ); default : console .log(4 ); }
任何表达式都可以成为 switch/case 的参数 case 分组1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let a = 3 ;switch (a) { case 4 : alert('Right!' ); break ; case 3 : case 5 : alert('Wrong!' ); alert("Why don't you take a math class?" ); break ; default : alert('The result is strange. Really.' ); }
函数 函数声明 1 2 3 function 函数名(参数列表 ) { }
局部变量,在函数中声明的变量只在函数内部可见 外部变量,函数也可以访问外部变量,函数对外部变量拥有全部的访问权限。函数也可以修改外部变量 只有在没有局部变量的情况下才会使用外部变量 ,如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量任何函数之外声明的变量都被称为全局变量。
全局变量在任意函数中都是可见的(除非被局部变量遮蔽)
减少全局变量的使用是一种很好的做法。现代的代码有很少甚至没有全局变量。大多数变量存在于它们的函数中。但是有时候,全局变量能够用于存储项目级别的数据
参数 可以通过参数将任意数据传递给函数 参数是函数声明中括号内列出的变量(它是函数声明时的术语) 参数是调用函数时传递给函数的值(它是函数调用时的术语) 1 2 3 4 5 6 7 8 9 function showMessage (from , text ) { from = '*' + from + '*' ; alert( from + ': ' + text ); } let from = "Ann" ;showMessage(from , "Hello" ); alert( from );
默认值 如果一个函数被调用,但有参数未被提供,那么相应的值就会变成 undefined 1 2 3 4 5 function showMessage (from , text = "no text given" ) { alert( from + ": " + text ); } showMessage("Ann" );