JavaScript 简介
JavaScript 定义
- 类型 :跨平台、面向对象的脚本语言(无需编译,由浏览器直接解析执行)
- 核心用途:控制网页行为,实现交互功能
- 功能示例 :
- 动态修改页面内容
- 操作元素属性(如切换图片路径)
- 表单输入验证
核心功能演示
(1) 改变页面内容
- 效果:点击按钮后,按钮文本内容发生改变
- 实现原理 :通过 DOM 操作直接修改元素内容
(2) 修改元素属性
- 案例 :灯泡开关效果
开灯
:将<img>
标签的 src 属性设置为灯泡亮起图片路径关灯
:修改为灯泡熄灭图片路径
- 技术点 :通过 JavaScript 操作元素属性值
(3) 表单验证
- 流程 :
- 用户输入数据
- JS 实时校验输入内容
- 动态显示验证结果(成功提示/错误警告)
- 应用场景 :用户名规则校验、密码强度检测等
JavaScript 引入方式
JavaScript 引入方式就是 HTML 和 JavaScript 的结合方式。JavaScript引入方式有两种:
- 内部脚本:将 JS代码定义在HTML页面中
- 外部脚本:将 JS代码定义在外部 JS文件中,然后引入到 HTML页面中
内部脚本(script 标签)
JavaScript程序可以在 <script>
与 </script>
标签之间插入到 HTML 文档的任何地方。 比如:
js
<!DOCTYPE HTML>
<html>
<body>
<p>script 标签之前 ...</p>
<script>
alert('Hello, world!'); //alert(数据) 是 JS的一个方法,作用是将参数数据以浏览器弹框的形式输出出来。
</script>
<p>...script 标签之后</p>
</body>
</html>
- 一般把脚本置于 元素的底部,可改善显示速度。因为浏览器在加载页面的时候会从上往下进行加载并解析。 我们应该让用户看到页面内容,然后再展示动态的效果。
外部脚本
如果你有大量的 JavaScript 代码,我们可以将它放入一个单独的文件
- 通过src 特性(attribute)添加到 HTML 文件中。eg:
<script src="/path/to/script.js"></script>
,/path/to/script .js
是脚本文件从网站根目录开始的绝对路径 - 提供当前页面的相对路径。eg:
src ="script .js"
表示当前文件夹中的"script .js"
文件。 - 提供一个完整的 URL 地址,eg:
<script src="<https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js>"></script>
要附加多个脚本,则使用多个标签:
js
<script src="/js/script1.js"></script>
<script src="/js/script2.js"></script>
一个单独的<script>
标签不能同时有 src 特性和内部包裹的代码, 这将不会工作:
js
<script src="file.js">
alert(1); // 此内容会被忽略,因为设定了 src
</script>
- 一般来说,只有最简单的脚本才嵌入到 HTML 中。更复杂的脚本存放在单独的文件中。这可以节省流量,并使得页面加载更快
- 外部脚本中不能包含
<script>
标签,在js文件中直接写 js 代码即可
代码结构
语句
语句是执行行为(action)的语法结构和命令
代码语句之间可以使用分号进行分割
例如,我们将 " Hello World" 这条信息一分为二:
js
alert('Hello');
alert('World');
通常,每条语句独占一行,以提高代码的可读性
分号
大部分时候可以省略分号,但是最好不要省略分号
注释
添加注释描述代码做了什么和为什么要这样做
单行注释以两个正斜杠字符 // 开始
js
//这行注释独占一行
alert('Hello');
alert('World'); // 这行注释跟随在语句后面
多行注释以一个正斜杠和星号开始 /*
并以一个星号和正斜杆结束 */
js
/* 两个消息的例子。
这是一个多行注释。
*/
alert('Hello');
alert('World');
快捷方式 :
在大多数的编辑器中,可以使用 Ctrl+/ 进行单行注释,Ctrl+Shift+/ 进行多行注释(选择代码,然后按下组合键)
不支持注释嵌套!
现代模式,"use strict"
"use strict"
当"use strict"
处于脚本文件的顶部时,则整个脚本文件都将以"现代"模式进行工作
"use strict"
可以被放在函数体的开头。这样则可以只在该函数中启用严格模式。但通常人们会在整个脚本中启用严格模式- 只有注释可以出现在
"use strict"
的上面 - use strict 一旦设置无法取消
浏览器控制台
当使用开发者控制台运行代码时,是默认不启动 use strict 的
有时,当 use strict 会对代码产生一些影响时,运行后会得到错误的结果。
在控制台中启用 use strict:
首先,可以尝试搭配使用 Shift+Enter 按键去输入多行代码,然后将 use strict 放在代码最顶部,就像这样:
js
'use strict'; <Shift+Enter 换行>
// ...你的代码
<按下 Enter 以运行>
其在大部分浏览器中都有效,像 Firefox 和 Chrome
如果依然不行,例如你使用的是旧版本的浏览器,那么有一种很丑但可靠的启用 use strict 的方法------将你的代码放在这样的包装器中:
js
(function() {
'use strict';
// ...你的代码...
})()
-
现代 JavaScript 支持 "classes" 和 "modules" ------ 高级语言结构(本教程后续章节会讲到),它们会自动启用 use strict 。因此,如果我们使用它们,则无需添加 "use strict" 指令
-
目前最好还是将 "use strict"; 写在脚本的顶部。稍后,当代码全都写在了 class和 module 中时,则可以将 "use strict"; 这行代码省略掉
变量
JavaScript 应用需要处理信息。eg:
1.一个网上商店 ------ 这里的信息可能包含正在售卖的商品和购物车
2.一个聊天应用 ------ 这里的信息可能包括用户和消息等等
变量就是用来储存 这些信息的 变量是数据的"命名存储"。我们可以使用变量来保存商品、访客和其他信息
变量
在 JavaScript 中声明一个变量,需要用 let
关键字
js
let message;//定义(声明)了一个名称为 "message" 的变量
message = 'Hello!';//通过赋值运算符 "=" 为变量添加一些数据
alert(message); // 显示变量内容Hello!
现在这个字符串已经保存到与该变量相关联的内存区域了,我们可以通过使用该变量名称访问它
简洁地,可以将变量定义和赋值合并成一行
js
let message = 'Hello!'; // 定义变量,并且赋值
alert(message); // 显示变量内容Hello!
在一行中声明多个变量:
js
let user = 'John', age = 25, message = 'Hello';
但并不推荐这样,为了更好的可读性,最好一行只声明一个变量
js
let user = 'John';
let age = 25;
let message = 'Hello';
一些程序员采用下面的形式书写多个变量:
js
let user = 'John',
age = 25,
message = 'Hello';
变量声明
变量的声明可以任意改变,声明值改变后,之前的数据就被从变量中删除了
js
let message;
message = 'Hello!';
message = 'World!'; // 值改变了
alert(message);
还可以声明两个变量,然后将其中一个变量的数据拷贝到另一个变量
js
let hello = 'Hello world!';
let message;
message = hello;// 将字符串 'Hello world' 从变量 hello 拷贝到 message
alert(hello); // Hello world!
alert(message); // Hello world!
// 现在两个变量保存着相同的数据
一个变量只能声明一次,对同一个变量进行重复声明会触发 error
- 存在禁止更改变量值的函数式编程语言。比如 Scala 或 Erlang
如果你试图保存其他值,它会强制创建一个新盒子(声明一个新变量),无法重用之前的变量
补充:
从ES6开始,JavaScript引入了 let 和 const 关键字,用于声明变量和常量。与 var 相比,let 和 const 提供了块级作用域,且对于重复声明的变量会抛出错误,从而提高了代码的可读性和准确性。
var 关键字
JavaScript 中 var
关键字与 let
类似,有以下地方和 let
不一样
-
作用域:全局变量
js{ var age = 20; } alert(age); // 在代码块中定义的age 变量,在代码块外边还可以使用
-
变量可以重复定义
js{ var age = 20; var age = 30;//JavaScript 会用 30 将之前 age 变量的 20 替换掉 } alert(age); //打印的结果是 30
针对如上的问题,ECMAScript 6 新增了 let
关键字来定义变量。它的用法类似于 var
,但是所声明的变量,只在 let
关键字所在的代码块内有效,且不允许重复声明。
const 关键字
const 用来声明一个只读的常量。一旦声明,常量的值就不能改变。
可以看到给 PI 这个常量重新赋值时报错了。
变量命名
JavaScript 的变量命名的要求:
- 变量名称必须仅包含字母,数字,符号 $ 和 _
- 首字符必须非数字
- 区分大小写
- 如果命名包括多个单词,通常采用驼峰式命名法。即除了第一个单词,其他的每个单词都以大写字母开头:myVeryLongName
- 保留字不能用作变量命名,如let 、 class 、 return 、 function
常量
声明一个常数(不变)变量,使用 const
声明的变量称为"常量"。它们不能被重新赋值,如果尝试修改就会发生报错
js
const myBirthday = '18.04.1982';
myBirthday = '01.01.2001'; // 错误,不能对常量重新赋值
常量的用法
-
当程序员能确定这个变量永远不会改变的时候,就可以使用 const 来确保这种行为,并且清楚地向别人传递这一事实
-
将常量用作别名,以便记住那些在执行之前就已知的难以记住的值。 使用大写字母和下划线来命名这些常量
例如,让我们以所谓的"web"(十六进制)格式为颜色声明常量:
js
const COLOR_RED = "#F00";
const COLOR_GREEN = "#0F0";
const COLOR_BLUE = "#00F";
const COLOR_ORANGE = "#FF7F00";
// ......当我们需要选择一个颜色
let color = COLOR_ORANGE;
alert(color); // #FF7F00
好处:
- COLOR_ORANGE 比 "#FF7F00" 更容易记忆
- 比起 COLOR_ORANGE 而言, "#FF7F00" 更容易输错
- 阅读代码时, COLOR_ORANGE 比 #FF7F00 更易懂
-
有些常量在执行期间被"计算"出来,但初始赋值之后就不会改变。eg:
const pageLoadTime = /* 网页加载所需的时间 */;
pageLoadTime
的值在页面加载之前是未知的,所以采用常规命名。但是它仍然是个常量,因为赋值之后不会改变。 -
大写命名的常量仅用作"硬编码(hard-coded)"值的别名
正确命名变量
变量名应该有清晰、明显的含义,对其存储的数据进行描述
一些可以遵循的规则:
- 使用易读的命名,比如 userName 或者 shoppingCart
- 变量名要在足够简洁的同时准确描述变量,像 data 和 value 这样的名称等于什么都没说,除非能够非常明显地从上下文知道数据和值所表达的含义
- 脑海中的术语要和团队保持一致。如果网站的访客称为"用户",则我们采用相关的变量命名,比如 currentUser(当前用户) 或者 newUser (新用户),而不要使用 currentVisitor (当前访问者)或者一个newManInTown(城里的新人)
- 声明一个新的变量,而非重新赋值现有的变量
数据类型
JavaScript 中的值都具有特定的类型
可以将任何类型的值存入变量
可以重新赋值
js
let message = "hello";
message = 123456;
Number 类型
number
类型代表整数和浮点数
"number" 类型无法表示大于 (253-1) (即 9007199254740991 ),或小于-(253-1) 的整数
数字可以有很多操作,比如,乘法 * 、除法 / 、加法 + 、减法 - 等等。
除了常规的数字,还包括所谓的"特殊数值"也属于这种类型:
Infinity 、 -Infinity 和 NaN
Infinity
代表数学概念中的 无穷大 ∞。是一个比任何数字都大的特殊值。
我们可以通过除以 0 来得到,它或者在代码中直接使用它:
js
alert( 1 / 0 ); // Infinity
alert( Infinity ); // Infinity
NaN
代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果
NaN 是粘性的。任何对 NaN 的进一步操作都会返回 NaN
eg:
js
alert( "not a number" / 2 ); // NaN,这样的除法是错误的
alert( "not a number" / 2 + 5 ); // NaN
BigInt 类型
BigInt
类型用于表示任意长度的整数
通过将 n 附加到整数字段的末尾来创建 BigInt 值
js
// 尾部的 "n" 表示这是一个 BigInt 类型
const bigInt = 1234567890123456789012345678901234567890n;
String 类型
JavaScript 中的字符串必须被括在引号里
js
let str = "Hello";
let str2 = 'Single quotes are ok too';
let phrase = `can embed another ${str}`;
在 JavaScript 中,有三种包含字符串的方式
- 双引号:
"Hello"
- 单引号:
'Hello'
- 反引号: `Hello`
- 双引号和单引号都是"简单"引用,在 JavaScript 中两者几乎没有什么差别
- 反引号是 功能扩展 引号。允许我们通过将变量和表达式包装在
${...}
中,来将它们嵌入到字符串中,eg:
js
let name = "John";
// 嵌入一个变量
alert( `Hello, ${name}!` ); // Hello, John!
// 嵌入一个表达式
alert( `the result is ${1 + 2}` ); // the result is 3
${...}
内的表达式会被计算,计算结果会成为字符串的一部分- 可以在
${...}
内放置任何东西:诸如名为 name 的变量,或者诸如 1 + 2 的算数表达式,或者其他一些更复杂的。
注意这仅仅在反引号内有效,其他引号不允许这种嵌入
js
alert( "the result is ${1 + 2}" );// the result is ${1 + 2}(使用双引号则不会计算 ${...} 中的内容)
-
JavaScript 中没有 character 类型。
-
在一些语言中,单个字符有一个特殊的 "character" 类型,在 C 语言和 Java 语言中被称为"char"
在 JavaScript 中没有这种类型。只有一种 string 类型,一个字符串可以包含零个(为空)、一个或多个字符
Boolean 类型(逻辑类型)
boolean
类型仅包含两个值: true
和 false
这种类型通常用于存储表示 yes 或 no 的值:
true 意味着 "yes,正确", false 意味着 "no,不正确
js
let nameFieldChecked = true; // yes, name field is checked
let ageFieldChecked = false; // no, age field is not checked
Boolean函数是JavaScript内置的用于将其他类型的值转换为布尔值的函数。
它遵循以下转换规则:
值 | 变成...... |
---|---|
0 , -0 , null , undefined , NaN , " "(只有空格的字符串) | false |
其他值 | true |
布尔值也可作为比较的结果:
js
let isGreater = 4 > 1;
alert( isGreater ); // true(比较的结果是 "yes")
"null" 值
特殊的 null
值不属于上述任何一种类型
它构成了一个独立的类型,只包含 null 值
JavaScript 中的 null 仅仅是一个代表"无"、"空"或"值未知"的特殊值
js
let age = null;//表示 age 是未知的
注意 :
js
var obj = null;
alert(typeof obj);//结果是 object
为什么打印上面的 obj 变量的数据类型,结果是object;这个官方给出了解释,下面是从官方文档截的图\
"undefined" 值
undefined
的含义是 未被赋值
如果一个变量已被声明,但未被赋值,那么它的值就是 undefined :
js
let age;
alert(age); // 弹出 "undefined"
从技术上讲,可以显式地将 undefined 赋值给变量:
js
let age = 100;// 将值修改为 undefined
age = undefined;
alert(age); // "undefined"
- 但是不建议这样做。通常,使用 null 将一个"空"或者"未知"的值写入变量中,而undefined 则保留作为未进行初始化的事物的默认初始值
object 类型和 symbol 类型
object
类型是一个特殊的类型
其他所有的数据类型都被称为"原始类型",因为它们的值只包含一个单独的内容(字符串、数字或者其他)
相反, object 则用于储存 数据集合 和更 复杂的实体
symbol
类型用于创建对象的唯一标识符。我们在这里提到 symbol 类型是为了完整性,但我们要在学完 object 类型后再学习它
typeof 运算符
typeof 运算符以字符串的形式返回数据类型。用于分别处理不同类型值的时候,或者快速进行数据类型检验
它支持两种语法形式:
- 作为运算符: typeof x
- 函数形式: typeof(x)
两种形式得到的结果是一样的
对 typeof x 的调用会以字符串的形式返回数据类型:
js
typeof(x) == "number"//运算数为数字
typeof(x) == "string"//运算数为字符串
typeof(x) == "boolean"//运算数为布尔值
typeof(x) == "object"//运算数为对象、数组和null
typeof(x) == "function"//运算数为函数
typeof(x) == "undefined"//运算数为未定义
eg:
js
typeof undefined // "undefined"
typeof 0 // "number"
typeof 10n // "bigint"
typeof true // "boolean"
typeof "foo" // "string"
typeof Symbol("id") // "symbol"
typeof Math // "object" (1)
typeof null // "object" (2)
typeof alert // "function" (3)
最后三行的额外说明:
- typeof null 的结果是 "object" 。这是官方承认的 typeof 的行为上的错误,这个问题来自于 JavaScript 语言的早期,并为了兼容性而保留了下来。 null 绝对不是一个object 。 null 有自己的类型,它是一个特殊值
- typeof alert 的结果是 "function" ,因为 alert 在 JavaScript 语言中是一个函数。我们会在下一章学习函数,那时我们会了解到,在 JavaScript 语言中没有一个特别的 "function"类型。函数隶属于 object 类型。但是 typeof 会对函数区分对待,并返回"function" 。这也是来自于 JavaScript 语言早期的问题。从技术上讲,这种行为是不正确的,但在实际编程中却非常方便
交互:alert、prompt 和 confirm
js 可以通过以下方式进行内容的输出,只不过不同的语句输出到的位置不同
-
使用 window.alert() 写入警告框
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> window.alert("hello js");//写入警告框 </script> </body> </html>
上面代码通过浏览器打开,我们可以看到如下图弹框效果
-
使用 document.write() 写入 HTML页面以输出
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> document.write("hello js 2~");//写入html页面 </script> </body> </html>
上面代码通过浏览器打开,我们可以在页面上看到
document.write(内容)
输出的内容
alert
alert
会显示一条信息,并等待用户按下 "OK"。 例如: alert("Hello");
弹出的这个带有信息的小窗口被称为 模态窗(即警告窗)。"modal" 意味着用户不能与页面的其他部分(例如点击其他按钮等)进行交互,直到他们处理完窗口。在上面示例这种情况下 ------ 直到用户点击"确定"按钮
alert
是window.alert
的简写在浏览器环境中,所有全局变量和函数(如
alert
、console
、document
)默认都是全局对象window
的属性。因此,
alert
等价于window.alert
,二者指向同一个函数。
prompt
prompt
函数接收两个参数:
result = prompt(title, [default]);
浏览器会显示一个带有文本消息的模态窗口,还有 input 框和确定/取消按钮
title
title
显示给用户的文本【可不写】
default
default
为 input 框的默认输入的初始文本【可不写,也可为空格】
访问者可以在提示输入栏中输入一些内容,然后按"确定"键。然后我们在 result 中获取该文本。或者他们可以按取消键或按 Esc 键取消输入,然后我们得到 null 作为 result
prompt 将返回用户在 input 框内输入的文本,如果用户取消了输入,则返回 null
举个例子:
js
let age = prompt('How old are you?', 100);
alert(`You are ${age} years old!`); // You are 100 years old!
- IE 浏览器会提供默认值
第二个参数是可选的。但是如果我们不提供的话,Internet Explorer 会把 "undefined" 插入到 prompt
我们可以在 Internet Explorer 中运行下面这行代码来看看效果:
let test = prompt("Test");
所以,为了 prompt 在 IE 中有好的效果,我们建议始终提供第二个参数:
let test = prompt("Test", ''); // <-- 用于 IE 浏览器
confirm
语法:
result = confirm(question);
confirm
函数显示一个带有 question
以及确定和取消两个按钮的模态窗口
点击确定返回 true
,点击取消返回 false
例如:
js
let isBoss = confirm("Are you the boss?");
alert( isBoss ); // 如果"确定"按钮被按下,则显示 true
这些方法都是模态的 :
它们暂停脚本的执行,并且不允许用户与该页面的其余部分进行交互,直到窗口被解除
两个限制:
-
模态窗口的确切位置由浏览器决定。通常在页面中心
-
窗口的确切外观也取决于浏览器。我们不能修改它
这就是简单的代价。还有其他一些方法可以显示更漂亮的窗口,并与用户进行更丰富的交互
补充 console.log()
执行 console.log(内容)
时,实际上是在告诉浏览器:"请在控制台上显示 内容"。这常用于调试和开发过程中,以便查看某个变量的值或跟踪程序的执行过程。 在浏览器界面按 F12
就可以看到下图的控制台
类型转换
大多数情况下,运算符和函数会自动将赋予它们的值转换为正确的类型
比如, alert 会自动将任何值都转换为字符串以进行显示。算术运算符会将值转换为数字
在某些情况下,则需要将值显式地转换为期望的类型
- 在本章中,不会讨论 object 类型。目前,将只学习原始类型
之后,在学习完 object 类型后,将在 对象 --- 原始值转换 一章中学习对象 --- 原始值转换
字符串转换 string
当需要一个字符串形式的值时,就会进行字符串转换。
比如,alert(value)
将 value
转换为字符串类型,然后显示这个值
我们也可以显式地调用 String(value)
来将 value
转换为字符串类型:
js
let value = true;
alert(typeof value); // boolean
value = String(value); // 现在,值是一个字符串形式的 "true"
alert(typeof value); // string
字符串转换最明显。 false 变成 "false" , null 变成 "null" 等
数字型转换 number
在算术函数和表达式中,会自动进行 number 类型转换。
比如,当把除法 /
用于非 number 类型:
js
alert( "6" / "2" ); // 3, string 类型的值被自动转换成 number 类型后进行计算
我们也可以使用 Number(value)
或 parseInt(value)
显式地将这个 value
转换为 number 类型
在 JavaScript 中,parseInt(value)
和 Number(value)
都用于将值转换为数字,但它们的转换逻辑和适用场景有显著差异。以下是关键区别的对比:
核心区别总结
特性 | parseInt(value) |
Number(value) |
---|---|---|
转换逻辑 | 解析字符串直到第一个非数字字符 | 严格转换整个字符串为数字 |
进制处理 | 支持显式指定进制(如二进制、十六进制) | 不能指定进制,自动识别进制进行转换 |
空字符串处理 | parseInt("") → NaN |
Number("") → 0 |
null 处理 |
parseInt(null) → NaN |
Number(null) → 0 |
布尔值处理 | parseInt(true/false) → NaN |
Number(true) → 1 , Number(false) → 0 |
非严格转换 | 忽略字符串尾部非数字字符 | 整个字符串必须完全符合数字格式,否则转换为 NaN |
共同点:
-
都字符串忽略前后空格
javascriptparseInt(" 123 "); // 123(忽略前后空格) Number(" 123 "); // 123(忽略前后空格)
-
都默认十进制转换
js//前导零(非严格模式) parseInt("010"); // 10(ES5+ 默认十进制,旧浏览器可能按八进制解析) parseInt("010", 8); // 8(显式指定八进制) Number("010"); // 10
底层机制差异
-
parseInt
的解析规则- 从左到右解析字符串,跳过前导空格。
- 遇到第一个非数字字符(包括
.
、e
)时停止解析。(即丢弃小数部分,无法正确转换科学计数法数字) - 若字符串以
0x
开头且未指定进制,默认按十六进制解析。 - 语法 :
parseInt(string, radix)
(radix
表示进制,范围 2~36)。
-
Number
的转换规则- 严格检查整个字符串是否为合法数字格式。
- 支持小数、十进制、科学计数法(
"1.23e5"
)、十六进制(0x
前缀)。 - 空字符串或纯空格字符串转为
0
。 - 无法解析时返回
NaN
。
最佳实践
-
使用
parseInt
的场景- 提取字符串开头的数字部分(如
"12px"
→12
)。 - 需要显式指定进制解析(如二进制、十六进制)。
- 提取字符串开头的数字部分(如
-
使用
Number
的场景-
严格验证字符串是否为完整数字。
-
转换非字符串类型(如布尔值、
null
)。
-
当我们从 string 类型源(如文本表单)中读取一个值,但期望输入一个数字时,通常需要进行显式转换
js
let str = "123";
alert(typeof str); // string
let num = Number(str); // 变成 number 类型 123
alert(typeof num); // 123
补充 :
一元正号运算符 +
用于将值转换为数字,其底层逻辑与 Number()
函数完全一致
js
varstr= +"20";
alert(str + 1); //21
布尔型转换 boolean
发生在逻辑运算中(将进行条件判断和其他类似的东西),也可以通过调用Boolean(value) 显式地进行转换
转换规则如下:
值 | 变成...... |
---|---|
0 , -0 , null , undefined , NaN , " "(只有空格的字符串) | false |
其他值 | true |
eg:
js
alert( Boolean(1) ); // true
alert( Boolean(0) ); // false
alert( Boolean("hello") ); // true
alert( Boolean("") ); // false
- 请注意:包含 0 的字符串 "0" 是 true
一些编程语言(比如 PHP)视 "0" 为 false 。但在 JavaScript 中,非空的字符串总是true
js
alert( Boolean("0") ); // true
alert( Boolean(" ") ); // 空白,也是 true(任何非空字符串都是 true)
使用场景:
在 Java 中使用字符串前,一般都会先判断字符串不是null,并且不是空字符才会做其他的一些操作,JavaScript也有类型的操作,代码如下:
js
var str = "abc";
//健壮性判断
if(str){ //JS 会自动进行类型转换
alert("转为true");
}else {
alert("转为false");
}
基础运算符,数学
术语:"一元运算符","二元运算符","运算元"
- 运算元 ------ 运算符应用的对象。比如说乘法运算
5 * 2
,有两个运算元:左运算元 5 和右运算元 2 。
有时候也称其为"参数"而不是"运算元"。 - 如果一个运算符对应的只有一个运算元 ,那么它是 一元运算符。比如说一元负号运算符
-
,它的作用是对数字进行正负转换:
js
let x = 1;
x = -x;
alert( x ); // -1,一元负号运算符生效
- 如果一个运算符拥有两个运算元,那么它是 二元运算符:
js
let x = 1, y = 3;
alert( y - x ); // 2,二元运算符减号做减运算
数学
支持以下数学运算:
加法 +
,
减法 -
,
乘法 *
,
除法 /
,
取余 %
,
求幂 **
.
% 和 ** 需要详细说明
取余 %
取余运算符是 % ,尽管它看起来很像百分数,但实际并无关联
a % b 的结果是 a 整除 b 的 余数
例如:
js
alert( 5 % 2 ); // 1,5 除以 2 的余数
alert( 8 % 3 ); // 2,8 除以 3 的余数
求幂 **
求幂运算 a ** b 是 a 乘以自身 b 次
例如:
js
alert( 2 ** 2 ); // 4 (2 * 2,自乘 2 次)
alert( 2 ** 3 ); // 8 (2 * 2 * 2,自乘 3 次)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2,自乘 4 次)
求幂的定义也适用于非整数。例如,平方根是以 1/2 为单位的求幂:
js
alert( 4 ** (1/2) ); // 2(1/2 次方与平方根相同)
alert( 8 ** (1/3) ); // 2(1/3 次方与立方根相同)
用二元运算符 + 连接字符串
通常,加号 + 用于求和
但是如果加号 + 被应用于字符串,它将合并(连接)各个字符串:
js
let s = "my" + "string";
alert(s); // mystring
- 只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。eg:
js
alert( '1' + 2 ); // "12"
更复杂的例子:
js
alert(2 + 2 + '1' ); // 结果是"41",不是 "221"
运算符按顺序工作。第一个 + 将两个数字相加,所以返回 4
,然后下一个 +
将字符串 1
加入其中,所以就是 4 + '1 ' = 41
- 二元
+
是唯一一个以这种方式支持字符串的运算符
若是减法和除法:
js
alert( 6 - '2' ); // 4,将'2' 转换为数字
alert( '6' / '2' ); // 3,将两个运算元都转换为数字
数字转化,一元运算符 +
加号+
有两种形式。除了上面讨论的二元运算符,还有一元运算符
一元运算符加号(加号+
应用于单个值),对数字没有任何作用。但是如果运算元不是数字,加号+
则会将其转化为数字。 eg:
js
// 对数字无效
let x = 1;
alert( +x ); // 1
// 转化非数字
alert( +true ); // 1
alert( +"" ); // 0
- 它的效果和Number(...) 相同,但是更加简短
将字符串转化为数字并求和
- 若直接将字符串相加,则其对应的数字只排列
js
let apples = "2";
let oranges = "3";
alert( apples + oranges ); // "23",二元运算符加号合并字符串
- 所以需要将字符串转化为数字再求和
js
let apples = "2";
let oranges = "3";
// 在二元运算符加号起作用之前,把所有的值都转化为数字
alert( +apples + +oranges ); // 5
//另一种更长的写法 alert( Number(apples) + Number(oranges) ); // 5
运算符优先级
每个运算符都有对应的优先级数字。数字越大,越先执行。如果 优先级相同,则按照由左至右的顺序执行。

赋值运算符=
它的优先级非常低,只有 3 ,所以其他计算先执行,然后 =
才执行赋值
赋值 = 返回一个值
=
是一个运算符,在 JavaScript 中,大多数运算符都会返回一个值。这对于 =来说也是如此。
语句 x = value
将值 value
写入 x
然后返回 x
。
一个在复杂语句中使用赋值的例子:
js
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
链式赋值
js
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
出于可读性,最好将这种代码分成几行:
js
c = 2 + 2;
b = c;
a = c;
复合赋值运算符
对一个变量做运算,并将新的结果存储在这个变量中
可以使用运算符 +=
、=
、/=
、-=
、%=
表示的意思是先将运算符左边变量值 和右边的数 相加(减、乘、除、除后取余),然后将结果赋值给左边的变量
js
let n = 2;
n += 5; // 现在 n = 7(等同于 n = n + 5)
n *= 2; // 现在 n = 14(等同于 n = n * 2)
alert( n ); // 14
自增/自减

如果我们想要将一个数加一,但是我们想使用其自增之前的值,那么我们需要使用后置形式
逗号运算符
逗号运算符能让我们处理多个语句,使用 , 将它们分开。每个语句都运行了,但是只有位置最后的语句的结果会被返回。eg:
js
let a = (1 + 2, 3 + 4);
alert( a ); // 7 (3 + 4 的结果)
- 逗号运算符的优先级非常低,比
=
还要低,因此上面例子中圆括号非常重要 - 用处:把几个行为放在一行上来进行复杂的运算。eg:
js
// 一行上有三个运算符
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
}
位运算符
位运算符把运算元当做 32 位整数,并在它们的二进制表现形式上操作。
位运算符: 按位与 ( &
)
按位或 ( |
)
按位异或 ( ^
)
按位非 ( ~
)
左移 ( <<
)
右移 ( >>
)
无符号右移 ( >>>
)
这些运算符很少被使用,一般是我们需要在最低级别(位)上操作数字时才使用。
值的比较

相等性检查==
双等号 ==
表示相等性检查,而单等号 a =
b 表示赋值
比较结果为 Boolean 类型
所有比较运算符均返回布尔值:
true
------ 表示"yes(是)","correct(正确)"或"the truth(真"
false
------ 表示"no(否)","wrong(错误)"或"not the truth(非真)"
eg:
js
alert( 2 > 1 ); // true(正确)
alert( 2 == 1 ); // false(错误)
alert( 2 != 1 ); // true(正确)
和其他类型的值一样,比较的结果可以被赋值给任意变量:
js
let result = 5 > 4; // 把比较的结果赋值给 result
alert( result ); // true
字符串比较
字符串的比较算法:
-
首先比较两个字符串的首位字符大小。
-
如果一方字符较大(或较小),则该字符串大于(或小于)另一个字符串。算法结束。【排序在后即为大 ,同一字母小写更大 】
-
否则,如果两个字符串的首位字符相等,则继续取出两个字符串各自的后一位字符进行比较。
-
重复上述步骤进行比较,直到比较完成某字符串的所有字符为止。
-
如果两个字符串的字符同时用完,那么则判定它们相等,否则还有未比较的字符的字符串更大 。
eg:
js
alert( 'Z' > 'A' ); // true
alert( 'Glow' > 'Glee' ); // true
alert( 'Bee' > 'Be' ); // true
不同类型间的比较
当对不同类型的值进行比较时,JavaScript 会首先将其转化为数字再判定大小。eg:
js
alert( '2' > 1 ); // true,字符串 '2' 会被转化为数字 2
alert( '01' == 1 ); // true,字符串 '01' 会被转化为数字 1
对于布尔类型值, true
会被转化为 1
、false
转化为 0
。eg:
严格相等===
普通的相等性检查 ==
存在一个问题,因为空字符串 和 false 转化后都为数字 0。所以 ==
不能区分出 0 和 false ;也同样无法区分空字符串和 false
严格相等运算符 ===
在进行比较时不会做任何的类型转换。
如果 a 和 b 属于不同的数据类型,那么 a === b
不会做任何的类型转换,而是立刻返回 false
同样的,与"不相等"符号 !=
类似,"严格不相等"表示为 !==
对 null 和 undefined 进行比较
当使用非严格相等 == 比较二者时
JavaScript 存在一个特殊的规则,会判定它们相等。只在非严格相等下,它们仅等于对方而不等于其他任何的值(它们属于不同的类型)
当使用数学式或其他比较方法< > <= >=
时: null
/undefined
会被转化为数字: null
被转化为 0
, undefined
被转化为 NaN
相等性检查 ==
和普通比较 符 > < >= <=
的代码逻辑是相互独立的
进行值的比较时, null 会被转化为数字 0
undefined
和 null
在相等性检查 ==
中不会进行任何的类型转换,除了它们之间互等外,不会等于任何其他的值,它们有自己独立的比较规则
特立独行的 undefined
undefined
与其他值进行 >
<
比较返回值都是 false
因为 undefined
在比较中被转换为了 NaN
,而 NaN
是一个特殊的数值型值,它与任何值进行比较都会返回 false
避免问题
-
除了严格相等 === 外,其他但凡是有undefined/null 参与的比较,我们都需要格外小心。
-
除非你非常清楚自己在做什么,否则永远不要使用
>=
>
<
条件分支:if 和 '?'
"if" 语句
if(...)
语句计算括号里的条件表达式,如果计算结果是 true ,就会执行对应的代码块。
如果有多个语句要执行,我们必须将要执行的代码块封装在大括号 {}
内:
js
if (year == 2015) {
alert( "That's correct!" );
alert( "You're so smart!" );
}
即使只有一条语句,建议每次使用 if 语句都用大括号 {} 来包装代码块,这样可以提高代码可读性
布尔转换
if (...)
语句会计算圆括号内的表达式,并将计算结果转换为布尔型
类型转换 一章中的转换规则:
数字 0 、空字符串 ""、null、undefined 、 NaN 都会被转换成 false 。因为它们被称为"假值(falsy)"
其他值被转换为 true ,所以它们被称为"真值(truthy)"
所以,if (...) 的 (...) 可以根据上面的转换规则填入常值,使其后的代码块{}
一定 执行 与 不执行
也可以将未计算的布尔值传入 if 语句
js
let cond = (year == 2015);
// 相等运算符(year == 2015)的结果是 true 或 false
if (cond) {
...
}
"else" 语句
if 语句有时会包含一个可选的 "else" 块。如果判断条件不成立,就会执行它内部的代码。 例如:
js
let year = prompt('In which year was ECMAScript-2015 specification published?', '');
if (year == 2015) {
alert( 'You guessed it right!' );
}
else {
alert( 'How can you be so wrong?' ); // 2015 以外的任何值
}
多个条件:"else if"
测试一个条件的几个变体,可以通过使用 else if
子句实现。 例如:
js
let year = prompt('In which year was ECMAScript-2015 specification published?', '');
if (year < 2015) {
alert( 'Too early...' );
}
else if (year > 2015) {
alert( 'Too late' );
}
else {
alert( 'Exactly!' );
}
条件运算符 '?'
根据一个条件去赋值一个变量。
语法:
js
let result = condition ? value1 : value2;
计算条件结果,如果结果为真,则返回 value1
,否则返回 value2
例如:
js
let accessAllowed = (age > 18) ? true : false;
可以省略 age > 18 外面的括号,但出于代码可读性,建议使用括号
多个 '?'
使用一系列问号 ? 运算符可以返回一个取决于多个条件的值。例如:
js
let age = prompt('age?', 18);
let message = (age < 3) ? 'Hi, baby!' :
(age < 18) ? 'Hello!' :
(age < 100) ? 'Greetings!' :
'What an unusual age!';
alert( message )
它是一个普通的检查序列,说明:
- 第一个问号检查 age < 3 。
- 如果为真 --- 返回 'Hi, baby!' 。否则,会继续执行冒号 ":" 后的表达式------检查 age <18 。
- 如果为真 --- 返回 'Hello!' 。否则,会继续执行下一个冒号 ":" 后的表达式------检查 age <100 。
- 如果为真 --- 返回 'Greetings!' 。否则,会继续执行最后一个冒号 ":" 后面的表达式------返回 'What an unusual age!' 。
'?' 的非常规使用
可以使用问号 ? 来代替 if 语句:
js
let company = prompt('Which company created JavaScript?', '');
(company == 'Netscape') ?
alert('Right!') : alert('Wrong.');
- ?和if的选择
- 当需要根据条件返回一个或另一个值问号时, 使用
?
- 当需要执行不同的代码分支时,使用
if
- 当需要根据条件返回一个或另一个值问号时, 使用
逻辑运算符
JavaScript 中有三个逻辑运算符: || (或), && (与), ! (非)。
虽然它们被称为"逻辑"运算符,但这些运算符却可以被应用于任意类型的值,而不仅仅是布尔值。它们的结果也同样可以是任意类型。
转化为布尔值规则
值 | 变成...... |
---|---|
0 , -0 , null , undefined , NaN , " "(只有空格的字符串) | |
其他值 | true |
||(或)
格式: ( 操作数 || 操作数 || 操作数 || ...);
只有两个操作数都是 false 的情况,结果才是 false
如果操作数不是布尔值,那么它将会被转化为布尔值来参与运算
大多数情况下,逻辑或 || 会被用在 if 语句中,用来测试是否有 任何给定的条件 为 true 。例如:
js
let hour = 9;
if (hour < 10 || hour > 18) {
alert( 'The office is closed.' );
}
也可以在一行代码上串联多个条件:
js
let hour = 12;
let isWeekend = true;
if (hour < 10 || hour > 18 || isWeekend) {
alert( 'The office is closed.' ); // 是周末
}
或 运算寻找第一个真值
给出多个参与 或 运算的值:
js
result = value1 || value2 || value3;
或运算符 || 做了如下的事情:
- 从左到右依次计算操作数。
- 处理每一个操作数时,都将其转化为布尔值。如果结果是 true ,就停止计算,返回这个操作数的初始值。
- 如果所有的操作数都被计算过(也就是,转换结果都是 false ),则返回最后一个操作数。
或 运算返回第一个真值 ,如果没有真值就返回最后一个值
返回的值是操作数的初始形式,不会做布尔转换
例如:
js
alert( 1 || 0 ); // 1(1 是真值)
alert( null || 1 ); // 1(1 是第一个真值)
alert( null || 0 || 1 ); // 1(第一个真值)
alert( undefined || null || 0 ); // 0(都是假值,返回最后一个值)
1. 获取变量列表或者表达式中的第一个真值
例如,我们有变量 firstName
、 lastName
和 nickName
,都是可选的(即可以是undefined,也可以是假值)。
我们用或运算 ||
来选择有数据的那一个,并显示出来(如果没有设置,则用"Anonymous" ):
js
let firstName = "";
let lastName = "";
let nickName = "SuperCoder";
alert( firstName || lastName || nickNae || "Anonymous"); // SuperCoder
如果所有变量的值都为假,结果就是 "Anonymous"
2. 短路求值
或 运算符 ||
的另一个用途是"短路求值"
指的是, ||
对其参数进行处理,直到达到第一个真值,然后立即返回该值,而无需处理其他参数
如果操作数不仅仅是一个值,而是一个有副作用的表达式,例如变量赋值或函数调用,那么这一特性的重要性就显而易见了
在下面这个例子中,只会打印第二条信息:
js
true || alert("not printed");
false || alert("printed");
在第一行中,或运算符 ||
在遇到 true
时立即停止运算,所以 alert
没有运行。
有时,人们利用这个特性,只在左侧的条件为假时才执行命令
&&(与)
格式:a && b;
只有两个操作数都是true时,与 运算才返回 true ,否则返回 false
if 语句的示例:
js
let hour = 12;
let minute = 30;
if (hour == 12 && minute == 30) {
alert( 'Time is 12:30' );
}
像 或 运算一样,与 运算的操作数可以是任意类型的值:
js
if (1 && 0) { // 作为 true && false 来执行
alert( "won't work, because the result is falsy" );
}
与运算寻找第一个假值
给出多个参加与运算的值:
js
result = value1 && value2 && value3;
与运算 &&
做了如下的事:
- 从左到右依次计算操作数
- 在处理每一个操作数时,都将其转化为布尔值。如果结果是 false ,就停止计算,并返回这个操作数的初始值
- 如果所有的操作数都被计算过(例如都是真值),则返回最后一个操作数
与 运算返回第一个假值,如果没有假值就返回最后一个值 例如:
js
alert( 1 && 5 ); // 5
alert( 0 && "no matter what" ); // 0
也可以在一行代码上串联多个值:
js
alert( 1 && 2 && null && 3 ); // null
alert( 1 && 2 && 3 ); // 3,最后一个值
- 不要用 || 或 && 来取代 if
!(非)
格式:
js
result = !value;
逻辑 非 运算符接受一个参数,并按如下运作:
- 将操作数转化为布尔类型: true/false
- 返回相反的布尔类型值
例如:
js
alert( !true ); // false
alert( !0 ); // true
两个非运算 !!
可以用来将某个值转化为布尔类型(负负为正),可以代替一个内置的 Boolean 函数完成同样的事
js
alert( !!"non-empty string" ); // true
alert( !!null ); // false
alert( Boolean("non-empty string") ); // true
alert( Boolean(null) ); // false
空值合并运算符 '??'
在本文中,将值既不是 null
也不是 undefined
的表达式称为"已定义的"。
a ?? b
的结果是:
-如果 a 是已定义的,则结果为 a的值
-如果 a 不是已定义的,则结果为 b的值
也就是说,如果第一个参数不是 null/undefined
,则 ??
返回第一个参数的值,否则,返回第二个参数的值
??
是一种获得两者中的第一个"已定义的"的值的语法,常用于为变量分配默认值,例如
js
// 当 height 的值为 null 或 undefined 时,将 height 的值设置为 100
height = height ?? 100;
也可以使用已知的运算符重写 result = a ?? b
,例如:
js
result = (a !== null && a !== undefined) ? a : b;
用处 :
- 通常是为可能是未定义的变量提供一个默认值。
例如,在这里,如果 user
是未定义的,我们则显示 Anonymous
:
js
let user;
alert(user ?? "Anonymous"); // Anonymous
如果 user
的值为除 null/undefined
外的任意值,那么我们看到的将是 user
的值:
js
let user = "John";
alert(user ?? "Anonymous"); // John
- 还可以使用
??
序列从一系列的值中选择出第一个非null/undefined
的值。
假设我们在变量 firstName
、 lastName
或 nickName
中存储着一个用户的数据。如果用户 决定不输入值,则所有这些变量的值都可能是未定义的。
我们想使用这些变量之一显示用户名,如果这些变量的值都是未定义的,则显示 "Anonymous"。
让我们使用 ??
运算符来实现这一需求:
js
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// 显示第一个已定义的值:
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
与 || 比较
或 运算符 ||
可以以 与 ??
运算符相同的方式使用。
例如,在上面的代码中,可以用 || 替换掉 ?? ,也可以获得相同的结果:
js
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// 显示第一个真值:
alert(firstName || lastName || nickName || "Anonymous"); // Supercoder
它们之间重要的区别是:
||
返回第一个 真 值??
返回第一个 已定义的 值
?? 与 && 或 || 一起使用
JavaScript 禁止将 ??
运算符与 &&
和 ||
运算符一起使用,除非使用括号明确指定了优先级
js
下面的代码会触发一个语法错误:
let x = 1 && 2 ?? 3; // Syntax error
可以明确地使用括号来解决这个问题:
let x = (1 && 2) ?? 3; // 正常工作了
alert(x); // 2
循环:while 和 for
"while" 循环
语法:
js
while (表达式) {
// 代码
// 所谓的"循环体"
}
当 表达式
为真时,执行循环体的 code
任何表达式或变量 都可以是循环条件。在 while
中的循环条件会被计算,计算结果会被转化为布尔值
例如,以下将循环输出当 1<i<=3
时的 i
值: while (i != 0) 可简写为 while (i) :
js
let i = 3;
while (i) { // 当 i 变成 0 时,条件为假,循环终止
alert( i );
i--;
}
循环体的单次执行叫作 一次迭代。上面示例中的循环进行了三次迭代
- 如果循环体只有一条语句,则可以省略大括号 {...} :
js
let i = 3;
while (i) alert(i--);
"do...while" 循环
do..while 语法将条件检查移至循环体下面:
js
do {
// 循环体
} while (condition);
循环首先执行循环体,然后检查条件,条件为真时,重复执行循环体。所以循环体至少执行一次
"for" 循环
for循环一般形式:
js
for (执行表达式1;判断表达式2; 执行表达式3) {
// ......循环体......
}
它的固定执行过程如下:
- 执行表达式1:一个或多个 赋值语句,对循环变量赋予初始值。
- 判断表达式2:一个 关系表达式, 在每次循环迭代之前检查,如果为 false,结束循环;
- 执行表达式3:循环变量的步进值,利用(i++)等对于循环变量进行操作的语句,定义控制循环变量每循环一次后按什么方式变化;
- for循环中执行代码块执行结束后继续执行第二步(判断表达式);第一步初始化只执行一次。
- 循环结束,程序继续向下执行。
使用for语句应该注意:
- for循环中的"表达式1、2、3"均可不写为空,但两个分号
(;;)
不能缺省- 省略"表达式1(循环变量赋初值)",表示 不对循环变量赋初始值。
- 省略"表达式2(循环条件)",不做其它处理,循环一直执行( 死循环 )
- 省略"表达式3( 循环变量增减量 )",不做其他处理,循环一直执行(死循环)
js
let i = 0; // 我们已经声明了 i 并对它进行了赋值
//可以移除执行表达式1
for (; i < 3; i++) { // 不再需要 "begin" 语句段
alert( i ); // 0, 1, 2
}
//也可以移除执行表达式3
for (; i < 3;) {
alert( i++ );
}
//还可以删除所有内容
for (;;) {
// 无限循环
}
- 表达式1可以是设置循环变量的初值的赋值表达式, 也可以是其他表达式 。
- 表达式1和表达式3可以是一个简单表达式也可以是多个表达式以逗号分割。
- 表达式2一般是 关系表达式 或 逻辑表达式 ,但也可是数值表达式或字符表达式, 只要其值非零 ,就执行循环体。
- 各表达式中的变量一定要在for循环 之前定义 。
-
执行表达式1
中的变量i
可在for语句中定义let i = 0;
,此时i
是"内联"变量声明。这样的变量只在循环中有效。- 变量
i
也可在for循环前定义,这样可以对最后的i
进行操作:
js
let i = 0;
for (i = 0; i < 3; i++) { // 使用现有的变量
alert(i); // 0, 1, 2
}
alert(i); //3,有效,因为是在循环之外声明的
跳出循环
随时都可以使用 break
指令强制退出 例如,下面这个循环要求用户输入一系列数字,在输入的内容不是数字时"终止"循环:
js
let sum = 0;
while (true) {
let value = +prompt("Enter a number", '');
if (!value) break; // (*)
sum += value;
}
alert( 'Sum: ' + sum );
如果用户输入空行或取消输入,在 (*)
行的 break
指令会被激活。它立刻终止循环,将控制权 传递给循环后的第一行,即,alert
根据需要,"无限循环 + break
" 的组合非常适用于不必在循环开始/结束时检查条件,但需要在中 间甚至是主体的多个位置进行条件检查的情况
继续下一次迭代
continue
指令不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)。
如果我们完成了当前的迭代,并且希望继续执行下一次迭代,我们就可以使用它。
下面这个循环使用 continue 来只输出奇数:
js
for (let i = 0; i < 10; i++) {
//如果为真,跳过循环体的剩余部分。
if (i % 2 == 0) continue;
alert(i); // 1,然后 3,5,7,9
}
对于偶数的 i
值, continue
指令会停止本次循环的继续执行,将控制权传递给下一次 for
循环的迭代(使用下一个数字)。因此 alert
仅被奇数值调用
- 禁止
break/continue
在 '?' 的右边
请注意非表达式的语法结构不能与三元运算符 ? 一起使用。特别是 break/continue
这样的指令是不允许这样使用的。 例如:
js
if (i > 5) {
alert(i);
} else {
continue;
}
用问号重写:
js
(i > 5) ? alert(i) : continue; // continue 不允许在这个位置,代码会停止运行,并显示有语法错误
break/continue 标签
有时候需要从一次从多层嵌套的循环中跳出来
下述代码中我们的循环使用了 i
和 j
,从 (0,0) 到 (3,3) 提示坐标 (i, j) :
js
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// 如果我想从这里退出并直接执行 alert('Done!')
}
}
alert('Done!');
有一种方法,可以在用户取消输入时来停止这个过程
在 input
之后的普通 break
只会打破内部循环。这还不够 ------ 标签可以实现这一功能!
标签 是在循环之前带有冒号的标识符:
js
labelName: for (...) {
...
}
break labelName
语句跳出循环至标签处(labelName
为标识符名):
js
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; // (*)
// 用得到的值做些事......
}
}
alert('Done!');
上述代码中, break outer
向上寻找名为 outer
的标签并跳出当前循环。 因此,控制权直接从 (*)
转至 alert('Done!')
我们还可以将标签移至单独一行:
continue
指令也可以与标签一起使用。在这种情况下,执行跳转到标记循环的下一次迭代
js
function showPrimes(n) {
nextPrime:
//这里使用了一个标签(nextPrime)的外部循环。
//这种结构允许我们在循环内部使用 continue 语句来跳过特定的迭代,而不仅仅是跳过当前迭代。
for (let i = 2; i < n; i++) {
for (let j = 2; j < i; j++) {
if (i % j == 0) continue nextPrime;
}
//如果 i 能被 j 整除(即 i % j == 0),则 i 不是素数。
//在这种情况下,使用 continue nextPrime; 来跳过当前外部循环的剩余迭代,并进入下一个数字的测试。
alert( i ); // 一个素数
}
}
- 只有在循环内部才能调用
break/continue
,并且标签必须位于指令上方的某个位置
"switch" 语句
语法
switch语句基本格式:
js
switch(表达式)
{
case 常量1:语句1;// 相当于if ( 表达式=== 常量1)
case 常量2:语句2;
...
case 常量n:语句n;
default:语句n+1;
break;
}
switch 语句可以替代多个 if 判断。
switch语句通过依次将表达式的值与常量值进行比对
如果严格相等 ,则将当前case语句以及后面所有的case语句全部执行,直到遇到最靠近的 break 语句(或者直到 switch 语句末尾)
如果非严格相等,则跳到下一个case语句
如果没有符合的 case,则执行 default 代码块(如果 default 存在),且default不会受到顺序的影响,default 语句也可以省略不用
例如:
js
let a = 2 + 2;
switch (a) {
case 3:
alert( 'Too small' );
break;
case 4:
alert( 'Exactly!' );
break;
case 5:
alert( 'Too large' );
break;
default:
alert( "I don't know such values" );
}
switch
语句时还应注意以下几点:
- switch可以嵌套使用
js
switch (表达式)
{
case 1:语句1;
case 2:语句2;
case 3:
switch (表达式) //switch语句可以支持嵌套
{
case 1:语句1';
case 2:语句2';
break;
default:
break;
}
case 4:语句4;
break;
default:
break;
}
switch
和case
都允许任意表达式- case后的执行语句是可以为空语句(空白,什么都不写),执行结果为无操作。可以达到目的:让不同情况 case 1(空语句) 和 case 2 都执行同样的代码
- 在case后的各 常量表达式 的值不能相同,否则会出现错误。
- 在case子句后如果没有 break ;会一直往后执行,直到遇到break;才会跳出switch语句
- 在case后,允许有多个语句,可以不用{} 括起来。
- 各case和default子句的先后顺序可以变动,而不会影响程序执行结果。
- 这里的相等是严格相等,被比较的值必须是相同的类型才能进行匹配。例如:
js
let arg = prompt("Enter a value?")
switch (arg) {
case '0':
case '1':
alert( 'One or zero' );
break;
case '2':
alert( 'Two' );
break;
case 3:
alert( 'Never executes!' );
break;
default:
alert( 'An unknown value' )
}
分析:
- 在
prompt
对话框输入0
、1
,第一个alert
弹出 - 输入
2
,第二个alert
弹出 - 但是输入
3
,因为prompt
的结果是字符串类型的"3"
,不严格相等===
于数字类型的3 ,所以case 3
不会执行!因此case 3
部分是一段无效代码。所以会执行default
分支
- 另外说明:
在prompt对话框输入2后,该值被视为字符型而不是数字
case '2'和case 2之间的区别在于条件的类型不同,前者匹配字符串'2',后者匹配数字2
函数
函数是程序的主要"构建模块"。函数使该段代码可以被调用很多次,而不需要写重复的代码
函数声明
函数定义格式有两种:
-
方式1
jsfunction 函数名(参数1,参数2..){ 要执行的代码 }
-
方式2
jsvar 函数名 = function (参数列表){ 要执行的代码 }
注意:
-
形式参数不需要类型。因为JavaScript是弱类型语言
jsfunction add(a, b){ return a + b; }
上述函数的参数 a 和 b 不需要定义数据类型,因为在每个参数前加上 var 也没有任何意义。
-
返回值也不需要定义类型,可以在函数内部直接使用return返回即可
新函数可以直接通过名称调用 。例如:
js
function showMessage() {
alert( 'Hello everyone!' );
}
showMessage();
showMessage();
调用 showMessage()
执行函数的代码。这里我们会看到显示两次消息
这个例子清楚地演示了函数的主要目的之一:避免代码重复
如果我们需要更改消息或其显示方式,只需在一个地方修改代码:输出它的函数
函数调用
函数调用与参数传递的基本规则
-
语法 :
函数名(实参列表);
例如:
let result = add(10, 20);
-
关键特性:
- 参数个数可任意:调用时传递的实参数量可以与函数定义的形参数量不同。
- 多余参数不会报错 :如需访问,需要特殊方式访问(如
arguments
对象或剩余参数)。 - 不足参数填充
undefined
:若实参少于形参,缺失的形参值为undefined
。
局部变量&外部变量
局部变量 :在函数中声明的变量只在该函数内部可见
外部变量:函数也可以访问在该函数外部声明的变量
函数也可以修改外部变量
js
let userName = 'John';
function showMessage() {
userName = "Bob"; // (1) 改变外部变量
let message = 'Hello, ' + userName;
alert(message);
alert( userName ); // John 在函数调用之前
showMessage();
alert( userName ); // Bob,值被函数修改了
}
- 优先级:局部变量>外部变量
参数
参数(也称"函数参数")可将任意数据传递给函数
在如下示例中,函数有两个参数: from 和 text
js
function showMessage(from, text) { // 参数:from 和 text
alert(from + ': ' + text);
}
showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)
在如下示例中,有一个变量 from
,并将它传递给函数。请注意:函数会修改 from
,但在函数外部看不到更改,因为函数修改的是复制的变量值副本:
js
function showMessage(from, text) {
from = '*' + from + '*'; // 让 "from" 看起来更优雅
alert( from + ': ' + text );
}
let from = "Ann";
showMessage(from, "Hello"); // *Ann*: Hello
// "from" 值相同,函数修改了一个局部的副本。
alert( from ); // Ann
默认值
如果未提供参数,那么其默认值则是 undefined
。
例如,之前提到的函数 showMessage(from, text)
可以只使用一个参数调用:
js
showMessage("Ann");
这样调用将输出 "*Ann*: undefined"
。这里没有参数 text
,所以程序假定 text === undefined
如果想在本示例中设定"默认"的 text
,那么我们可以在 =
之后指定它:
js
function showMessage(from, text = "no text given") {
alert( from + ": " + text );
}
//现在如果 text 参数未被 后面的相同函数 传递,它将会得到值 "no text given"
showMessage("Ann"); // Ann: no text given
这里 "no text given"
是一个字符串,但它可返回值以是更复杂的表达式,并且只会在缺少参数时才会被计算和分配
后备的默认参数
也可以将参数默认值的设置放在函数执行部分(相较更后期)而不是函数声明的时候
js
//判断参数是否被省略掉,我们可以拿它跟 undefined 做比较:
function showMessage(text) {
if (text === undefined) {
text = 'empty message';
}
alert(text);
}
showMessage(); // empty message
//也可以使用 || 运算符:
// 如果 "text" 参数被省略或者被传入空字符串,则赋值为 'empty'
function showMessage(text) {
text = text || 'empty';
...
}
返回值
函数可以将一个值返回到调用代码中作为结果。
最简单的例子是将两个值相加的函数:
js
function sum(a, b) {
return a + b;
}
let result = sum(1, 2);
alert( result ); // 3
指令 return
可以在函数的任意位置。当执行到达时,函数停止,并将值返回给调用代码(分配给上述代码中的 result
)
在一个函数中可能会出现很多次 return
。例如:
js
function checkAge(age) {
if (age >= 18) {
return true;
} else {
return confirm('Got a permission from the parents?');
}
}
let age = prompt('How old are you?', 18);
if ( checkAge(age) ) {
alert( 'Access granted' );
} else {
alert( 'Access denied' );
}
分析:
checkAge
函数接受一个名为age
的参数。如果age
大于等于18,函数会返回true
。否则,它将使用confirm
函数询问用户是否得到了父母的许可,然后返回询问的结果。let age = prompt('How old are you?', 18);
这行代码将通过prompt
函数询问用户年龄,并将用户输入的值存储在age
变量中。如果用户没有输入值,默认值为18。- 接下来的
if...else
语句检查调用checkAge
函数时的结果。如果返回值为true
,将显示一个包含'Access granted'的警报框。如果返回值为false
(或者用户没有得到父母的许可),则显示一个包含'Access denied'的警报框。
这段代码创建了一个简单的年龄验证系统,根据用户输入的年龄和是否得到了父母的许可来决定是否授予访问权限。
只使用 return
但没有返回值也是可行的。但这会导致函数立即退出。例如:
js
function showMovie(age) {
if ( !checkAge(age) ) {
return;
}
alert( "Showing you the movie" ); // (*)
// ...
}
在上述代码中,如果 checkAge(age)
返回 false
,那么 showMovie
将不会运行到alert
- 空值的
return
或没有return
的函数返回值为undefined
如果函数无返回值,它就会像返回 undefined
一样:
js
function doNothing() { /* 没有代码 */ }
alert( doNothing() === undefined ); // true
空值的 return
和 return undefined
等效:
js
function doNothing() {
return;
}
alert( doNothing() === undefined ); // true
- JavaScript 默认会在
return
之后加上分号。应该在return
的同一行开始写表达式,或者至少在return
右边放上左括号:return (
函数命名
函数就是行为。所以它们的名字通常是动词 。它应该简短且尽可能准确地描述函数的作用。
一种普遍的做法是用动词前缀来开始一个函数,这个前缀模糊地描述了这个行为。团队内部必须就前缀的含义达成一致。
例如,以 "show" 开头的函数通常会显示某些内容。
函数以以下前缀命名的含义
"get..."
------ 返回一个值,
"calc..."
------ 计算某些内容,
"create..."
------ 创建某些内容,
"check..."
------ 检查某些内容并返回 boolean 值,等
这类名字的示例:
js
showMessage(..) // 显示信息
getAge(..) // 返回 age(gets it somehow)
calcSum(..) // 计算求和并返回结果
createForm(..) // 创建表格(通常会返回它)
checkPermission(..) // 检查权限并返回 true/false
有了前缀,只需瞥一眼函数名,就可以了解它的功能是什么,返回什么样的值
- 一个函数应该只包含函数名所指定的功能,而不是做更多与函数名无关的功能
- 两个独立的行为通常需要两个函数,即使它们通常被一起调用(在这种情况下,我们可以创建第三个函数来调用这两个函数)
函数 == 注释
函数应该简短且只有一个功能。如果这个函数功能复杂,那么把该函数拆分成几个小的函数是值得的
"自描述"通常指的是对象的属性直接描述了该属性的用途或含义,而不需要外部文档或注释来解释,它的名称就足够清晰地说明它的用途或含义
作用域
全局作用域:在脚本的最外层定义的变量和函数拥有全局作用域,它们可以被脚本中的任何其他代码访问。
函数作用域:在函数内部声明的变量和函数拥有函数作用域,它们只能在该函数内部访问。
块级作用域:ES6引入了let和const关键字,可以在块级作用域(如if语句或循环)内声明变量。在块级作用域内声明的变量只能在该块级作用域内部访问。
访问规则 和 赋值规则
访问规则 :获取一个变量的值,这个行为叫做 访问
当在函数内部访问变量时,JavaScript会沿着作用域链向外查找该变量,一旦找到则停止查找,立即访问。如果在最外层仍未找到,则变量被认为是未定义的(直接报错:该变量 is not defined
)。
赋值规则
当给变量赋值时,JavaScript会沿着作用域链向外查找该变量的值,一旦找到则停止查找,立即赋值。如果在最外层仍未找到,则那么这个变量定义就被视为全局变量,直接赋初始值
javascript
function fn() {
num = 100
}
fn()
// fn 调用以后,要给 num 赋值
// 查看自己的作用域内部没有 num 变量
// 就会向上一级查找
// 上一级就是全局作用域,发现依旧没有
// 那么就会把 num 定义为全局的变量,并为其赋值
// 所以 fn() 以后,全局就有了一个变量叫做 num 并且值是 100
console.log(num) // 100
预解析
js
代码在运行的时候,会经历两个环节 解释代码 和 执行代码
解释代码
-
因为是在所有代码执行之前进行解释,所以叫做 预解析(预解释)
-
需要解释的内容有两个
- 声明式函数
- 在内存中先声明有一个变量名是函数名,并且这个名字代表的内容是一个函数
var
关键字- 在内存中先声明有一个变量名
- JavaScript 中的变量声明会被提升至其所在作用域的顶部。这种行为被称为"变量提升"。但是赋值操作仍然在原地执行。因此,无论变量声明在作用域中的哪个位置,都会在代码执行前被提升至作用域(一个
<script>
标签内)的顶部。这意味着你可以在变量声明之前使用该变量,但其值会是undefined
(赋值式函数)。这就是二者可被调用情况不同的原因。 - 例子
- 声明式函数
js
console.log(x); // 输出:undefined
var x = 5;
console.log(x); // 输出:5
重名问题
JavaScript遵循"就近原则 ",即最后声明的变量、函数或类会覆盖之前声明的同名变量、函数或类。
如果有两个或更多的函数具有相同的名称,那么在调用该函数时,将调用最后定义的函数。
总结&补充
- 函数可以访问外部变量。但它只能从内到外起作用。函数外部的代码看不到函数内的局部变量
- 函数可以返回值。如果没有返回值,则其返回的结果是
undefined
- 为了使代码简洁易懂,建议在函数中主要使用局部变量和参数,而不是外部变量。
- 代码中出现函数名,后面加括号表示调用 该函数,不加括号表示函数源码
函数表达式
语法
在前面章节使用的语法称为函数声明:
js
function sayHi() {
alert( "Hello" );
}
另一种创建函数的语法称为函数表达式,通常写成这样:
js
let sayHi = function() {
alert( "Hello" );
};
在这里,一个函数表示一个"行为"的值 ,函数被创建并像其他赋值一样,被明确地分配给了一个变量。不管函数是被怎样定义的,都只是一个存储在变量 sayHi
中的值。
上面这两段示例代码的意思是一样的:"创建一个函数,并把它存进变量 sayHi
"
打印函数
还可以用 alert 打印这个变量值:
alert( sayHi ); // 显示函数代码
将会看到这样的输出
js
function sayHi() {
alert("Hello");
}
注意:最后一行代码并不会运行函数,因为 sayHi
后没有括号。在其他编程语言中,只要提到函数的名称都会导致函数的调用执行,但 JavaScript 不是这样。
在 JavaScript 中,函数是一个值,所以可以把它当成值对待。上面代码显示了一段字符串值,即函数的源码。
在某种意义上,一个函数是一个特殊值,我们既可以像 sayHi()
这样调用它,也可以像使用其他类型的值一样使用它。
复制函数
还可以复制函数到其他变量:
js
function sayHi() { // (1) 创建
//或者使用let sayHi = function(){...};
alert( "Hello" );
}
let func = sayHi; // (2) 复制
func(); // Hello // (3) 运行复制的值(正常运行)!
sayHi(); // Hello // 这里也能运行
解释一下上段代码发生的细节:
- (1) 行声明创建了函数,并把它放入到变量 sayHi 。
- (2) 行将 sayHi 复制到了变量 func 。请注意: sayHi 后面没有括号 。如果有括号 , func = sayHi() 会把 sayHi() 的调用结果写进 func ,而不是 sayHi 函数本身。
- 现在函数可以通过 sayHi() 和 func() 两种方式进行调用。
- 为什么函数表达式结尾有一个分号
;
,而函数声明没有:
js
function sayHi() {
// ...
}
let sayHi = function() {
// ...
};
因为
- 函数声明是属于一个语法结构的代码块,在代码块的结尾不需要加分号 ; ,像 if { ... } , for { } , function f { } 等语法结构后面都不用加
- 函数表达式是一个赋值语句而不是代码块 ,是在语句内部的:
let sayHi = ...;
,作为一个值。不管值是什么,都建议在语句末尾添加分号 ; 。所以这里的分号与函数表达式本身没有任何关系,它只是用于终止语句
回调函数
写一个包含三个参数的函数 ask(question, yes, no)
:
question
关于问题的文本
yes
当回答为 "Yes" 时,要运行的脚本
no
当回答为 "No" 时,要运行的脚本
函数需要提出 question (问题)
,并根据用户的回答,调用 yes()
或 no()
:
js
function ask(question, yes, no) {
if (confirm(question))
yes()
else no();
}
function showOk() {
alert( "You agreed." );
//当用户点击"确定"时,它会弹出一个对话框显示"You agreed."
}
function showCancel() {
alert( "You canceled the execution." );
}
// 用法:函数 showOk 和 showCancel 被作为参数传入到 ask
ask("Do you agree?", showOk, showCancel);
//调用ask函数,传入一个询问问题字符串"Do you agree?",以及两个回调函数showOk和showCancel。
ask 的两个参数值 showOk 和 showCancel 可以被称为 回调函数 或简称 回调 主要思想是我们传递一个函数,并期望在稍后必要时将其"回调"。在这个例子中, showOk 是回答 "yes" 的回调, showCancel 是回答 "no" 的回调 可以用函数表达式对同样的函数进行大幅简写:
js
function ask(question, yes, no) {
if (confirm(question))
yes()
else no();
}
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled the execution."); }
)
这里直接在 ask(...)
调用内进行函数声明。这两个函数没有名字,所以叫 匿名函数。这样的函数在 ask 外无法访问(因为没有对它们分配变量)
函数表达式和函数声明的区别
函数表达式:
- 在 一个表达式中或另一个语法结构中创建的函数
- 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用
函数声明:
- 在主代码流中声明为单独的语句的函数
- 在函数声明被定义前后,它都可以被调用
- 严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见
js
let age = 16; // 拿 16 作为例子
if (age < 18) {
welcome(); // \ (运行)
// |
function welcome() { // |
alert("Hello!"); // | 函数声明在声明它的代码块内任意位置都可用
} // |
// |
welcome(); // / (运行)
} else {
function welcome() {
alert("Greetings!");
}
}
// 在这里,我们在花括号外部调用函数,我们看不到它们内部的函数声明。
welcome(); // 结果:Error: welcome is not defined
如何使 welcome 在 if 外可见?
使用函数表达式,首先声明 了一个名为welcome的变量 (未定义)。然后,根据用户的年龄,我们为这个变量分配了一个函数 。最后,调用这个函数,不会再出现"welcome is not defined"的错误了。
js
let age = prompt("What is your age?", 18);
let welcome;//声明了一个名为welcome的变量
if (age < 18) {
welcome = function() { //分配一个函数
alert("Hello!");
};
} else {
welcome = function() {
alert("Greetings!");
};
}
welcome(); // 现在可以了
或者使用问号运算符 ?
来进一步对代码简化:
js
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
function() { alert("Hello!"); } :
function() { alert("Greetings!"); };
welcome(); // 现在可以了
如何选择函数声明与函数表达式?
首先考虑函数声明语法。它能够为组织代码提供更多的灵活 性。因为我们可以在声明这些函数之前调用这些函数。
...但是,如果由于某种原因而导致函数声明不能实现目的(例如上面的例子),那么应该使用函数表达式
箭头函数,基础知识
语法
let func = (arg1, arg2, ...argN) => expression
含义:创建了一个函数 func ,它接受参数 arg1..argN ,然后使用参数对右侧的expression 求值并返回其结果
例如:
js
let sum = (a, b) => a + b;
/* 这个箭头函数是下面这个函数的更短的版本:
let sum = function(a, b) {
return a + b;
};
*/
alert( sum(1, 2) ); // 3
- 如果只有一个参数,还可以省略掉参数外的圆括号
let double = n => n * 2;
- 如果没有参数,括号将是空的(但括号应该保留)
let sayHi = () => alert("Hello!");
- 利用箭头函数动态创建一个函数:
js
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
() => alert('Hello') :
() => alert("Greetings!");
welcome();
多行的箭头函数
当需要用到多行的表达式或语句时,要用花括号{}
括起来。如果需要函数返回一个值,那么必须使用一个普通的 return 将需要返回的值进行返回。 例如:
js
let sum = (a, b) => { // 花括号表示开始一个多行函数
let result = a + b;
return result; // 如果我们使用了花括号,那么我们需要一个显式的 "return"进行返回
};
alert( sum(1, 2) ); // 3