Skip to content

ECMAScript

HTML 中的 JavaScript

<script>元素

将 JavaScript 插入 HTML 的主要方法是使用<script>元素。<script>元素有8个属性:

  1. src:指定外部脚本文件的URL。当使用这个属性时,<script>标签内部不应该包含任何JavaScript代码。
  2. type:规定脚本的MIME类型。在HTML5中,默认值是text/javascript,并且这个属性不是必需的。
  3. async:异步执行外部脚本文件。这意味着脚本在被下载时,页面将继续解析。这个属性只对外部脚本文件有效(即使用src属性的情况)。
  4. defer:延迟执行外部脚本,直到页面完成解析。同样,这个属性只对外部脚本文件有效。
  5. charset:规定在外部脚本文件中使用的字符编码。这个属性很少使用,因为大多数脚本都是用UTF-8编码的。
  6. crossorigin:配置相关的CORS(跨源资源共享)设置。这对于加载跨域脚本时非常有用。
  7. integrity:允许配置SRI(子资源完整性)检查,这可以确保脚本文件在下载过程中未被篡改。
  8. nomodule:为不支持模块化脚本的旧浏览器提供备用脚本。当浏览器支持模块化脚本时(使用type="module"),带有nomodule属性的脚本不会被执行。

注意事项

  1. 放置位置:传统上,<script> 元素放置在 <head> 的内部或 <body> 的底部。但现代实践推荐使用 async 或 defer 属性,以避免阻塞文档的解析。
  2. 外部与内部脚本:不要在同一个 <script> 元素中既包含外部 src 属性又编写内部代码。浏览器会忽略内部代码并只执行 src 属性指向的外部文件。
  3. 脚本执行顺序:没有 defer 或 async 属性的脚本会按照它们在文档中出现的顺序立即执行。defer 属性的脚本会按照它们在文档中出现的顺序在文档解析完成后执行。带有 async 属性的脚本会在它们加载完成后尽快执行,执行顺序不能保证。
  4. type 属性:虽然在HTML5中不是必须的,但在使用模块化JavaScript时,需要设置 type = "module"
  5. 安全性
    • 使用 integrity 属性可以确保脚本未被篡改。
    • 使用 crossorigin 属性可以正确处理跨源脚本的错误报告。
    • 对于用户生成的内容,要避免直接插入到 <script> 标签内部,以防止跨站脚本攻击(XSS)。
  6. 性能考虑
    • 使用 async 和 defer 可以提高页面的加载性能。
    • 对于非必要的或非关键的脚本,可以考虑延迟加载或按需加载。
  7. 浏览器兼容性
    • 考虑到旧版本的浏览器可能不支持某些属性,如 async、defer、module。
    • 对于 type="module" 的脚本,旧版浏览器将会忽略它们,可以使用 nomodule 属性提供备选方案。

动态加载<script>

  • 函数加载
js
function loadScript(url, callback) {
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = url;
  script.onload = function() {
    callback();
  };
  script.onerror = function() {
    console.error('Script load error: ' + url);
  };
  document.head.appendChild(script);
}

loadScript('path/to/your/script.js', function() {
  console.log('Script loaded successfully!');
});
  • 预加载
html
<link rel="preload" href="path/to/your/script.js" as="script">
js
// 当某个事件发生时,比如用户交互或特定条件满足时,动态加载脚本
function loadScriptDynamically() {
  var script = document.createElement('script');
  script.src = 'path/to/your/script.js';
  // 其它属性设置,如 type, async, defer 等
  document.body.appendChild(script);
}

哪种方法更好?

  • 如果你的脚本需要在页面加载的特定时刻执行,并且对加载顺序有严格要求,动态加载脚本可能是更好的选择。
  • 如果你想提前获取资源以确保它们在需要时立即可用,但不需要立即执行,或者你想为即将到来的导航预加载资源,那么 <link rel="preload"> 可能是更好的选择。
  • 对于关键路径上的资源或者在页面渲染过程中很早就需要的资源,使用 <link rel="preload"> 是一种优化性能的有效方法。

实际上,这两种技术可以结合使用。你可以预加载一个脚本以确保它被快速加载,并在必要时通过JavaScript动态插入该脚本到页面中,从而控制执行时机。

两种方式

在Web开发中,JavaScript代码可以以两种方式包含在网页中:行内代码(内联)和外部文件。每种方式都有其优缺点,通常情况下,选择哪一种取决于具体的场景和需求。

行内代码(内联)

html
<!DOCTYPE html>
<html>
<head>
  <title>内联JavaScript示例</title>
</head>
<body>
  <h1>点击按钮弹出提示</h1>
  <button onclick="showAlert()">点击我</button>

  <script>
    function showAlert() {
      alert('你好,世界!');
    }
  </script>
</body>
</html>
  1. 优点

    • 简便:对于小的脚本或快速原型开发,内联JavaScript可以直接写在HTML文件中,无需创建额外的文件。
    • 无需请求:由于代码直接存在于HTML中,浏览器不需要发起额外的HTTP请求来获取脚本。
    • 依赖性:对于一些依赖于HTML元素的脚本,内联可以确保当脚本运行时,所依赖的DOM元素已经存在。
  2. 缺点

    • 性能:内联脚本会增加HTML文档的大小,可能会导致更长的加载时间,尤其是对于大型的脚本。
    • 可维护性:随着应用的增长,内联JavaScript可以快速变得难以管理和维护。
    • 缓存:内联脚本不能被浏览器缓存,这意味着每次页面加载时都要重新下载相同的代码。

外部文件

html
<!DOCTYPE html>
<html>
<head>
  <title>外部JavaScript示例</title>
  <script src="script.js"></script>
</head>
<body>
  <h1>点击按钮弹出提示</h1>
  <button onclick="showAlert()">点击我</button>
</body>
</html>
js
// script.js 文件内容
function showAlert() {
  alert('你好,世界!');
}
  1. 优点

    • 缓存:外部脚本文件可以被浏览器缓存,减少了后续页面访问的加载时间。
    • 可维护性:将脚本放在外部文件中可以提高代码的组织性和可维护性。
    • 复用性:外部脚本可以被多个页面共用,减少了代码重复。
    • 并行加载:现代浏览器可以同时下载多个外部脚本,优化加载效率。
  2. 缺点:

    • 额外的HTTP请求:每个外部脚本文件都需要通过一个额外的HTTP请求来加载,这可能会增加加载时间,尤其是在网络状况不佳的情况下。
    • 依赖性问题:如果HTML元素依赖于外部脚本,则必须确保在脚本执行前元素已经加载和解析。

<noscript>元素

<noscript>元素在HTML中用于定义在不支持脚本或者脚本被禁用时显示的替代内容。这个元素可以帮助改善那些无法使用JavaScript的用户的体验。它确保了当脚本不可用时,用户依然可以获取关于网站的基本信息和功能。

html
<!DOCTYPE html>
<html>
<head>
    <title>示例</title>
</head>
<body>
    <noscript>
        <!-- 如果浏览器不支持JavaScript,或者JavaScript被禁用,以下内容将被显示 -->
        <p>本网站需要JavaScript支持,请启用JavaScript以继续浏览。</p>
    </noscript>
</body>
<script>
    // 如果浏览器支持JavaScript,这里的代码将被执行
</script>
</html>

语言基础

语法

1、区分大小写

无论是变量、函数名还是操作符,都区分大小写

2、标识符

  • 第一个字符必须是字母、下划线( _ )或美元符号( $ )
  • 剩余的其他字符可以是字母、下划线、美元符号或数字

TIP

按照惯例,ECMAScript 标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写,如:myCar

3、注释

javascript
// 单行注释

/* 
  这是一个更长的,
  多行注释
*/

4、严格模式

所有现代浏览器都支持严格模式,严格模式会影响 JavaScript 执行的很多方面。

javascript
'use strict' //对整个脚本启用严格模式,在脚本开头加上这一行

function doSomething() {
  //单独启用一个函数严格模式
  'use strict'
  //函数体
}

变量

在JavaScript中,变量是用于存储和引用数据的标识符。变量的命名规则:

  • 变量名可以包含字母、数字、下划线(_)和美元符号($)。
  • 变量名必须以字母、下划线或美元符号开头。
  • JavaScript 中的变量名是区分大小写的。

词法环境与变量环境

在JavaScript中,词法环境(Lexical Environment)和变量环境(Variable Environment)是执行上下文(Execution Context)的两个组成部分,它们用于定义在代码执行时如何处理变量和函数。这两个概念在ECMAScript规范中有着重要的作用,尤其是在理解作用域、闭包和变量提升等概念时。

  1. 词法环境(Lexical Environment)

    • 定义:词法环境是一种规范类型,用于定义在特定代码片段执行期间如何识别标识符(变量和函数名)。
    • 组成:
      • 环境记录(Environment Record):存储变量和函数声明的实际位置。
      • 外部词法环境的引用:指向外部代码的词法环境,用于实现作用域链。
    • 特点:词法环境是按代码书写时的字面结构(词法结构)组织的,因此它是静态的。
  2. 变量环境(Variable Environment)

    • 定义:变量环境是执行上下文的一个组成部分,用于在早期ECMAScript版本中处理变量声明。
    • 作用:在ES6及更早版本中,主要用于处理var声明的变量,因为let和const声明的变量不会在变量环境中提升。

词法环境 vs 变量环境

  • 共同点:它们都是执行上下文的组成部分,用于存储识别和处理变量和函数的信息。
  • 差异:
    • 词法环境:用于存储let、const声明的变量以及函数声明。它更加动态,可以随着代码块(如块语句和函数体)的执行而创建新的词法环境。
    • 变量环境:早期的概念,主要用于处理var声明的变量。在现代JavaScript中,这个概念变得不那么重要,因为let和const的引入改变了变量声明的行为。

闭包和词法环境

闭包是词法环境的一个直接应用。当函数可以记住并访问其在定义时的词法环境时,这个函数就形成了闭包。即使函数在其词法环境外部被调用,它仍然可以访问该环境中的变量。

变量声明

  1. var 关键字

    • 函数作用域:var 声明的变量在整个包含它的函数中都是可见的,不管声明它的代码在函数中的什么位置。

      TIP

      如果需要定义多个变量,可以在一条语句中用逗号分隔每个变量(及可选的初始化):

      js
      var message = 'hi', isFound = false, age = 19

      WARNING

      在严格模式下,不能定义名为 eval 和 arguments 的变量,否则会导致语法错误。

    • 变量提升(Hoisting):使用 var 声明的变量会被提升到函数的顶部(或全局作用域的顶部,如果它被声明在函数外)。这意味着无论变量实际在哪里声明,都会被视为在函数顶部声明。

    • 全局作用域:如果 var 在任何函数外部声明,它属于全局作用域,这意味着它可以在代码的任何其他部分被访问和修改。

    • 无块级作用域:var 不具备块级作用域(block scope),即在一个块(如 if 语句或 for 循环中)声明的变量在外部也是可见的。

  2. let 关键字

    • 块作用域:与 var 关键字声明的变量可能会溢出到外部作用域,let 关键字声明的变量具有其定义所在的块作用域,如:if,for 。
    • 无变量提升:使用 let 声明的变量不会在其作用域内被提升,这意味着它们在声明之前不能被引用。相反,var 声明的变量会被提升到其作用域的顶部。
    • 不允许重复声明:在同一个作用域内,不能用 let 重复声明同一个变量。相反,可以使用 var 重复声明同一个变量。
    • 暂时性死区:在代码块内,在声明 let 变量之前的区域被称为暂时性死区,在这个区域内访问变量会导致ReferenceError。
    • 全局对象属性:如果在全局作用域中使用 let 声明变量,这个变量不会成为全局对象(window)的属性,而 var 声明的变量则会。
  3. const 关键字

    • 块作用域:与 let 类似,const 声明的变量也具有块级作用域,这意味着它们只能在声明它们的块或子块中访问。
    • 必须初始化:使用 const 声明变量时,必须同时初始化该变量,因为之后你将无法更改其值。
    • 不可重新赋值:const 声明的变量不能被重新赋值,但是如果它是一个对象或数组,对象的属性或数组的元素可以被修改。
    • 不可重复声明:在同一个作用域内,const 不允许重复声明。
    • 非完全不变:虽然 const 声明的变量不能被重新赋值,但如果它们指向的是对象或数组,这些对象或数组的内容是可以被改变的。换句话说,const 保证的是变量引用的地址不变,而不是其指向的数据不变。

声明风格及最佳实践

  1. 使用 const 和 let: 自从ES6起,应该优先使用 const , 其次使用 let ,禁止使用 var。const 用于那些不需要重新赋值的变量,而 let 用于那些需要重新赋值的变量。
  2. 块级作用域: const 和 let 都是块级作用域的,这意味着它们在声明它们的块内以及任何包含的子块中是有效的。使用块级作用域变量有助于避免在全局作用域或函数作用域中不小心创建变量。
  3. 声明位置: 将变量尽可能地靠近它们被使用的位置声明,这样做可以提高代码的可读性和维护性。
  4. 变量命名: 使用有意义的变量名,这样其他阅读代码的人可以更容易理解变量的用途。避免使用单个字母的变量名,除非它们是在小范围如循环中使用。
  5. 小驼峰命名法: 变量应该使用小驼峰命名法(camelCase),即第一个单词以小写字母开始,后续单词的首字母大写。
  6. 不变性: 当你知道变量值不会改变时,总是使用 const。这不仅可以提供变量不可变的语义意义,而且可以帮助防止程序在运行时无意中更改变量值。
  7. 避免全局变量: 尽可能避免全局变量的使用,因为它们可以在代码的任何地方被访问和修改,这会增加出错的概率。
  8. 单一职责: 每个变量应该只有一个职责。不要在同一个变量中存储多种类型的信息。
  9. 解构赋值: 使用ES6的解构赋值来从对象或数组中提取多个属性或元素,这样可以让代码更加简洁。
  10. 不要使用链式赋值: 避免使用链式赋值,例如 let a = b = c = 5;,这样的代码虽然简短,但是可读性差,并且可能会不小心创建全局变量。
  11. 代码一致性: 在整个项目中保持一致的声明风格。如果你的团队中有多名开发者,考虑使用像ESLint这样的工具来强制执行这些规则。
  12. 注释: 对于那些含义不明显的变量,或者那些在特定上下文中有特殊用途的变量,添加注释可以帮助其他开发者理解其用途。

原始值与引用值

  1. 原始值:当你操作原始值时,你操作的是值本身。例如,在变量赋值时,实际上是将值复制到新变量。

    • String:字符串类型,例如 "Hello World"。
    • Number:数字类型,可以是整数或浮点数,例如 42 或 3.14。
    • BigInt:用于表示大整数。
    • Boolean:布尔类型,只有两个值:true 和 false。
    • Undefined:一个变量被声明了但没有被赋值时,其值为 undefined。
    • Null:表示“没有值”或“值未知”。
    • Symbol:一种实例是唯一且不可更改的数据类型。
    js
    let a = 10;
    let b = a;
    a = 20;
    console.log(a); // 20
    console.log(b); // 10
  2. 引用值:与原始值不同,引用值不是直接存储在变量中的。当你创建一个引用类型的值时,变量实际上保存的是一个指向内存中该对象位置的引用。当你将引用值从一个变量复制到另一个变量时,复制的是引用,而不是实际的对象。

    • 对象(Object):例如,{name: "Alice", age: 25}。
    • 数组(Array):例如,[1, 2, 3, 4, 5]。
    • 函数(Function):JavaScript中的函数也是对象。
    js
    let obj1 = { value: 10 };
    let obj2 = obj1;
    obj1.value = 20;
    console.log(obj1.value); // 20
    console.log(obj2.value); // 20
  3. 行为差异

    • 变量赋值:

      • 原始值:复制值本身。
      • 引用值:复制对象的引用。
    • 比较操作:

      • 原始值:比较值本身。
      • 引用值:比较引用(即内存地址),而不是对象本身的内容。
    • 函数传递参数:

      • 原始值:按值传递(传递值的副本)。
      • 引用值:按引用传递(传递对象的引用)。

执行上下文与作用域

  1. 执行上下文

    • 每当函数被调用时,都会为该函数创建一个新的执行上下文。这个上下文包括了函数调用时的所有信息,如:
      • 变量对象:包含了函数的参数、内部变量和函数声明。
      • 作用域链:确定当前上下文中可访问的变量和函数。
      • this 值:根据函数调用方式的不同,this 值可以不同。
    • JavaScript运行时会维护一个称为执行上下文栈的结构,用于存储所有执行上下文。当函数被调用时,一个新的上下文被创建并推入这个栈中;函数执行完毕后,其上下文从栈中弹出,控制权返回到当前上下文。
  2. 作用域

    • 作用域是指程序中定义变量的区域,这影响了变量的可见性和生命周期。在JavaScript中,主要有两种类型的作用域:
      • 全局作用域:在代码最外层定义的变量拥有全局作用域,这些变量在代码的任何地方都可被访问。
      • 局部作用域:在函数内部定义的变量拥有局部作用域,它们只能在函数内部被访问。
    • ES6引入了let和const关键字,使得JavaScript支持块级作用域,即变量在声明它们的块或for/while循环之内是有效的。
  3. 作用域链

    • 当代码在一个上下文中执行时,JavaScript会查找变量的值,这个查找过程是沿着作用域链进行的。作用域链是一个由当前上下文的变量对象以及上层上下文的变量对象组成的列表。

内存管理

  1. 栈与堆

    • 栈(Stack)
      • 栈是一种线性的数据结构,遵循后进先出(LIFO)的原则。
      • 栈用于存储原始数据类型(如字符串、数字、布尔值)以及函数调用。
      • 栈内存分配和回收自动进行,非常快速。
      • JavaScript中的变量(尤其是在函数中声明的局部变量)和函数调用的上下文通常存储在栈中。
      • 每当发生函数调用时,函数的参数和局部变量被压入栈中。当函数返回时,这些数据被弹出栈。
    • 堆(Heap)
      • 堆是一种非结构化的内存区域,用于存储复杂的数据结构。
      • 堆用于存储引用数据类型,如对象、数组和函数。
      • 堆内存的分配和回收是不确定的,且相对较慢。
      • 在堆中存储的数据大小和生命周期不定,由垃圾回收器管理。
      • 当你在代码中创建对象或数组时,它们被存储在堆中,而变量则存储对这些数据的引用。

    栈和堆的区别

    • 内存分配:栈内存的分配和回收由编程语言自动管理,而堆内存通常需要手动分配和回收(在JavaScript中,这是通过垃圾回收机制自动完成的)。
    • 性能:栈内存的分配和读取速度通常比堆内存快。
    • 容量:栈的大小通常有限且固定,而堆的大小更灵活,可以动态扩展。
  2. 内存分配

    • JavaScript在创建变量(原始值或对象)时自动分配内存。
    • 对于原始类型(如数字、字符串、布尔值等),内存分配通常发生在栈上。
    • 对于引用类型(如对象、数组、函数等),内存分配发生在堆上。
  3. 自动垃圾回收

    • 标记-清除:这是最常见的垃圾回收算法。垃圾回收器周期性地从根开始查找所有从根开始可以到达的对象,然后清除那些不能到达的对象。
    • 标记-整理:结合了标记-清除和压缩算法。通过标记-清除找到并标记垃圾对象,然后将存活的对象向一端移动,从而整理内存。
    • 引用计数:这是一种较早的垃圾回收算法,它跟踪每个值被引用的次数。当引用次数降至零时,对象将被回收。但这种方法存在循环引用问题。
    • 分代收集:将内存分为新生代和老生代两代。大部分对象在创建后很快就变得不再使用,因此将新创建的对象放入新生代。
    • 增量收集:为了避免垃圾回收在运行时产生长时间的暂停,增量收集算法将垃圾回收分成多个小步骤执行。
  4. 内存泄漏

    • 常见原因
      • 全局变量:不经意地创建全局变量是最常见的内存泄漏原因。例如,在没有使用var、let或const关键字的情况下赋值给一个新变量,该变量自动成为全局变量,其生命周期贯穿整个程序,导致内存不能被释放。
      • 未清理的定时器和回调函数:使用setInterval定时器而未清理,或者注册事件监听器却未在不需要时移除,可能会导致相关的函数和变量继续占用内存。
      • 闭包:虽然闭包是JavaScript中一个强大的特性,但不正确的使用可能导致内存泄漏,因为闭包内的变量在外部函数执行完毕后仍然保持在内存中。
      • DOM引用:JavaScript对象引用着已从DOM中删除的元素,这阻止了这些DOM元素所占用的内存被回收。
      • 循环引用:在某些情况下,两个或更多对象互相引用,形成闭环,可能导致它们所占用的内存不被释放。虽然现代浏览器的垃圾回收机制能够处理很多循环引用的情况,但在某些复杂的情况下仍然可能出现问题。
    • 检测监视
      • 使用浏览器的开发者工具:现代浏览器(如Chrome、Firefox)提供了开发者工具来监视内存的使用情况,可以帮助识别内存泄漏。
      • JavaScript分析代码:通过编写代码来监控内存使用,如定期检查window.performance.memory(只在Chrome中可用)。
    • 解决方案
      • 避免全局变量:无意中创建的全局变量可能导致内存泄漏。使用严格模式('use strict')可以帮助避免这个问题。
      • 移除事件监听器:如果不再需要DOM元素的事件监听器,请确保移除它们。
      • 避免闭包中的内存泄漏:闭包是常见的内存泄漏来源。确保只保留必要的引用,并在不需要时解除引用。
      • 清除定时器和回调:如果有使用setInterval或setTimeout,确保在不需要时清除它们。
      • 使用WeakMap和WeakSet:WeakMap和WeakSet中的对象引用不会阻止垃圾回收。如果合适,可以使用它们而不是Map和Set。
      • 断开引用:将变量、对象属性或数组元素设置为null或undefined可以断开它们对内存中对象的引用。
      • 清空数组:如果你不再需要数组中的数据,可以通过设置数组为空或数组长度为零来清空数组。
      • 关闭Web Workers:如果使用了Web Workers,确保在不需要时终止它们。
      • 使用工具和库:考虑使用内存管理工具或库,如lodash中的_.memoize来帮助缓存。

数据类型

JavaScript中的数据类型大致可以分为两类:原始类型和对象类型。

  1. 原始类型(Primitive Types)

    • String: 用于表示文本数据,例如 "Hello, World!"。
    • Number: 表示整数和浮点数,例如 42 或 3.14159。JavaScript中的所有数字都是浮点数,即使是整数也是用浮点数表示的。
    • BigInt: 用于表示非常大的整数。BigInt类型的值比Number类型可以表示的范围还要大,可以通过在整数后面加n来创建,例如 9007199254740991n。
    • Boolean: 有两个值 true 和 false,用于逻辑操作。
    • undefined: 一个没有赋值的变量会有一个 undefined 值。
    • null: 表示“没有值”或“非对象”的空值,经常用来表示未知或缺失的数据。
    • Symbol: ES6引入的一种新的数据类型,表示唯一的、不可变的原始值,常用作对象属性的键。
  2. 对象类型(Object Types)

    • Object: JavaScript中的基本构造块,是一个键值对的集合。一个对象可能表示一个日期 (new Date())、一个复杂的数学运算 (new Math()),或者几乎任何东西。
    • Array: 用于存储有序集合的特殊类型的对象,例如 [1, 2, 3]。
    • Function: 函数其实是一种特殊类型的对象,可以被调用。

typeof 操作符

在JavaScript中,typeof 操作符用来检测给定变量的数据类型。它是一个一元操作符,返回一个表示变量数据类型的字符串。

以下是使用 typeof 操作符可能得到的字符串结果:

  • "undefined": 如果变量未定义或未赋值。
  • "boolean": 如果值是布尔值 true 或 false。
  • "number": 如果值是数字。
  • "string": 如果值是字符串。
  • "symbol": 如果值是符号 (由 Symbol() 构造器创建)。
  • "object": 如果值是一个对象(不包括 null)或者是 null。
  • "function": 如果值是函数。

有一点需要特别注意,那就是 typeof null 会返回 "object",这实际上是JavaScript的一个历史遗留问题,null 被认为是一个空对象的引用。

undefined 类型

在JavaScript中,undefined 是一个原始数据类型,它有一个特殊的值 undefined。它用于表示变量已被声明但尚未被赋予一个具体的值,或者根本就没有被声明。

几个要点:

  • 变量声明未赋值:当你声明了一个变量但没有赋值时,默认值是 undefined。

  • 函数没有返回值:如果一个函数没有返回语句,那么函数调用的结果是 undefined。

  • 对象属性不存在:当你尝试访问一个对象上不存在的属性时,结果是 undefined。

  • 方法或函数参数未传递:当函数调用时,未提供所有参数,那么未提供的参数默认为 undefined。

TIP

与数字型参运算时,值为NaN;与布尔运算时,值为false

null 类型

在JavaScript中,null 是一个特殊的关键字,用于表示一个空值(null value)。它是JavaScript原始类型(primitive type)之一,代表一个意图为“无值”的变量。与 undefined 不同,null 通常是由程序员显式地赋值,而不是由JavaScript引擎隐式地设置。

几个要点:

  • 意图表示:null 通常用于故意表示一个非对象的空值,它是对变量的有意“清空”。

  • 类型:尽管 null 表示“无对象”,但 typeof null 返回的是 "object",这实际上是JavaScript最初设计的一个错误,并被保留下来。

  • 比较:null 在与 undefined 比较时有一些特殊的规则。使用双等号(==)比较时,null 和 undefined 被认为是相等的,因为它们都表示没有值。

  • 在对象上下文中的使用:null 通常用在期望对象但是没有对象可提供的场合。例如,在处理可能没有值的对象属性或者在期望返回对象的函数但是没有对象返回时,null 就会被用作一个占位符。

  • 释放引用:null 可用于帮助垃圾收集器释放对象的引用。将一个变量设置为 null 会切断变量与其之前引用的任何对象之间的链接,这样有助于从内存中清除不再需要的对象。

  • 默认参数:在函数参数列表中,null 可以作为一个参数的默认值,这表明该参数是可选的。

  • 与 undefined 的区别:虽然 null 和 undefined 都可以用来表示“无值”,但它们的使用场景和语义含义有所不同。null 是一个明确的赋值,表明变量不应该有值。相反,undefined 表示变量已声明但未赋值,或者完全未声明。

TIP

特指一个空对象,在布尔运算中被认为是false

Number 类型

在JavaScript中,number 类型是一种原始数据类型,用于表示整数和浮点数(小数)。

  1. 值的类型

    • 整数:Int
      • 十进制:0 开头,取值范围 0~9
      • 二进制:0b 开头,取值范围 0~1
      • 八进制:0o 开头,取值范围 0~7
      • 十六进制:0x 开头,取值范围 0~F
    • 浮点数:Float
      • 小数点:必须包含小数点,且小数点后面必须至少有一个数字
      • 科学记数法:e 或 E 结尾
    • 特殊数值:
      • Infinity:表示无穷大,任何超过 Number.MAX_VALUE 的值都会变成 Infinity。
      • -Infinity:表示负无穷大,任何低于 Number.MIN_VALUE 的值都会变成 -Infinity。
      • NaN(Not a Number):表示一个非数值,通常是因为错误的数学运算产生的结果。
  2. 值的范围

    • 最大整数取值: Number.MAX_VALUE
    • 最小整数取值: Number.MIN_VALUE
    • 最大整数运算范围取值: 2^53,超出则运算会不准确
    • 最小整数运算范围取值: -2^53,超出则运算会不准确
    • 精度问题:由于浮点数的表示方式,一些运算可能会有精度问题。例如,0.1 + 0.2 并不精确等于 0.3
  3. NaN(Not a Number)

    • NaN == NaN  和  NaN === NaN  都会返回  false
    • 当算术运算返回一个未定义的或无法表示的值时,NaN就产生了。但是,NaN并不一定用于表示某些值超出表示范围的情况。将某些不能强制转换为数值的非数值转换为数值的时候,也会得到NaN
    • 0 除以0会返回NaN,但是其他数除以0则会返回Infinity-Infinity
  4. 数值转换

    • Number(value):尝试将整个字符串转换为数字,包括整数和浮点数。如果字符串包含无法转换为数字的字符,Number() 会返回 NaN。
      • 布尔值:ture转换为1false转换为0
      • 数值:直接返回
      • null:返回0
      • undefined:返回NaN
      • 字符串
        • 包含数值字符,包括数值字符前面带加 、减号的情况,则转换为十进制数值
        • 包含有效的浮点值格式,则转换为相应的浮点值
        • 包含有效的十六进制格式,则转换为与该十六进制值对应的十进制整数值
        • 空字符串,则返回 0
        • 除上述情况之外的其他字符,则返回NaN
      • 对象:调用valueOf()方法,并按照上述规则转换返回的值 ;如果转换结果是 NaN,则调用toString()方法,再按照转换字符串的规则转换
    • parseInt(string, radix):主要用于解析整数,如果遇到小数点会停止解析,只返回整数部分。
      • string:要被解析的字符串直到遇到非数字字符,然后停止并返回已解析的整数部分。如果字符串开头就是非数字字符,函数会返回 NaN。。如果传入的不是字符串,则会被转换为字符串(使用 toString 方法)。
      • radix(可选):表示要解析的数字的基数(即数制),取值范围是 2 到 36。如果省略该参数或其值为 0,parseInt 会根据字符串来决定数字的基数。通常,十进制数字省略基数,但为了清晰,建议总是指定基数。
    • parseFloat(string):专门用于解析浮点数,会解析直到遇到非数字字符。
      • 从字符串的第一个字符开始解析,直到遇到一个非数字字符。如果字符串以非数字字符开头,则返回 NaN。
      • 解析的浮点数与原始数字的字符串表示的精度相同。
      • 能够正确解析使用科学记数法(例如 "5e2" = 500)的字符串。
      • 只解析十进制数。
  5. 属性

    • Number.MAX_VALUE:表示的最大正数
    • Number.MIN_VALUE:表示的最小正数(非零)
    • Number.NaN:表示“非数字”值的特殊值 NaN
    • Number.NEGATIVE_INFINITY:表示负无穷大的特殊值
    • Number.POSITIVE_INFINITY:表示正无穷大的特殊值
    • Number.EPSILON:表示两个可表示数之间的最小间隔
    • Number.MAX_SAFE_INTEGER:表示的最大安全整数(2^53 - 1)
    • Number.MIN_SAFE_INTEGER:表示的最小安全整数(-2^53 + 1)
  6. 方法

    • Number.isFinite(value):用于检测传入的参数是否为一个有限数。与全局的 isFinite() 函数不同,Number.isFinite() 不会强制将参数转换为数字。
    • Number.isInteger(value):用于判断给定的参数是否为整数。直接检查值是否为整数,而不进行任何转换。
    • Number.isNaN(value):用于确定一个值是否严格等于 NaN 。直接检查值是否为整数,而不进行任何转换。
    • Number.isSafeInteger(testValue):用于判断一个值是否为“安全整数”。可以避免精度损失的问题。
    • Number.parseFloat(string):与全局 parseFloat() 函数相同,用于将字符串解析为浮点数。
    • Number.parseInt(string[, radix]):与全局 parseInt() 函数相同,用于将字符串解析为整数。
    • toExponential([fractionDigits]):用于数字转换为指数形式的字符串。
    • toFixed([digits]):将数字格式化为指定小数位数的字符串
    • toLocaleString([locales, options]):将一个数字转换成一种特定的语言环境表示格式。
    • toPrecision([precision]):将数字格式化为指定长度的字符串
    • toString([radix]):将数字转换为字符串,可指定基数
    • valueOf():返回包装对象的原始数值

String 类型

  1. 字面量

    • 双引号:""
    • 单引号:''
    • 反引号:``
  2. 嵌套

    • 外单内双
    • 外双内单
  3. 转义符

    • \n:换行
    • \\:斜杠\
    • \':单引号
    • \":双引号
    • \t:tab 缩进
    • \b:空格
  4. 拼接

    • 普通字符串拼接:'...' + ..."..." + ...
    • 模板字符串拼接:`...${...}...`

    TIP

    数值相加,字符相连

  5. 属性

    • length:返回字符串的长度(字符数量)
  6. 方法

    • [@@iterator]():用于返回一个新的迭代器对象,该对象遍历字符串中的每个字符。

    • charAt(index):返回特定索引处的字符。支持负索引。

    js
    let str = "Hello, world!";
    
    console.log(str.at(0));    // 输出:"H"
    console.log(str.at(7));    // 输出:"w"
    console.log(str.at(-1));   // 输出:"!"
    console.log(str.at(-4));   // 输出:"r"
    • charCodeAt(index):用于返回字符串中指定位置的字符。不支持负索引。
    js
    let str = "Hello, world!";
    
    console.log(str.charAt(0)); // 输出:"H"
    console.log(str.charAt(7)); // 输出:"w"
    console.log(str.charAt(22)); // 输出:"", 因为索引超出了字符串范围
    • concat(strN):用于将一个或多个字符串与原始字符串连接起来,形成一个新的字符串。
    js
    let hello = "Hello, ";
    let world = "world!";
    let exclamation = " How are you?";
    
    let sentence = hello.concat(world, exclamation);
    console.log(sentence); // 输出:"Hello, world! How are you?"
    • startsWith(searchString[, position]):用于判断一个字符串是否以另一个给定的子字符串开始。区分大小写。
    js
    let str = "Hello, world!";
    
    console.log(str.startsWith("Hello")); // 输出: true
    console.log(str.startsWith("world")); // 输出: false
    console.log(str.startsWith("world", 7)); // 输出: true
    • endsWith(searchString[, length]):用于确定一个字符串是否以指定的子字符串结尾。
    js
    let str = "Hello, world!";
    
    console.log(str.endsWith("world!")); // 输出:true
    console.log(str.endsWith("Hello, world!")); // 输出:true
    console.log(str.endsWith("hello")); // 输出:false
    console.log(str.endsWith("world", 11)); // 输出:false,因为字符串被当作 "Hello, worl" 处理
    • includes(searchString[, position]):用于判断一个字符串是否包含另一个指定的子字符串。
    js
    let str = "Hello, world!";
    
    console.log(str.includes("world")); // 输出:true
    console.log(str.includes("World")); // 输出:false(大小写敏感)
    console.log(str.includes("hello")); // 输出:false
    console.log(str.includes("world", 8)); // 输出:false(从索引8开始搜索)
    console.log(str.includes("world", 7)); // 输出:true(从索引7开始搜索)
    • indexOf(searchValue[, fromIndex]):用于返回指定值在调用字符串对象中首次出现的位置。如果没有找到该值,则返回 -1。从字符串的开头(或指定位置)向后搜索。
    js
    let str = "Hello, world! Welcome to the universe.";
    
    console.log(str.indexOf("world")); // 输出:7
    console.log(str.indexOf("World")); // 输出:-1 (因为大小写不匹配)
    console.log(str.indexOf("o"));     // 输出:4 ("o" 首次出现的位置)
    console.log(str.indexOf("o", 5));  // 输出:8 (从索引5开始搜索 "o")
    console.log(str.indexOf("hello")); // 输出:-1 (找不到匹配)
    • lastIndexOf(searchValue[, fromIndex]):用于返回指定值在调用字符串对象中最后一次出现的位置。如果没有找到该值,则方法返回 -1。从字符串的末尾(或指定位置)向前搜索。
    js
    let str = "Hello, world! Hello, universe!";
    
    console.log(str.lastIndexOf("Hello")); // 输出:19
    console.log(str.lastIndexOf("hello")); // 输出:-1 (因为大小写不匹配)
    console.log(str.lastIndexOf("world", 10)); // 输出:7 (在前10个字符中搜索)
    console.log(str.lastIndexOf("world", 20)); // 输出:7 (在前20个字符中搜索)
    console.log(str.lastIndexOf("test")); // 输出:-1 (找不到匹配)
    • localeCompare(compareString[, locales[, options]]):用于比较两个字符串,并根据比较结果返回一个数字。
    js
    let str1 = "a";
    let str2 = "c";
    
    console.log(str1.localeCompare(str2)); // 输出一个负数,因为 "a" 在 "c" 之前
    console.log(str2.localeCompare(str1)); // 输出一个正数,因为 "c" 在 "a" 之后
    console.log(str1.localeCompare("a"));  // 输出:0,因为两个字符串相等
    • match(regexp):用于在字符串中执行正则表达式匹配。返回一个数组。
    js
    let str = "For more information, visit the MDN!";
    let regex = /more/;
    
    let result = str.match(regex);
    console.log(result[0]);    // 输出:"more"
    console.log(result.index); // 输出:4
    console.log(result.input); // 输出原始字符串:"For more information, visit the MDN!"
    • matchAll(regexp):用于返回一个包含所有匹配正则表达式的结果及其捕获组的迭代器。与 match() 不同,matchAll() 需要一个带有全局标志 g 。返回一个迭代器。
    js
    let str = "test1test2";
    let regex = /(test(\d?))/g;
    
    let matches = [...str.matchAll(regex)];
    
    matches.forEach(match => {
        console.log(`全匹配: ${match[0]}, 捕获组1: ${match[1]}, 捕获组2: ${match[2]}`);
        // 输出例如 "全匹配: test1, 捕获组1: test1, 捕获组2: 1"
    });
    • padEnd(targetLength [, padString]):用于在当前字符串的末尾填充指定的字符,直到达到指定的长度。如果原字符串的长度已经等于或超过指定的长度,则返回原字符串。
    js
    let str = "Hello";
    
    console.log(str.padEnd(10));         // 输出:"Hello     "(填充空格到长度10)
    console.log(str.padEnd(10, "1234")); // 输出:"Hello12341"(使用 "1234" 进行填充,最后一个字符被截断)
    console.log(str.padEnd(5));          // 输出:"Hello"(原字符串长度已经是5,不进行填充)
    • padStart(targetLength [, padString]):用于在当前字符串的开头填充指定的字符,直到字符串达到指定的长度。如果原字符串的长度已经等于或超过指定的长度,则返回原字符串。
    js
    let str = "5";
    
    console.log(str.padStart(3, "0"));    // 输出:"005"
    console.log(str.padStart(2, "123"));  // 输出:"25"(使用 "123" 进行填充,但只取 "2")
    console.log(str.padStart(1));         // 输出:"5"(原字符串长度已经是1,不进行填充)
    • String.rawtemplateString:用于获取一个模板字符串的原始字符串形式,即处理字符串字面量时忽略所有的转义字符(如 \n、\t 等)。
    js
    // 普通字符串中的转义
    let normalString = `Line1\nLine2`;
    console.log(normalString);
    // 输出:
    // Line1
    // Line2
    
    // 使用 String.raw
    let rawString = String.raw`Line1\nLine2`;
    console.log(rawString);
    // 输出:Line1\nLine2
    • repeat(count):将字符串重复指定次数,返回一个新字符串
    js
    let str = "abc";
    console.log(str.repeat(3)); // "abcabcabc"
    • replace(regexp|substr, newSubstr|function):用来替换字符串中匹配到的子串。不会改变原始字符串,而是返回一个新的字符串。
    js
    // 使用子字符串替换
    let str = "Hello world!";
    let newStr = str.replace("world", "JavaScript");
    console.log(newStr); // 输出: "Hello JavaScript!"
    
    // 使用正则表达式替换
    let str = "Apples are round, and apples are juicy.";
    let newStr = str.replace(/apples/gi, "oranges");
    console.log(newStr); // 输出: "Oranges are round, and oranges are juicy."
    
    // 使用函数替换
    let str = "John Smith";
    let newStr = str.replace(/(\w+)\s(\w+)/, function(match, p1, p2) {
      return p2 + ", " + p1;
    });
    console.log(newStr); // 输出: "Smith, John"
    • replaceAll(regexp|substr, newSubstr|function):用于替换字符串中所有匹配项。不会改变原始字符串,而是返回一个新的字符串。
    js
    // 使用子字符串替换
    let str = "Hello world, hello moon";
    let newStr = str.replaceAll("hello", "goodbye");
    console.log(newStr); // 输出: "Hello world, goodbye moon"
    
    // 使用正则表达式替换
    let str = "Apples are round, and apples are juicy.";
    let newStr = str.replaceAll(/apples/gi, "oranges");
    console.log(newStr); // 输出: "Oranges are round, and oranges are juicy."
    
    // 使用替换函数
    let str = "John Smith";
    let newStr = str.replaceAll(/(\w+)\s(\w+)/, function(match, p1, p2) {
      return p2 + ", " + p1;
    });
    console.log(newStr); // 输出: "Smith, John"
    • search(regexp):用于搜索字符串中与正则表达式匹配的子串,从字符串的起始位置开始搜索,并返回第一个匹配项的索引。如果没有找到匹配项,则返回 -1。
    js
    let str = "Hello World!";
    let index = str.search(/world/i); // 使用不区分大小写的标志 'i'
    console.log(index); // 输出: 6
    • slice(beginIndex[, endIndex]):用于提取字符串的一部分,不会修改原始字符串,而是返回一个新字符串。
    js
    let str = "Hello, world!";
    console.log(str.slice(7)); // 输出: "world!"
    console.log(str.slice(7, 12)); // 输出: "world"
    console.log(str.slice(-6)); // 输出: "world!"
    console.log(str.slice(-6, -1)); // 输出: "world"

    TIP

    • 如果 beginIndex 等于或大于字符串的长度,slice() 会返回一个空字符串。
    • 如果 endIndex 大于字符串的长度,slice() 会提取到字符串的末尾。
    • 如果 beginIndex 和 endIndex 相等或 endIndex 小于 beginIndex,slice() 也会返回一个空字符串。
    • split(separator[, limit]):用于将一个字符串分割成一个字符串数组。不会修改原始字符串,而是返回一个新字符串。
    js
    let str = "apple, banana, cherry";
    let words = str.split(", ");
    console.log(words); // 输出: ["apple", "banana", "cherry"]
    
    let letters = str.split("");
    console.log(letters); // 输出: ["a", "p", "p", "l", "e", ",", " ", "b", "a", "n", "a", "n", "a", ",", " ", "c", "h", "e", "r", "r", "y"]
    
    let limitedWords = str.split(", ", 2);
    console.log(limitedWords); // 输出: ["apple", "banana"]
    • substring(indexStart[, indexEnd]):用于返回字符串的指定部分,不会修改原始字符串,而是返回一个新字符串。
    js
    let str = "Mozilla";
    
    console.log(str.substring(1, 3)); // 输出: "oz"
    console.log(str.substring(2));    // 输出: "zilla"
    console.log(str.substring(5, 2)); // 输出: "zil"

    TIP

    • 如果 indexStart 大于 indexEnd,substring 会交换这两个参数。
    • 如果参数为负数或非数值,substring 会将其视为 0 。
    • toLocaleLowerCase([locale, ...otherLocale]):用于根据任何语言环境的大小写映射规则,将字符串中的所有字符转换为小写。不会改变原字符串,而是返回一个新的字符串。
    js
    let str = "Hello World!";
    console.log(str.toLocaleLowerCase()); // 输出: "hello world!"
    
    // 在土耳其语中,大写的 'I' 转换为小写不同于其他语言
    let strInTurkish = "İstanbul";
    console.log(strInTurkish.toLocaleLowerCase('tr-TR')); // 输出: "istanbul"
    • toLocaleUpperCase([locale, ...otherLocale]):用于将字符串中的所有字符根据任何语言环境的大小写映射规则转换为大写。不会改变原字符串,而是返回一个新的字符串。
    js
    let str = "Hello World!";
    console.log(str.toLocaleUpperCase()); // 输出: "HELLO WORLD!"
    
    // 在土耳其语中,小写的 'i' 转换为大写不同于其他语言
    let strInTurkish = "istanbul";
    console.log(strInTurkish.toLocaleUpperCase('tr-TR')); // 输出: "İSTANBUL"
    • toLowerCase():用于将字符串中的所有字符转换为小写。不会改变原字符串,而是返回一个新的字符串。
    js
    let str = "Hello World!";
    let lowerStr = str.toLowerCase();
    
    console.log(lowerStr); // 输出: "hello world!"
    • toUpperCase():用于将字符串中的所有字符转换为大写。不会改变原字符串,而是返回一个新的字符串。
    js
    let str = "Hello World!";
    let upperStr = str.toUpperCase();
    
    console.log(upperStr); // 输出: "HELLO WORLD!"
    • trim():用于从字符串的两端删除空白字符,不会影响字符串中间的空白字符。不会改变原字符串,而是返回一个新的字符串。
    js
    let str = "   Hello World!   ";
    let trimmedStr = str.trim();
    
    console.log(trimmedStr); // 输出: "Hello World!"
    • trimStart(), trimLeft():用于从字符串的开始(左端)移除空白字符,不影响字符串的末尾或中间的空白字符。不会改变原字符串,而是返回一个新的字符串。
    js
    let str = "   Hello World!";
    let trimmedStr = str.trimStart();
    
    console.log(trimmedStr); // 输出: "Hello World!"
    • trimEnd(), trimRight():用于从字符串的末尾(右端)移除空白字符。,不影响字符串的末尾或中间的空白字符。不会改变原字符串,而是返回一个新的字符串。
    js
    let str = "Hello World!   ";
    let trimmedStr = str.trimEnd();
    
    console.log(trimmedStr); // 输出: "Hello World!"
    • toString():返回调用该方法的字符串对象的值。
    js
    let strObj = new String("Hello World!");
    let str = strObj.toString();
    
    console.log(str); // 输出: "Hello World!"
    • valueOf():返回 String 对象的原始字符串值。
    js
    let strObj = new String("Hello World!");
    let str = strObj.valueOf();
    
    console.log(str); // 输出: "Hello World!"

Boolean 类型

在JavaScript中,boolean 类型是一个原始数据类型,它只有两个字面值:true 和 false。boolean 类型用于逻辑运算,通常用来做决策和条件测试。

几个要点:

  • 字面值:true 和 false 是JavaScript中的保留关键字,它们用来直接表示布尔真值和假值。
  • 类型转换:其他类型的值可以通过 Boolean() 函数或双重否定操作符 !! 转换成布尔值。

TIP

  1. 除了''0NaNnullundefined转化为false,其他均转化为true
  2. 与数字型参与运算时,true值为1false值为0

BigInt 类型

在JavaScript中,BigInt 是一种新增的原始数据类型,它提供了一种方法来表示大于 Number.MAX_SAFE_INTEGER 的整数。这对于处理非常大的整数,比如在金融、科学计算或使用大整数库时非常有用。通过在整数后面添加 n 来创建,或者使用 BigInt() 函数。

javascript
let a = 1n
BigInt(value) // 不是构造函数,因此不能使用 new 关键字。尝试这样做会导致一个错误。

注意

  1. 不能与 Number 直接混合运算:尽管 BigInt 和 Number 可以进行比较,但不能直接进行算术运算。需要先将 Number 显式转换为 BigInt,或反之。
  2. JSON 序列化:BigInt 类型不能被JSON.stringify() 正常序列化,尝试这样做将会导致一个错误。

Symbol 类型

在JavaScript中,Symbol 是一种自ECMAScript 2015(ES6)引入的原始数据类型。Symbol 类型的主要目的是创建一个唯一的标识符,它在JavaScript中用于为对象添加独一无二的属性键(property keys),从而避免属性名的冲突。这特别有用于大型代码库、第三方库或者浏览器的内部实现,其中可能有大量属性和方法,并且需要避免重名的风险。

javascript
// 创建一个新的 Symbol。每个 Symbol 都是唯一的
let sym = Symbol();
// 创建一个带有描述的 Symbol。描述是一个字符串,仅用于调试
let sym = Symbol('my symbol')
  1. 属性

    • Symbol.iterator:用于定义默认的迭代器。当一个对象需要被迭代(比如在 for...of 循环中),Symbol.iterator 方法被调用并返回迭代器对象。
    js
    let iterable = [1, 2, 3];
    let iterator = iterable[Symbol.iterator]();
    console.log(iterator.next().value); // 1
    • Symbol.asyncIterator:提供了一个默认的异步迭代器。当对象被异步迭代(如在 for await...of 循环中),此方法被调用并返回一个异步迭代器。
    js
    // 使用时通常定义在一个异步生成器函数内部
    async function* asyncGenerator() {
      yield 1;
      yield 2;
      yield 3;
    }
    let asyncIterable = {
      [Symbol.asyncIterator]: asyncGenerator
    };
    • Symbol.match:用于定义一个正则表达式匹配器,该方法被 String.prototype.match() 方法调用。
    js
    let regex = /[a-e]/;
    console.log("hello".match(regex)); // ['e']
    • Symbol.matchAll:用于定义一个正则表达式对象的匹配器,它返回一个迭代器,产生所有匹配的结果。当字符串的 matchAll 方法被调用时,它会调用这个方法。
    js
    let regex = /t(e)(st(\d?))/g;
    let string = 'test1test2';
    let matches = [...string.matchAll(regex)];
    console.log(matches[0]);  // 输出: ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', groups: undefined]
    • Symbol.replace:被 String.prototype.replace() 方法调用,用来替换字符串中匹配的部分。
    js
    let regex = /foo/;
    console.log("barfoo".replace(regex, "baz")); // "barbaz"
    • Symbol.search:被 String.prototype.search() 方法调用,用来搜索字符串中的匹配项。
    js
    let regex = /hello/;
    console.log("hello world".search(regex)); // 0
    • Symbol.split:被 String.prototype.split() 方法调用,用来分割字符串。
    js
    let regex = /-/;
    console.log("2018-12-31".split(regex)); // ["2018", "12", "31"]
    • Symbol.toPrimitive:用于将对象转换为相应的原始值。通常被内部逻辑调用。
    js
    let obj = {
      [Symbol.toPrimitive](hint) {
        if (hint === 'number') {
          return 42;
        }
        return null;
      }
    };
    console.log(+obj); // 42
    • Symbol.toStringTag:被 Object.prototype.toString() 方法内部使用,以创建对象的默认字符串描述。
    js
    let obj = {
      [Symbol.toStringTag]: "MyObject"
    };
    console.log(obj.toString()); // [object MyObject]
    • Symbol.hasInstance:用于判断某对象是否为某构造器的实例。
    js
    class MyClass {
      static [Symbol.hasInstance](instance) {
        return Array.isArray(instance);
      }
    }
    console.log([] instanceof MyClass); // true
    • Symbol.isConcatSpreadable:布尔值,决定了一个对象在使用 Array.prototype.concat() 方法时是否可以展平(flatten)。
    js
    let array = [1, 2, 3];
    let fakeArray = { 0: 'a', 1: 'b', length: 2, [Symbol.isConcatSpreadable]: true };
    console.log(array.concat(fakeArray));  // 输出: [1, 2, 3, 'a', 'b']
    • Symbol.species:用于创建派生对象时确定构造函数。它是一个静态访问器属性,允许子类覆盖对象默认的构造器。
    js
    class MyArray extends Array {
      static get [Symbol.species]() { return Array; }
    }
    let a = new MyArray(1, 2, 3);
    let mapped = a.map(x => x * x);
    console.log(mapped instanceof MyArray);  // 输出: false
    console.log(mapped instanceof Array);    // 输出: true
    • Symbol.unscopables:用于指定某些属性名在 with 环境绑定中不应被包括。这可以防止一些内置对象的属性被 with 语句暴露。
    js
    Array.prototype.foo = "It's foo";
    console.log(Array.prototype[Symbol.unscopables]);  // 输出: {copyWithin: true, entries: true, fill: true, ...}
    with(Array.prototype) {
      console.log(foo);  // 输出: "It's foo"
      // 但是如果 foo 是 unscopables 的一部分,这里就会出错
    }
    • description:返回 Symbol 对象的可选描述的字符串
    js
    let sym = Symbol("desc");
    console.log(sym.description); // "desc"
  2. 方法

    • Symbol.for(key):使用给定的键在全局 Symbol 注册表中搜索现有的 Symbol。如果找到,则返回该 Symbol;否则,创建一个新的 Symbol 并注册。
    js
    let sym1 = Symbol.for("mySymbol");
    let sym2 = Symbol.for("mySymbol");
    console.log(sym1 === sym2); // true
    • Symbol.keyFor(sym):返回全局 Symbol 注册表中与特定 Symbol 关联的键。
    js
    let sym = Symbol.for("apple");
    console.log(Symbol.keyFor(sym)); // "apple"
    • toString():返回 Symbol 的字符串表示。
    js
    let sym = Symbol("desc");
    console.log(sym.toString()); // "Symbol(desc)"
    • valueOf():返回 Symbol 对象的原始值。
    js
    let sym = Symbol("example");
    console.log(sym.valueOf() === sym); // true

注意

  1. 唯一性:每个通过 Symbol() 函数创建的 Symbol 都是唯一的。即使你用相同的参数创建两个 Symbol,它们也是不相等的。
  2. 不可更改(immutable):Symbol 一旦被创建,就不能被修改。
  3. 不是构造函数:Symbol 不是构造函数,因此不能使用 new 关键字。尝试这样做会导致一个错误。
  4. 可选的描述:你可以给 Symbol 提供一个可选的描述(字符串),这在调试时很有用,但这个描述不影响 Symbol 的唯一性。
  5. 作为对象属性:Symbol 常用于作为对象属性的键。
  6. 不可枚举:用 Symbol 作为对象属性键的属性不会出现在普通的对象属性枚举中,例如 for...in 循环或 Object.keys()。但可以通过 Object.getOwnPropertySymbols() 获取。
  7. 全局注册表:Symbol 提供了一个全局注册表,通过 Symbol.for(key) 和 Symbol.keyFor(sym) 可以在全局环境中创建和共享 Symbol。

Object 类型

在 JavaScript 中,几乎所有的事物都是对象。这意味着除了原始类型(如数字、字符串和布尔值)以外,其他的数据类型都可以视为对象。对象通过 new 操作符后跟对象类型的名称(如:Object / Function / Array / Date / Number / String / Boolean等)来创建,一个对象可以被视为一个属性(properties)的集合,其中每个属性都有一个键(key)和对应的值(value)。

  1. 创建

    • 使用对象字面量:这是创建对象最简单和最常用的方式。
    js
    let person = {}
    person.name = 'Matt'
    person.age = 18
    js
    let person = {
      name:'Matt',
      age:18
    }

    TIP

    在对象字面量表示法中,属性名可以是字符串、数值或 Symbol 值。

    1. 字符串:

      • 属性名可以不加引号,如果它们是有效的JavaScript标识符。例如:{ firstName: "John" }。
      • 如果属性名包含空格、特殊字符或保留字,或者它的格式不是有效的标识符,必须用引号括起来,例如:{ "first name": "John", "2ndLevel": "advanced" }。
    2. 数字:数字可以作为属性名,但在对象字面量中,它们会被转换成字符串。例如:obj = { 42: "answer" } 等价于 obj = { "42": "answer" }。

    3. Symbol:Symbol 类型提供了一种方式来创建唯一的属性名,它是不可枚举的,有助于防止属性名的冲突。例如:

      js
      const uniqueId = Symbol("id");
      const obj = {
          [uniqueId]: "12345"
      };
    4. 动态属性名:可以在对象字面量中使用方括号 [] 来设置动态或计算后的属性名。例如:

      js
      let propName = "name";
      let obj = {
          [propName]: "John"
      };
    • 使用 new 关键字:使用 new 关键字和 Object 构造函数显式创建一个新对象。
    js
    let person = new Object()
    person.name = 'Matt'
    person.age = 18
    • 使用 Object.create() 方法:创建一个新对象,同时指定该对象的原型对象和属性。
    js
    let obj = Object.create(null) // 创建一个没有原型的空对象
    js
    let proto = { a:1 }
    let obj = Object.create(proto)
    • 使用工厂函数:创建一个函数,该函数封装了创建和初始化新对象的代码,然后返回新对象。
    js
    function createObj() {
      return {
        property1: 'value1',
        property2: 'value2'
      };
    }
    let obj = createObj();
    • 使用构造函数:创建一个构造函数,然后使用 new 关键字来创建对象的实例。
    js
    function MyObject() {
      this.property1 = 'value1';
      this.property2 = 'value2';
    }
    let obj = new MyObject();
  2. 操作符

    • 点操作符 (.):用于访问对象的属性或方法,如,obj.property 用于访问 obj 对象的 property 属性。
    • 方括号操作符 ([]):也用于访问对象的属性,允许使用变量或表达式作为属性名,例如 obj["property"] 或 obj[variable]。当属性名不是有效的标识符时(如包含空格或特殊字符),或者属性名是动态确定的,这种方式特别有用。
    • delete 操作符:用于删除对象的属性。如果删除成功或属性不存在,则返回 true;如果属性存在但无法删除,则返回 false。如,delete obj.property 用于删除 obj 对象的 property 属性。
    • in 操作符:用于检查对象是否具有给定的属性。如果对象自身或其原型链中有该属性,则返回 true。如,"property" in obj 检查 obj 是否有一个名为 property 的属性。
    • instanceof 操作符:用于测试一个对象是否是一个构造函数的实例。如果对象是指定构造函数的实例,则返回 true。如,obj instanceof Object 检查 obj 是否是 Object 的实例。
    • 展开运算符 (...):用于复制对象的属性到一个新对象中。例如,let newObj = {...obj} 创建了一个 obj 的浅拷贝。
    • new 操作符:用于创建一个用户自定义对象或内置对象的实例。与构造函数一起使用,例如 new Object() 或 new MyConstructor()。
  3. 属性

    • constructor:指向创建实例对象的构造函数。例如,如果你有一个对象 obj,那么 obj.constructor 就是用来创建 obj 的构造函数。
  4. 方法

    • Object.create(proto, [propertiesObject]):创建一个新对象,使用现有的对象来提供新创建的对象的原型。
    js
    let proto = {
      sayHello: function() {
        console.log('Hello!');
      }
    };
    
    let obj = Object.create(proto);
    obj.sayHello(); // 输出: 'Hello!'
    • Object.assign(target, ...sources):将一个或多个源对象的所有可枚举属性复制(浅复制)到目标对象,并返回目标对象。
    js
    let obj1 = { a: 1, b: 2 };
    let obj2 = { b: 3, c: 4 };
    let result = Object.assign({}, obj1, obj2);
    console.log(result); // { a: 1, b: 3, c: 4 }
    • Object.keys(obj):返回一个包含给定对象所有可枚举属性名称的数组。
    js
    let exampleObject = { a: 1, b: 2, c: 3 };
    let keys = Object.keys(exampleObject);
    console.log(keys); // 输出: ["a", "b", "c"]
    • Object.values(obj):返回一个包含给定对象所有可枚举属性值的数组。
    js
    let exampleObject = { a: 1, b: 2, c: 3 };
    let values = Object.values(exampleObject);
    console.log(values); // 输出: [1, 2, 3]
    • Object.entries(obj):返回一个包含给定对象自身所有可枚举属性键值对的数组。
    js
    let exampleObject = { a: 1, b: 2, c: 3 };
    let entries = Object.entries(exampleObject);
    console.log(entries); // 输出: [["a", 1], ["b", 2], ["c", 3]]
    • Object.fromEntries(iterable):将一个键值对列表转换为一个对象。这是 Object.entries() 的逆操作,常用于将 Map 或键值对数组转换回对象。
    js
    // 从数组创建对象:
    let entries = [['a', 1], ['b', 2], ['c', 3]];
    let obj = Object.fromEntries(entries);
    console.log(obj); // 输出: { a: 1, b: 2, c: 3 }
    
    // 从 Map 创建对象:
    let map = new Map([['a', 1], ['b', 2], ['c', 3]]);
    let obj = Object.fromEntries(map);
    console.log(obj); // 输出: { a: 1, b: 2, c: 3 }
    • Object.defineProperty(obj, prop, descriptor):在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
    js
    // 定义一个新属性:
    let obj = {};
    Object.defineProperty(obj, 'newProp', {
      value: 10,
      writable: false,
      enumerable: true,
      configurable: true
    });
    console.log(obj.newProp); // 输出: 10
    
    // 修改现有属性:
    let obj = { a: 1 };
    Object.defineProperty(obj, 'a', {
      value: 2,
      writable: true
    });
    console.log(obj.a); // 输出: 2
    • Object.defineProperties(obj, props):在一个对象上同时定义多个新属性或修改现有属性,并返回这个对象。
    js
    let obj = {};
    
    Object.defineProperties(obj, {
      'property1': {
        value: true,
        writable: true
      },
      'property2': {
        value: 'Hello',
        writable: false
      },
      'property3': {
        get: function() { return this.property2; }
      }
    });
    
    console.log(obj.property1); // 输出: true
    console.log(obj.property2); // 输出: 'Hello'
    console.log(obj.property3); // 输出: 'Hello'
    • Object.getOwnPropertyDescriptor(obj, prop):返回指定对象上一个自有属性对应的属性描述符。
    js
    let obj = { a: 1 };
    let descriptor = Object.getOwnPropertyDescriptor(obj, 'a');
    console.log(descriptor);
    // 输出: { value: 1, writable: true, enumerable: true, configurable: true }
    • Object.getOwnPropertyDescriptors(obj):返回指定对象上所有自有(非继承)属性的属性描述符。这包括每个属性的 value、writable、get、set、configurable 和 enumerable 特性。
    js
    let obj = {
      a: 1,
      get b() { return 2; }
    };
    let descriptors = Object.getOwnPropertyDescriptors(obj);
    console.log(descriptors);
    
    // 输出:
    // {
    //   a: {value: 1, writable: true, enumerable: true, configurable: true},
    //   b: {enumerable: true, configurable: true, get: [Function: get b], set: undefined}
    // }
    • Object.hasOwn(obj, prop):检查对象是否具有指定的自有属性。这是 hasOwnProperty 的现代替代方法,更安全,因为它不依赖于对象原型链上的 hasOwnProperty 方法。
    js
    let example = { a: 1 };
    console.log(Object.hasOwn(example, 'a')); // 输出: true
    console.log(Object.hasOwn(example, 'toString')); // 输出: false

    为什么使用 Object.hasOwn 而非 Object.prototype.hasOwnProperty

    使用 Object.hasOwn() 而不是 Object.prototype.hasOwnProperty() 的一个主要理由是安全性。如果对象是通过 Object.create(null) 创建的,或者有一个名为 hasOwnProperty 的自有属性,则 Object.prototype.hasOwnProperty() 可能无法正常工作。相比之下,Object.hasOwn() 作为一个静态方法,不会受到对象自有属性的影响。

    js
    let obj = Object.create(null);
    // obj.hasOwnProperty('a'); // 这将会抛出错误,因为 obj 没有 hasOwnProperty 方法
    console.log(Object.hasOwn(obj, 'a')); // 输出: false
    • hasOwnProperty(prop):判断对象自身(而不是其原型链上)是否具有指定的属性。
    js
    let example = { a: 1 };
    console.log(example.hasOwnProperty('a')); // 输出: true
    console.log(example.hasOwnProperty('toString')); // 输出: false
    • Object.getPrototypeOf(obj):用于获取指定对象的原型(即内部 [[Prototype]] 属性的值)
    js
    let example = new Date();
    let proto = Object.getPrototypeOf(example);
    
    console.log(proto === Date.prototype); // 输出: true

    __proto__ 的区别

    虽然 __proto__ 属性在许多浏览器中可用,但它不是标准属性,并且在现代JavaScript编程中应避免使用。Object.getPrototypeOf() 提供了一种标准且更可靠的方式来获取对象的原型。

    • Object.setPrototypeOf(obj, prototype):用于设置一个指定对象的原型(即内部 [[Prototype]] 属性)到另一个对象或 null。
    js
    let obj = {};
    let newPrototype = {
      sayHello: function() {
        console.log('Hello');
      }
    };
    
    Object.setPrototypeOf(obj, newPrototype);
    obj.sayHello(); // 输出: Hello
    • isPrototypeOf(object): 判断当前对象是否出现在另一个对象的原型链上。
    js
    function MyObject() {}
    let myInstance = new MyObject();
    
    console.log(MyObject.prototype.isPrototypeOf(myInstance)); // 输出: true

    与 instanceof 操作符的区别

    instanceof 检查一个对象是否是另一个对象的实例,而 isPrototypeOf 检查一个对象是否是另一个对象的原型。

    • propertyIsEnumerable(prop):判断指定的属性是否可枚举。
    js
    let example = { a: 1 };
    console.log(example.propertyIsEnumerable('a')); // 输出: true
    
    example.property1 = 'Hello';
    Object.defineProperty(example, 'property2', {
      value: 'World',
      enumerable: false // 设置为不可枚举
    });
    
    console.log(example.propertyIsEnumerable('property1')); // 输出: true
    console.log(example.propertyIsEnumerable('property2')); // 输出: false
    • Object.freeze(obj):冻结(浅冻结)一个对象。冻结后的对象再也不能被修改。
    js
    let obj = { a: 1 };
    Object.freeze(obj);
    
    obj.a = 2; // 无效操作,因为对象被冻结
    console.log(obj.a); // 输出: 1
    
    obj.b = 2; // 无效操作,因为对象被冻结
    console.log(obj.b); // 输出: undefined
    • Object.isFrozen(obj):判断一个对象是否被冻结。
    js
    let obj = { a: 1 };
    
    Object.freeze(obj);
    
    console.log(Object.isFrozen(obj)); // 输出: true
    • Object.seal(obj):封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。
    js
    let obj = { a: 1 };
    Object.seal(obj);
    
    obj.a = 2; // 有效操作,因为封闭的对象允许修改已有属性的值
    console.log(obj.a); // 输出: 2
    
    obj.b = 3; // 无效操作,因为不能向封闭的对象添加新属性
    console.log(obj.b); // 输出: undefined
    
    delete obj.a; // 无效操作,因为不能从封闭的对象删除属性
    console.log(obj.a); // 输出: 2
    • Object.isSealed(obj):判断一个对象是否被封闭。
    js
    let obj = { a: 1 };
    Object.seal(obj);
    
    console.log(Object.isSealed(obj)); // 输出: true
    • Object.preventExtensions(obj):阻止任何扩展一个对象。只影响对象自身的可扩展性,不影响其原型链。
    js
    let obj = { a: 1 };
    Object.preventExtensions(obj);
    
    obj.b = 2; // 无效操作,因为对象不可扩展
    console.log(obj.b); // 输出: undefined
    
    obj.a = 3; // 有效操作,现有属性可以被修改
    console.log(obj.a); // 输出: 3
    
    delete obj.a; // 有效操作,现有属性可以被删除
    console.log(obj.a); // 输出: undefined
    • Object.isExtensible(obj):判断一个对象是否是可扩展的。只判断对象自身的可扩展性,不判断其原型链。
    js
    let obj = {};
    console.log(Object.isExtensible(obj)); // 输出: true
    
    Object.preventExtensions(obj);
    console.log(Object.isExtensible(obj)); // 输出: false

    与 Object.preventExtensions, Object.seal, Object.freeze 相关

    如果对象已经被 Object.preventExtensions(), Object.seal(), 或 Object.freeze() 处理,那么它将不再是可扩展的。

    • Object.is(value1, value2):判断两个值是否是同一个值。
    js
    console.log(Object.is(25, 25)); // 输出: true
    console.log(Object.is('foo', 'foo')); // 输出: true
    console.log(Object.is('foo', 'bar')); // 输出: false
    console.log(Object.is(null, null)); // 输出: true
    console.log(Object.is(undefined, undefined)); // 输出: true
    console.log(Object.is(window, window)); // 输出: true
    console.log(Object.is([], [])); // 输出: false
    
    // 特殊案例
    console.log(Object.is(NaN, NaN)); // 输出: true
    console.log(NaN === NaN); // 输出: false
    console.log(Object.is(0, -0)); // 输出: false
    console.log(0 === -0); // 输出: true
    • Object.getOwnPropertyNames(obj):返回一个由指定对象的所有自有属性的属性名组成的数组,不考虑属性是否可枚举。
    js
    let obj = Object.create({}, {
      getFoo: {
        value: function() { return this.foo; },
        enumerable: false
      }
    });
    obj.foo = 1;
    
    let propertyNames = Object.getOwnPropertyNames(obj);
    console.log(propertyNames); // 输出: ["foo", "getFoo"]

    与 Object.keys() 的区别

    Object.keys() 只返回对象的可枚举属性,而 Object.getOwnPropertyNames() 返回所有自有属性,无论它们是否可枚举。

    • Object.getOwnPropertySymbols(obj):返回一个数组,包含指定对象自己的(非继承的)所有符号属性。
    js
    let sym1 = Symbol('foo');
    let sym2 = Symbol('bar');
    let obj = {
        [sym1]: 'value1',
        [sym2]: 'value2'
    };
    
    let symbols = Object.getOwnPropertySymbols(obj);
    console.log(symbols); // 输出: [Symbol(foo), Symbol(bar)]
    • toLocaleString([locales [, options]]):返回对象的本地化字符串表示。
    js
    let number = 1337.79;
    console.log(number.toLocaleString('de-DE')); // 在德语环境中输出: "1.337,79"
    
    let date = new Date();
    console.log(date.toLocaleString('en-US')); // 在美国英语环境中输出日期,格式可能为: "12/31/2022, 2:24:38 PM"
    • toString():返回对象的字符串表示。
    js
    let obj = {};
    console.log(obj.toString()); // 输出: "[object Object]"
    
    let arr = [1, 2, 3];
    console.log(arr.toString()); // 输出: "1,2,3"
    
    let date = new Date();
    console.log(date.toString()); // 输出: 日期的字符串表示,例如 "Mon Apr 12 2021 22:00:00 GMT+0200 (Central European Summer Time)"
    
    // 重写 toString()
    function Person(name) {
        this.name = name;
    }
    
    Person.prototype.toString = function() {
        return "Person named " + this.name;
    };
    
    let alice = new Person("Alice");
    console.log(alice.toString()); // 输出: "Person named Alice"
    • valueOf():返回对象的原始值。
    js
    function MyNumberType(n) {
        this.number = n;
    }
    
    MyNumberType.prototype.valueOf = function() {
        return this.number;
    };
    
    let myObj = new MyNumberType(4);
    console.log(myObj + 3); // 输出: 7

数据类型转换

  1. 显式转换
    • 字符串转换:
      • 使用String()函数。
      • 使用.toString()方法。
    • 数字转换:
      • 使用Number()函数进行转换。
      • 使用一元加号+(如+variable)。
      • 使用parseInt()和parseFloat()用于从字符串转换。
    • 布尔转换:
      • 使用Boolean()函数进行转换。
  2. 隐式转换
    • 字符串和数字:
      • 加法操作符(+)会将数字转换为字符串。
      • 其他算术操作符(如-, *, /)会将字符串转换为数字。
    • 布尔值转换为数字:
      • 在算术操作中,true自动转换为1,false转换为0。
    • 对象转换为原始类型:
      • 对象在需要原始类型值时,会调用toString()或valueOf()方法。
js
let value = "5";

// 显式转换为数字
let number = Number(value); // number = 5

// 隐式转换为数字
let implicitNumber = value * 1; // implicitNumber = 5

// 显式转换为字符串
let string = String(number); // string = "5"

// 隐式转换为字符串
let implicitString = number + ""; // implicitString = "5"

// 转换为布尔值
let booleanValue = Boolean(value); // booleanValue = true

TIP

  • 除了空字符串、0、0n、NaN、null、undefined 为 false,其他均为 true。
  • null和undefined在数字转换中有特殊行为:Number(null)转换为0,而Number(undefined)转换为NaN。

操作符

操作符通常会调用 valueOf()、toString()方法来取得可以计算的值 。

一元操作符

  1. 递增/递减操作符:++--

    • 前缀版:先运算后赋值
    • 后缀版:先赋值后运算
    • 字符串:有效数值,先转换后应用且变为数值类型;无效数值,转换为 NaN 且变为数值类型
    • 布尔值:false,先转为 0 后应用且变为数值类型;true,先转为 1 后应用且变为数值类型
    • 浮点值 :加 1 或减 1
    • 对象:调用 valueOf()方法取得可操作的值 (若是 NaN,则调用 toString()方法后应用)且变为数值类型
  2. 一元加和减:+-

    • 一元负值符(-):返回操作数的负值
    • 一元正值符(+):如果操作数在之前不是 number,试图将其转换为 number
    • 字符串:有效数值,先转换后应用且变为数值类型;无效数值,转换为 NaN 且变为数值类型
    • 布尔值:false,先转为 0 后应用且变为数值类型;true,先转为 1 后应用且变为数值类型
    • 浮点值 :加 1 或减 1
    • 对象:调用 valueOf()方法取得可操作的值 (若是 NaN,则调用 toString()方法后应用)且变为数值类型
  3. delete:用于删除对象的属性。

  4. void:用于执行一个表达式并返回 undefined。

  5. typeof:用于确定一个变量或表达式的类型。

位操作符

  1. 按位非(~):对数字的每一位进行取反。最终效果等同于对原数值取反并减 1。
  2. 按位与(&):对两个数的位进行AND操作。只有两个相应的位都为1时,结果位才为1。
  3. 按位或(|):对两个数的位进行OR操作。只要两个相应的位中有一个为1时,结果位就为1。
  4. 按位异或(^):对两个数的位进行XOR操作。当两个相应的位不同(一个为1,一个为0)时,结果位为1。
  5. 左移(<<):将第一个操作数的二进制表示向左移动指定位数。保留符号位(即正负符号)。
  6. 有符号右移(>>):将第一个操作数的二进制表示向右移动指定位数。保留符号位(即正负符号)。
  7. 无符号右移(>>>):将第一个操作数的二进制表示向右移动指定位数。不保留符号位,左边空出的位用0填充。对于非负数,>>>和>>效果相同。

布尔操作符

  1. 逻辑非(!)

    • 对象:false
    • 空字符串:true
    • 非空字符串:false
    • 数值 0:true
    • 非 0 数值(包括 infinity):false
    • null:true
    • NaN:true
    • undefined:true
  2. 逻辑与(&&)

    • 第一个操作数是对象,则返回第二个操作数
    • 第二个操作数是对象,则只有第一个操作数求值为 true 才会返回该对象
    • 两个操作数都是对象,则返回第二个操作数
    • 有一个操作数是 null,则返回 null
    • 有一个操作数是 NaN,则返回 NaN
    • 有一个操作数是 undefined,则返回 undefined
    • 表达式expr1 && expr2返回expr1的值如果它可以转换为false;否则返回expr2的值(短路行为)。
  3. 逻辑或(||)

    • 第一个操作数是对象,则返回第一个操作数
    • 第一个操作数求值为 false,则返回第二个操作数
    • 两个操作数都是对象,则返回第一个操作数
    • 两个操作数是 null,则返回 null
    • 两个操作数是 NaN,则返回 NaN
    • 两个操作数是 undefined,则返回 undefined
    • 表达式expr1 || expr2返回expr1的值如果它可以转换为true;否则返回expr2的值(短路行为)。

算术操作符

  1. 加法(+)

    • 如果有任一操作数是 NaN ,则返回 NaN 。
    • 如果是 Infinity 加 Infinity ,则返回 Infinity 。
    • 如果是 -Infinity 加 -Infinity ,则返回 -Infinity 。
    • 如果是 Infinity 加 -Infinity ,则返回 NaN 。
    • 如果是 +0 加 +0 ,则返回 +0 。
    • 如果是 -0 加 +0 ,则返回 +0 。
    • 如果是 -0 加 -0 ,则返回 -0 。
    • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面。
    • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起。
    • 如果有任一操作数是对象、数值或布尔值,则调用 toString() 方法以获取字符串,然后再应用上述规则。
  2. 减法(-)

    • 如果两个操作数都是数值,则执行数学减法运算并返回结果。
    • 如果有任一操作数是 NaN ,则返回 NaN 。
    • 如果是 Infinity 减 Infinity ,则返回 NaN 。
    • 如果是 -Infinity 减 -Infinity ,则返回 NaN 。
    • 如果是 Infinity 减 -Infinity ,则返回 Infinity 。
    • 如果是 -Infinity 减 Infinity ,则返回 -Infinity 。
    • 如果是 +0 减 +0 ,则返回 +0 。
    • 如果是 +0 减 -0 ,则返回 -0 。
    • 如果是 -0 减 -0 ,则返回 +0 。
    • 如果有任一操作数是字符串、布尔值、null 或 undefined ,则先在后台使用 Number() 函数将其转换为数值,然后再应用上述规则。如果转换结果是 NaN ,则返回 NaN 。
    • 如果有任一操作数是对象,则调用其 valueOf() 方法取得表示它的数值。如果该值是 NaN ,则返回 NaN 。如果对象没有 valueOf() 方法,则调用其 toString() 方法,然后再将字符串转换为数值。
  3. 乘法(*)

    • 如果操作数都是数值,则执行常规的乘法运算,即两个正值相乘是正值,两个负值相乘也是正值,正负符号不同的值相乘是负值。如果不能表示乘积,则返回 Infinity 或 - Infinity 。
    • 如果有任一操作数是 NaN,则返回 NaN 。
    • 如果是 Infinity 乘以 0 ,则返回 NaN 。
    • 如果是 Infinity 乘以非 0 的有限数值,则根据第二个操作数的符号返回 Infinity 或 - Infinity 。
    • 如果是 Infinity 乘以 Infinity ,则返回 Infinity 。
    • 如果有不是数值的操作数,则先在后台用 Number() 函数将其转换为数值,然后再应用上述规则。
  4. 除法(/)

    • 如果操作数都是数值,则执行常规的除法运算,即两个正值相除是正值,两个负值相除也是正值,正负符号不同的值相除是负值。如果不能表示商,则返回 Infinity 或 - Infinity 。
    • 如果有任一操作数是 NaN,则返回 NaN 。
    • 如果是 Infinity 除以 Infinity ,则返回 NaN 。
    • 如果是 0 除以 0 ,则返回 NaN 。
    • 如果是非 0 的有限值除以 0 ,则根据第一个操作数的符号返回 Infinity 或 - Infinity 。
    • 如果是 Infinity 除以任何数值,则根据第二个操作数的符号返回 Infinity 或 -Infinity 。
    • 如果有不是数值的操作数,则先在后台用 Number() 函数将其转换为数值,然后再应用上述规则。
  5. 取模(%)

    • 如果操作数是数值,则执行常规除法运算,返回余数。
    • 如果被除数是无限值,除数是有限值,则返回 NaN 。
    • 如果被除数是有限值,除数是 0 ,则返回 NaN 。
    • 如果是 Infinity 除以 Infinity ,则返回 NaN 。
    • 如果被除数是有限值,除数是无限值,则返回被除数。
    • 如果被除数是 0 ,除数不是 0 ,则返回 0 。
    • 如果有不是数值的操作数,则先在后台用 Number() 函数将其转换为数值,然后再应用上述规则。
  6. 指数(**)

    • 计算一个数的指数幂。这个操作符在ES2016(也称为ES7)中被引入,提供了一种比以前使用Math.pow()更简洁的方法来执行指数运算。

比较操作符

  1. 关系(> >= < <=)

    • 如果操作数都是数值,则执行数值比较。
    • 如果操作数都是字符串,则逐个比较字符串中对应字符的编码。
    • 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较。
    • 如果有任一操作数是对象,则调用其 valueOf() 方法,然后再应用上述规则。如果没有 valueOf() 方法,则调用 toString() 方法,然后再应用上述规则。
    • 如果有任一操作数是布尔值,则将其转换为数值再执行比较。
  2. 相等(== !=)

    • 如何任一操作数是布尔值,则将其转换为数值再比较是否相等。false 转换为 0 ,true 转换为 1 。
    • 如果一个操作数是字符串,另一个操作数是数值,则尝试将字符串转换为数值,再比较是否相等。
    • 如果一个操作数是对象,另一个操作数不是,则调用 valueOf() 方法,然后再应用上述规则。
    • null 和 undefined 相等。
    • null 和 undefined 不能转换为其他类型的值再进行比较。
    • 如果有任一操作数是 NaN ,则相等返回 false ,不相等返回 true 。
    • 如果两个操作数都是对象,则比较是不是同一个对象。
  3. 全等(=== !==):与相等类似,但在比较时不转换操作数。

  4. in:用于检查对象是否拥有特定的属性。

  5. instanceof:用于检查一个对象是否是一个特定构造函数的实例,或者是否存在于该构造函数的原型链上。

条件操作符

如果 condition 为真,则结果取 value1。否则为 value2。

javascript
condition ? value1 : value2

赋值操作符

将右边操作数的值赋给左边的变量。

=+=-=*=/=%=**=<<=>>=>>>=&=^=|=

解构赋值

  1. 解构对象

    • 基础解构

      js
      let {name,age} = {name:"张三",age:22};
    • 变量别名

      js
      let name="王五";
      let {name:newName,age} = {name:"张三",age:22};
    • 变量默认值

      js
      let {name,age,gender="男"} = {name:"张三",age:22};
  2. 解构数组

    • 基础解构

      js
      let [str1,str2,str3] = ["HTML5","JavaScript","Vue","React","NodeJS"];
    • 选择解构

      js
      let [ , , , value1 ,value2] = ["HTML5","JavaScript","Vue","React","NodeJS"];
    • 扩展运算符

      js
      let [value,...other] = ["HTML5","JavaScript","Vue","React","NodeJS"];
    • 默认值

      js
      let [v1,v2,v3="Vue"] = ["HTML5","JavaScript"];
    • 交换变量值

      js
      let name1 = "张三";
      let name2 = "李四";
      [name2,name1] = [name1,name2];
  3. 解构嵌套

    • 浅层解构

      js
      let {works} = {
        name:"坤坤",
        age:25,
        like:['唱','跳','rap','篮球'],
        works:{music:['Wait Wait Wait','鸡你太美'],movies:['童话二分之一','鬼畜区常青树']},
        friend:['丞丞','大宝贝','大黑牛']
      }
    • 连续解构

      js
      let {works:{music}} = {
        name:"坤坤",
        age:25,
        like:['唱','跳','rap','篮球'],
        works:{music:['Wait Wait Wait','鸡你太美'],movies:['童话二分之一','鬼畜区常青树']},
        friend:['丞丞','大宝贝','大黑牛']
      }
    • 数组与对象混用

      js
      let {works:{music:[,result]}} = {
        name:"坤坤",
        age:25,
        like:['唱','跳','rap','篮球'],
        works:{music:['Wait Wait Wait','鸡你太美'],movies:['童话二分之一','鬼畜区常青树']},
        friend:['丞丞','大宝贝','大黑牛']
      }
    • 解构+别名

      js
      let {works:{music:[,result],movies:newMov}} = {
        name:"坤坤",
        age:25,
        like:['唱','跳','rap','篮球'],
        works:{music:['Wait Wait Wait','鸡你太美'],movies:['童话二分之一','鬼畜区常青树']},
        friend:['丞丞','大宝贝','大黑牛']
      }

逗号操作符

在一条语句中执行多个操作,也可以辅助赋值,在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值。

javascript
let num1 = 1, num2 = 2, num3 = 3

let num = (5, 1, 4, 8, 0) //num的值为0

优先级

优先级运算符
1. []
2() new
3! ~ - + ++ -- typeof void delete
4\* / %
5+ -
6< <= > >= in instanceof
7== != === !==
8&&
9||
10? :
11= += -= \*= /= %=

语句

if

javascript
if (condition) {
  statement
}
javascript
if (condition) {
  statement_1
} else {
  statement_2
}
javascript
if (condition_1) {
  statement_1
} else if (condition_2) {
  statement_2
} else if (condition_n_1) {
  statement_n
} else {
  statement_last
}

TIP

false、undefined、null、0、NaN、空字符串将被计算出为假( false),当传递给条件语句所有其他的值,包括所有对象会被计算为真 (true)。

do...while

javascript
do {
  statement
} while (condition)

while

javascript
while (condition) {
  statement
}

for

javascript
for ([initialExpression]; [condition]; [incrementExpression]) {
  statement
}

for...in

javascript
for (variable in object) {
  statements
}

TIP

  • 用于遍历对象的可枚举属性。
  • 在每次迭代中,将变量绑定到对象的每个可枚举属性上。
  • 可以遍历对象自身的属性以及继承的属性(通过原型链)。
  • 通常用于遍历普通对象的属性,不适用于遍历数组或类似数组的对象。
  • 迭代顺序不保证,可能会出现无序遍历。

for...of

javascript
for (variable of collection) {
  statement
}

TIP

  • 用于遍历可迭代对象的元素。
  • 在每次迭代中,将变量绑定到可迭代对象的每个元素上。
  • 可以遍历数组、字符串、Set、Map、类数组对象(如 arguments 对象)等可迭代对象。
  • 不会遍历对象的属性,只遍历对象的值。
  • 迭代顺序与可迭代对象的迭代器(Iterator)实现有关,通常是按照插入顺序或特定顺序进行。

label

javascript
label: statement

continue

javascript
continue [label];

break

javascript
break [label];

switch

javascript
switch (expression) {
  case label_1:
      statements_1
      [break;]
  case label_2:
      statements_2
      [break;]
  ...
  default:
      statements_def
      [break;]
}

Block

block 语句(又称为块语句)是由一对花括号 {} 包围的一组语句。

js
{
  let foo = 'foo'
}

Empty

Empty 语句是指一个单独的分号 ; ,它不执行任何操作。但在某些特定的情况下,它可以作为一种语法占位符。

js
let i = 0;
for (; i < 5; i++) {
    // 初始化部分被省略
}

throw

throw 语句用于抛出异常。当使用 throw 抛出一个错误时,程序的正常执行流程会被中断,并且将控制流转移到最近的异常处理程序(catch 代码块)。

js
throw "Error occurred";  // 抛出一个字符串错误
throw new Error("Error occurred");  // 抛出一个 Error 对象

try...catch

try...catch 语句用于捕获和处理在 try 块中抛出的异常。

js
try {
    // 尝试执行的代码
} catch (error) {
    // 处理异常的代码
} finally {
    // 无论是否有异常都会执行的代码
}

debugger

debugger 语句用作一个断点,当运行代码的 JavaScript 引擎支持调试功能时,它会在执行到这一行时暂停执行。

js
function potentiallyBuggyFunction() {
    // 一些代码
    debugger;  // 在这里暂停
    // 更多代码
}

import

import 语句用于从其他模块中导入绑定(即变量、函数、类等)。这是 ES6(ECMAScript 2015)引入的模块功能的一部分,允许你创建可维护、可重用和封装良好的代码。

  • 导入整个模块:
js
import * as myModule from '/path/to/module.js';
  • 导入特定的绑定:
js
import { myFunction, myVariable } from '/path/to/module.js';
  • 导入默认导出:
js
import myDefault from '/path/to/module.js';
  • 结合默认导出和其他导出:
js
import myDefault, { myFunction, myVariable } from '/path/to/module.js';
  • 导入时重命名绑定:
js
import { myFunction as func, myVariable as var } from '/path/to/module.js';
  • 只执行模块以获取副作用(不导入任何绑定):
js
import '/path/to/module.js';

注意事项

  • 静态导入: import 语句是静态的,这意味着它们不能在块级作用域(如 if 语句或函数内部)中使用,且必须出现在模块的顶层。
  • 模块路径: 路径可以是相对路径或绝对路径,也可以是 URL。路径通常以 .js 结尾,尽管有些模块加载器和打包工具允许省略扩展名。
  • 模块上下文: 使用 import 导入的代码在模块作用域中运行,这意味着模块内声明的变量、函数、类等不会自动成为全局作用域的一部分。
  • 浏览器兼容性: 尽管现代浏览器大多支持 ES6 模块,但在旧浏览器中可能需要转换工具(如 Babel)和模块打包工具(如 Webpack)。
  • 动态导入: ES2020 引入了动态导入语法,允许你根据需要异步地加载模块,例如:import('/path/to/module.js').then(module => { /*使用模块*/ })

模块

导出

  1. 命名导出:在模块中,通过export关键字将变量、函数、类等导出,使其在其他模块中可用。一个模块文件可以有多个命名导出。

    js
    // module1.js
    export const foo = 'Hello';
    export function bar() {
      console.log('World');
    }
  2. 默认导出:可以使用export default语法导出模块的默认值。一个模块文件只能有一个默认导出。

    js
    // module2.js
    const baz = 'Default export';
    export default baz;

导入

  1. 命名导入:在其他模块中使用import语句导入被命名导出的变量、函数、类等。

    js
    // main.js
    import { foo, bar } from './module1';
    console.log(foo); // 输出: Hello
    bar(); // 输出: World
  2. 默认导入:对于默认导出的模块,可以使用import语句导入。

    js
    // main.js
    import baz from './module2';
    console.log(baz); // 输出: Default export
  3. 别名导入:可以使用别名来导入模块的成员,通过as关键字。

    js
    // main.js
    import { foo as myFoo, bar as myBar } from './module1';
    console.log(myFoo); // 输出: Hello
    myBar(); // 输出: World
  4. 导入整个模块:使用* as语法导入整个模块的所有成员。

    js
    // main.js
    import * as myModule1 from './module1';
    console.log(myModule1.foo); // 输出: Hello
    myModule1.bar(); // 输出: World
  5. 动态导入:可以使用import()函数进行动态导入,返回一个Promise。

    js
    // main.js
    const modulePath = './module1';
    
    import(modulePath)
      .then(myModule => {
        console.log(myModule.foo); // 输出: Hello
        myModule.bar(); // 输出: World
      })
      .catch(error => console.log('Error:', error));

标签

  1. 导入:在HTML中,可以使用<script type="module">标签引入JavaScript模块。

    js
    <!-- index.html -->
    <script type="module" src="main.js"></script>
  2. 写入

    js
    <script type="module">
      console.log("This is module code.");
    </script>

标准内置对象

JavaScript的标准内置对象包括一系列构造器(用于创建数据类型)和非构造器对象。

构造器对象

这些对象是函数,用于创建新的对象或具有特定类型的数据结构。

Function 函数

在JavaScript中,Function 对象是JavaScript的每个函数的基础。它允许你定义函数、调用函数,并为所有函数提供一些基本的属性和方法。

  1. 创建

    • 函数声明:会在任何代码执行之前先被读取并添加到执行上下文(又称“函数声明提升”)

      js
      function myFunction(a, b) {
          return a + b;
      }
    • 函数表达式:不会在任何代码执行之前先被读取并添加到执行上下文

      js
      const myFunction = function(a, b) {
          return a + b;
      };
    • 箭头函数:

      js
      const myFunction = (a, b) => a + b;

      TIP

      • 不支持 this 指向。
      • 如果只有一个参数,可以不用括号;只有没有参数,或者多个参数的情况下,才需要使用括号。
      • 如果只有一条语句,可以不用花括号且会隐式返回这行代码的值;只有多条语句的情况下,才需要使用花括号。
      • 不能使用 arguments、super、和 new.target,也不能用作构造函数,也没有 prototype 属性。
    • 构造器:

      js
      const myFunction = new Function('a', 'b', 'return a + b');
  2. 参数

    • 默认参数

      js
      function myFunction(x = 10, y = 20) {
          return x + y;
      }
      
      console.log(myFunction());       // 输出 30
      console.log(myFunction(5));      // 输出 25
      console.log(myFunction(5, null)); // 输出 5,因为null不等于undefined
      • 如果没有显式传值或传递的参数值为undefined,将使用默认参数值。如果传递null或其他“假值”(如0、''等),则不会触发默认参数。
      • arguments 对象的值不反映参数的默认值,只反映传给函数的参数。
      • 箭头函数也可以使用默认参数,但必须使用括号。
    • ...rest 参数(也称为剩余参数)

      js
      function myFuncWithRest(...rest) {
          console.log(rest.length); // 显示参数个数
          rest.forEach(arg => {
              console.log(arg); // 显示每个参数
          });
      }
      
      myFuncWithRest(1, 'hello', true); // 输出 3, 1, 'hello', true
      • 必须位于参数列表中的最后一个参数
      • 在箭头函数中也可以使用 ...rest 参数。
      • 实参多形参少,只接收形参一一对应的实参,多余实参忽略
      • 实参少形参多,只接收形参一一对应的实参,多余形参接收 undefined
      • 参数按顺序初始化,后定义默认值的参数可以引用先定义的参数

      与扩展运算符的区别

      • 扩展运算符用于展开数组或对象。
      • ...rest 参数用于收集函数中传递的额外参数到一个数组中。
  3. 内部对象与属性

    • arguments 对象

      js
      function myFunc() {
          console.log(arguments.length); // 显示传递给函数的参数个数
          for (let i = 0; i < arguments.length; i++) {
              console.log(arguments[i]); // 显示每个参数
          }
      }
      
      myFunc(1, 'hello', true); // 输出 3, 1, 'hello', true
      • arguments 提供了对传递给函数的每个参数的访问。
      • 只有以 function 关键字定义函数时才会有。
      • arguments 是类数组对象,但不是真正的数组。它具有一个 length 属性,但没有数组的方法,如 map、filter 或 reduce。
      • 箭头函数中没有 arguments 对象,但可以在包装函数中把它提供给箭头函数。
      • arguments.callee 属性是一个指向 arguments 对象所在函数的指针,用于引用该函数自身。
      • 在严格模式下,使用 arguments.callee 会抛出错误。
      • 在严格模式下,不会建立arguments和命名参数间的动态关系。
      • 在非严格模式下,修改 arguments 对象中的值会影响对应的命名参数。

      与 ...rest 参数的比较

      • arguments 提供了函数调用时传递的所有参数,包括那些没有被命名参数接收的。
      • ...rest 参数(剩余参数语法)是ES6的新特性,允许将一个不定数量的参数表示为一个数组。
    • this 对象

      • 全局上下文中:

        • 在全局执行上下文(浏览器中的全局代码)中,this 指向全局对象:在浏览器中是 window,在Node.js中是 global。
      • 函数中:

        • 在普通函数(函数声明)调用中,this 的值指向全局对象(非严格模式)或 undefined(严格模式)。
      • 对象方法中:

        • 当函数作为对象的方法被调用时,this 指向该方法所属的对象。
      • 构造函数中:

        • 在构造函数内部,this 指向新创建的对象。
      • 箭头函数中:

        • 箭头函数不绑定自己的 this,它继承自封闭执行上下文的 this 值。
      • 事件处理器中:

        • 在事件监听函数中,this 通常指向触发事件的元素。
      • 显式设置 this:

        • call() 方法调用一个函数,其第一个参数将成为函数体内的this,其余参数将直接传递给函数。
        js
        function greet() {
            console.log(`Hello, I am ${this.name}`);
        }
        
        const user = { name: 'Alice' };
        
        greet.call(user); // 输出: Hello, I am Alice
        • apply() 方法的工作方式与call()类似,唯一的区别是apply()接收两个参数:第一个参数是this的值,第二个参数是一个数组,数组中的元素将作为单独的参数传递给函数。
        js
        function sum(a, b) {
            return a + b;
        }
        
        console.log(sum.apply(null, [1, 2])); // 输出: 3
        • bind() 方法创建一个新函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
        js
        function fn() {
            console.log(this.name);
        }
        
        const bar = fn.bind({name: "Alice"});
        bar(); // 输出: Alice

        区别和选择

        • call():当你知道参数的数量并且想立即调用函数时使用。
        • apply():在参数数量未知或作为数组传递时使用。
        • bind():当你不想立即调用函数,而是稍后再调用,并且想预设this值和初始参数时使用。
    • new.target 属性

      检测构造函数和类构造函数是否是通过 new 运算符被调用的。在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined。

      js
      function MyFunction() {
          if (!new.target) {
              throw 'MyFunction 必须通过 new 调用';
          }
          console.log('通过 new 调用');
      }
      
      // 正确使用 new
      let obj = new MyFunction(); // 输出 "通过 new 调用"
      
      // 直接调用函数将抛出错误
      // MyFunction(); // 抛出错误
  4. 作用域

    • 函数作用域是指变量和函数在函数内部被声明时,它们只能在该函数内部被访问和修改。
    • 函数内部声明的变量(私有变量)对于外部作用域是不可见的,这有助于防止全局作用域的污染。
  5. 闭包 Closure

    js
    function outerFunction() {
        let outerVariable = 'I am outside!';
    
        function innerFunction() {
            console.log(outerVariable);
        }
    
        return innerFunction;
    }
    
    const myInnerFunction = outerFunction();
    myInnerFunction(); // 输出: "I am outside!"
    • 基本概念
      • 闭包是由两部分组成的:一个函数以及该函数创建时的词法环境。词法环境包含了在函数创建时作用域中的所有局部变量。
    • 如何形成
      • 函数嵌套:在一个函数内部定义另一个函数。
      • 外部变量的引用:内部函数引用了其外部(封闭)函数的变量。
    • 工作原理
      • 当一个函数被定义时,它的词法作用域(即它被定义的位置)决定了它可以访问哪些变量。
      • 即使外部函数已经返回,内部函数仍然可以访问外部函数的变量。这是因为函数保留了它们被创建时的作用域链。
    • 闭包特点
      • 函数嵌套:闭包通常涉及一个函数内部定义另一个函数。
      • 访问外部变量:内部函数可以访问定义它的外部函数的变量。
      • 记住环境:即使外部函数已经返回,内部函数仍然可以访问外部函数的变量。这是因为函数保留了它们被创建时的作用域链。
    • 闭包用途
      • 封装私有变量:闭包可以在对象外部隐藏变量,提供对象私有性。
      • 模块模式:使用闭包可以创建模块,其中包含私有状态和控制暴露的公共接口。
      • 函数工厂:闭包可以用来动态创建函数。
      • 记忆化(Memoization):闭包允许函数记住之前的计算结果。

    注意事项

    • 内存泄漏:由于闭包可以长时间保持对外部变量的引用,因此可能导致内存泄漏。需要确保不再需要的闭包被适当地处理(将变量、对象属性或数组元素设置为null或undefined)。
    • 性能考虑:虽然闭包非常有用,但滥用闭包可能会导致性能问题,尤其是在涉及大量数据或频繁调用时。
  6. 尾调用

    尾调用是指一个函数的最后一个动作是返回另一个函数的调用。这种调用方式特别的之处在于不需要维护自己的堆栈帧(函数用完即删),这使得尾调用在资源使用上非常高效。

    js
    'use strict'
    
    function outerFn(a, b) {
      return innerFn(a + b)
    }
    
    function outerFn(a, b) {
      if(a < b) return a
      return innerFn(a + b)
    }
    
    function outerFn(condition) {
      return condition ? innerFn1() : innerFn2()
    }

    尾调用优化(简称TCO)

    优化条件:

    • 代码在严格模式下执行
    • 外部函数的返回值是对尾调用函数的调用
    • 尾调用函数返回后不需要执行额外的逻辑
    • 尾调用函数不是引用外部函数作用域中自由变量的闭包
  7. 递归

    • 函数名
    js
    function factorial(n) {
        if (n === 0 || n === 1) {
            return 1;
        } else {
            return n * factorial(n - 1)
        }
    }
    • 函数表达式
    js
    const factorial = function foo(n) {
        if (n === 0 || n === 1) {
            return 1;
        } else {
            return n * foo(n - 1);
        }
    }
    • arguments.callee()
    js
    function factorial(n) {
        if (n === 0 || n === 1) {
            return 1;
        } else {
            return n * argument.callee(n - 1)
        }
    }
    • 尾递归
    js
    function factorial(n, acc = 1) {
        if (n <= 1) return acc;
        return factorial(n - 1, n * acc)
    }
  8. 立即调用函数表达式

    立即调用函数表达式(Immediately Invoked Function Expression,简称IIFE)是一种在JavaScript中定义并立即执行函数的模式。IIFE通常用于创建一个独立的作用域,这对于避免变量污染全局作用域非常有用。

    javascript
    //方法1
    (function() { })();
    (() => { })();
    
    //方法2
    (function(){ }())
    
    //方法3
    !function(){ }()
    +function(){ }()
    -function(){ }()
    ~function(){ }()

    TIP

    立即调用函数表达式只会执行一次,之后会被销毁。

  9. 回调函数

    js
    // 一个简单的回调函数示例
    function fetchData(callback) {
        setTimeout(() => {
            callback("数据加载完成");
        }, 1000);
    }
    
    fetchData((data) => {
        console.log(data); // 1秒后打印 "数据加载完成"
    });
    • 回调函数的本质
      • 函数作为值:在JavaScript中,函数是"一等公民"。这意味着函数可以像其他值一样被赋值给变量,作为参数传递,或者作为其他函数的返回值。回调函数正是基于这一概念,它是一个被传递到另一个函数中并在那里被调用的函数。
      • 延迟执行:回调函数通常用于实现延迟执行。也就是说,你可以控制何时调用这个函数,这对于异步编程尤其重要。
    • 异步编程中的回调函数
      • 事件处理:在事件驱动的编程模型中(如用户交互、网络请求等),回调函数用于定义当特定事件发生时应该执行的操作。
      • 避免阻塞:JavaScript是单线程的,意味着同一时间只能执行一个操作。通过使用回调函数处理异步操作(如定时器、HTTP请求等),我们可以避免阻塞主线程。
    • 回调函数的问题和解决方案
      • 回调地狱:深层嵌套的回调函数会导致代码难以阅读和维护,这通常被称为“回调地狱”。
      • 解决方案:为了解决这个问题,现代JavaScript引入了Promise和async/await。它们提供了更好的错误处理和更清晰的代码结构。
    • 实际应用
      • 高阶函数:例如,数组的.map(), .filter(), .reduce()等方法都接受回调函数,用于操作数组的每个元素。
      • 自定义回调:在自己的函数中使用回调,可以让这些函数变得更加灵活和动态。
  10. 返回值

    • 使用 return 关键字:
      • return 关键字用于从函数中返回一个值。一旦函数执行到 return 语句,函数就会立即停止执行,并返回指定的值。
    • 默认返回值:
      • 如果函数没有显式地使用 return 语句,或者 return 后面没有任何值,那么函数默认返回 undefined。
    • 返回函数:
      • 函数可以返回另一个函数,这是高阶函数和闭包概念的基础。 :::waring 当 return 后面有多个值则返回最后一个值。 :::
  11. 函数简写

    • 箭头函数
    js
    const myFunction = (arg1, arg2) => {
        return arg1 + arg2;
    };
    // 如果函数体只有一行表达式,可以省略花括号和return关键字:
    const myFunction = (arg1, arg2) => arg1 + arg2;
    • 对象方法
    js
    // 传统写法:
    const myObject = {
        myMethod: function(arg) {
            return arg;
        }
    };
    
    // 简写形式:
    const myObject = {
        myMethod(arg) {
            return arg;
        }
    };
    • 默认参数值
    js
    // 传统写法:
    function myFunction(arg) {
        arg = arg || 'default';
        return arg;
    }
    
    // 简写形式:
    function myFunction(arg = 'default') {
        return arg;
    }
  12. 属性

    • length:表示函数期望接收的参数个数,但不包括剩余参数(...args)和具有默认值的参数。
    • name:返回函数的名称。
      • 对于匿名函数,这个属性通常是空字符串。
      • 对于函数表达式和函数声明,它返回给定的函数名。
    • prototype:表示 Function 构造函数的原型。
  13. 方法

    • @@hasInstance:一个符号,即 Symbol.hasInstance。用于确定一个对象是否是构造函数的实例。自定义 instanceof 运算符的行为。
    js
    class MyClass {
        static [Symbol.hasInstance](instance) {
            // 假设只要对象有一个特定属性,就被认为是 MyClass 的实例
            return instance.hasOwnProperty('mySpecialProperty');
        }
    }
    
    let obj = {
        mySpecialProperty: true
    };
    
    console.log(obj instanceof MyClass); // 输出 true
    • apply(thisArg, [argsArray]):调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
    js
    function greet(greeting, name) {
        console.log(greeting + ', ' + name);
    }
    
    // 使用 apply 调用函数
    greet.apply(null, ['Hello', 'Alice']); // 输出:Hello, Alice
    
    // 可以将任意对象作为 thisArg
    const obj = { name: 'Bob' };
    function sayName() {
        console.log(this.name);
    }
    sayName.apply(obj); // 输出:Bob
    • call(thisArg, argN):调用一个函数,其具有一个指定的this值和分别提供的参数(参数的列表)。
    js
    function greet(greeting, name) {
        console.log(greeting + ', ' + name);
    }
    
    // 使用 call 调用函数
    greet.call(null, 'Hello', 'Alice'); // 输出:Hello, Alice
    
    // 将对象作为 thisArg
    const obj = { name: 'Bob' };
    function sayName(age) {
        console.log(this.name + ' is ' + age + ' years old');
    }
    sayName.call(obj, 25); // 输出:Bob is 25 years old
    • bind(thisArg[, argN]):创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
    js
    function greet(greeting, name) {
        console.log(greeting + ' ' + name);
    }
    
    // 创建一个绑定函数
    const greetJohn = greet.bind(null, 'Hello', 'John');
    
    // 调用绑定函数
    greetJohn(); // 输出:Hello John
    • toString():返回一个表示当前函数源代码的字符串。
    js
    function exampleFunction(a, b) {
        return a + b;
    }
    
    const str = exampleFunction.toString();
    console.log(str);
    // 输出函数exampleFunction的源代码:
    // function exampleFunction(a, b) {
    //     return a + b;
    // }

Array 数组

数组也是一个对象,它和我们普通对象功能类似,也是用来存储一些值,不同的是普通对象使用字符串作为属性名,而数据使用数字来作为索引操作元素,索引是从 0 开始的整数。

  1. 创建

    • 字面量:

      javascript
      let arr = [element0, element1, ..., elementN];
    • 构造函数:

      javascript
      let arr = new Array([element0, element1, ..., elementN]);
      let arr = Array(element0, element1, ..., elementN);

    TIP

    let arr = [4]let ar = new Array(4)是不等效的,arr = [4]表示arr[0] = 4arr.length = 1,后者4指数组长度。但let arr = [3,4]let arr = new Array(3,4)是相等的。

  2. 空位

    • 数组空位通常发生在以下情况:

      • 使用超出数组长度的索引直接赋值。

        js
        let arr = [1, 2, 3];
        arr[5] = 5; // arr变为 [1, 2, 3, <2 empty items>, 5]
      • new 创建指定长度但未初始化的数组。

        js
        let arr = new Array(3); // arr为 [<3 empty items>]
      • 数组字面量初始化时手动创建空位。

        js
        let arr = [1,,,2,,3]
      • 删除数组中的元素。

        js
        let arr = [1, 2, 3];
        delete arr[1]; // arr变为 [1, <1 empty item>, 3]
    • 特性和行为

      • 数组的 length 属性会包含空位。
      • 空位在数组的 map(), forEach(), filter() 等方法中会被跳过。
      • 在使用 join() 或 toString() 方法时,空位会被视为 undefined,但在 JSON.stringify() 中会被转换为 null。
      js
      let arr = [1, , 3];
      console.log(arr.length); // 输出 3
      
      arr.forEach((value, index) => {
          console.log(index + ": " + value);
      });
      // 输出 "0: 1" 和 "2: 3",跳过了空位
      
      console.log(arr.map(x => 0)); // 输出 [0, <1 empty item>, 0]
      
      console.log(arr.join('-')); // 输出 "1--3"
      console.log(JSON.stringify(arr)); // 输出 "[1,null,3]"

    注意事项

    • 数组的空位可能会导致代码的行为不符合预期,尤其是在使用数组的迭代方法时。
    • 在某些情况下,处理数组空位时的行为可能因JavaScript引擎的不同而有所差异。
    • 通常建议避免在数组中创建空位,以确保代码的一致性和可预测性。
    • 如果确实需要处理数组中的空位,最好明确地使用 undefined 或其他值来替代空位。
  3. 索引

    • 从零开始:数组索引从零开始,这意味着数组中的第一个元素位于索引 0,第二个元素位于索引 1,依此类推。
    • 整数索引:数组索引必须是非负整数。如果尝试使用非整数(如字符串或浮点数)作为索引,那么这将不会被当作数组索引来处理,而是被视为数组的属性或键名。
    • 动态长度:JavaScript数组的长度是动态的,可以根据需要增长或缩减。如果你将一个元素放置在超出当前数组长度的索引位置,数组会自动增长以包含该索引,而新增的空间会被填充为 undefined。
  4. 遍历

    • for循环

      js
      let arr = [1, 2, 3, 4, 5];
      for (let i = 0; i < arr.length; i++) {
          console.log(arr[i]);
      }
    • for...of 循环

      js
      for (const item of arr) {
          console.log(item);
      }
    • forEach() 方法

      js
      arr.forEach(function(item, index) {
          console.log(item); // 元素值
          console.log(index); // 元素索引
      });
  5. 属性

    • length:表示数组中的元素数量。
  6. 方法

    • [@@iterator]():一个符号,即 Symbol.iterator。用于返回一个新的 Array 迭代器对象,该对象包含数组的每个索引的值。
    js
    const array = [1, 2, 3];
    const iterator = array[Symbol.iterator]();
    
    console.log(iterator.next().value); // 输出 1
    console.log(iterator.next().value); // 输出 2
    console.log(iterator.next().value); // 输出 3

    Symbol.iterator 主要用在以下场景

    • 使用 for...of 循环: 当你使用 for...of 循环遍历数组时,Symbol.iterator 会自动被调用。
    • 扩展运算符与数组: 扩展运算符(...)背后也是使用迭代器来遍历数组。
    • 解构赋值: 在解构数组时,也会使用到数组的迭代器。
    • Array.isArray(value): 判断传递的值是否是一个数组。
    js
    console.log(Array.isArray([1, 2, 3]));  // 输出:true
    console.log(Array.isArray({foo: 123})); // 输出:false
    console.log(Array.isArray('foobar'));   // 输出:false
    console.log(Array.isArray(undefined));  // 输出:false
    • Array.from(arrayLike[, mapFn[, thisArg]]): 从类数组对象或可迭代对象创建新的数组实例。
    js
    // 从字符串创建数组
    const strArray = Array.from('foo');
    console.log(strArray); // 输出: ['f', 'o', 'o']
    
    // 从 Set 创建数组
    const set = new Set(['foo', 'bar', 'baz', 'foo']);
    const setArray = Array.from(set);
    console.log(setArray); // 输出: ['foo', 'bar', 'baz']
    
    // 使用 map 函数
    const range = Array.from({ length: 5 }, (v, k) => k);
    console.log(range); // 输出: [0, 1, 2, 3, 4]
    • Array.fromAsync(arrayLike[, mapFn[, thisArg]]): 从异步可迭代对象创建新的数组实例。与 Array.from() 用法一样。
    • Array.of(elementN): 根据一组参数创建新的数组实例,无论参数数量或类型。
    js
    console.log(Array.of(1));         // 输出:[1]
    console.log(Array.of(1, 2, 3));   // 输出:[1, 2, 3]
    console.log(Array.of(undefined)); // 输出:[undefined]

    与 Array 构造函数的主要区别

    在于处理单个数字参数的方式。例如,new Array(3) 创建一个具有三个空槽位的数组,而 Array.of(3) 创建一个包含单个元素 3 的数组。

    • push(elementN): 向数组的末尾添加一个或多个元素,并返回新数组的长度。会改变原数组
    js
    const animals = ['pig', 'goat', 'sheep'];
    const count = animals.push('cow');
    
    console.log(count);  // 输出:4
    console.log(animals); // 输出:['pig', 'goat', 'sheep', 'cow']
    • unshift(elementN): 向数组的开头添加一个或更多元素,并返回新的长度。
    js
    var fruits = ["Banana", "Orange", "Apple"];
    fruits.unshift("Mango", "Peach");
    console.log(fruits); // 输出: ["Mango", "Peach", "Banana", "Orange", "Apple"]
    • pop(): 删除数组的最后一个元素,并返回该元素。会改变原数组
    js
    const fruits = ['Apple', 'Banana', 'Cherry'];
    const last = fruits.pop();
    
    console.log(last);   // 输出:Cherry
    console.log(fruits); // 输出:['Apple', 'Banana']
    • shift(): 删除数组的第一个元素,并返回该元素。
    js
    const myArray = [1, 2, 3];
    const first = myArray.shift();
    
    console.log(first);      // 输出:1
    console.log(myArray);    // 输出:[2, 3]
    • splice(start[, deleteCount[, itemN]]): 通过删除现有元素和/或添加新元素来改变一个数组的内容。
    js
    const months = ['Jan', 'March', 'April', 'June'];
    months.splice(1, 0, 'Feb');
    
    console.log(months); // 输出 ["Jan", "Feb", "March", "April", "June"]
    • toSpliced(): 返回一个通过删除或替换现有元素创建的新数组。

    • at(index): 接收一个整数值并返回该索引处的元素,支持负数索引表示从末尾开始。其中 -1 表示数组的最后一个元素,-2 表示倒数第二个元素,依此类推。

    js
    const array = [10, 20, 30, 40, 50];
    
    console.log(array.at(0));  // 输出 10,数组的第一个元素
    console.log(array.at(-1)); // 输出 50,数组的最后一个元素
    console.log(array.at(2));  // 输出 30,数组的第三个元素
    • includes(searchElement[, fromIndex]): 判断数组是否包含某个元素,根据情况返回 true 或 false。
    js
    const array = [1, 2, 3];
    
    console.log(array.includes(2));     // 输出:true
    console.log(array.includes(4));     // 输出:false
    console.log(array.includes(3, 3));  // 输出:false
    console.log(array.includes(3, -1)); // 输出:true
    • indexOf(searchElement[, fromIndex]): 返回数组中可以找到给定元素的第一个索引,如果不存在,则返回 -1。
    js
    const array = [2, 5, 9];
    
    console.log(array.indexOf(2));     // 输出:0
    console.log(array.indexOf(7));     // 输出:-1
    console.log(array.indexOf(9, 2));  // 输出:2
    console.log(array.indexOf(2, -1)); // 输出:-1
    console.log(array.indexOf(2, -3)); // 输出:0
    • lastIndexOf(searchElement[, fromIndex]): 返回指定元素在数组中的最后一个的索引,如果不存在则返回 -1。
    js
    const array = [2, 5, 9, 2];
    
    console.log(array.lastIndexOf(2));     // 输出:3
    console.log(array.lastIndexOf(7));     // 输出:-1
    console.log(array.lastIndexOf(2, 3));  // 输出:3
    console.log(array.lastIndexOf(2, 2));  // 输出:0
    console.log(array.lastIndexOf(2, -2)); // 输出:0
    console.log(array.lastIndexOf(2, -1)); // 输出:3
    • find(callback[element[, index[, array]]](, thisArg)): 返回数组中满足提供的测试函数的第一个元素的值时立即停止搜索,否则返回 undefined。不会改变原数组
    js
    const array = [5, 12, 8, 130, 44];
    
    const found = array.find(element => element > 10);
    
    console.log(found); // 输出 12
    • findIndex(callback[element[, index[, array]]](, thisArg)): 返回数组中满足提供的测试函数的第一个元素的索引时立即停止搜索。,否则返回 -1。不会改变原数组
    js
    const array = [5, 12, 8, 130, 44];
    
    const isLargeNumber = (element) => element > 13;
    
    const index = array.findIndex(isLargeNumber);
    
    console.log(index); // 输出 3
    • findLast(callback[element[, index[, array]]](, thisArg)): 从数组的末尾开始搜索,返回满足提供的测试函数的第一个元素的值。如果没有找到对应元素,则返回 undefined。不会改变原数组
    js
    const array1 = [5, 12, 50, 130, 44];
    
    const found = array1.findLast((element) => element > 45);
    
    console.log(found); // 输出 130
    • findLastIndex(callback[element[, index[, array]]](, thisArg)): 从数组的末尾开始搜索,返回数组中最后一个满足提供的测试函数的元素的索引,否则返回 -1。不会改变原数组
    js
    const array1 = [5, 12, 50, 130, 44];
    
    const isLargeNumber = (element) => element > 45;
    
    console.log(array1.findLastIndex(isLargeNumber)); // 输出 3
    • entries(): 返回一个新的 Array Iterator 对象,包含数组中每个索引的键/值对。
    js
    const fruits = ['Apple', 'Banana', 'Cherry'];
    const iterator = fruits.entries();
    
    for (let entry of iterator) {
        console.log(entry);
    }
    
    // 输出:
    // [0, 'Apple']
    // [1, 'Banana']
    // [2, 'Cherry']
    • keys(): 返回一个新的 Array Iterator 对象,包含数组中每个索引的键。
    js
    const array = ['a', 'b', 'c'];
    const iterator = array.keys();
    
    for (let key of iterator) {
        console.log(key);
    }
    
    // 输出:
    // 0
    // 1
    // 2
    • values(): 返回一个新的 Array Iterator 对象,包含数组中每个索引的值。
    js
    var array = ['a', 'b', 'c'];
    var iterator = array.values();
    
    for (let value of iterator) {
      console.log(value);
    }
    // 输出:
    // a
    // b
    // c
    • forEach(callback(element[, index[, array]])): 对数组的每个元素执行一次给定的函数。不会改变原数组
    js
    const array = ['a', 'b', 'c'];
    
    array.forEach(element => {
        console.log(element);
    });
    
    // 输出:
    // a
    // b
    // c
    • every(callback(element[, index[, array]])): 测试数组的所有元素是否都通过了指定函数的测试。
    js
    const array = [1, 30, 39, 29, 10, 13];
    
    const isBelowThreshold = (currentValue) => currentValue < 40;
    
    console.log(array.every(isBelowThreshold)); // 输出 true
    • filter(callback[element[, index[, array]]](, thisArg)): 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。不会改变原数组。
    js
    const numbers = [1, 2, 3, 4, 5, 6];
    
    const evenNumbers = numbers.filter(number => number % 2 === 0);
    
    console.log(evenNumbers); // 输出 [2, 4, 6]
    • map(callback[element[, index[, array]]](, thisArg)): 创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。不会改变原数组。
    js
    const array1 = [1, 4, 9, 16];
    const map1 = array1.map((x) => x * 2);
    
    console.log(map1); // 输入 [2, 8, 18, 32]
    • some(callback[element[, index[, array]]](, thisArg)): 测试数组中是不是至少有1个元素通过了被提供的函数测试。
    js
    const array = [1, 2, 3, 4, 5];
    
    const even = (element) => element % 2 === 0;
    
    console.log(array.some(even)); // 输出:true
    • flat([depth]): 按照指定深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。空数组元素会被忽略。不会改变原数组。
    js
    const nestedArray = [1, [2, [3, [4]]]];
    
    const flatArray = nestedArray.flat();
    console.log(flatArray); // 输出:[1, 2, [3, [4]]]
    
    const flatArrayDeep = nestedArray.flat(2);
    console.log(flatArrayDeep); // 输出:[1, 2, 3, [4]]
    • flatMap(callback[element[, index[, array]]](, thisArg)): 首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。不会改变原数组。
    js
    const array = [1, 2, 3, 4];
    
    const newArray = array.flatMap(x => [x, x * 2]);
    console.log(newArray); // 输出:[1, 2, 2, 4, 3, 6, 4, 8]
    • reduce(callback[element[, index[, array]]](, thisArg)): 对数组中的每个元素执行一个由您提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。
    js
    const numbers = [1, 2, 3, 4];
    const sum = numbers.reduce((acc, cur) => acc + cur, 0);
    
    console.log(sum); // 输出:10
    • reduceRight(callback[element[, index[, array]]](, thisArg)): 对数组中的每个元素执行一个由您提供的 reducer 函数(降序执行),将其结果汇总为单个返回值。
    js
    const array = [[0, 1], [2, 3], [4, 5]];
    const flattened = array.reduceRight((acc, cur) => acc.concat(cur), []);
    
    console.log(flattened); // 输出:[4, 5, 2, 3, 0, 1]
    • concat(valueN): 用于合并两个或多个数组。不会改变现有的数组,而是返回一个新数组。
    js
    const array1 = ['a', 'b', 'c'];
    const array2 = ['d', 'e', 'f'];
    const array3 = array1.concat(array2);
    
    console.log(array3); // 输出:['a', 'b', 'c', 'd', 'e', 'f']

    TIP

    扩展运算符(...)也常被用来达到类似的效果,并且在某些情况下更为灵活和直观。例如,[...array1, ...array2] 会产生与 array1.concat(array2) 相同的结果。

    • join([separator]): 将数组中的所有元素连接成一个字符串并返回。undefined 或 null,会被转换为空字符串。不会改变原数组
    js
    const elements = ['Fire', 'Air', 'Water'];
    
    console.log(elements.join());        // 输出:"Fire,Air,Water"
    console.log(elements.join(''));      // 输出:"FireAirWater"
    console.log(elements.join('-'));     // 输出:"Fire-Air-Water"
    • sort(): 对数组的元素进行原地排序,并返回排序后的数组。
    js
    const months = ['March', 'Jan', 'Feb', 'Dec'];
    months.sort();
    console.log(months); // 输出 ["Dec", "Feb", "Jan", "March"]
    • toSorted(): 返回一个排序后的新数组,而不修改原数组。

    • reverse(): 颠倒数组中元素的顺序。

    js
    const array = [1, 2, 3];
    array.reverse();
    
    console.log(array); // 输出:[3, 2, 1]
    • toReversed(): 返回一个元素顺序被颠倒的新数组,而不修改原数组。

    • slice(start[, end]): 返回一个新数组对象,这一对象是一个由开始到结束(不包括结束)选择的数组的一部分浅拷贝。原始数组不会被修改。

    js
    const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
    const someAnimals = animals.slice(2);
    
    console.log(someAnimals); // 输出:['camel', 'duck', 'elephant']
    • fill(value[, start[, end]]): 用一个固定值填充数组的全部或部分,会改变原数组
    js
    const array = [1, 2, 3, 4, 5];
    
    // 使用 fill 方法
    array.fill(0, 2, 4);
    
    console.log(array); // 输出:[1, 2, 0, 0, 5]
    • with(index, value): 返回一个新数组,其中指定索引处的元素已被提供的新值替换。不会改变原数组
    js
    const arr = [1, 2, 3, 4, 5];
    
    console.log(arr.with(2, 6)); // [1, 2, 6, 4, 5]
    • copyWithin(target, start, end): 浅复制数组的一部分到同一数组中的另一个位置,并返回它,不改变原数组的长度,会修改原数组
    js
    const array = [1, 2, 3, 4, 5];
    
    array.copyWithin(0, 3, 5);
    console.log(array); // 输出:[4, 5, 3, 4, 5]
    • toLocaleString([locales[, options]]): 返回一个字符串表示数组中的元素。
    • toString(): 返回一个字符串表示指定的数组及其元素。

Object 自定义

创建自定义对象的通常方式是创建 Object 的一个新实例,然后再给它添加属性和方法。

  1. 属性类型

    • 数据属性:包含一个数据值和三个描述其行为的属性。若要修改属性的默认特性,则必须使用 Object.defineProperty() 方法

      • [[Value]]:存储属性的值。这是属性实际持有的数据,例如数字、字符串、或对象。
      • [[Writable]]:一个布尔值,表示属性的值是否可以被修改。如果设置为 false,属性的值就是只读的。
      • [[Enumerable]]:一个布尔值,表示属性是否可以通过 for...in 循环或 Object.keys() 方法返回。如果设置为 false,该属性将不出现在枚举中。
      • [[Configurable]]:一个布尔值,表示属性的描述符是否可以被改变,以及属性是否可以从对应的对象上删除。如果设置为 false,属性不能被删除,且除了 [[Value]] 和 [[Writable]],其他特性也不能被修改。
    • 访问器属性:不包含数据值,但包含一对 getter 和 setter 函数(不是必须的)。在访问属性时,这些函数用于确定返回的值(getter)或在设置属性值时执行操作(setter)。若要定义属性,则必须使用 Object.defineProperty() 方法。

      • [[Get]]:一个函数,在读取属性时被调用,返回属性的有效值。
      • [[Set]]:一个函数,在属性值被修改时调用,用于控制属性值的更改。
      • [[Enumerable]]:与数据属性中的 [[Enumerable]] 相同,表示属性是否可以枚举。
      • [[Configurable]]:与数据属性中的 [[Configurable]] 相同,表示属性是否可配置。
  2. 语法增强

    • 属性值简写

      js
      let name = 'Matt'
      
      let person = {
        name
      }
    • 可计算属性

      js
      const nameKey = 'name'
      const ageKey = 'age'
      const jobKey = 'job'
      
      let person = {
        [nameKey]:'Matt',
        [ageKey]:18,
        [jobKey]:'Software Engineer'
      }
      js
      const nameKey = 'name'
      const ageKey = 'age'
      const jobKey = 'job'
      let uniToken = 0
      
      function getUniKey(key){
        return `${key}_${uniToken++}`
      }
      
      let person = {
        [getUniKey(nameKey)]:'Matt',
        [getUniKey(ageKey)]:18,
        [getUniKey(jobKey)]:'Software engineer'
      }
    • 方法名简写

      js
      let person = {
        sayName(name){
          console.log(`My name is ${name}`)
        }
      }
      
      person.sayName('Matt')
      js
      const methodKey = 'sayName'
      
      let person = {
        [methodKey](name){
          console.log(`My name is ${name}`)
        }
      }
      
      person.sayName('Matt')
  3. 对象解构

    js
    let person = {
      name:'Matt',
      age:18
    }
    
    let { name: personName, age: personAge } = person
    
    console.log(personName) // Matt
    console.log(personAge)  // 18
    js
    let person = {
      name:'Matt',
      age:18
    }
    
    let { name, age } = person
    
    console.log(name) // Matt
    console.log(age)  // 18
    js
    let person = {
      name:'Matt',
      age:18
    }
    
    let { name , job } = person
    
    console.log(name) // Matt
    console.log(job)  // undefined
    js
    let person = {
      name:'Matt',
      age:18
    }
    
    let { name , job = 'Software Engineer' } = person
    
    console.log(name) // Matt
    console.log(job)  // Software Engineer
  4. 对象遍历

    • for...in 循环:用于遍历对象的所有可枚举属性,包括继承的可枚举属性。

      js
      const obj = { a: 1, b: 2, c: 3 };
      for (let key in obj) {
        console.log(key, obj[key]);
      }
    • Object.keys():返回一个包含对象所有自有(非继承)可枚举属性名称的数组。可以与 Array.prototype.forEach() 或其他数组迭代方法结合使用。

      js
      const obj = { a: 1, b: 2, c: 3 };
      Object.keys(obj).forEach(key => {
        console.log(key, obj[key]);
      });
    • Object.values():返回一个包含对象所有自有可枚举属性值的数组。可以用于迭代对象的值。

      js
      const obj = { a: 1, b: 2, c: 3 };
      Object.values(obj).forEach(value => {
        console.log(value);
      });
    • Object.entries():返回一个给定对象自己的可枚举属性键值对数组,其元素是 [key, value] 对。可以与 Array.prototype.forEach() 或其他数组迭代方法结合使用。

      js
      const obj = { a: 1, b: 2, c: 3 };
      Object.entries(obj).forEach(([key, value]) => {
        console.log(key, value);
      });
    • for...of 与 Object.entries():for...of 循环可以与 Object.entries() 结合使用,提供一种更现代的迭代对象属性的方式。

      js
      const obj = { a: 1, b: 2, c: 3 };
      for (const [key, value] of Object.entries(obj)) {
        console.log(key, value);
      }

    注意事项

    • 使用 for...in 循环时,可能需要使用 Object.hasOwnProperty() 方法来过滤掉继承的属性。
    • 这些方法仅适用于可枚举属性。对于不可枚举属性或更复杂的对象结构,可能需要使用其他技术。
  5. 对象合并

    • 展开运算符(...):用于复制对象的属性到一个新对象中。如,let newObj = {...obj} 创建了一个 obj 的浅拷贝。

      js
      const obj1 = { a: 1, b: 2 };
      const obj2 = { b: 3, c: 4 };
      const mergedObj = { ...obj1, ...obj2 };
      // mergedObj 现在是 { a: 1, b: 3, c: 4 }
    • Object.assign():用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象。

      js
      const obj1 = { a: 1, b: 2 };
      const obj2 = { b: 3, c: 4 };
      const mergedObj = Object.assign({}, obj1, obj2);
      // mergedObj 现在是 { a: 1, b: 3, c: 4 }

    注意事项

    1. 浅拷贝:这两种方法都执行浅拷贝。如果对象的属性值是另一个对象(包括数组、函数等),合并的是这个值对象的引用。因此,如果修改了这个嵌套对象的属性,那么这个修改会反映在所有引用了该对象的地方。
    2. 属性覆盖:如果在源对象中有相同的属性,则后面的属性值会覆盖前面的属性值。这意味着在多个对象合并的过程中,最后一个对象的属性将优先级最高。
    3. 不可枚举属性和原型链属性:Object.assign() 和展开运算符只复制源对象的自身可枚举属性,不可枚举属性和原型链上的属性不会被复制。
    4. 错误处理:如果在复制过程中遇到错误(如尝试写入只读属性),Object.assign() 会立即停止并抛出错误,此时目标对象可能只完成了部分复制。
    5. null 和 undefined:Object.assign() 和展开运算符都会忽略 null 或 undefined 源对象。
    6. 深拷贝:对于深度合并的需求(即需要递归合并对象的属性),Object.assign() 和展开运算符都不适用。这种情况下需要使用深拷贝技术。可以考虑使用像 Lodash 这样的库提供的深拷贝函数(如 _.merge()),或者自己实现深拷贝逻辑。
  6. 原型与原型链

    • 每个构造函数都会创建一个 prototype 属性(又称“显式原型对象”),而由构造函数创建的实例对象都会暴露一个 __proto__ 属性(又称“隐式原型对象”)

    • 在原型对象上添加的任何属性和方法,由同一个构造函数创建的多个实例对象共享同一个原型对象中的属性和方法

      js
      let Person = function () {}
      
      Person.prototype.name = 'Matt'
      Person.prototype.age = 18
      Person.prototype.job = 'Software Engineer'
      Person.prototype.sayName = function () {
        console.log(this.name)
      }
      
      let p1 = new Person()
      console.log(p1.age)  // 18
      p1.sayName()  // Matt
      
      let p2 = new Person()
      console.log(p2.job)  // Software Engineer
      p2.sayName()  // Matt
    • 每个原型对象都会创建一个 constructor 属性,该属性指回与之关联的构造函数

      js
      console.log(Person.prototype.constructor === Person) // true
    • 每个构造函数的原型对象都会终止于 Object 的原型对象,而 Object 原型对象的原型是 Null

      js
      console.log(Person.prototype.__proto__ === Object.prototype)  // true
      console.log(Person.prototype.__proto__.constructor === Object)  // true
      console.log(Person.prototype.__proto__.__proto__ === null)  // true
    • 构造函数、原型对象和实例是3个完全不同的对象

      js
      console.log(p1 !== Person)  // true
      console.log(p1 !== Person.prototype)  // true
      console.log(Person !== Person.prototype)  //true
    • 实例与构造函数原型对象之间有直接联系(实例对象的隐式原型等于构造函数的显示原型),而与构造函数之间没有直接联系

      js
      console.log(p1.__proto__ === Person.prototype)  // true
      console.log(p1.__proto__.constructor === Person)  // true
    • 当以对象字面量形式重写原型的属性和方法,其 constructor 属性将指向完全不同的新对象(Object 构造函数),而不再指向原来的构造函数。若要恢复,则需要额外添加 constructor 属性,但推荐使用 Object.defineProperty() 方法来定义 constructor 属性。

      js
      /**
       * 不推荐
        * 会创建一个 [[Enumerable]] 为 true 的属性,而原生constructor属性默认为 false
        */
      Person.prototype = {
        constructor: Person,
        name:'Matt',
        age:18,
        job:'Software Engineer',
        sayName(){
          console.log(this.name)
        }
      }
      js
      /**
       * 推荐写法
        */
      Person.prototype = {
        name:'Matt',
        age:18,
        job:'Software Engineer',
        sayName(){
          console.log(this.name)
        }
      }
      
      Object.defineProperty(Person.prototype,'constructor',{
        enumerable:false,
        value:Person
      })
    • 当访问一个对象的属性或方法时,如果该对象实例自身不存在这个属性或方法,JavaScript 会沿着这个对象的原型链向上查找,直到找到这个属性或方法,或到达原型链的末端(通常是 Object.prototype)。如果在原型链上找不到相应的属性或方法,则返回 undefined。

      js
      p1.name = 'Jack'
      console.log(p1.name)  // Jack,来自实例自身
      console.log(p2.name)  // Matt,来自原型对象
  7. 类与继承

    • 定义

      • 类声明

        js
        class MyClass {
          constructor(prop1, prop2) {
            this.prop1 = prop1;
            this.prop2 = prop2;
          }
        
          get getMethod() {
            // ...
          }
        
          set setMethod() {
            // ...
          }
        
          methods() {
            // ...
          }
        
          static staticMethod() {
            // ...
          }
        }
        
        const myInstance = new MyClass(arg1, arg2);
      • 类表达式

        js
        const myInstance = class {
          constructor(prop1, prop2) {
            this.prop1 = prop1;
            this.prop2 = prop2;
          }
        
          get getMethod() {
            // ...
          }
        
          set setMethod() {
            // ...
          }
        
          methods() {
            // ...
          }
        
          static staticMethod() {
            // ...
          }
        }

      类的构成

      - 构造函数(Constructor):可选,constructor 是一个特殊的方法,用于在创建类实例时初始化对象。它是在用 new 关键字创建类的新实例时被调用的。
      - 实例方法(Method):可选,在类的大括号 {} 中定义的函数被称为方法。这些方法将被类的实例继承。
      - Getter与Setter:可选,使用 get 关键字和 set 关键字定义的方法是特殊类型的方法。用于控制对特定属性的访问和修改。
      - 静态方法(Static Methods):可选,使用 static 关键字定义的方法是静态方法。它们不是被实例继承的,而是直接通过类调用。
      
    • 继承

      • 使用 extends 关键字创建子类(派生类)

        js
        class ParentClass {
          // 父类方法和属性
        }
        
        class ChildClass extends ParentClass {
          // 子类方法和属性
        }
      • 使用 super() 调用父类构造函数,并将返回的实例赋值给 this ,同时只能在子类中使用且仅限于类构造函数、实例方法和静态方法内部

        js
        class Person {
          constructor(name) {
            this.name = name;
          }
        }
        
        class Student extends Person {
          constructor(name, grade) {
            super(name); // 调用父类的 constructor
            this.grade = grade;
          }
        }
        
        const student = new Student('Alice', 6);
        console.log(student.name); // Alice
        console.log(student.grade); // 6

Date 日期与时间

  1. 创建

    javascript
    new Date();
    new Date(value);
    new Date(dateString);
    new Date(year, monthIndex [, date [, hours [, minutes [, seconds ]]]]);

    TIP

    • monthIndex 是从“0”开始计算的,这就意味着一月份为“0”,十二月份为“11”
    • 当 Date 作为构造函数调用并传入多个参数时,如果数值大于合理范围时将向前进一位
  2. 方法

    • [@@toPrimitive]():一个符号,即:Symbol.toPrimitive 。用于指定 Date 对象如何转换为原始值。
    • Date.now():返回当前时间的 Unix 时间戳。
    js
    console.log(Date.now()); // 输出类似于 1638202430000(这个值会根据当前时间而变化)
    • Date.parse():解析日期字符串并返回相应日期的 Unix 时间戳。
    js
    console.log(Date.parse('March 21, 2021')); // 输出: 1616284800000
    console.log(Date.parse('2021-03-21T00:00:00.000Z')); // 输出: 1616284800000

    简而言之

    Unix 时间戳是一个全球统一的时间度量,表示自特定时刻(Unix 纪元,即 1970 年 1 月 1 日 00:00:00 UTC)以来的时间长度。因此,无论您在世界上的哪个时区,同一时刻的 Unix 时间戳都是相同的。

    • getFullYear():获取年份(四位数)。
    js
    console.log(new Date().getFullYear()); // 输出类似于 2023(这个值会根据当前年份而变化)
    • getMonth():获取月份(0-11,0代表一月)。
    js
    console.log(new Date().getMonth()); // 输出类似于 0-11 的值(这个值会根据当前月份而变化)
    • getDate():获取月份中的日(1-31)。
    js
    console.log(new Date().getDate()); // 输出类似于 15(这个值会根据当前日期而变化)
    • getDay():获取星期中的日(0-6,星期日为0)。
    js
    console.log(new Date().getDay()); // 输出类似于 0-6 的值(这个值会根据当前是星期几而变化)
    • getHours():获取小时(0-23)。
    js
    console.log(new Date().getHours()); // 输出: 当前小时数,范围为 0-23
    • getMinutes():获取分钟(0-59)。
    js
    console.log(new Date().getMinutes()); // 输出: 当前的分钟数,范围为 0-59
    • getSeconds():获取秒数(0-59)。
    js
    console.log(new Date().getSeconds()); // 输出: 当前的秒数,范围为 0-59
    • getTime():获取自1970年1月1日以来的毫秒数。
    js
    console.log(new Date().getTime()); // 输出: 当前时间的 Unix 时间戳

    与 Date.now() 区别

    • getTime() 用于获取特定 Date 对象所表示的那一刻的 Unix 时间戳。
    • Date.now() 用于获取当前时刻的 Unix 时间戳。
    • setFullYear(year[, month[, day]]):设置年份。
    js
    let date = new Date();
    console.log('原日期:', date.toString());
    
    // 设置年份为 2021
    date.setFullYear(2021);
    console.log('更新后的日期:', date.toString());
    
    // 同时设置年份为 2021,月份为 0(一月),日期为 1
    date.setFullYear(2021, 0, 1);
    console.log('再次更新后的日期:', date.toString());
    • setMonth(month[, day]):设置月份。
    js
    let date = new Date();
    console.log('原日期:', date.toString());
    
    // 设置月份为 0(一月)
    date.setMonth(0);
    console.log('更新后的日期:', date.toString());
    
    // 同时设置月份为 0(一月),日期为 1
    date.setMonth(0, 1);
    console.log('再次更新后的日期:', date.toString());
    • setDate(day):设置月份中的日。
    js
    let date = new Date();
    console.log('原日期:', date.toString());
    
    // 设置日期为月份的第一天
    date.setDate(1);
    console.log('更新后的日期:', date.toString());
    • setHours(hours[, minutes[, seconds[, milliseconds]]]):设置小时。
    js
    let date = new Date();
    console.log('原始日期和时间:', date.toString());
    
    // 只设置小时
    date.setHours(20);
    console.log('更新小时后的时间:', date.toString());
    
    // 同时设置小时、分钟和秒
    date.setHours(20, 30, 45);
    console.log('更新小时、分钟和秒后的时间:', date.toString());
    • setMinutes(minutes[, seconds[, milliseconds]]):设置分钟。
    js
    let date = new Date();
    console.log('原始日期和时间:', date.toString());
    
    // 设置分钟
    date.setMinutes(30);
    console.log('更新分钟后的时间:', date.toString());
    
    // 同时设置分钟和秒
    date.setMinutes(30, 45);
    console.log('更新分钟和秒后的时间:', date.toString());
    • setSeconds(seconds[, milliseconds]):设置秒数。
    js
    let date = new Date();
    console.log('原始日期和时间:', date.toString());
    
    // 设置秒数
    date.setSeconds(30);
    console.log('更新秒数后的时间:', date.toString());
    
    // 同时设置秒数和毫秒
    date.setSeconds(30, 500);
    console.log('更新秒数和毫秒后的时间:', date.toString());
    • setTime(timeValue):通过 Unix 时间戳设置日期和时间。
    js
    let date = new Date();
    console.log('原始日期和时间:', date.toString());
    
    // 设置新的时间(例如:Unix 时间戳 1638288000000)
    date.setTime(1638288000000);
    console.log('更新后的日期和时间:', date.toString());
    • toLocaleString([locales[, options]]):根据本地化环境格式化日期和时间。
    js
    // 假设我们有一个 Unix 时间戳
    let timeStamp = 1638288000000; // 例如,2021年12月1日 00:00:00 UTC 的时间戳
    timeStamp += 1000 * 60 * 60 * 8  // 将 UTC 时间转换为北京时间(东八区,UTC+8)
    
    // 使用时间戳创建一个新的 Date 对象
    let date = new Date(timeStamp);
    
    // 如果你需要以特定的时区显示时间,比如北京时间(UTC+8),
    console.log('北京时间:', date.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }));
    • toLocaleDateString([locales[, options]]):根据本地化环境格式化日期部分。
    js
    let date = new Date();
    
    // 使用默认的本地设置和格式化选项
    console.log(date.toLocaleDateString());
    
    // 使用特定的本地设置(例如,美国英语)
    console.log(date.toLocaleDateString('en-US'));
    
    // 使用特定的本地设置和格式化选项(例如,长格式的中文日期)
    console.log(date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }));
    • toLocaleTimeString([locales[, options]]):根据本地化环境格式化时间部分。
    js
    let date = new Date();
    
    // 使用默认的本地设置和格式化选项
    console.log(date.toLocaleTimeString());
    
    // 使用特定的本地设置(例如,美国英语)
    console.log(date.toLocaleTimeString('en-US'));
    
    // 使用特定的本地设置和格式化选项(例如,长格式的中文时间)
    console.log(date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }));
    • toDateString():将 Date 对象的日期部分(即年、月、日、星期)转换为易读的字符串形式。
    js
    console.log(new Date().toDateString()); // 输出类似于 "Tue Jan 26 2021"
    • toTimeString():将 Date 对象的时间部分(即小时、分钟、秒和时区)转换为易读的字符串格式。
    js
    console.log(new Date().toTimeString()); // 输出类似于 "14:45:10 GMT+0200 (Eastern European Standard Time)"
    • toString():将日期和时间转换为字符串。
    js
    console.log(new Date().toString()); // 输出类似于 "Tue Jan 26 2021 14:45:10 GMT+0200 (Eastern European Standard Time)"
    • valueOf():返回Date对象的原始值(自1970年1月1日以来的 Unix 时间戳)。
    js
    console.log(new Date().valueOf()); // 输出: 当前日期和时间的 Unix 时间戳

RegExp 正则

  1. 创建

    javascript
    let regex = /pattern/flags;
    javascript
    let regex = new RegExp("pattern", "flags");
  2. 模式 pattern

    方括号描述
    [abc]查找方括号之间的任何字符。
    [^abc]查找任何不在方括号之间的字符。
    [0-9]查找任何从 0 至 9 的数字。
    [a-z]查找任何从小写 a 到小写 z 的字符。
    [A-Z]查找任何从大写 A 到大写 Z 的字符。
    [A-z]查找任何从大写 A 到小写 z 的字符。
    [adgk]查找给定集合内的任何字符。
    [^adgk]查找给定集合外的任何字符。
    (red|blue|green)查找任何指定的选项。
    元字符描述
    .查找单个字符,除了换行和行结束符。
    \w查找单词字符。
    \W查找非单词字符。
    \d查找数字。
    \D查找非数字字符。
    \s查找空白字符。
    \S查找非空白字符。
    \b匹配单词边界。
    \B匹配非单词边界。
    \0查找 NUL 字符。
    \n查找换行符。
    \f查找换页符。
    \r查找回车符。
    \t查找制表符。
    \v查找垂直制表符。
    \xxx查找以八进制数 xxx 规定的字符。
    \xdd查找以十六进制数 dd 规定的字符。
    \uxxxx查找以十六进制数 xxxx 规定的 Unicode 字符。
    量词描述
    n+匹配任何包含至少一个 n 的字符串。
    n*匹配任何包含零个或多个 n 的字符串。
    n?匹配任何包含零个或一个 n 的字符串。
    n{X}匹配包含 X 个 n 的序列的字符串。
    n{X,Y}匹配包含 X 至 Y 个 n 的序列的字符串。
    n{X,}匹配包含至少 X 个 n 的序列的字符串。
    n$匹配任何结尾为 n 的字符串。
    ^n匹配任何开头为 n 的字符串。
    ?=n匹配任何其后紧接指定字符串 n 的字符串。
    ?!n匹配任何其后没有紧接指定字符串 n 的字符串。
  3. 标记 flags

    匹配模式描述
    g全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束
    i不区分大小写,表示在查找匹配时忽略 pattern 和字符串的大小写
    m多行模式,表示查找到一行文本末尾时会继续查找
    sdotAll模式,表示元字符 . 匹配任何字符(包括 \n 或 \r )
    uUnicode模式,启用 Unicode 匹配
    y粘附模式,表示只查找从 lastIndex 开始及之后的字符串
  4. 属性

    • global:布尔值,表示正则表达式是否具有全局搜索标志g。

      js
      let regex = /hello/g;
      console.log(regex.global); // 输出 true
    • ignoreCase:布尔值,表示正则表达式是否具有不区分大小写标志i。

      js
      let regex = /hello/i;
      console.log(regex.ignoreCase); // 输出 true
    • unicode:布尔值,表示正则表达式是否设置了 u 标记。

      js
      let regex = /\u{61}/u;
      console.log(regex.unicode); // true
    • sticky:布尔值,表示正则表达式是否设置了 y 标记(仅从正则表达式的 lastIndex 属性表示的索引处搜索)。

      js
      let text = "hello world";
      let regexSticky = /world/y; // 使用sticky标志
      
      regexSticky.lastIndex = 6; // 设置搜索的起始位置
      console.log(regexSticky.test(text)); // 返回true,因为"world"出现在索引6的位置
        ```
    • RegExp.lastIndex:一个整数,表示下一次匹配开始的字符串索引。只有正则表达式使用了表示全局检索的 "g" 或者粘性检索的 "y" 标志时,该属性才会起作用。

      js
        let text = "cat, bat, sat, fat";
        let pattern = /.at/g; // 全局模式
      
        let match = pattern.exec(text);
        console.log(match[0]);    // 输出 "cat"
        console.log(pattern.lastIndex); // 输出 4
      
        match = pattern.exec(text);
        console.log(match[0]);    // 输出 "bat"
        console.log(pattern.lastIndex); // 输出 9
      js
      let text = "cat, bat, sat, fat";
      let stickyPattern = /.at/y; // 粘性模式
      
      stickyPattern.lastIndex = 5;
      let match = stickyPattern.exec(text);
      console.log(match[0]);    // 输出 "bat"
      console.log(stickyPattern.lastIndex); // 输出 9
    • multiline:布尔值,表示正则表达式是否具有多行标志m。

      js
      let regex = /hello/m;
      console.log(regex.multiline); // 输出 true
    • dotAll:布尔值,表示正则表达式是否设置了 s 标记。

      js
      let regexDotAll = /foo.bar/s; // 使用dotAll标志
      console.log(regexDotAll.test('foo\nbar')); // 没有s标志,这将返回false
    • source:返回正则表达式的文本。

      js
      let regex = /hello/i;
      console.log(regex.source); // 输出 "hello"
    • flags:返回正则表达式的所有标志。

      js
      let regex = /hello/gim;
      console.log(regex.flags); // 输出 "gim"
  5. 方法

    • exec(str):在字符串中执行搜索匹配。返回一个结果数组或 null。

      javascript
      let regex = /\d+/; // 匹配一个或多个数字
      console.log(regex.exec("room 101")); // 输出:["101"]

      TIP

      返回结果为数组时,包含两个额外的属性:

      • index:字符串中匹配模式的起始位置
      • input:要查找的字符串
    • test(str):测试字符串是否匹配模式。如果匹配,返回 true;否则返回 false。

      javascript
      let regex = /hello/i;
      console.log(regex.test("Hello world")); // 输出:true
    • toString():返回一个表示该正则表达式的字符串。

      js
      let myExp = new RegExp("a+b+c");
      console.log(myExp.toString());       // /a+b+c/
      
      let foo = new RegExp("bar", "g");
      console.log(foo.toString());         // /bar/g

Map 映射

  1. 创建

    js
    let map = new Map();
    
    let map = new Map([
      ['key1', 'value1'],
      ['key2', 'value2']
    ]);
  2. 属性

    • size:返回 Map 对象中键值对的数量。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 使用 .size 属性获取 Map 中的元素数量
    console.log(myMap.size);  // 输出: 0
  3. 方法

    • [@@iterator]():一个符号,即:Symbol.iterator ,Map 对象默认的迭代器。

    • set(key, value):设置 Map 中的键和值。如果键已经存在,则更新其关联的值。

    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    • get(key):根据键获取对应的值。如果键不存在,则返回 undefined。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    // 获取一个存在的键的值
    let value = myMap.get('key2');  // 返回 'value2'
    console.log(value);
    • clear():清空 Map 中的所有键值对。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    console.log(myMap.size);  // 输出: 3
    
    // 清空 Map
    myMap.clear();
    
    console.log(myMap.size);  // 输出: 0
    • delete(key):删除指定的键值对。如果删除成功,返回 true;如果键不存在,返回 false。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    // 删除一个键值对
    myMap.delete('key2');
    
    console.log(myMap.size);  // 输出: 2
    • entries():返回一个新的迭代器对象,它包含 Map 对象中每个元素的 [key, value] 数组。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    // 获取迭代器对象
    let iterator = myMap.entries();
    
    // 遍历迭代器
    for (let entry of iterator) {
        console.log(entry);  // 输出: ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']
    }
    • forEach(callback([value[, key[, map]]]) [, thisArg]):按插入顺序为 Map 对象中的每个键值对调用一次提供的回调函数。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    // 使用 forEach 遍历 Map
    myMap.forEach(function(value, key, map) {
        console.log(`Key: ${key}, Value: ${value}`);
        // 这里也可以访问 map 对象
    });
    
    // 输出将是:
    // Key: key1, Value: value1
    // Key: key2, Value: value2
    // Key: key3, Value: value3
    • has(key):检查 Map 中是否存在指定的键。返回布尔值。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    console.log(map1.has('key2')); // 输出 true
    console.log(map1.has('key4')); // 输出 false
    • keys():返回一个新的迭代器对象,它包含 Map 对象中每个元素的键。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    for(let val of myMap.keys()) {
      console.log(val); // 输出 'key1' 'key2' 'key3'
    }
    • values():返回一个新的迭代器对象,它包含 Map 对象中每个元素的值。
    js
    // 创建一个新的 Map 对象
    let myMap = new Map();
    
    // 向 Map 中添加一些键值对
    myMap.set('key1', 'value1');
    myMap.set('key2', 'value2');
    myMap.set('key3', 'value3');
    
    for(let val of myMap.values()) {
      console.log(val); // 输出 'value1' 'value2' 'value3'
    }

    使用场景

    • 当需要键为非字符串类型时。
    • 当需要保持键值对的插入顺序时。
    • 当需要频繁添加或移除键值对时,因为 Map 的性能在这些操作上通常优于普通对象。

WeakMap 弱映射

  1. 创建

    js
    let wm = new WeakMap();
    
    let key1 = { id: 1 }
    let key2 = { id: 2 }
    let wm = new WeakMap([
      ['key1', 'value1'],
      ['key2', 'value2']
    ]);
  2. 方法

    • set(key, value):在 WeakMap 中设置一对键值。如果键已存在,则更新其关联的值。
    • get(key):返回 WeakMap 中与指定键关联的值。如果该键不存在,则返回 undefined。
    • has(key):判断 WeakMap 中是否存在指定的键。返回布尔值。
    • delete(key):从 WeakMap 中删除指定的键值对。如果操作成功,返回 true。
  3. 特点

    • 键必须是对象:在 WeakMap 中,每个键都必须是一个对象。尝试使用非对象作为键会导致抛出错误。
    • 弱引用键:WeakMap 中的键是弱引用的,这意味着它们不阻止垃圾回收器回收键所引用的对象。
    • 不可枚举:由于键是弱引用的,WeakMap 的键值对数量或键列表无法确定,因此 WeakMap 不支持遍历操作,也没有 size 属性。

    使用场景

    • 存储私有数据:在编程中,WeakMap 可以用来存储对象的私有数据,或者与对象相关的元数据,当对象被回收时,这些数据也会自动消失。
    • 缓存:可以用作缓存,特别是当你希望自动释放那些不再需要的缓存项时。

Set 集合

  1. 创建

    js
    let mySet = new Set();
    
    let mySet = new Set([1, 2, 3, 4, 5]);
  2. 属性

    • size:返回 Set 中值的数量。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 使用 size 属性获取 Set 中的元素数量
    console.log(mySet.size);  // 输出: 0
  3. 方法

    • [@@iterator]():一个符号,即:Symbol.iterator ,Set 对象默认的迭代器。

    • add(value):向 Set 添加一个新的元素。

    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些值
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    • clear():清除 Set 对象中的所有元素。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些键值对
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    
    console.log(mySet.size);  // 输出: 3
    
    // 清空 Set
    mySet.clear();
    
    console.log(mySet.size);  // 输出: 0
    • delete():删除 Set 中的指定元素。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些键值对
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    
    // 删除一个键值对
    mySet.delete('key2');
    
    console.log(mySet.size);  // 输出: 2
    • entries():返回一个新的迭代器对象,该对象包含 Set 对象中每个元素的 [value, value] 数组。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些键值对
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    
    // 获取迭代器对象
    let iterator = mySet.entries();
    
    // 遍历迭代器
    for (let entry of iterator) {
        console.log(entry);  // 输出: ['key1', 'value1'], ['key2', 'value2'], ['key3', 'value3']
    }
    • forEach(callback(value[, key[, set]]) [, thisArg]):对 Set 中的每个元素执行一次提供的回调函数。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些键值对
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    
    // 使用 forEach 遍历 Set
    mySet.forEach(function(value, key, mySet) {
        console.log(`Key: ${key}, Value: ${value}`);
        // 这里也可以访问 map 对象
    });
    
    // 输出将是:
    // Key: value1, Value: value1
    // Key: value2, Value: value2
    // Key: value3, Value: value3
    • has(value):判断 Set 中是否存在指定的元素。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些键值对
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    
    console.log(mySet.has('value1')); // 输出 true
    console.log(mySet.has('value4')); // 输出 false
    • keys():返回一个新的迭代器对象,它按插入顺序包含了 Set 对象中每个元素的值。由于 Set 中没有键的概念,所以 keys() 方法的行为与 values() 方法相同。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些键值对
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    
    for(let val of mySet.keys()) {
      console.log(val); // 输出 'value1' 'value2' 'value3'
    }
    • values():返回一个新的迭代器对象,它按插入顺序包含了 Set 对象中每个元素的值。
    js
    // 创建一个新的 Set 对象
    let mySet = new Set();
    
    // 向 Set 中添加一些键值对
    mySet.add('value1');
    mySet.add('value2');
    mySet.add('value3');
    
    for(let val of mySet.values()) {
      console.log(val); // 输出 'value1' 'value2' 'value3'
    }
  4. 特点

    • 唯一性: 在 Set 中,所有值都是唯一的。任何值在 Set 中只能有一个。
    • 数据类型: Set 可以存储任何类型的值,无论是原始值还是对象引用。
    • 迭代顺序: Set 对象的元素是按照插入顺序进行迭代的。

    使用场景

    • 去重: 因为 Set 中的元素是唯一的,它可以用于数组去重。
    • 集合操作: Set 可以用于执行集合操作,如并集、交集和差集。

WeakSet 弱集合

  1. 创建

    js
    let ws = new WeakSet();
    
    let key1 = { id: 1 }
    let key2 = { id: 2 }
    let ws = new WeakSet(['key1', 'key2']);
  2. 方法

    • add(value):向 WeakSet 添加一个新的对象。
    • delete(value):从 WeakSet 中删除一个对象。
    • has(value):判断 WeakSet 中是否存在指定的对象。
  3. 特点

    • 仅接受对象作为成员:WeakSet 中的元素必须是对象。尝试添加非对象值会抛出异常。
    • 弱引用:WeakSet 中的对象都是弱引用,即如果没有其他引用指向这些对象,它们可能会被垃圾回收机制回收。
    • 不可枚举:由于 WeakSet 内部的对象引用是弱引用,且可能随时消失,因此 WeakSet 不能被遍历,这意味着没有方法可以获取 WeakSet 的全部内容。
    • 无法获取大小:由于 WeakSet 不能被遍历,因此也没有提供获取集合大小的属性或方法。

    使用场景

    • 存储对象的私有或临时数据:如果你需要跟踪对象是否被特定集合引用,同时不想干扰垃圾回收,WeakSet 是一个不错的选择。
    • 防止内存泄漏:由于 WeakSet 中的对象是弱引用的,当对象没有其他引用时,它们可以被自动清理,从而减少内存泄漏的可能性。

Iterator 迭代器

  1. 迭代与遍历

    • 遍历
      • 定义:遍历通常指的是访问数据结构中的每个元素,以某种方式检查或更新这些元素的过程。
      • 细节:遍历一词经常用于树和图这类复杂数据结构,表示按照某种顺序(如前序、中序、后序)访问所有节点。
    • 迭代
      • 定义:迭代是指访问数据结构中每个元素的过程,通常是为了执行某种操作(如打印、修改值等)。
      • 细节:迭代可以是显式的,如通过循环(for、while);也可以是隐式的,如使用迭代器对象。

    迭代 vs 遍历

    迭代是遍历的一种形式,特指按照一定顺序逐个访问元素的过程。遍历是一个更广泛的概念,不仅包括迭代,还包括像递归这样的访问方式。

  2. 可迭代与可遍历

    • 可遍历
      • 定义:可遍历是一个广义概念,指的是可以通过一种机制(可能是迭代器、递归或其他任何方式)访问数据结构中的每个元素的特性。
      • 应用:这个术语并不是 JavaScript 中的专有术语,但在描述复杂数据结构如图和树时经常使用。
    • 可迭代
      • 定义:在 JavaScript 中,可迭代对象是指可以用迭代器遍历的对象。可迭代对象实现了迭代器协议,即它们有一个特殊的 @@iterator 方法,该方法返回一个迭代器对象。
      • 判断:使用 console.log(obj[Symbol.iterator]) 是否打印 undefined,如:数组、字符串、NodeList、arguments、Map 和 Set 都是可迭代对象。
      • 特性:
        • for...of 循环:可以直接在 for...of 循环中遍历可迭代对象。
        • 扩展运算符(...):在函数调用、数组字面量等场合中可以使用扩展运算符。
        • 解构赋值:可以使用解构赋值从可迭代对象中提取值。

    可迭代 vs 可遍历

    可迭代是 JavaScript 中特定的概念,指的是实现了迭代器协议的对象。可遍历则是一个更广泛的概念,表示可以通过某种方式访问所有元素的特性。

    可迭代 vs 类数组

    类数组对象有数字索引和 length 属性,但并不一定是可迭代的。这意味着你不能总是使用 for...of 循环或其他迭代器工具(如 Array.from(), 扩展操作符 ... 等)来遍历它们。

    • 类数组对象,但不是可迭代对象(在没有被显式定义 Symbol.iterator 的环境中):
      • 旧版本浏览器中的 arguments 对象。
      • HTMLElement 的集合,如 document.forms。
    • 类数组对象,同时也是可迭代对象:
      • 字符串(String),尽管它们表现得像数组,还实现了可迭代协议。
      • NodeList 对象,最新的 DOM 规范定义了 NodeList 为可迭代的。
    • 不是类数组对象,但是可迭代对象:
      • Map 和 Set,它们有自己的迭代器实现,即使它们不像数组那样有数字索引和 length 属性。
  3. 可迭代协议与迭代器协议

    • 可迭代协议(Iterable Protocol)
      • 可迭代协议允许 JavaScript 对象定义或自定义它们的迭代行为。换句话说,它允许一个对象被 for...of 循环遍历。
      • 为了成为可迭代对象,对象必须实现 Symbol.iterator 方法。无参数调用这个方法会返回一个符合迭代器协议的对象。
    • 迭代器协议(Iterator Protocol)
      • 迭代器协议定义了一种标准方式来产生一个对象的值序列。一个对象如果符合迭代器协议,那么它必须实现一个 next() 方法,该方法返回一个对象,这个对象包含两个属性:
        • value:表示迭代器返回的当前元素的值。
        • done:一个布尔值,如果迭代器已经遍历完所有元素,则为 true;如果迭代器可以产生序列中的下一个值,则为 false。

    关系和区别

    • 关系:可迭代协议和迭代器协议在迭代过程中密切相关。一个对象如果是可迭代的(符合可迭代协议),它必须提供一个方法来获取一个迭代器(符合迭代器协议)。
    • 区别:可迭代协议侧重于定义对象如何提供迭代器,而迭代器协议侧重于迭代器对象如何迭代数据。
  4. 创建

    • 内置迭代器

      js
      let arr = ['Jack', 'Matt', 'Sam'];
      let iterator = arr[Symbol.iterator]();
      
      console.log(iterator.next()); // { value: 'Jack', done: false }
      console.log(iterator.next()); // { value: 'Matt', done: false }
      console.log(iterator.next()); // { value: 'Sam', done: false }
      console.log(iterator.next()); // { value: undefined, done: true }
    • 自定义对象迭代器

      js
      let obj = {
        name:'Jack',
        age:18,
        gender:'male',
        job:'JavaScript Engineer',
      
        [Symbol.iterator](){
          let entries = Object.entries(this)
          let index = 0
          return {
            next: () => {
              if(index < entries.length){
                return {
                  value: entries[index++],
                  done: false
                }
              }else{
                return {
                  value: undefined,
                  done:true
                }
              }
            }
          }
        }
      }
      let iterator = obj[Symbol.iterator]();
      
      console.log(iterator.next()); // { value: Jack, done: false }
      console.log(iterator.next()); // { value: Matt, done: false }
      console.log(iterator.next()); // { value: Sam, done: false }
      console.log(iterator.next()); // { value: undefined, done: true }
    • 自定义类迭代器

      js
      class Foo {
        constructor(name, age, gender, job, friends) {
          this.name = name
          this.age = age
          this.gender = gender
          this.job = job
          this.friends = friends
        }
      
        [Symbol.iterator]() {
          let index = 0
          return {
            next: () => {
              if (index < this.friends.length) {
                return {
                  value: this.friends[index++],
                  done: false
                }
              } else {
                return {
                  value: undefined,
                  done: true
                }
              }
            }
          }
        }
      }
      
      let foo = new Foo('Alice', 30, 'female', 'developer', ['Bob', 'Charlie', 'David'])
      let iterator = foo[Symbol.iterator]()
      
      console.log(iterator.next()) // { value: 'Bob', done: false }
      console.log(iterator.next()) // { value: 'Charlie', done: false }
      console.log(iterator.next()) // { value: 'David', done: false }
      console.log(iterator.next()) // { value: undefined, done: true }
  5. 终止

    • for-of 循环通过 break、return 或 throw 提前退出。

      • 内置迭代器:return() 方法会被调用,但并不会强制迭代器关闭。

        js
        let arr = ['Jack', 'Matt', 'Sam']
        let iter = arr[Symbol.iterator]()
        
        iter.return = function () {
          console.log('Exiting early')
          return { done: true }
        }
        
        for (let i of iter) {
          if (i === 'Matt') break
          console.log(i)
        }
        /**
        * Jack
        * Exiting early
        */
        
        for (let i of iter) {
          console.log(i)
        }
        // Sam
      • 自定义迭代器:return() 方法会被调用,且会强制迭代器关闭。

        js
        let obj = {
          name:['Jack', 'Matt', 'Sam'],
          [Symbol.iterator](){
            let index = 0
            return {
              next: () => {
                if(index < this.name.length){
                  return {
                    value: this.name[index++],
                    done: false
                  }
                }else{
                  return {
                    value: undefined,
                    done:true
                  }
                }
              },
              return: () => {
                console.log('Exiting early')
                return { done: true }
              }
            }
          }
        }
        
        for(let i of obj){
          if(i === 'Matt') break
          console.log(i)
        }
        /**
         * Jack
        * Exiting early
        */
        
        try {
          for (let i of obj) {
            if (i === 'Matt') throw 'err'
            console.log(i)
          }
        } catch (e) {
          console.log(e)
        }
        /**
         * Jack
        * Exiting early
        */
        
        let [a, b] = obj
        // Exiting early
    • 解构赋值时并未解构所有值。

  6. 应用场景

    • 原生语言特性:for...of 语句、展开运算符(...)、yield*、解构赋值
    • 创建可迭代对象:new Array、new String、new Map、new WeakMap、new Set、new WeakSet
    • 对象方法调用:Promise.all()、Promise.race()、Array.from()

在 JavaScript 中符合迭代协议的对象

数组(Array)/ 字符串(String)/ Map / Set / TypedArray / Generator / Arguments / NodeList

Generator 生成器

  1. 生成器函数

    通过使用 function* 语法声明,内部通过 yield 关键字可以暂停执行并稍后从暂停的地方继续执行。当调用生成器函数时,并不立即执行函数体中的代码,而是返回一个生成器对象。

    js
    // 方式一:函数声明
    function* generatorFunction() { }
    
    // 方式二:函数表达式
    let generatorFunction = function* () { }
    
    // 方式三:对象字面量方法
    let foo = {
      *generatorFunction(){ }
    }
    
    // 方式四:类实例方法
    class Foo {
      *generatorFunction(){ }
    }
    
    // 方式五:类静态方法
    class Foo {
      static* generatorFunction() { }
    }

    注意

    • 箭头函数不能用来定义生成器函数。
    • 标识生成器的星号不受两侧空格的影响。
  2. 创建

    • 自定义生成器

      js
      function* generatorFunction() {
          yield 'Jack'
          const val = yield 'Matt'
          console.log(val)
          yield 'Sam'
          yield 'David'
      }
      
      const generatorObject = generatorFunction()
      
      console.log(generatorObject.next())
      console.log(generatorObject.next())
      console.log(generatorObject.next('Alice'))
      console.log(generatorObject.next())
      console.log(generatorObject.next())
      /**
       * {value: 'Jack', done: false}
      * {value: 'Matt', done: false}
      * 'Alice'
      * {value: 'Sam', done: false}
      * {value: undefined, done: true}
      * {value: undefined, done: true}
      */
    • 自定义类生成器

      js
      class Foo {
        constructor(name, age, gender, job, friends) {
          this.name = name
          this.age = age
          this.gender = gender
          this.job = job
          this.friends = friends
        }
      
        *[Symbol.iterator]() {
          yield* Object.values(this.friends)
        }
      }
      
      let foo = new Foo('Alice', 30, 'female', 'developer', ['Bob', 'Charlie', 'David'])
      let iterator = foo[Symbol.iterator]()
      
      console.log(iterator.next()) // { value: 'Bob', done: false }
      console.log(iterator.next()) // { value: 'Charlie', done: false }
      console.log(iterator.next()) // { value: 'David', done: false }
      console.log(iterator.next()) // { value: undefined, done: true }

    TIP

    1. 生成器对象遵循迭代器协议,相当于一种特殊的迭代器,同样必须调用 next() 方法来开始或恢复执行。
    2. next() 方法返回值包含两个属性:value 和 done ,且支持传递参数,但参数会传递给上一个 yield 表达式的结果
    3. yield 关键字只能在生成器函数内部使用且会保留函数作用域。不能嵌套在非生成器函数中,否则会抛出语法错误。
    4. yield 关键字用于暂停函数的执行并产生一个值。当调用生成器的 next() 方法时,函数会执行到下一个 yield 语句处。
    5. yield* 表达式用于在一个生成器函数内部委托到另一个迭代器或生成器或者简单地迭代一个可迭代对象(如数组)。

    首次传参

    js
    function* generatorFunction(name) {
      console.log(name)
      // ...
    }
    
    const generatorObject = generatorFunction('Mick')
  3. 终止

    • return():强制生成器进入关闭状态,value 就是终止生成器对象的值。

      js
      function* genFn(){
        for (let val of [1, 2, 3]){
          yield val
        }
      }
      
      const gen = genFn()
      
      console.log(gen.next()) // {value: 1, done: false}
      console.log(gen.return(4))  // {value: 4, done: true}
      console.log(gen.next()) // {value: undefined, done: true}

      注意

      for...of 循环等内置语言结构会忽略状态为 done:true 的迭代器对象内部返回的值。

    • throw():在暂停的时候将一个提供的错误注入到生成器对象中,如果错误未被处理,生成器就会关闭,否则还可以恢复执行,且会跳过对应的 yield 。

    js
    function* genFn() {
      for (let val of [1, 2, 3]) {
        yield val
      }
    }
    
    const gen = genFn()
    
    console.log(gen.next()) // {value: 1, done: false}
    try {
      gen.throw('Exiting early')
    } catch (e) {
      console.log(e)
    }
    console.log(gen.next()) // {value: 3, done: false}

Promise 期约

  1. 同步与异步

    同步(Synchronous)和异步(Asynchronous)是编程中描述函数调用和操作行为的两个重要概念。它们代表了代码执行的不同方式,尤其是在涉及耗时操作(如 I/O 操作、网络请求等)时。 在 JavaScript 和许多其他编程语言中,代码的默认行为是同步的。这意味着代码中的语句按照它们出现的顺序依次执行,每个语句在进入下一个之前完成执行。

    • 同步(Synchronous)
      • 特点
        • 同步操作会阻塞代码的继续执行,直到该操作完成。
        • 同步代码的执行顺序通常易于理解,因为它按照代码的书写顺序依次执行。
        • 同步操作可能导致性能问题,特别是当操作需要很长时间才能完成时(例如,读写大文件、进行复杂计算等)。
      • 示例场景
        • 数据库操作,当需要立即获得查询结果时。
        • 文件操作,如逐行读取文件并处理。
    • 异步(Asynchronous)
      • 特点
        • 异步操作不会立即完成,且不会阻塞后续代码的执行。
        • 异步操作通常涉及回调函数、事件、Promises 或 Async/Await,这些用于在操作完成时接收通知和处理结果。
        • 异步编程可以提高应用程序的响应性和性能,特别是在处理 I/O 密集型或网络请求时。
      • 示例场景
        • 网络请求,如从服务器获取数据。
        • 大文件的读写操作,这类操作可以在后台进行,而不影响主线程的执行。
      • 编程方式
        • 回调函数(Callbacks)
          • 早期异步编程的主要方式。
          • 函数调用完成后执行的函数。
          • 缺点:可能导致“回调地狱”,代码难以阅读和维护。
        • Promises
          • 为异步操作提供了更好的语法。
          • Promise 是一个代表最终完成或失败的异步操作的对象。
          • 支持链式调用(.then() 和 .catch())。
          • 解决了回调地狱的问题。
        • Async/Await
          • 异步编程的现代方式,基于 Promises。
          • 使用 async 关键字定义异步函数。
          • 使用 await 关键字等待异步操作的结果。
          • 代码看起来像同步代码,更易于理解和维护。
    • 同步 vs 异步
      • 阻塞 vs 非阻塞:同步操作是阻塞的,而异步操作是非阻塞的。
      • 执行顺序:同步代码的执行顺序易于跟踪,执行完一个操作才开始下一个操作。异步代码的执行顺序更难跟踪,操作可以在未来的任意时间点完成。
      • 资源利用:异步编程使得可以更高效地利用系统资源,特别是在涉及到多个耗时操作时。
    • 选择使用
      • 在处理 I/O 操作、网络请求或其他耗时操作时,通常建议使用异步编程,以避免阻塞主线程,保持应用程序的响应性。
      • 在操作必须按特定顺序同步执行,或当异步逻辑过于复杂时,可以选择同步编程。
  2. 基本特征

    • 状态:
      • Pending(进行中):初始状态,既不是成功,也不是失败。
      • Fulfilled(已成功):意味着操作成功完成。
      • Rejected(已失败):意味着操作失败。
    • 不可变性:
      • 一旦 Promise 的状态改变(从 pending 变为 fulfilled 或 rejected),它就不能再变,其状态和结果是不可变的。
      • 一旦 Promise 进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。跟在添加这个处理程序的代码之后的同步代码一定会在处理程序之前先执行。
  3. 创建

    Promise 对象是通过 new Promise() 构造函数创建的,构造函数接受一个函数作为参数,这个函数有两个参数:resolve 和 reject。

    javascript
    const myPromise = new Promise((resolve, reject) => {
      // 异步操作的代码...
    
      if (/* 操作成功 */) {
        resolve(value); // 成功时,调用 resolve 并传递解决值
      } else {
        reject(reason); // 失败时,调用 reject 并传递拒绝理由
      }
    });
    
    myPromise
      .then(value => {
        // 处理成功的情况
      })
      .catch(reason => {
        // 处理失败的情况
      })
      .finally(() => {
        // 总会执行
      });

    链式调用

    Promise 支持链式调用,这意味着你可以在一个 then() 调用后紧接着另一个 then() 调用,以此类推。每个 then() 可以修改数据并传递给链中的下一个 then()。

  4. 方法

    • Promise.resolve(value):用于创建一个状态为已解决(fulfilled)的 Promise,其中 value 参数是这个 Promise 解析的值。
    js
    Promise.resolve(value)
      .then(res => {
        console.log(res); // 输出: 开始
        return '下一步';
      })
      .then(res => {
        console.log(res); // 输出: 下一步
      });
    • Promise.reject(reason):用于创建一个状态为已拒绝(rejected)的 Promise,其中 reason 参数是拒绝这个 Promise 的原因。
    js
    function checkUserAccess(user) {
      if (!user.hasAccess) {
        return Promise.reject(new Error('用户无权访问'));
      }
      // 如果用户有访问权限,则进行其他操作
    }
    
    checkUserAccess(currentUser)
      .then(() => {
        // 处理访问权限的逻辑
      })
      .catch(error => {
        console.error(error.message); // "用户无权访问"
      });
    • Promise.all(iterable):用于将多个 Promise 实例包装成一个新的 Promise 实例。只有当所有的 Promise 都完成时,返回的 Promise 才会成功解决,解析值是一个数组,包含所有 Promise 的解析值。如果任何一个 Promise 失败,则整个 Promise.all 调用失败。
    js
    Promise.all([userPromise, productPromise])
      .then(([userData, productData]) => {
        console.log('用户数据:', userData);
        console.log('商品数据:', productData);
      })
      .catch(error => {
        console.error('一个或多个 Promise 失败:', error);
      });
    • Promise.allSettled(iterable):用于将多个 Promise 实例包装成一个新的 Promise 实例。无论每个 Promise 成功或失败,都会等待所有 Promise 完成,返回的每个 Promise 结果都会标明是成功还是失败。
    js
    Promise.allSettled([userPromise, productPromise])
      .then(results => {
        results.forEach((result) => {
          if (result.status === 'fulfilled') {
            console.log('成功:', result.value);
          } else {
            console.log('失败:', result.reason);
          }
        });
      });
    • Promise.race(iterable):将多个 Promise 实例包装成一个新的 Promise 实例。返回的 Promise 将由第一个解决或拒绝的 Promise 决定。
    js
    Promise.any([promise1, promise2, promise3])
      .then((firstResolvedOrRejectValue) => {
        console.log('数据获取成功:', firstResolvedOrRejectValue);
      })
      .catch((error) => {
        console.error('所有操作失败:', error);
      });
    • Promise.any(iterable):将多个 Promise 实例包装成一个新的 Promise 实例。返回的 Promise 将由第一个成功解决的 Promise 决定。如果所有 Promise 都失败了,则返回一个聚合错误。
    js
    Promise.any([promise1, promise2, promise3])
      .then((firstResolvedValue) => {
        console.log('数据获取成功:', firstResolvedValue);
      })
      .catch((error) => {
        console.error('所有操作失败:', error);
      });
    • then(onFulfilled, onRejected):添加解决(fulfilled)和拒绝(rejected)回调到当前 Promise,并返回一个新的 Promise。允许链式调用。
    • catch(onRejected):添加一个拒绝(rejected)回调到当前 Promise,并返回一个新的 Promise。主要用于处理 Promise 的错误。
    • finally(onFinally):添加一个回调,无论 Promise 成功或失败都会执行。不接受任何参数,不改变 Promise 的状态或值。
  5. Async 与 Await

    • async:用于声明异步函数,支持函数声明、函数表达、箭头函数和方法。

      javascript
      async function foo() { }
      
      const foo = async function() { }
      
      const foo = async () => { }
      
      class Foo {
        async bar() { }
      }

      TIP

      • async 函数始终返回 Promise 对象,其结果由 async 函数执行的返回值决定
      • 若使用 return 关键字返回值,则该值会被 Promise.resolve() 隐式包装成一个 Promise 对象;若没有 return 则返回 undefined 。
    • await:暂停异步函数的执行,等待 Promise 的成功结果。

      javascript
      async function fetchData() {
        const data = await someAsyncOperation();
        console.log(data);
      }

      TIP

      • 若 await 等待一个 Promise 对象,则返回 Promise 成功的结果值。
      • 若 await 等待一个非 Promise 对象,则当作 Promise 成功的结果值返回。

      DANGER

      await 关键字必须在 async 函数中使用,不能在顶级上下方如 <script> 标签或模块中使用(立即执行 async 函数除外)。

    • 错误处理

      • 使用 try...catch 语句处理异步函数中的错误。
      js
      async function fetchData() {
        try {
          const data = await someAsyncOperation();
          console.log(data);
        } catch (error) {
          console.error('An error occurred:', error);
        }
      }
    • 并行执行

      • 使用 Promise.all 来同时执行多个异步操作。
      js
      async function fetchMultipleData() {
        const [result1, result2] = await Promise.all([fetchData1(), fetchData2()]);
        // 处理 result1 和 result2
      }

Proxy 代理

Proxy 对象是一个用于创建对象的包装器(wrapper),它允许你对基本操作进行拦截和自定义。这些操作包括属性查找、赋值、枚举、函数调用等。Proxy 可以用来创建各种类型的行为,例如验证、格式化、通知,甚至是性能优化。

  1. 创建

    js
    const proxy = new Proxy(target, handler)
    • target: 你希望代理的对象。
    • handler: 一个定义了捕获器(trap)的对象,捕获器在操作目标对象时会被调用。
  2. 捕获器方法

    • handler.get(target, key, receiver): 拦截对象属性的读取操作。其中,target 指的是被代理的对象、key 是要访问的属性名、receiver 通常是代理对象自身。
    js
    let obj = { a: 1 };
    
    let proxy = new Proxy(obj, {
      get(target, key, receiver) {
        return Reflect.get(target, key, receiver);
      }
    })
    
    console.log(proxy.a); // 输出: 1
    console.log(proxy); // 输出:{ a: 1 }
    • handler.set(target, key, value, receiver): 返回布尔值,拦截对象属性的设置操作。
    js
    let obj = { a: 1 };
    
    let proxy = new Proxy(obj, {
      set(target, key, value, receiver) {
        return Reflect.set(target, key, value, receiver);
      }
    });
    
    proxy.a = 2;
    console.log(proxy.a); // 输出: 2
    console.log(proxy); // 输出:{ a: 2 }
    • handler.getOwnPropertyDescriptor(target, key): 返回一个对象或undefined,拦截获取对象属性描述符的操作。
    js
    const target = {
        prop1: 'value1'
    };
    
    const handler = {
        getOwnPropertyDescriptor: function(target, prop) {
            console.log(`Getting descriptor for ${prop}`);
            return Reflect.getOwnPropertyDescriptor(target, prop);
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    const descriptor = Object.getOwnPropertyDescriptor(proxy, 'prop1');
    console.log(descriptor);
    /**
     * 输出
     * {
     *   value: 'value1',
     *   writable: true,
     *   enumerable: true,
     *   configurable: true
     * }
     */
    • handler.defineProperty(target, key, descriptor): 返回布尔值,拦截定义新属性或修改现有属性定义的操作,其中 descriptor: 属性描述符,例如 value、writable、enumerable 和 configurable。
    js
    const target = {};
    
    const handler = {
        defineProperty: function(target, key, descriptor) {
            console.log(`Property ${key} is being defined`);
            return Reflect.defineProperty(target, key, descriptor);
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    Object.defineProperty(proxy, 'newKey', {
        value: 42,
        writable: true
    });
    
    console.log(proxy.newKey); // 输出 42
    • handler.deleteProperty(target, key):返回布尔值,拦截删除属性的操作。
    js
    const target = {
        prop1: 'value1',
        prop2: 'value2'
    };
    
    const handler = {
        deleteProperty: function(target, key) {
            console.log(`Property ${key} is being deleted`);
            return Reflect.deleteProperty(target, key);
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    delete proxy.prop1; // 将触发 handler.deleteProperty
    
    console.log(target); // { prop2: 'value2' }
    • handler.ownKeys(target): 返回一个可枚举对象,拦截获取对象自有属性键的操作。
    js
    const target = {
        prop1: 'value1',
        prop2: 'value2'
    };
    
    const handler = {
        ownKeys: function(target) {
            console.log(`Getting own property keys of the target`);
            return Reflect.ownKeys(target);
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    console.log(Object.keys(proxy)); // 输出:Getting own property keys of the target 和 ['prop1', 'prop2']
    • handler.getPrototypeOf(target): 返回一个对象或 null,拦截获取对象原型的操作。
    js
    const target = { a: 123 }
    
    const newTarget = Object.create(target)
    
    const handler = {
      getPrototypeOf: function (target) {
        return Reflect.getPrototypeOf(target)
      }
    }
    
    const proxy = new Proxy(newTarget, handler)
    
    console.log(Object.getPrototypeOf(proxy)) // 输出 { a: 123 }
    • handler.setPrototypeOf(target, property): 返回布尔值,拦截设置对象原型的操作。
    js
    const target = {};
    
    const handler = {
        setPrototypeOf: function(target, prototype) {
            console.log(`Setting prototype of the target`);
            return Reflect.setPrototypeOf(target, prototype);
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    const newProto = {newMethod: function() {}};
    Object.setPrototypeOf(proxy, newProto); // 触发 handler.setPrototypeOf
    console.log(Object.getPrototypeOf(proxy) === newProto); // 输出 true
    • handler.has(target, key): 返回布尔值,拦截 in 操作符。
    js
    const target = {
        key1: 'value1',
        key2: 'value2'
    };
    
    const handler = {
        has: function(target, key) {
            console.log(`Checking if ${key} is in target`);
            return key in target;
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    console.log('key1' in proxy); // 输出:Checking if key1 is in target 和 true
    console.log('key3' in proxy); // 输出:Checking if key3 is in target 和 false
    • handler.isExtensible(target): 返回布尔值,拦截检查对象是否可扩展的操作。
    js
    const target = {};
    
    const handler = {
        isExtensible: function(target) {
            console.log(`Checking if target is extensible`);
            return Reflect.isExtensible(target);
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    console.log(Object.isExtensible(proxy)); // 输出:Checking if target is extensible 和 true 或 false
    • handler.preventExtensions(target): 返回布尔值,拦截防止对象扩展的操作。
    js
    const target = {};
    
    const handler = {
        preventExtensions: function(target) {
            console.log(`Preventing extensions on the target`);
            return Reflect.preventExtensions(target);
        }
    };
    
    const proxy = new Proxy(target, handler);
    
    Object.preventExtensions(proxy); // 触发 handler.preventExtensions
    console.log(Object.isExtensible(proxy)); // 输出 false,表明 proxy 不能再添加新属性
    • handler.apply(targetFunc, thisArg, argumentsList): 拦截函数调用。其中,targetFunc:目标函数、thisArg:被调用时的上下文对象、argumentsList:被调用时的参数数组。
    js
    function sum(a, b) {
        return a + b;
    }
    
    const handler = {
        apply: function(target, thisArg, argumentsList) {
            console.log(`Calculate sum: ${argumentsList}`);
            const result = target(...argumentsList);
            console.log(`Result: ${result}`);
            return result;
        }
    };
    
    const proxy = new Proxy(sum, handler);
    
    console.log(proxy(5, 3));
    • handler.construct(targetFunc, argumentsList, newTargetFunc): 用于拦截 new 操作符的捕获器(trap)。当一个函数或构造函数通过 new 调用创建一个新对象时,handler.construct() 方法会被触发。
    js
    function MyConstructor(arg) {
        this.prop = arg;
    }
    
    const handler = {
        construct(target, args) {
            console.log(`Called: new ${target.name}(${args.join(', ')})`);
            return new target(...args);
        }
    };
    
    const ProxyConstructor = new Proxy(MyConstructor, handler);
    
    const instance = new ProxyConstructor('test');
    console.log(instance.prop); // 输出: test

更好地与 Proxy 配合

在使用 Proxy 创建代理对象时,Reflect 的方法可以与 Proxy 的陷阱(traps)直接对应。这种一致性使得 Proxy 陷阱的实现更加简单和直接,因为在许多情况下,陷阱中的操作只是调用相应的 Reflect 方法。

Error 错误

在JavaScript中,Error 对象是一个内置的对象类型,用于表示运行时错误。它是所有异常处理的基础,并且可以被用于用户自定义的错误管理。

  1. 创建

    js
    let myError = new Error("Error message");
  2. 抛出错误

    js
    throw new Error("Something went wrong");
  3. 捕获错误

    js
    try {
        // 尝试执行的代码
    } catch (error) {
        // 处理错误
    }
  4. 错误的堆栈跟踪

    js
    console.log(myError.stack);
  5. 错误类型

    • SyntaxError
      • 描述:当尝试解析不符合语法规范的代码时,会抛出 SyntaxError。
      • 常见原因:语法写错,例如括号、括号、引号不匹配,或者使用了不存在的语法结构。
      • 示例:let array = [1, 2, 3];
    • ReferenceError
      • 描述:当尝试引用一个不存在的变量时,会抛出 ReferenceError。
      • 常见原因:引用了未声明的变量,或者在不允许的作用域外访问了某个变量。
      • 示例:console.log(undeclaredVariable);
    • TypeError
      • 描述:当一个值的类型不符合预期或在不允许的情况下尝试修改值时,会抛出 TypeError。
      • 常见原因:调用非函数类型的值,尝试改变不可变的值(如 undefined 或 null)。
      • 示例:null.foo();
    • RangeError
      • 描述:当一个值不在其允许的范围或大小时,会抛出 RangeError。
      • 常见原因:数组长度为负,数字超出范围等。
      • 示例:new Array(-1);
    • URIError
      • 描述:与全局 URI 处理函数有关的错误,例如在使用 encodeURI() 或 decodeURI() 时,如果传入的参数不是有效的 URI 组件,则会抛出 URIError。
      • 常见原因:给 URI 函数传递了错误的参数。
      • 示例:decodeURI('%');
  6. 属性

    • name:name 属性表示错误的类型或名称。对于 Error 的实例,默认值是 "Error"。
    • message:message 属性包含有关错误的简短描述,通常是一个字符串。这个描述旨在向开发者提供关于发生了什么类型的错误的有用信息。

异步代码中的错误处理

在异步代码(如使用 Promises 或 async/await)中,错误处理略有不同。例如,Promise 链中的错误需要使用 .catch() 方法来捕获,而 async 函数中的错误可以使用传统的 try...catch 语法处理。

ArrayBuffer 二进制数据

ArrayBuffer 代表一个通用的、固定长度的二进制数据缓冲区。ArrayBuffer 对象提供了一种在内存中存储二进制数据的机制,但它本身不能直接被操作。相反,它通过 TypedArray 视图或 DataView 对象来进行操作。

  1. 创建

    js
    let buffer = new ArrayBuffer(16); // 创建一个包含16字节的 ArrayBuffer
  2. 使用 TypedArray 视图

    js
    let buffer = new ArrayBuffer(16);
    let intArray = new Int32Array(buffer); // 创建一个包含32位有符号整数的视图
    
    intArray[0] = 42;
    console.log(new DataView(buffer).getInt32(0, true)); // 输出: 42
  3. 使用 DataView 对象

    js
    let buffer = new ArrayBuffer(16);
    let dataView = new DataView(buffer);
    
    dataView.setInt32(0, 42, true); // 将32位有符号整数写入缓冲区的偏移量0处
    
    console.log(new Int32Array(buffer)[0]); // 输出: 42
  4. 属性

    • byteLength:返回此数组缓冲区的长度(以字节为单位)。
    • maxByteLength:返回此数组缓冲区可以调整大小的最大长度(以字节为单位)。
    • resizable:返回此数组缓冲区是否可以调整大小。
  5. 方法

    • ArrayBuffer.isView():用于判断一个值是否为 TypedArray 的视图对象。
    js
    const buffer = new ArrayBuffer(16)
    console.log(ArrayBuffer.isView(buffer))
    • resize():将 ArrayBuffer 大小调整为指定大小(以字节为单位)。
    js
    const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
    console.log(buffer.byteLength); // Expected output: 8
    
    buffer.resize(12);
    console.log(buffer.byteLength);
    • slice(start?, end?):截取 ArrayBuffer 的一部分。
    js
    const buf1 = new ArrayBuffer(8)
    console.log(buf1) // byteLength: 8
    const buf2 = buf1.slice(5)
    console.log(buf2) // byteLength: 3

SharedArrayBuffer 共享二进制数据

SharedArrayBuffer 允许在多个 Web Workers 之间共享二进制数据。与普通的 ArrayBuffer 不同,SharedArrayBuffer 允许多个线程直接访问相同的内存空间,这为多线程编程提供了一种更高效的方式。

注意事项

  • 内存同步: 由于多个线程可以同时访问相同的内存,需要注意内存同步的问题。Atomics 对象和 SharedArrayBuffer 通常一起使用,以确保对共享内存的原子操作。
  • Web Workers: SharedArrayBuffer 主要用于在 Web Workers 之间共享数据。Web Workers 是在后台运行的 JavaScript 线程,允许在不阻塞主线程的情况下执行并行任务。
  • 跨域限制: 出于安全原因,浏览器对 SharedArrayBuffer 实施了跨域限制,需要通过设置 HTTP 头 Cross-Origin-Opener-Policy 和 Cross-Origin-Embedder-Policy 来显式允许在不同域的页面之间共享 SharedArrayBuffer。
  1. 创建

    js
    let sharedBuffer = new SharedArrayBuffer(16); // 创建一个包含16字节的 SharedArrayBuffer
  2. 使用 TypedArray 视图

    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let intArray = new Int32Array(sharedBuffer); // 创建一个包含32位有符号整数的视图
    
    intArray[0] = 42;
  3. 属性

    • byteLength:返回此 SharedArrayBuffer 的长度(以字节为单位)。
    • growable:返回此 SharedArrayBuffer 是否可以增长。
    • maxByteLength:返回此 SharedArrayBuffer 可以增长到的最大长度(以字节为单位)。
  4. 方法

    • grow(newLength):将 SharedArrayBuffer 增长到指定的大小(以字节为单位)。
    js
    const buffer = new SharedArrayBuffer(8, { maxByteLength: 16 });
    
    if (buffer.growable) {
      console.log("SAB is growable!");
      buffer.grow(12);
    }
    • slice(begin? end?):截取 SharedArrayBuffer 的一部分。
    js
    const buffer = new SharedArrayBuffer(16)
    console.log(buffer.slice(4, 12))

DataView 数据视图

DataView 提供了一种灵活的方式来读取和写入 ArrayBuffer 中的数据。DataView 允许以不同的数据类型和字节序(大端序或小端序)从底层的二进制缓冲区中读取和写入数据。

  1. 创建

    js
    let buffer = new ArrayBuffer(16);
    let dataView = new DataView(buffer);
  2. 使用 DataView 视图

    js
    let buffer = new ArrayBuffer(16);
    let dataView = new DataView(buffer);
    
    // 写入一个 32 位有符号整数到偏移量为 0 的位置
    dataView.setInt32(0, 42, true); // true 表示使用小端序
    
    // 读取一个 16 位无符号整数,偏移量为 4
    let value = dataView.getUint16(4, false); // false 表示使用大端序
    
    console.log(value); // 输出: 根据设置的数据而定
  3. 属性

    • buffer: 返回与 DataView 关联的 ArrayBuffer 对象。
    • byteLength: 返回 DataView 对象关联的 ArrayBuffer 的字节长度。
    • byteOffset: 返回 DataView 对象在关联的 ArrayBuffer 中的字节偏移量。
  4. 方法

    • getUint8(byteOffset, littleEndian): 以 8 位无符号整数的形式从指定位置读取数据。
    • getInt8(byteOffset, littleEndian): 以 8 位有符号整数的形式从指定位置读取数据。
    • getUint16(byteOffset, littleEndian): 以 16 位无符号整数的形式从指定位置读取数据。
    • getInt16(byteOffset, littleEndian): 以 16 位有符号整数的形式从指定位置读取数据。
    • getUint32(byteOffset, littleEndian): 以 32 位无符号整数的形式从指定位置读取数据。
    • getInt32(byteOffset, littleEndian): 以 32 位有符号整数的形式从指定位置读取数据。
    • getFloat32(byteOffset, littleEndian): 以 32 位浮点数的形式从指定位置读取数据。
    • getFloat64(byteOffset, littleEndian): 以 64 位浮点数的形式从指定位置读取数据。
    • setUint8(byteOffset, value, littleEndian): 以 8 位无符号整数的形式写入数据到指定位置。
    • setInt8(byteOffset, value, littleEndian): 以 8 位有符号整数的形式写入数据到指定位置。
    • setUint16(byteOffset, value, littleEndian): 以 16 位无符号整数的形式写入数据到指定位置。
    • setInt16(byteOffset, value, littleEndian): 以 16 位有符号整数的形式写入数据到指定位置。
    • setUint32(byteOffset, value, littleEndian): 以 32 位无符号整数的形式写入数据到指定位置。
    • setInt32(byteOffset, value, littleEndian): 以 32 位有符号整数的形式写入数据到指定位置。
    • setFloat32(byteOffset, value, littleEndian): 以 32 位浮点数的形式写入数据到指定位置。
    • setFloat64(byteOffset, value, littleEndian): 以 64 位浮点数的形式写入数据到指定位置。

非构造器对象

这些对象通常表示全局的属性和函数。

Math 数学

Math  是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math  不是一个函数对象。Math  不是一个构造器。 Math  用于  Number  类型。它不支持  BigInt。

  1. 属性

    • Math.PI:圆周率 π 的近似值。
    • Math.E:自然对数的底数 e。
    • Math.LN2:2 的自然对数。
    • Math.LN10:10 的自然对数。
    • Math.LOG2E:以 2 为底 e 的对数。
    • Math.LOG10E:以 10 为底 e 的对数。
    • Math.SQRT1_2:1/2 的平方根。
    • Math.SQRT2:2 的平方根。
  2. 方法

    • Math.abs(x): 返回x的绝对值。
    • Math.ceil(x): 对x向上取整,返回大于或等于x的最小整数。
    • Math.floor(x): 对x向下取整,返回小于或等于x的最大整数。
    • Math.round(x): 返回x四舍五入后的整数值。
    • Math.max(x, y, ..., z): 返回一组数中的最大值。
    • Math.min(x, y, ..., z): 返回一组数中的最小值。
    • Math.pow(x, y): 返回x的y次幂。
    • Math.sqrt(x): 返回x的平方根。
    • Math.random(): 生成一个0到1之间的伪随机数。
    • Math.log(x): 返回x的自然对数。
    • Math.exp(x): 返回E的x次幂。
    • Math.sin(x), Math.cos(x), Math.tan(x): 分别返回角x的正弦、余弦和正切值。
    • Math.asin(x), Math.acos(x), Math.atan(x): 分别返回x的反正弦、反余弦和反正切值。

    范围随机整数

    js
    //得到一个两数之间的随机整数,不含最大值,含最小值
    function getRandomInt(min, max) {
      min = Math.ceil(min)
      max = Math.floor(max)
      return Math.floor(Math.random() * (max - min)) + min
    }
    
    //得到一个两数之间的随机整数,含最大值,含最小值
    function getRandomIntInclusive(min, max) {
      min = Math.ceil(min)
      max = Math.floor(max)
      return Math.floor(Math.random() * (max - min + 1)) + min
    }

Reflect 反射

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

  1. 方法
    • Reflect.get(target, key, receiver): 拦截对象属性的读取操作。
    • Reflect.set(target, key, value, receiver): 返回布尔值,拦截对象属性的设置操作。
    • Reflect.getOwnPropertyDescriptor(target, key): 返回一个对象或undefined,拦截获取对象属性描述符的操作。
    • Reflect.defineProperty(target, key, descriptor): 返回布尔值,拦截定义新属性或修改现有属性定义的操作。
    • Reflect.deleteProperty(target, key):返回布尔值,拦截删除属性的操作。
    • Reflect.ownKeys(target): 返回一个可枚举对象,拦截获取对象自有属性键的操作。
    • Reflect.getPrototypeOf(target): 返回一个对象或 null,拦截获取对象原型的操作。
    • Reflect.setPrototypeOf(target, key): 返回布尔值,拦截设置对象原型的操作。
    • Reflect.has(target, key): 返回布尔值,拦截 in 操作符。
    • Reflect.isExtensible(target): 返回布尔值,拦截检查对象是否可扩展的操作。
    • Reflect.preventExtensions(target): 返回布尔值,拦截防止对象扩展的操作。
    • Reflect.apply(targetFunc, thisArg, argumentsList): 拦截函数调用。
    • Reflect.construct(targetFunc, argumentsList, newTarget): 返回一个对象,拦截构造函数调用。

Global 全局

在JavaScript中,全局对象(Global Object)是一个顶层对象,它的属性和方法在所有的代码中都是可用的。不同的JavaScript环境有不同的全局对象:

  • 在浏览器环境中,全局对象通常是window对象。
  • 在Node.js环境中,全局对象是global。
  • 在Web Workers中,全局对象是self。

全局对象作为全局变量的宿主,意味着所有在全局作用域中声明的变量都会成为全局对象的属性。

  1. 属性

    • undefined:特殊值 undefined

    • NaN:特殊值 NaN

    • Infinity:特殊值 Infinity

    • globalThis:globalThis 是 JavaScript 中的一个标准对象,它提供了一个统一的方式来引用不同环境中的全局对象。

      js
      // 定义一个全局变量
      globalThis.myGlobalVar = 'Hello, world!';
      
      // 在不同的环境中访问此变量
      console.log(globalThis.myGlobalVar); // 输出: "Hello, world!"

      TIP

      在 JavaScript 的历史中,不同的环境(如浏览器、Node.js)有着不同的全局对象(例如,在浏览器中是 window,在 Node.js 中是 global),这导致了跨环境代码的兼容性问题。globalThis (ECMAScript 2020 引入)旨在解决这个问题,提供一个统一的、环境无关的方法来访问全局对象。

  2. 方法

    • encodeURI():用于编码整个URI(但不会编码特殊字符,如 :, /, ?, & 和 #)。
    • encodeURIComponent():用于编码单个URI组件(除了字母、数字以及 !, ~, *, ', (, ) 之外的所有字符)。
    • decodeURI(): 解码由 encodeURI 创建的特殊字符编码。
    • decodeURIComponent():解码由 encodeURIComponent 创建的任何特殊字符编码。
    • eval():用于执行一段表示为字符串的JavaScript代码。它是一个强大但同时也是一个应该谨慎使用的功能。

      安全注意事项

      1. 安全风险:如果 eval() 执行的代码是从不受信任的来源获取的,那么它可能会引入安全漏洞,因为这段代码有可能执行恶意操作。
      2. 作用域问题:eval() 中的代码可以访问并修改其调用环境的作用域,这可能导致代码难以理解和维护。
      3. 性能问题:使用 eval() 执行的代码通常比直接执行的代码运行得慢,因为解释器/编译器无法在脚本加载时优化这段代码。
    • isNaN:用于检查其参数是否是非数字值(NaN)。与 Number.isNaN() 方法不同的是,isNaN() 会首先尝试将传入的参数转换为一个数字,然后判断这个数字是否非数字值。
    • isFinite():用于检测传入的参数是否是一个有限的数值。与 Number.isFinite() 方法不同的是,isFinite() 会首先尝试将传入的参数转换为一个数字,然后判断这个数字是否有限。
    • parseFloat():用于将字符串解析成浮点数。如果字符串的前缀可以解析为有效的浮点数,则返回该数值,否则返回 NaN。
    • parseInt():用于将字符串解析成整数。如果字符串的前缀可以解析为有效的整数,则返回该整数,否则返回 NaN。

JSON

在 JavaScript 中定义了原生 JSON 对象,用于将 JavaScript 对象序列化为 JSON 字符串,以及将 JSON 数组解析为 JavaScript 对象。JSON (JavaScript Object Notation) 也是一种轻量级的数据格式,可以方便地表示复杂数据结构。

  1. 语法

    • 简单值:字符串、数值、布尔值和 null,不支持undefined 。

      json
      "Hello World!"

      TIP

      JavaScript字符串和JSON字符串的主要区别是,JSON字符串必须使用双引号(单引号会导致语法错误)。

    • 对象:JSON中的对象属性名必须始终带有双引号,不使用双引号或使用单引号会导致语法错误。

      json
      {
        "name": "mike",
        "age": 18
      }
    • 数组

      json
      [
        {
          "title": "JSON",
          "authors": [
            "TOM",
            "JACK"
          ],
          "year": 2022
        },
        {
          "title": "JavaScript",
          "authors": [
            "TONI"
          ],
          "year": 2021
        }
      ]
  2. 方法

    • JSON.parse(): 将JSON字符串转换为JavaScript对象。
    • JSON.stringify(): 将JavaScript对象转换为JSON字符串。

Atomics 原子

Atomics 提供了一组原子操作,用于在多线程环境中执行同步和通信。它通常与 SharedArrayBuffer 配合使用,以确保在多个线程之间共享的内存中进行原子操作,避免竞态条件(race conditions)和数据不一致性问题。

  1. 方法

    • Atomics.add(typedArray, index, value): 原子地将一个值添加到类型化数组中指定索引的元素,并返回更新后的值。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    Atomics.add(arr, 0, 5);
    
    console.log(arr[0]); // 输出: 5
    • Atomics.and(typedArray, index, value): 在类型化数组中指定索引处,原子地执行按位与操作,将元素与提供的值进行按位与,并返回更新后的值。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    arr[0] = 0b1010; // 10 in binary
    
    Atomics.and(arr, 0, 0b1100); // 12 in binary
    
    console.log(arr[0].toString(2)); // 输出: 1000 (8 in binary)
    • Atomics.compareExchange(typedArray, index, expectedValue, replacementValue): 原子地比较类型化数组中指定索引处的值与期望的值。如果它们相等,就用新值交换并返回原始值。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    arr[0] = 42;
    
    let oldValue = Atomics.compareExchange(arr, 0, 42, 100);
    
    console.log(oldValue); // 输出: 42
    console.log(arr[0]);   // 输出: 100
    • Atomics.exchange(typedArray, index, value): 原子地用提供的值交换类型化数组中指定索引处的值,并返回原始值。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    arr[0] = 42;
    
    let oldValue = Atomics.exchange(arr, 0, 100);
    
    console.log(oldValue); // 输出: 42
    console.log(arr[0]);   // 输出: 100
    • Atomics.isLockFree(size): 返回一个布尔值,指示给定大小(以字节为单位)是否适用于 Atomics 操作的无锁大小。
    js
    let isLockFree = Atomics.isLockFree(4); // 检查 4 字节是否为无锁大小
    console.log(isLockFree); // 输出: true 或 false,取决于平台支持
    • Atomics.load(typedArray, index): 原子地加载类型化数组中指定索引处的值并返回它。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    arr[0] = 42;
    
    let loadedValue = Atomics.load(arr, 0);
    
    console.log(loadedValue); // 输出: 42
    • Atomics.store(typedArray, index, value): 原子地将提供的值存储在类型化数组中的指定索引处。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    Atomics.store(arr, 0, 42);
    
    console.log(arr[0]); // 输出: 42
    • Atomics.wait(typedArray, index, value, timeout): 原子地检查类型化数组中指定索引处的值是否等于提供的值。如果不等于,它会使当前线程进入睡眠状态,直到值发生变化或经过指定的超时时间。
    • Atomics.waitAsync(typedArray, index, value, timeout): Atomics.wait 的版本,返回一个 promise,成功时解析为唤醒原因的字符串,如果操作超时则拒绝。
    • Atomics.notify(typedArray, index, count): 唤醒在类型化数组中指定索引处等待的睡眠线程。count 参数指定要唤醒的线程数。
    js
    const sab = new SharedArrayBuffer(1024);
    const int32 = new Int32Array(sab);
    
    // 在一个线程中等待
    Atomics.wait(int32, 0, 0);
    console.log(int32[0]); // 123
    
    // 在另一个线程中通知条件变化
    Atomics.store(int32, 0, 123);
    Atomics.notify(int32, 0, 1);
    • Atomics.or(typedArray, index, value): 在类型化数组中指定索引处,原子地执行按位或操作,将元素与提供的值进行按位或,并返回更新后的值。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    arr[0] = 0b1010; // 10 in binary
    
    Atomics.or(arr, 0, 0b1100); // 12 in binary
    
    console.log(arr[0].toString(2)); // 输出: 1110 (14 in binary)
    • Atomics.sub(typedArray, index, value): 原子地从类型化数组中指定索引处的元素中减去一个值,并返回更新后的值。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    arr[0] = 10;
    
    Atomics.sub(arr, 0, 5);
    
    console.log(arr[0]); // 输出: 5
    • Atomics.xor(typedArray, index, value): 在类型化数组中指定索引处,原子地执行按位异或操作,将元素与提供的值进行按位异或,并返回更新后的值。
    js
    let sharedBuffer = new SharedArrayBuffer(16);
    let arr = new Int32Array(sharedBuffer);
    
    arr[0] = 0b1010; // 10 in binary
    
    Atomics.xor(arr, 0, 0b1100); // 12 in binary
    
    console.log(arr[0].toString(2)); // 输出: 0110 (6 in binary)

TypedArray 类型化数组

TypedArray 是 JavaScript 中一组类似数组的对象,表示二进制数据的一个特定类型。这些对象提供了一种更直接、更高效地操作二进制数据的方式。TypedArray 是 ECMAScript 2015 (ES6) 引入的,用于处理二进制数据的需求。

  1. 属性

    • TypedArray.BYTES_PER_ELEMENT: 表示当前 TypedArray 对象中每个元素的字节数。
    • buffer: 返回与当前 TypedArray 对象关联的 ArrayBuffer 或 SharedArrayBuffer 对象。
    • byteLength: 返回当前 TypedArray 对象占用的字节数。
    • byteOffset: 返回当前 TypedArray 对象相对于其关联的 ArrayBuffer 开始位置的字节偏移量。
    • length: 返回当前 TypedArray 对象中的元素个数。
  2. 方法

    • copyWithin(target, start, end): 复制类型化数组中的一系列元素到数组的另一个位置。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    intArray.copyWithin(0, 3, 5);
    console.log(intArray); // 输出: Int32Array [4, 5, 3, 4, 5]
    • entries(): 返回一个包含类型化数组每个元素的键/值对的迭代器。
    js
    let intArray = new Int32Array([10, 20, 30]);
    let entries = intArray.entries();
    
    for (let entry of entries) {
      console.log(entry);
    }
    // 输出:
    // [0, 10]
    // [1, 20]
    // [2, 30]
    • every(callback): 测试类型化数组中是否所有元素都通过了由提供的函数实现的测试。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let isAllEven = intArray.every(value => value % 2 === 0);
    console.log(isAllEven); // 输出: false
    • fill(value, start, end): 用指定的值填充类型化数组的所有元素。
    js
    let intArray = new Int32Array(5);
    intArray.fill(42, 1, 4);
    console.log(intArray); // 输出: Int32Array [0, 42, 42, 42, 0]
    • filter(callback): 使用由提供的函数实现的测试创建一个新的类型化数组,其中包含所有通过测试的元素。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let evenArray = intArray.filter(value => value % 2 === 0);
    console.log(evenArray); // 输出: Int32Array [2, 4]
    • find(callback): 返回类型化数组中满足提供的测试函数的第一个元素。
    js
    let intArray = new Int32Array([10, 20, 30, 40, 50]);
    let foundValue = intArray.find(value => value > 25);
    console.log(foundValue); // 输出: 30
    • findIndex(callback): 返回类型化数组中满足提供的测试函数的第一个元素的索引。
    js
    let intArray = new Int32Array([10, 20, 30, 40, 50]);
    let foundIndex = intArray.findIndex(value => value > 25);
    console.log(foundIndex); // 输出: 2
    • forEach(callback): 针对类型化数组中的每个元素执行一次提供的函数。
    js
    let intArray = new Int32Array([1, 2, 3]);
    intArray.forEach(value => console.log(value));
    // 输出:
    // 1
    // 2
    // 3
    • includes(searchElement, fromIndex): 确定类型化数组是否包含某个元素。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let includesThree = intArray.includes(3);
    console.log(includesThree); // 输出: true
    • indexOf(searchElement, fromIndex): 返回给定元素在类型化数组中第一次出现的索引。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let index = intArray.indexOf(3);
    console.log(index); // 输出: 2
    • join(separator): 将类型化数组的所有元素连接成一个字符串。
    js
    let intArray = new Int32Array([1, 2, 3]);
    let joinedString = intArray.join("-");
    console.log(joinedString); // 输出: "1-2-3"
    • keys(): 返回一个包含类型化数组每个元素的键的迭代器。
    js
    let intArray = new Int32Array([10, 20, 30]);
    let keys = intArray.keys();
    
    for (let key of keys) {
      console.log(key);
    }
    // 输出:
    // 0
    // 1
    // 2
    • lastIndexOf(searchElement, fromIndex): 返回给定元素在类型化数组中最后一次出现的索引。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 3, 5]);
    let lastIndex = intArray.lastIndexOf(3);
    console.log(lastIndex); // 输出: 4
    • map(callback): 使用提供的函数对类型化数组中的每个元素进行操作,创建一个新的类型化数组。
    js
    let intArray = new Int32Array([1, 2, 3]);
    let squaredArray = intArray.map(value => value ** 2);
    console.log(squaredArray); // 输出: Int32Array [1, 4, 9]
    • reduce(callback): 从左到右应用一个函数对类型化数组中的每个元素进行累积。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let sum = intArray.reduce((accumulator, currentValue) => accumulator + currentValue);
    console.log(sum); // 输出: 15
    • reduceRight(callback): 从右到左应用一个函数对类型化数组中的每个元素进行累积。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let sum = intArray.reduceRight((accumulator, currentValue) => accumulator + currentValue);
    console.log(sum); // 输出: 15
    • reverse(): 在原地反转类型化数组的元素。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    intArray.reverse();
    console.log(intArray); // 输出: Int32Array [5, 4, 3, 2, 1]
    • set(source, offset): 将指定数组或类似数组对象的元素复制到类型化数组的指定位置。
    js
    let sourceArray = new Int32Array([10, 20, 30]);
    let targetArray = new Int32Array(5);
    targetArray.set(sourceArray, 1);
    console.log(targetArray); // 输出: Int32Array [0, 10, 20, 30, 0]
    • slice(start, end): 提取类型化数组的一部分并返回一个新的类型化数组。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let slicedArray = intArray.slice(1, 4);
    console.log(slicedArray); // 输出: Int32Array [2, 3, 4]
    • some(callback): 测试类型化数组中是否至少有一个元素通过了由提供的函数实现的测试。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let hasEven = intArray.some(value => value % 2 === 0);
    console.log(hasEven); // 输出: true
    • sort(compareFunction): 在原地对类型化数组的元素进行排序。
    js
    let intArray = new Int32Array([5, 3, 1, 4, 2]);
    intArray.sort();
    console.log(intArray); // 输出: Int32Array [1, 2, 3, 4, 5]
    • subarray(begin, end): 返回一个新的类型化数组,其中包含从指定的开始索引到指定的结束索引的元素。
    js
    let intArray = new Int32Array([10, 20, 30, 40, 50]);
    let subArray = intArray.subarray(1, 4);
    console.log(subArray); // 输出: Int32Array [20, 30, 40]
    • toLocaleString(locales, options): 返回表示类型化数组的元素的字符串。
    js
    let intArray = new Int32Array([1000, 2000, 3000]);
    let formattedString = intArray.toLocaleString('en-US');
    console.log(formattedString); // 输出: "1,000,2,000,3,000"
    • toString(): 返回表示指定的类型化数组及其元素的字符串。
    js
    let intArray = new Int32Array([1, 2, 3]);
    let arrayString = intArray.toString();
    console.log(arrayString); // 输出: "1,2,3"
    • values(): 返回一个包含类型化数组每个元素的值的迭代器。
    js
    let intArray = new Int32Array([10, 20, 30]);
    let values = intArray.values();
    
    for (let value of values) {
      console.log(value);
    }
    // 输出:
    // 10
    // 20
    // 30
    • at(index): 返回类型化数组中指定索引的元素。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let valueAtIndex = intArray.at(2);
    console.log(valueAtIndex); // 输出: 3
    • TypedArray.of(...elements): 使用变量数量的参数创建一个新的类型化数组。
    js
    let intArray = Int32Array.of(1, 2, 3, 4, 5);
    console.log(intArray); // 输出: Int32Array [1, 2, 3, 4, 5]
    • TypedArray.from(source, mapFn, thisArg): 从类似数组或可迭代对象创建一个新的类型化数组。
    js
    let sourceArray = [1, 2, 3, 4, 5];
    let intArray = Int32Array.from(sourceArray, value => value * 2);
    console.log(intArray); // 输出: Int32Array [2, 4, 6, 8, 10]
    • toReversed(): 返回顺序反转的新类型化数组。
    js
    let intArray = new Int32Array([1, 2, 3, 4, 5]);
    let reversedArray = intArray.toReversed();
    console.log(reversedArray); // 输出: Int32Array [5, 4, 3, 2, 1]
    • toSorted(compareFunction): 返回元素排序的新类型化数组。
    js
    let intArray = new Int32Array([5, 3, 1, 4, 2]);
    let sortedArray = intArray.toSorted((a, b) => a - b);
    console.log(sortedArray); // 输出: Int32Array [1, 2, 3, 4, 5]
    • with(callback): 返回通过在每个元素上调用提供的函数得到的新类型化数组。
    js
    let intArray = new Int32Array([1, 2, 3]);
    let squaredArray = intArray.with(value => value ** 2);
    console.log(squaredArray); // 输出: Int32Array [1, 4, 9]
  3. 类型

    • new Int8Array(): 包含 8 位带符号整数的类型化数组。
    • new Uint8Array(): 包含 8 位无符号整数的类型化数组。
    • new Uint8ClampedArray(): 包含 8 位无符号整数,被夹紧在 0 和 255 之间的类型化数组。
    • new Int16Array(): 包含 16 位带符号整数的类型化数组。
    • new Uint16Array(): 包含 16 位无符号整数的类型化数组。
    • new Int32Array(): 包含 32 位带符号整数的类型化数组。
    • new Uint32Array(): 包含 32 位无符号整数的类型化数组。
    • new Float32Array(): 包含 32 位浮点数的类型化数组。
    • new Float64Array(): 包含 64 位浮点数的类型化数组。

读书、摄影、画画、弹琴、编程