进阶学习 Javascript ? 来看看这篇系统复习笔记 [ 基础知识篇 ]

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 关键字。

        js 复制代码
        let message; // 定义变量
        let message = 'Hello!'; // 定义变量,并且赋值
        let user = 'John', age = 25, message = 'Hello';
      • 当值改变的时候,之前的数据就被从变量中删除了

      • 一个变量只能声明一次,对同一个变量进行重复声明会触发 error。

    • 变量命名

      • 变量命名存在限制,必须仅包含字母、数字、符号 $_,且首字符必须非数字。
      • 若命名包括多个单词,通常采用驼峰式命名法 (camelCase)例如:myVeryLongName
    • 保留字保留字列表 这张表中的保留字无法用作变量命名,因为它们被用于编程语言本身。

    • 历史遗留变量声明符 varvarlet 都能声明变量,但存在部分不同。

      • 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("删除操作已取消");
}

条件分支与逻辑

  1. if...else 语句

    js 复制代码
    if (condition) {
        // condition 结果为 true 时执行
    } else if (anotherCondition) {
        // condition 为假且 anotherCondition 为真时执行
    } else {
        // 所有条件都为假时执行
    }
  2. 三元运算符 (?:)

    js 复制代码
    const result = condition ? valueIfTrue : valueIfFalse;
运算符 名称 说明
` ` 逻辑或 返回第一个真值或最后一个值
&& 逻辑与 返回第一个假值或最后一个值
! 逻辑非 返回操作数的相反布尔值
?? 空值合并 返回第一个已定义(非null/undefined)的值

空值合并运算符 (??) 是 ES2020 (ES11) 引入的一个重要特性,它提供了一种简洁的方式来处理 nullundefined 的默认值情况。

出于安全原因,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是已定义的值)

循环与选择

基本循环类型
  1. while循环

    javascript 复制代码
    while (condition) {
        // 循环体
    }
  2. do...while循环

    javascript 复制代码
    do {
        // 循环体(至少执行一次)
    } while (condition);
  3. for循环

    javascript 复制代码
    for (initialization; condition; step) {
        // 循环体
    }
迭代循环
  1. for...in循环(对象的所有键遍历)

    javascript 复制代码
    let user = {
      name: "John",
      age: 30,
      isAdmin: true
    };
    for (let key in user) {
      alert( key );  // name, age, isAdmin
      alert( user[key] ); // John, 30, true
    }
  2. for...of循环(可迭代对象遍历):不能获取当前元素的索引,只是获取元素值。

    javascript 复制代码
    let fruits = ["Apple", "Orange", "Plum"];
    for (let fruit of fruits) {
      alert( fruit ); // Apple, Orange, Plum
    }
循环控制语句
  1. break:这个指令强制停掉整个循环。

    javascript 复制代码
    while (true) {
        if (condition) break; // 立即退出循环
    }
  2. continue:这个指令不会停掉整个循环,而是停止当前这一次迭代,并强制启动新一轮循环。

    javascript 复制代码
    for (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 匹配时会执行。

用法

  1. 基本语法

    js 复制代码
    switch (expression) {
        case value1:
            // 当 expression === value1 时执行的代码
            break;
        case value2:
            // 当 expression === value2 时执行的代码
            break;
        // 可以有任意数量的 case 语句
        default:
            // 当没有匹配的 case 时执行的代码
    }
  2. 多case合并

    js 复制代码
    let 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 种原始类型stringnumberbigintbooleansymbolnullundefined

1 种引用类型Object

其中 Symbol 和 BigInt 是ES6 中新增的数据类型。

两种类型的区别在于存储位置的不同

  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

当作对象的原始类型

**对象包装器(Object Wrapper)**是指当对原始值调用方法或访问属性时,JavaScript 自动创建的临时对象。这些包装器使得原始值能够像对象一样使用方法和属性。

JavaScript 有 5 种对象包装器: StringNumberBooleanSymbolBigInt

当在原始值上访问属性或调用方法时,JavaScript 会执行以下步骤:

  1. 创建一个包含原始类型值的对应类型包装器对象,
  2. 在该对象上调用方法或访问属性,返回结果,
  3. 销毁该临时对象,只留下原始值。

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)
数值表示方法
  1. 可以使用下划线 _ 作为分隔符,使得数字具有更强的可读性:let billion = 1_000_000_000;

  2. 通过在数字后面附加字母e并指定零的个数来缩短数字:let billion = 1e9;

  3. 十六进制(0x),二进制(0b)和八进制(0o)数字:

    js 复制代码
    alert( 0xff ); // 十六进制形式的 255
    let a = 0b11111111; // 二进制形式的 255
    let b = 0o377; // 八进制形式的 255
特殊数值

除了常规的数字,还包括特殊数值:Infinity-InfinityNaN

  • 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.EMath.LN2Math.LN10Math.LOG2EMath.LOG10EMath.PI等,快捷调用方便运算。

同时 Math 库还支持各种数学方法:

  1. 基本数学运算:Math.abs(x)Math.sign(x)(符号函数)、Math.sqrt(x)
  2. 舍入方法:Math.round(x)(四舍五入)、Math.floor(x)(向下取整)、Math.ceil(x)(向上取整)、Math.trunc(x)(去除小数) 等
  3. 幂与对数:Math.pow(x, y)Math.exp(x)Math.log(x)
  4. 三角函数:Math.sin(x)Math.cos(x)Math.tan(x)Math.asin(x)
  5. 随机数:Math.random()
  6. 极值与求和:Math.max(...nums)Math.min(...nums)Math.sum(...nums)
bigInt 类型

BigInt 是 JavaScript 在 ES2020 中引入的一种新的原始数据类型,用于表示任意精度的整数,可以安全地表示和操作大于 (2^53^ - 1) 的整数。

创建方法

  1. n 附加到整数字段的末尾来创建,如:const bigInt = 123n;
  2. BigInt() 构造函数:const bigInt = BigInt("123");

注意

  1. 只能表示整数,不能表示小数。
  2. 不会像 Number 那样自动转换为浮点数。
  3. 不能直接与 Number 类型进行数学运算。
  4. 默认不支持 JSON 序列化。
  5. 性能相对 Number 类型较慢。
string 类型

String(字符串)是 JavaScript 中用于表示文本数据的基本数据类型,用于存储和操作字符序列。

所有的字符串都使用 UTF-16 编码。即:每个字符都有对应的数字代码。有特殊的方法可以获取代码表示的字符,以及字符对应的代码。

字符串一旦创建就不能修改,所有操作都返回新字符串。

创建方法
  1. 字面量表示法

    • JavaScript 中的字符串必须被括在引号里,可用 双引号单引号反引号

      双引号单引号都是"简单"引用,在 JavaScript 中两者几乎没有什么差别。

      反引号功能扩展 引号。它们允许我们通过将变量和表达式包装在 ${...} 中,来将它们嵌入到字符串中。

      js 复制代码
      let name = "John";
      // 嵌入一个变量
      alert( `Hello, ${name}!` ); // Hello, John!
      // 嵌入一个表达式
      alert( `the result is${1 + 2}` ); // the result is 3
  2. String 构造函数

    js 复制代码
    const 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位置开始。

  1. 使用方括号 [pos]
  2. 调用 str.charAt(pos) 方法
  3. str.codePointAt(pos):返回在 pos 位置的字符代码
  4. 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()中。

js 复制代码
let 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 类型仅包含两个值:truefalse

null 值

特殊的 null 值不属于上述任何一种类型。

它构成了一个独立的类型,只包含 null 值。

undefined 值

特殊值 undefinednull 一样自成类型。

undefined 的含义是 未被赋值

如果一个变量已被声明,但未被赋值,那么它的值就是 undefined

从技术上讲,可以显式地将 undefined 赋值给变量,但是不建议这样做。

symbol 类型

symbol 类型用于创建对象的唯一标识符。这里提到 symbol 类型是为了完整性,要在学完 object 类型后再学习它。

引用类型

其他所有的数据类型都被称为"原始类型",因为它们的值只包含一个单独的内容(字符串、数字或者其他)。object 则用于储存数据集合和更复杂的实体。

Object

在 JavaScript 中,对象几乎渗透到了这门编程语言的方方面面。

创建对象的方式
  1. 对象字面量(最常用)

    js 复制代码
    let user = {
      name: "John",         // 键 "name",值 "John"
      age: 30,              // 键 "age",值 30
      "likes birds": true,  // 多词属性名必须加引号
    };
  2. Object 构造函数

    js 复制代码
    let user = new Object({name: "John", age: 30,}); // "构造函数" 的语法
  3. Object.create() :创建带有指定原型的对象。原型链相关部分介绍。

对象属性操作
  1. 访问属性

    js 复制代码
    user.name;    // 点表示法
    user["name"]; // 方括号表示法
  2. 添加/修改属性:访问属性时,若属性不存在,则自动创建属性并添加信息;属性存在则进行修改。

    js 复制代码
    user.job = 'Developer'; // 添加
    user['age'] = 31;       // 修改
  3. 删除属性

    js 复制代码
    delete user.age;
  4. 检查属性存在

    • JavaScript 的对象能够被访问任何属性,读取不存在的属性只会得到 undefined
    • 大部分情况下与 undefined 进行比较来判断即可。但存储的值是 undefined 的时候,只有 in 运算符的判断结果是对的。
    • in 会检查整一个原型链,仅检查自有属性需用到 hasOwnProperty
    js 复制代码
    user.noSuchProperty === undefined; // true
    'name' in user; // true
    user.hasOwnProperty('name'); // true
属性类型
  1. 多词属性

    • 可以用多字词语来作为属性名,但必须给它们加上引号。
    • 访问多词属性时不能用点符号,只能用方括号访问。
    js 复制代码
    let user = {
      "likes birds": true,  // 多词属性名必须加引号
    };
    user["likes birds"] = true; // 方括号访问属性进行修改
  2. 计算属性:当创建一个对象时,我们可以在对象字面量中使用方括号。

    js 复制代码
    let fruit = prompt("Which fruit to buy?", "apple");
    let bag = {
      [fruit]: 5, // 属性名是从 fruit 变量中得到的
    };
    alert( bag.apple ); // 5 如果 fruit="apple"
属性特性
  1. 属性值简写:在实际开发中,我们通常用已存在的变量当做属性名。若属性名跟变量名一样,则可以使用简写方法。

    js 复制代码
    let name = "John", age = 30;
    let user = {
     name: name,
     age: age,
    };
    // 上面的写法与下面的写法得到结果相同
    let user = {
     name,
     age,
    };
  2. 属性名称不受保留字限制:属性名称可用保留字。

    js 复制代码
    // 这些属性都没问题
    let obj = {
      for: 1,
      let: 2,
      return: 3
    };

    但注意:一个名为 __proto__ 的属性不能被设置为一个非对象的值,后面原型链部分内容会解释。

  3. 对象属性排序:根据规范,整数属性会被进行排序,其他属性则按照创建的顺序显示。

    js 复制代码
    let codes = {
      "4": "Germany",
      "2": "Switzerland",
      "3": "Great Britain",
      "1": "USA"
    };
    for(let code in codes) {
      alert(code); // 1, 2, 3, 4
    }
    js 复制代码
    let user = {
      name: "John",
      surname: "Smith"
    };
    user.age = 25;
    // 非整数属性是按照创建的顺序来排列的
    for (let prop in user) {
      alert( prop ); // name, surname, age
    }

    若不想自动按整数名称排列,给每个键名加一个加号 "+" 前缀即可。

    js 复制代码
    let 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(不报错)

请注意:?. 语法使其前面的值成为可选值,但不会对其后面的起作用。

其他变体

  1. ?.() 用于调用一个可能不存在的函数

    js 复制代码
    let userAdmin = {
      admin() {alert("I am admin");}
    };
    let userGuest = {};
    
    userAdmin.admin?.(); // I am admin
    userGuest.admin?.(); // 啥都没发生(没有这样的方法)
  2. 语法 ?.[]允许从一个可能不存在的对象上安全地读取属性

    js 复制代码
    let key = "firstName";
    let user1 = {
      firstName: "John"
    };
    let user2 = null;
    
    alert( user1?.[key] ); // John
    alert( user2?.[key] ); // undefined
  3. ?.delete 一起使用

    js 复制代码
    delete user?.name; // 如果 user 存在,则删除 user.name
symbol 类型

symbol 值表示唯一的标识符,是带有可选描述的"原始唯一值"。

symbol 生成的值作为属性或者方法的时候,一定要保存下来,否则后续无法使用。

symbol 总是不同的,即使它们有相同的名字,所以它们永远不会与其他属性名冲突。

基本的使用方法

js 复制代码
let name = Symbol('name'); // 描述符为 name,但值是随机且唯一的。
let obj= {
  [name]: 'lnj',
}
obj[name] // lnj

symbol 的特性

  1. symbol 保证是唯一的。即使我们创建了许多具有相同描述的 symbol,它们的值也是不同:

    js 复制代码
    let id1 = Symbol("id");
    let id2 = Symbol("id");
    alert(id1 == id2); // false
  2. symbol 不会被自动转换为字符串,这是一种防止混乱的"语言保护",因为字符串和 symbol 有本质上的不同,不应该意外地将它们转换成另一个。

    如果我们真的想显示一个 symbol,我们需要在它上面调用 .toString(),如下所示:

    js 复制代码
    let id = Symbol("id");
    alert(id.toString()); // Symbol(id),现在它有效了

    或者获取 symbol.description 属性,只显示描述(description):

    js 复制代码
    let id = Symbol("id");
    alert(id.description); // id
  3. symbol 在特定情况会被忽略:这是一般"隐藏符号属性"原则的一部分,如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。

    1. symbol 属性不参与 for..in 循环。
    2. Object.keys(user) 也会忽略它们。
    3. 相反,Object.assign 会同时复制字符串和 symbol 属性:这里并不矛盾,就是这样设计的。这里的想法是当我们克隆或者合并一个 object 时,通常希望 所有 属性被复制。
  4. 获取 symbol 键:

    1. Object.getOwnPropertySymbols(obj):它会返回一个只包含 Symbol 类型的键的数组。
    2. Reflect.ownKeys(obj),它会返回 所有 键。
访问器属性(getter/setter)

访问器属性由 "getter" 和 "setter" 方法表示。在对象字面量中,它们用 getset 表示。

从外表看,访问器属性看起来就像一个普通属性。这就是访问器属性的设计思想。

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

应用场景

  1. 便捷访问或设置多个属性值,如上例。

  2. getter/setter 可以用作"真实"属性值的包装器,以便对它们进行更多的控制。

    • 例如,如果我们想禁止太短的 user 的 name,我们可以创建一个 setter name,并将值存储在一个单独的属性 _name 中:

      js 复制代码
      let 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。但是,这儿有一个众所周知的约定,即以下划线 "_" 开头的属性是内部属性,不应该从对象外部进行访问。

  3. 修改单一数据使得多个数据都能同时被修改

    • 我们可能会决定存储 birthday,而不是 age,因为它更精确,更方便。但我们又想在修改birthday后,让 age 随之更新。我们可以用 getter 来简化这一流程。

      js 复制代码
      let 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 也是可访问的

值的比较

基本比较类型

  1. 宽松相等比较(==):宽松比较会在比较前进行类型转换(强制类型转换),然后再比较值。

    • 如果一个是 null,另一个是 undefined → true
    • 如果一个是数字,另一个是字符串 → 将字符串转为数字再比较
    • 如果有布尔值 → 将布尔值转为数字(true→1,false→0)再比较
    • 如果一个是对象,另一个是基本类型 → 调用对象的 valueOf() 或 toString() 方法转换
    js 复制代码
    console.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 (特殊情况)
  2. 严格相等比较(===):严格比较不会进行类型转换,要求值和类型都相同才返回 true。

    js 复制代码
    console.log(5 === 5);       // true
    console.log(5 === '5');    // false (Number ≠ String)
    console.log(true === 1);   // false (Boolean ≠ Number)
    console.log(null === undefined); // false

特殊值的比较

  1. NaN 的比较:NaN(Not a Number)与任何值(包括自己)比较都返回 false。

    js 复制代码
    console.log(NaN === NaN);  // false
    console.log(NaN == NaN);   // false

    检测 NaN 的正确方法

    • 使用 isNaN(NaN) 函数

    • 使用 Number.isNaN(NaN) (ES6)

    • 利用 NaN 是唯一不等于自身的值:

      js 复制代码
      function isNaNValue(x) {return x !== x;}
  2. null 和 undefined 的比较

    js 复制代码
    null == 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
  3. +0 和 -0 的比较:在 ES6 中,+0 和 -0 是被区分开的。

    js 复制代码
    console.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 还提供关系运算符:

  1. 数学运算符:大于(>)、小于(<)、大于等于(>=)、小于等于(<=)

    这些运算符也会进行类型转换:

    javascript 复制代码
    console.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')
  2. 字符串比较规则:字符串按字典顺序逐字符比较(基于 Unicode 编码)

    javascript 复制代码
    console.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
相关推荐
zwjapple3 小时前
docker-compose一键部署全栈项目。springboot后端,react前端
前端·spring boot·docker
像风一样自由20206 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem6 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊6 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术7 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing7 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止7 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall7 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴7 小时前
简单入门Python装饰器
前端·python
袁煦丞8 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作