Javascript 基础知识
- Javascript 编程语言
- Javascript 基础知识
- Javascript 更多引用类型
- Javascript 函数进阶知识
- Javascript 引用类型进阶知识
- Javascript 面向对象
- Javascript 错误处理
- Javascript Promise与async
- Javascript 生成器 Generator
- Javascript 模块 Module
- Javascript 浏览器

JavaScript 简介
JavaScript 是一门 跨平台
、面向对象
的 脚本语言,最初被创建的目的是"使网页更生动"。
JavaScript 的作用:
- 使网页可交互(例如拥有复杂的动画,可点击的按钮,通俗的菜单等)
- 在服务端执行,例如 Node.js,可以在网页上添加更多功能,不仅仅是下载文件(例如在多台电脑之间的协同合作)
- 可以在任意搭载了 JavaScript 引擎 的设备中执行。
- 这意味着,在浏览器中,JavaScript 可以改变网页(DOM)的外观与样式。同样地,在服务器上,Node.js 中的 JavaScript 可以对浏览器上编写的代码发出的客户端请求做出响应。
浏览器中 JavaScript 的功能:
- 在网页中添加新的 HTML,修改网页已有内容和网页的样式。
- 响应用户的行为,响应鼠标的点击,指针的移动,按键的按动。
- 向远程服务器发送网络请求,下载和上传文件。
- 获取或设置 cookie,向访问者提出问题或发送消息。
- 记住客户端的数据("本地存储")。
现代的 JavaScript 是一种"安全的"编程语言:
- 网页中的 JavaScript 没有直接访问操作系统的功能,不能读、写、复制和执行硬盘上的任意文件。现代浏览器允许 JavaScript 做一些受限制的文件操作。
- 不同的标签页/窗口之间通信受限。若要进行通信,则需从一个标签页通过 JavaScript 打开的另外一个标签页。但若两个标签页打开的不是同一个网站(域名、协议或者端口任一不相同的网站),它们都不能相互通信。
- 为了用户的信息安全,JavaScript 从其他网站/域的服务器中接收数据时,需要来自远程服务器给出明确协议(在 HTTP header 中)。
手册与规范
JavaScript 的标准化组织是 ECMA------这个欧洲信息与通信系统标准化协会提供基于 Javascript 的标准化方案(ECMA 原先是欧洲计算机制造商协会的首字母缩写)。这种标准化版本的 JavaScript 被称作 ECMAScript,在所有支持该标准的应用中以相同的方式工作。公司可以使用开放标准语言来开发他们自己的 JavaScript 实现版本。ECMAScript 标准在 ECMA-262 规范中进行文档化。
ECMA-262 规范 包含了大部分深入的、详细的、规范化的关于 JavaScript 的信息。这份规范明确地定义了这门语言。
但正因其规范化,对于新手来说难以理解。所以,如果你需要关于这门语言细节最权威的信息来源,这份规范就很适合你(去阅读)。但它并不适合日常使用。
每年都会发布一个新版本的规范。最新的规范草案请见 tc39.es/ecma262/。
想了解最新最前沿的功能,包括"即将纳入规范的"(所谓的 "stage 3"),请看这里的提案 github.com/tc39/propos...
MDN(Mozilla)JavaScript 索引 是一个带有用例和其他信息的主要的手册。它是一个获取关于个别语言函数、方法等深入信息的很好的信息来源。JavaScript 参考 - JavaScript | MDN (mozilla.org)
基础知识
"script" 标签
我们几乎可以使用 <script>
标签将 JavaScript 程序插入到 HTML 文档的任何位置,当浏览器遇到 <script>
标签,代码会自动运行。
如果你有大量的 JavaScript 代码,我们可以将它放入一个单独的文件。
脚本文件可以通过 src
特性(attribute)添加到 HTML 文件中。
html
<script src="/path/to/script.js"></script>
基础代码结构
-
语句:语句是执行行为(action)的语法结构和命令。
-
分号 :代码中可编写任意数量的语句,语句之间可以使用分号进行分割。
换行符
被理解成"隐式"的分号,大多数情况下可以省略分号 ,这也被称为 自动分号插入。- 但有时存在无法确定是否需要自动插入分号的情况,这种错误很难被找到和解决。
- 建议最好不要省略分号。
-
注释:
- 单行注释以
//
开始。 - 多行注释以
/*
开始并以*/
结束。
- 单行注释以
-
变量:变量是数据的"命名存储"。
-
变量声明:
-
在 JavaScript 中声明变量,我们需要用到
let
关键字。jslet message; // 定义变量 let message = 'Hello!'; // 定义变量,并且赋值 let user = 'John', age = 25, message = 'Hello';
-
当值改变的时候,之前的数据就被从变量中删除了
-
一个变量只能声明一次,对同一个变量进行重复声明会触发 error。
-
-
变量命名
- 变量命名存在限制,必须仅包含字母、数字、符号
$
和_
,且首字符必须非数字。 - 若命名包括多个单词,通常采用驼峰式命名法 (camelCase)例如:
myVeryLongName
。
- 变量命名存在限制,必须仅包含字母、数字、符号
-
保留字 :保留字列表 这张表中的保留字无法用作变量命名,因为它们被用于编程语言本身。
-
历史遗留变量声明符 var :
var
与let
都能声明变量,但存在部分不同。var
没有块级作用域,声明的变量全局可见。var
允许重新声明var
声明的变量,可以在其声明语句前被使用,因为函数开始的时候,就会处理var
声明(脚本启动对应全局变量)。
-
变量存入:我们可以将任何类型的值存入变量。(例如,一个变量可以在前一刻是个字符串,下一刻就存储一个数字)
-
-
常量 :声明一个常数(不变)变量,可以使用
const
而非let
。
现代模式,"use strict"
长久以来,JavaScript 不断加入新的特性并兼容旧代码。但这样存在缺点,JavaScript 创造者的任何错误或不完善的决定也将永远被保留在 JavaScript 语言中。
ES5 规范增加新的语言特性并且修改一些已经存在的特性。为保证旧功能正常使用,大部分修改默认不生效。你需要"use strict"
指令来明确地激活这些特性。
'use strict'
处于脚本文件的顶部时,则整个脚本文件都将以**"现代"模式**进行工作。请确保 "use strict"
出现在脚本的最顶部,否则严格模式可能无法启用。进入了严格模式之后没有办法取消。
js
"use strict";
// ... 代码以现代模式工作
现代 JavaScript 支持 "class" 和 "module" ------ 高级语言结构(本教程后续章节会讲到),它们会自动启用 use strict
。因此,如果我们使用它们,则无需添加 "use strict"
指令。
当代码全都写在了 class 和 module 中时,则可以将 "use strict";
这行代码省略掉。
交互
JavaScript 提供了三种基本的浏览器交互方法,用于与用户进行简单的信息交换。这些方法会暂停脚本执行,直到用户做出响应。
使用这些方法会在浏览器显示一个模态窗口,这会阻塞 JavaScript 执行直到对话框关闭,用户必须处理该窗口后才能继续与页面交互,
alert 方法
alert
方法显示一个包含指定消息的模态对话框。仅用于信息展示,不获取用户输入。
javascript
alert("操作成功完成!");
prompt 方法
prompt
方法显示一个对话框,提示用户输入信息。用户输入始终以字符串形式返回,即使输入的是数字。如果用户取消则返回 null
。
javascript
let age = prompt('请输入您的年龄', '18');
if (age !== null) {
alert(`您输入的年龄是: ${age}`);
} else {
alert('您取消了输入');
}
参数说明:
- 第 1 个参数:显示给用户的提示文本(必需)
- 第 2 个参数:输入框的默认值(可选)
confirm 方法
confirm
方法显示一个带有问题和两个按钮的对话框。用户点击确定返回 true
,点击取消返回 false
。
javascript
let isDelete = confirm("确定要删除此项吗?");
if (isDelete) {
// 执行删除操作
alert("项目已删除");
} else {
alert("删除操作已取消");
}
条件分支与逻辑
-
if...else 语句
jsif (condition) { // condition 结果为 true 时执行 } else if (anotherCondition) { // condition 为假且 anotherCondition 为真时执行 } else { // 所有条件都为假时执行 }
-
三元运算符 (?:)
jsconst result = condition ? valueIfTrue : valueIfFalse;
运算符 | 名称 | 说明 | ||
---|---|---|---|---|
` | ` | 逻辑或 | 返回第一个真值或最后一个值 | |
&& |
逻辑与 | 返回第一个假值或最后一个值 | ||
! |
逻辑非 | 返回操作数的相反布尔值 | ||
?? |
空值合并 | 返回第一个已定义(非null/undefined)的值 |
空值合并运算符 (??
) 是 ES2020 (ES11) 引入的一个重要特性,它提供了一种简洁的方式来处理 null
或 undefined
的默认值情况。
出于安全原因,JavaScript 禁止将 ??
运算符与 &&
和 ||
运算符一起使用,除非使用括号明确指定了优先级。
js
result = a ?? b
//类似
result = (a !== null && a !== undefined) ? a : b;
区别示例:
javascript
const count = 0;
console.log(count || 10); // 10 (0被视为假值)
console.log(count ?? 10); // 0 (0是已定义的值)
循环与选择
基本循环类型
-
while循环:
javascriptwhile (condition) { // 循环体 }
-
do...while循环:
javascriptdo { // 循环体(至少执行一次) } while (condition);
-
for循环:
javascriptfor (initialization; condition; step) { // 循环体 }
迭代循环
-
for...in循环(对象的所有键遍历)
javascriptlet user = { name: "John", age: 30, isAdmin: true }; for (let key in user) { alert( key ); // name, age, isAdmin alert( user[key] ); // John, 30, true }
-
for...of循环(可迭代对象遍历):不能获取当前元素的索引,只是获取元素值。
javascriptlet fruits = ["Apple", "Orange", "Plum"]; for (let fruit of fruits) { alert( fruit ); // Apple, Orange, Plum }
循环控制语句
-
break:这个指令强制停掉整个循环。
javascriptwhile (true) { if (condition) break; // 立即退出循环 }
-
continue:这个指令不会停掉整个循环,而是停止当前这一次迭代,并强制启动新一轮循环。
javascriptfor (let i = 0; i < 10; i++) { if (i % 2 === 0) continue; // 跳过本次迭代 console.log(i); // 只输出奇数 }
禁止
break/continue
在 '?' 的右边 :请注意非表达式的语法结构不能与三元运算符?
一起使用。特别是break/continue
这样的指令是不允许这样使用的。
标签语句
标签是在循环之前带有冒号的标识符。
有时候需要一次从多层嵌套的循环中跳出来,可以使用标签与break
配合完成这个过程。
注意,避免过度使用以免降低代码可读性。
javascript
outerLoop: for (let i = 0; i < 3; i++) {
innerLoop: for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) break outerLoop;
}
}
标签并不允许"跳到"代码的任意位置
js
break label; // 跳转至下面的 label 处(无效)
label: for (...)
break
指令与标签的配合必须在被标签标记的代码块内。
js
label: {
break label; // 有效
}
switch 语句
switch
语句为多分支选择的情况提供了一个更具描述性的方式,可以替代多个 if
判断。
特性:
switch
使用严格相等比较 (===
) 来匹配 case 值,不会进行类型转换。- 如果没有
break
语句,控制流会继续执行下一个 case 的代码,利用该特性可以实现多 case 合并。 default
分支是可选的,当没有 case 匹配时会执行。
用法:
-
基本语法
jsswitch (expression) { case value1: // 当 expression === value1 时执行的代码 break; case value2: // 当 expression === value2 时执行的代码 break; // 可以有任意数量的 case 语句 default: // 当没有匹配的 case 时执行的代码 }
-
多case合并
jslet fruit = 'orange'; switch (fruit) { case 'apple': case 'pear': case 'banana': console.log('这是常见水果'); break; case 'dragonfruit': console.log('这是异域水果'); break; default: console.log('未知水果'); }
基础运算符
- 运算元 : 运算符应用的对象。 比如说乘法运算
5 * 2
,有两个运算元:左运算元5
和右运算元2
。有时候人们也称其为"参数"而不是"运算元"。 - 一元运算符 :一个运算符对应的只有一个运算元。 比如说一元负号运算符(unary negation)
-
,它的作用是对数字进行正负转换 - 二元运算符:一个运算符拥有两个运算元。 减号还存在二元运算符形式。
JavaScript支持以下数学运算 :运算符优先级 - JavaScript | MDN (mozilla.org)
函数基础
函数是 JavaScript 程序的主要构建模块,它允许我们将代码封装为可重复使用的单元。
使用 function
关键字创建函数,括号内传入参数,之后用函数名加括号
的形式调用函数。
js
function showMessage(name) {
alert('hello, ' + name);
}
showMessage('world');
数据类型
在 JavaScript 中有 8 种基本的数据类型。
7 种原始类型 :string
,number
,bigint
,boolean
,symbol
,null
和 undefined
。
1 种引用类型 :Object
其中 Symbol 和 BigInt 是ES6 中新增的数据类型。
两种类型的区别在于存储位置的不同:
- 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
当作对象的原始类型
**对象包装器(Object Wrapper)**是指当对原始值调用方法或访问属性时,JavaScript 自动创建的临时对象。这些包装器使得原始值能够像对象一样使用方法和属性。
JavaScript 有 5 种对象包装器: String
、Number
、Boolean
、Symbol
、BigInt
。
当在原始值上访问属性或调用方法时,JavaScript 会执行以下步骤:
- 创建一个包含原始类型值的对应类型包装器对象,
- 在该对象上调用方法或访问属性,返回结果,
- 销毁该临时对象,只留下原始值。
JavaScript 引擎高度优化了这个过程。它甚至可能跳过创建额外的对象。但是它仍然必须遵守规范,并且表现得好像它创建了一样。
构造器仅供内部使用
在 JavaScript 中,由于历史原因,可以使用类似 new Number(1)
的语法,明确地为原始类型创建"对象包装器"。但由于容易出现问题,所以极其不推荐。
另一方面,调用不带 new
(关键字)的 String/Number/Boolean
函数是可以的且有效的。它们将值转换为相应的类型:转成字符串、数字或布尔值(原始类型),如:let num = Number("123");
。
原始类型
number 类型
Number 是 JavaScript 中用于表示数字的基本数据类型,包括整数和浮点数。JavaScript 以 64
位的 IEEE 754
格式表示所有数字,也被称为"双精度浮点数"。
数值范围如下,若超出范围会造成精度问题 :
- 最大安全整数:
Number.MAX_SAFE_INTEGER
(2^53^ - 1) - 最小安全整数:
Number.MIN_SAFE_INTEGER
-(2^53^ - 1) - 最大值:
Number.MAX_VALUE
(约 1.79E+308) - 最小值:
Number.MIN_VALUE
(约 5E-324)
数值表示方法
-
可以使用下划线 _ 作为分隔符,使得数字具有更强的可读性:
let billion = 1_000_000_000;
-
通过在数字后面附加字母
e
并指定零的个数来缩短数字:let billion = 1e9;
-
十六进制(
0x
),二进制(0b
)和八进制(0o
)数字:jsalert( 0xff ); // 十六进制形式的 255 let a = 0b11111111; // 二进制形式的 255 let b = 0o377; // 八进制形式的 255
特殊数值
除了常规的数字,还包括特殊数值:Infinity
、-Infinity
和 NaN
。
Infinity
代表无穷大 ∞,比任何数字都大的特殊值,可通过除以 0 得到。NaN
代表一个计算错误,由一个不正确数学操作所得到的结果,比如运算中包含字符串。任何对 NaN 的进一步数学运算都会返回 NaN。
Number 包装器对象常用方法
实例方法(通过数字实例调用)
方法 | 描述 | 示例 |
---|---|---|
num.toFixed(digits) |
返回指定小数位数的字符串表示 | (123.456).toFixed(2) 返回值:123.46 |
num.toExponential(fractionDigits) |
返回科学计数法表示的字符串 | (12345).toExponential(2) 返回值:1.23e+4 |
num.toPrecision(precision) |
返回指定精度的字符串表示 | (123.456).toPrecision(4) 返回值:123.5 |
num.toString(radix) |
转换为指定基数的字符串 | (255).toString(16) 返回值:ff |
num.valueOf() |
返回原始数值 | new Number(123).valueOf() 返回值:123 |
静态方法(通过 Number 构造函数调用)
语法 | 描述 | 示例 |
---|---|---|
Number.isFinite(value) |
检查是否为有限数(常规数字) | Number.isFinite(123) 返回值:true |
Number.isInteger(value) |
检查是否为整数 | Number.isInteger(123.0) 返回值:true |
Number.isNaN(value) |
检查是否为 NaN | Number.isNaN(NaN) 返回值:true |
Number.isSafeInteger(value) |
检查是否为安全整数 | Number.isSafeInteger(2**53) 返回值:false |
Number.parseFloat(string) |
解析字符串为浮点数 | Number.parseFloat("123.45px") 返回值:123.45 |
Number.parseInt(string, radix) |
解析字符串为整数 | Number.parseInt("1010", 2) 返回值:10 |
Number 构造函数属性
属性 | 描述 | 值 |
---|---|---|
Number.EPSILON |
1 与大于 1 的最小浮点数之差 | ≈ 2.22e-16 |
Number.MAX_SAFE_INTEGER |
最大安全整数 | 9007199254740991 (2^53 - 1) |
Number.MIN_SAFE_INTEGER |
最小安全整数 | -9007199254740991 (-(2^53 - 1)) |
Number.MAX_VALUE |
可表示的最大正数 | ≈ 1.79e+308 |
Number.MIN_VALUE |
可表示的最小正数 | ≈ 5e-324 |
Number.NaN |
表示非数字值 | NaN |
Number.POSITIVE_INFINITY |
正无穷大 | Infinity |
Number.NEGATIVE_INFINITY |
负无穷大 | -Infinity |
Math 库
Math 是 JavaScript 内置的数学工具对象,提供了一系列数学常数和函数,用于执行常见的数学运算。
Math 常量有Math.E
、Math.LN2
、Math.LN10
、Math.LOG2E
、Math.LOG10E
、Math.PI
等,快捷调用方便运算。
同时 Math 库还支持各种数学方法:
- 基本数学运算:
Math.abs(x)
,Math.sign(x)
(符号函数)、Math.sqrt(x)
等 - 舍入方法:
Math.round(x)
(四舍五入)、Math.floor(x)
(向下取整)、Math.ceil(x)
(向上取整)、Math.trunc(x)
(去除小数) 等 - 幂与对数:
Math.pow(x, y)
、Math.exp(x)
、Math.log(x)
等 - 三角函数:
Math.sin(x)
、Math.cos(x)
、Math.tan(x)
、Math.asin(x)
等 - 随机数:
Math.random()
- 极值与求和:
Math.max(...nums)
、Math.min(...nums)
、Math.sum(...nums)
bigInt 类型
BigInt 是 JavaScript 在 ES2020 中引入的一种新的原始数据类型,用于表示任意精度的整数,可以安全地表示和操作大于 (2^53^ - 1) 的整数。
创建方法:
- 将
n
附加到整数字段的末尾来创建,如:const bigInt = 123n;
- BigInt() 构造函数:
const bigInt = BigInt("123");
注意:
- 只能表示整数,不能表示小数。
- 不会像 Number 那样自动转换为浮点数。
- 不能直接与 Number 类型进行数学运算。
- 默认不支持 JSON 序列化。
- 性能相对 Number 类型较慢。
string 类型
String(字符串)是 JavaScript 中用于表示文本数据的基本数据类型,用于存储和操作字符序列。
所有的字符串都使用 UTF-16 编码。即:每个字符都有对应的数字代码。有特殊的方法可以获取代码表示的字符,以及字符对应的代码。
字符串一旦创建就不能修改,所有操作都返回新字符串。
创建方法
-
字面量表示法
-
JavaScript 中的字符串必须被括在引号里,可用
双引号
、单引号
、反引号
。双引号 和单引号都是"简单"引用,在 JavaScript 中两者几乎没有什么差别。
反引号 是 功能扩展 引号。它们允许我们通过将变量和表达式包装在
${...}
中,来将它们嵌入到字符串中。jslet name = "John"; // 嵌入一个变量 alert( `Hello, ${name}!` ); // Hello, John! // 嵌入一个表达式 alert( `the result is${1 + 2}` ); // the result is 3
-
-
String 构造函数
jsconst strPrim = String("World");
特殊字符
所有的特殊字符都以反斜杠字符 \
开始。它也被称为"转义字符"。
字符 | 描述 |
---|---|
\n |
换行 |
\r |
在 Windows 文本文件中,两个字符 \r\n 的组合代表一个换行。而在非 Windows 操作系统上,它就是 \n 。这是历史原因造成的,大多数的 Windows 软件也理解 \n 。 |
\' , \" |
引号 |
\\ |
反斜线 |
\t |
制表符 |
\b , \f , \v |
退格,换页,垂直标签 ------ 为了兼容性,现在已经不使用了。 |
\xXX |
具有给定十六进制 Unicode XX 的 Unicode 字符,例如:'\x7A' 和 'z' 相同。 |
\uXXXX |
以 UTF-16 编码的十六进制代码 XXXX 的 Unicode 字符,例如 \u00A9 ------ 是版权符号 © 的 Unicode。它必须正好是 4 个十六进制数字。 |
\u{X...XXXXXX} (1 到 6 个十六进制字符) |
具有给定 UTF-32 编码的 Unicode 符号。一些罕见的字符用两个 Unicode 符号编码,占用 4 个字节。这样我们就可以插入长代码了。 |
字符串长度
length
属性表示字符串长度
length
是一个属性 。掌握其他编程语言的人,有时会错误地调用 str.length()
而不是 str.length
。
访问字符
第一个字符从0
位置开始。
- 使用方括号
[pos]
- 调用
str.charAt(pos)
方法 str.codePointAt(pos)
:返回在pos
位置的字符代码charCodeAt(index)
:返回字符的 UTF-16 编码
方括号是获取字符的一种现代化方法,而 charAt
是历史原因才存在的。
它们之间的唯一区别是,如果没有找到字符,[]
返回 undefined
,而 charAt
返回一个空字符串
String 包装器常用方法
查询方法
方法 | 描述 | 示例 |
---|---|---|
includes(searchStr) |
是否包含子串 | "hello".includes("ell") 结果:true |
indexOf(searchStr) |
返回子串首次出现的位置 | "hello".indexOf("l") 结果:2 |
lastIndexOf(searchStr) |
返回子串最后出现的位置 | "hello".lastIndexOf("l") 结果:3 |
startsWith(searchStr) |
是否以子串开头 | "hello".startsWith("he") 结果:true |
endsWith(searchStr) |
是否以子串结尾 | "hello".endsWith("lo") 结果:true |
按位(bitwise)NOT 技巧 :
~n
等于-(n+1)
。在
if
测试中indexOf
有一点不方便,如果找不到位置返回的数是-1,所以无法直接放进去if()中。
jslet str = "Widget with id"; if (str.indexOf("Widget")) { alert("We found it"); // 不工作! } if ((str.indexOf("Widget")) != -1) { alert("We found it"); //工作 } if (~str.indexOf("Widget")) { alert( 'Found it!' ); // 正常运行,且缩短代码 }
修改方法(返回新字符串)
方法 | 描述 | 示例 |
---|---|---|
concat(...strings) |
连接字符串 | "a".concat("b","c") 结果:"abc" |
padStart(targetLength, padString) |
前补全 | "5".padStart(3,"0") 结果:"005" |
padEnd(targetLength, padString) |
后补全 | "5".padEnd(3,"0") 结果:"500" |
repeat(count) |
重复字符串 | "ab".repeat(2) 结果:"abab" |
replace(searchValue, replaceValue) |
替换子串 | "hello".replace("l","x") 结果:"hexlo" |
replaceAll(searchValue, replaceValue) |
替换所有子串 | "hello".replaceAll("l","x") 结果:"hexxo" |
slice(start, end) |
提取子串 | "hello".slice(1,4) 结果:"ell" |
substring(start, end) |
提取子串 | "hello".substring(1,4) 结果:"ell" |
substr(start, length) |
提取子串 | "hello".substr(1,3) 结果:"ell" |
toLowerCase() |
转小写 | "HELLO".toLowerCase() 结果:"hello" |
toUpperCase() |
转大写 | "hello".toUpperCase() 结果:"HELLO" |
trim() |
去除两端空白 | " hello ".trim() 结果:"hello" |
trimStart() |
去除开头空白 | " hello ".trimStart() 结果:"hello " |
trimEnd() |
去除结尾空白 | " hello ".trimEnd() 结果:" hello" |
转换和分割方法
方法 | 描述 | 示例 |
---|---|---|
split(separator) |
分割为数组 | "a,b,c".split(",") 结果:["a","b","c"] |
toString() |
返回字符串值 | new String("hi").toString() 结果:"hi" |
valueOf() |
返回原始值 | new String("hi").valueOf() 结果:"hi" |
normalize(form) |
Unicode 规范化 | "é".normalize("NFC") 结果:"é" |
boolean 类型
boolean 类型仅包含两个值:true
和 false
。
null 值
特殊的 null
值不属于上述任何一种类型。
它构成了一个独立的类型,只包含 null
值。
undefined 值
特殊值 undefined
和 null
一样自成类型。
undefined
的含义是 未被赋值
。
如果一个变量已被声明,但未被赋值,那么它的值就是 undefined
从技术上讲,可以显式地将 undefined
赋值给变量,但是不建议这样做。
symbol 类型
symbol
类型用于创建对象的唯一标识符。这里提到 symbol
类型是为了完整性,要在学完 object
类型后再学习它。
引用类型
其他所有的数据类型都被称为"原始类型",因为它们的值只包含一个单独的内容(字符串、数字或者其他)。object
则用于储存数据集合和更复杂的实体。
Object
在 JavaScript 中,对象几乎渗透到了这门编程语言的方方面面。
创建对象的方式
-
对象字面量(最常用)
jslet user = { name: "John", // 键 "name",值 "John" age: 30, // 键 "age",值 30 "likes birds": true, // 多词属性名必须加引号 };
-
Object 构造函数
jslet user = new Object({name: "John", age: 30,}); // "构造函数" 的语法
-
Object.create() :创建带有指定原型的对象。原型链相关部分介绍。
对象属性操作
-
访问属性
jsuser.name; // 点表示法 user["name"]; // 方括号表示法
-
添加/修改属性:访问属性时,若属性不存在,则自动创建属性并添加信息;属性存在则进行修改。
jsuser.job = 'Developer'; // 添加 user['age'] = 31; // 修改
-
删除属性
jsdelete user.age;
-
检查属性存在
- JavaScript 的对象能够被访问任何属性,读取不存在的属性只会得到
undefined
。 - 大部分情况下与
undefined
进行比较来判断即可。但存储的值是undefined
的时候,只有in
运算符的判断结果是对的。 - 但
in
会检查整一个原型链,仅检查自有属性需用到hasOwnProperty
jsuser.noSuchProperty === undefined; // true 'name' in user; // true user.hasOwnProperty('name'); // true
- JavaScript 的对象能够被访问任何属性,读取不存在的属性只会得到
属性类型
-
多词属性:
- 可以用多字词语来作为属性名,但必须给它们加上引号。
- 访问多词属性时不能用点符号,只能用方括号访问。
jslet user = { "likes birds": true, // 多词属性名必须加引号 }; user["likes birds"] = true; // 方括号访问属性进行修改
-
计算属性:当创建一个对象时,我们可以在对象字面量中使用方括号。
jslet fruit = prompt("Which fruit to buy?", "apple"); let bag = { [fruit]: 5, // 属性名是从 fruit 变量中得到的 }; alert( bag.apple ); // 5 如果 fruit="apple"
属性特性
-
属性值简写:在实际开发中,我们通常用已存在的变量当做属性名。若属性名跟变量名一样,则可以使用简写方法。
jslet name = "John", age = 30; let user = { name: name, age: age, }; // 上面的写法与下面的写法得到结果相同 let user = { name, age, };
-
属性名称不受保留字限制:属性名称可用保留字。
js// 这些属性都没问题 let obj = { for: 1, let: 2, return: 3 };
但注意:一个名为
__proto__
的属性不能被设置为一个非对象的值,后面原型链部分内容会解释。 -
对象属性排序:根据规范,整数属性会被进行排序,其他属性则按照创建的顺序显示。
jslet codes = { "4": "Germany", "2": "Switzerland", "3": "Great Britain", "1": "USA" }; for(let code in codes) { alert(code); // 1, 2, 3, 4 }
jslet user = { name: "John", surname: "Smith" }; user.age = 25; // 非整数属性是按照创建的顺序来排列的 for (let prop in user) { alert( prop ); // name, surname, age }
若不想自动按整数名称排列,给每个键名加一个加号
"+"
前缀即可。jslet codes = { "+4": "Germany", "+2": "Switzerland", "+3": "Great Britain", "+1": "USA" }; for (let code in codes) { alert( +code ); // 4, 2, 3, 1 }
对象存储
对象是通过"引用"存储和复制的。对象的变量存储的不是对象本身,而是该对象"在内存中的地址"
js
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // 通过 "admin" 引用来修改
alert(user.name); // 'Pete',修改能通过 "user" 引用看到
对象常用方法
静态方法
方法 | 描述 | 示例 |
---|---|---|
Object.assign() |
合并对象 | Object.assign({}, {a:1}, {b:2}) → {a:1, b:2} |
Object.entries() |
返回键值对数组 | Object.entries({a:1}) → [['a',1]] |
Object.keys() |
返回可枚举属性名数组 | Object.keys({a:1}) → ['a'] |
Object.values() |
返回可枚举属性值数组 | Object.values({a:1}) → [1] |
Object.freeze() |
冻结对象(不可修改) | Object.freeze(obj) |
Object.seal() |
密封对象(可修改现有属性) | Object.seal(obj) |
Object.is() |
严格比较 | Object.is(NaN, NaN) → true |
可选链 "?."
如果可选链 ?.
前面的值为 undefined
或者 null
,它会停止运算并返回 undefined
。
下面这是一种使用 ?.
的安全访问方式:
js
//如果未声明变量 user,那么 user?.anything 会触发一个错误
user?.address;// ReferenceError: user is not defined
let user = {}; // user 没有 address 属性
alert( user?.address?.street ); // undefined(不报错)
请注意:?.
语法使其前面的值成为可选值,但不会对其后面的起作用。
其他变体:
-
将
?.()
用于调用一个可能不存在的函数jslet userAdmin = { admin() {alert("I am admin");} }; let userGuest = {}; userAdmin.admin?.(); // I am admin userGuest.admin?.(); // 啥都没发生(没有这样的方法)
-
语法
?.[]
允许从一个可能不存在的对象上安全地读取属性jslet key = "firstName"; let user1 = { firstName: "John" }; let user2 = null; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined
-
将
?.
跟delete
一起使用jsdelete user?.name; // 如果 user 存在,则删除 user.name
symbol 类型
symbol
值表示唯一的标识符,是带有可选描述的"原始唯一值"。
symbol
生成的值作为属性或者方法的时候,一定要保存下来,否则后续无法使用。
symbol
总是不同的,即使它们有相同的名字,所以它们永远不会与其他属性名冲突。
基本的使用方法:
js
let name = Symbol('name'); // 描述符为 name,但值是随机且唯一的。
let obj= {
[name]: 'lnj',
}
obj[name] // lnj
symbol 的特性:
-
symbol
保证是唯一的。即使我们创建了许多具有相同描述的 symbol,它们的值也是不同:jslet id1 = Symbol("id"); let id2 = Symbol("id"); alert(id1 == id2); // false
-
symbol
不会被自动转换为字符串,这是一种防止混乱的"语言保护",因为字符串和 symbol 有本质上的不同,不应该意外地将它们转换成另一个。如果我们真的想显示一个 symbol,我们需要在它上面调用
.toString()
,如下所示:jslet id = Symbol("id"); alert(id.toString()); // Symbol(id),现在它有效了
或者获取
symbol.description
属性,只显示描述(description):jslet id = Symbol("id"); alert(id.description); // id
-
symbol 在特定情况会被忽略:这是一般"隐藏符号属性"原则的一部分,如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。
- symbol 属性不参与
for..in
循环。 Object.keys(user)
也会忽略它们。- 相反,
Object.assign
会同时复制字符串和 symbol 属性:这里并不矛盾,就是这样设计的。这里的想法是当我们克隆或者合并一个 object 时,通常希望 所有 属性被复制。
- symbol 属性不参与
-
获取 symbol 键:
Object.getOwnPropertySymbols(obj)
:它会返回一个只包含 Symbol 类型的键的数组。Reflect.ownKeys(obj)
,它会返回 所有 键。
访问器属性(getter/setter)
访问器属性由 "getter" 和 "setter" 方法表示。在对象字面量中,它们用 get
和 set
表示。
从外表看,访问器属性看起来就像一个普通属性。这就是访问器属性的设计思想。
js
let user = {
name: "John",
surname: "Smith",
// 当读取 user.fullName 时,getter 起作用
get fullName() {
return `${this.name} ${this.surname}`;
},
// 当写入 user.fullName 时,setter 起作用
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// set fullName 将以给定值执行
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
应用场景:
-
便捷访问或设置多个属性值,如上例。
-
getter/setter 可以用作"真实"属性值的包装器,以便对它们进行更多的控制。
-
例如,如果我们想禁止太短的
user
的 name,我们可以创建一个 settername
,并将值存储在一个单独的属性_name
中:jslet user = { get name() { return this._name; }, set name(value) { if (value.length < 4) { alert("Name is too short, need at least 4 characters"); return; } this._name = value; } }; user.name = "Pete"; alert(user.name); // Pete user.name = ""; // Name 太短了......
name 被存储在
_name
属性中,并通过 getter 和 setter 进行访问。从技术上讲,外部代码可以使用
user._name
直接访问 name。但是,这儿有一个众所周知的约定,即以下划线"_"
开头的属性是内部属性,不应该从对象外部进行访问。
-
-
修改单一数据使得多个数据都能同时被修改
-
我们可能会决定存储
birthday
,而不是age
,因为它更精确,更方便。但我们又想在修改birthday
后,让age
随之更新。我们可以用 getter 来简化这一流程。jslet user = { birthday : new Date(2000, 1, 1), get age() { let todayYear = new Date().getFullYear(); return todayYear - this.birthday.getFullYear(); }, }; alert( user.birthday ); // birthday 是可访问的 alert( user.age ); // ......age 也是可访问的
-
值的比较
基本比较类型
-
宽松相等比较(==):宽松比较会在比较前进行类型转换(强制类型转换),然后再比较值。
- 如果一个是 null,另一个是 undefined → true
- 如果一个是数字,另一个是字符串 → 将字符串转为数字再比较
- 如果有布尔值 → 将布尔值转为数字(true→1,false→0)再比较
- 如果一个是对象,另一个是基本类型 → 调用对象的 valueOf() 或 toString() 方法转换
jsconsole.log(5 == 5); // true console.log(5 == '5'); // true (String '5' 转换为 Number 5) console.log(true == 1); // true (Boolean true 转换为 Number 1) console.log(null == undefined); // true (特殊情况)
-
严格相等比较(===):严格比较不会进行类型转换,要求值和类型都相同才返回 true。
jsconsole.log(5 === 5); // true console.log(5 === '5'); // false (Number ≠ String) console.log(true === 1); // false (Boolean ≠ Number) console.log(null === undefined); // false
特殊值的比较
-
NaN 的比较:NaN(Not a Number)与任何值(包括自己)比较都返回 false。
jsconsole.log(NaN === NaN); // false console.log(NaN == NaN); // false
检测 NaN 的正确方法:
-
使用
isNaN(NaN)
函数 -
使用
Number.isNaN(NaN)
(ES6) -
利用 NaN 是唯一不等于自身的值:
jsfunction isNaNValue(x) {return x !== x;}
-
-
null 和 undefined 的比较:
jsnull == undefined; // true null === undefined; // false null == 0; // false (null 只等于 undefined) undefined == 0; // false
但若进行数学比较,则 null 和 undefined 会转为 0。注意,
< > <= >=
与==
是互相独立的,==
的比较范围更大,所以会出现以下结果:js// 奇怪的结果 alert( null > 0 ); // (1) false alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) true
-
+0 和 -0 的比较:在 ES6 中,+0 和 -0 是被区分开的。
jsconsole.log(+0 === -0); // true console.log(+0 == -0); // true // 区分 +0 和 -0 console.log(Object.is(+0, -0)); // false (ES6)
对象比较
对象(包括数组和函数)的比较是基于引用而非值。
javascript
let obj1 = { a: 1 };
let obj2 = { a: 1 };
let obj3 = obj1;
console.log(obj1 == obj2); // false (不同引用)
console.log(obj1 === obj2); // false
console.log(obj1 == obj3); // true (相同引用)
console.log(obj1 === obj3); // true
// 数组比较同样基于引用
console.log([] == []); // false
console.log([1,2] == [1,2]); // false
深度比较对象的方法:
javascript
function deepEqual(x, y) {
if (x === y) return true;
if (typeof x !== 'object' || x === null ||
typeof y !== 'object' || y === null) {
return false;
}
const keysX = Object.keys(x);
const keysY = Object.keys(y);
if (keysX.length !== keysY.length) return false;
for (const key of keysX) {
if (!keysY.includes(key)) return false;
if (!deepEqual(x[key], y[key])) return false;
}
return true;
}
比较运算符
除了相等比较,JavaScript 还提供关系运算符:
-
数学运算符:大于(>)、小于(<)、大于等于(>=)、小于等于(<=)
这些运算符也会进行类型转换:
javascriptconsole.log('5' > 4); // true ('5' → 5) console.log(true > false); // true (true→1, false→0) console.log('Z' > 'A'); // true (按字母顺序) console.log('2' > '12'); // true (字符串比较,'2' > '1')
-
字符串比较规则:字符串按字典顺序逐字符比较(基于 Unicode 编码)
javascriptconsole.log('a' < 'b'); // true console.log('apple' < 'banana'); // true console.log('10' < '2'); // true (比较第一个字符 '1' < '2')
ES6 新增方法:Object.is()
Object.is()
是 ES6 新增的比较方法,类似于 ===,但有以下个区别:
javascript
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false
console.log(Object.is(5, '5')); // false
类型转换
对象到原始值的转换
对象转换为原始值时,会调用 valueOf()
和 toString()
方法:
javascript
let obj = {
valueOf() { return 123; },
toString() { return "456"; }
};
// 优先调用valueOf()
Number(obj); // 123
// 优先调用toString()
String(obj); // "456"
数组的转换
javascript
String([1, 2, 3]); // "1,2,3"![
Number([]); // 0
Number([1]); // 1
Number([1, 2]); // NaN
转换为字符串规则
使用String(value)
,对值进行字符串转换。
原始值 | 转换结果 |
---|---|
undefined | "undefined" |
null | "null" |
true | "true" |
false | "false" |
数字 | 数字字符串 |
对象 | 调用toString() |
转换为数字规则
使用Number(value)
,对值进行数字转换。
原始值 | 转换结果 |
---|---|
undefined | NaN |
null | 0 |
true | 1 |
false | 0 |
字符串 | 解析数字,失败则NaN |
对象 | 先valueOf()后toString()再解析 |
转换为布尔值规则
使用Boolean(value)
,对值进行布尔值转换。
原始值 | 转换结果 |
---|---|
undefined | false |
null | false |
0, -0, NaN | false |
"" (空字符串) | false |
其他所有值 | true |