语法

hello world!

console.log('Hello world!');

注释

Node.js 注释是在 JavaScript 代码中添加的说明性文本,用于解释代码的目的、功能、实现细节或其他相关信息。注释在 Node.js 中的作用与在其他 JavaScript 环境中一样,可以帮助开发人员更好地理解和维护代码。

Node.js 支持两种常见的注释类型:单行注释和多行注释。

  1. 单行注释:使用 // 开始,直到行尾为止。例如:
// 这是一个单行注释,用于解释下面这行代码的功能
const message = "Hello, world!";
  1. 多行注释:使用 /* 开始,使用 */ 结束,可以跨越多行。例如:
/*
这是一个多行注释,用于解释下面这段代码的功能
这段代码创建了一个函数,用于打印传入的消息
*/
function printMessage(message) {
    console.log(message);
}

注释应该清晰、简洁地解释代码的意图,遵循良好的注释规范,以便其他开发人员能够轻松理解和维护代码。在 Node.js 中,注释通常用于文档化 API、指导开发过程、标记代码段和添加说明性注解。

PS:

  • VScode中输入/**后回车可自动启用多行注释

常量

在 Node.js 中,常量指的是使用 const 关键字声明的变量。常量在声明后不能被重新赋值,但可以是任何 JavaScript 支持的数据类型,包括原始类型(如数字、字符串、布尔值)和复合类型(如数组、对象、函数)。

下面是关于 Node.js 常量的一些详解:

  1. 声明常量:使用 const 关键字声明常量,语法如下:
const PI = 3.14159;
const MAX_SIZE = 100;
  1. 初始化:常量在声明时必须进行初始化,即赋予初始值。

  2. 不可重新赋值:声明后的常量不能再次赋值,试图重新赋值会导致语法错误。

const PI = 3.14159;
PI = 3.14; // SyntaxError: Assignment to constant variable.
  1. 块级作用域:常量具有块级作用域,与使用 let 声明的变量类似。
{
    const LOCAL_CONSTANT = 'local';
    console.log(LOCAL_CONSTANT); // 'local'
}
console.log(LOCAL_CONSTANT); // ReferenceError: LOCAL_CONSTANT is not defined
  1. 常量命名规范:通常使用全大写字母和下划线来命名常量,以便与变量区分开来,提高可读性。
const MAX_CONNECTIONS = 100;
const API_KEY = 'your-api-key';
  1. 注意事项:尽管常量的值不能被修改,但如果常量是一个对象或数组,那么对象或数组内部的属性或元素仍然是可变的。这意味着常量所引用的对象或数组本身不能被重新赋值,但它们的属性或元素可以被修改。
const person = {
    name: 'Alice',
    age: 30
};

person.age = 31; // 合法,修改对象的属性
person = {}; // 不合法,试图重新分配常量

const numbers = [1, 2, 3];
numbers.push(4); // 合法,修改数组
numbers = [1, 2, 3, 4]; // 不合法,试图重新分配常量

在编写 Node.js 应用程序时,常量通常用于存储不变的值,例如配置参数、数学常量或程序中的固定值。通过使用常量,可以提高代码的可维护性和可读性,并减少在代码中意外修改值的风险。

如果明确知道一个标识符定义后不再修改,应该尽量使用const声明为常量,减少被改的风险,减少BUG

变量

在 Node.js 中,变量是用来存储和操作数据的标识符。Node.js 支持使用 letvar 关键字声明变量。

下面是关于 Node.js 变量的一些详解:

  1. 声明变量:使用 letvar 关键字声明变量。语法如下:
let age = 30;
var name = "John";
  1. 初始化:变量可以在声明时初始化,也可以在后续的代码中进行赋值。如果未初始化,则变量的值为 undefined
let x; // 未初始化的变量
let y = 10; // 初始化为 10
  1. 作用域:使用 let 声明的变量具有块级作用域,而使用 var 声明的变量具有函数级作用域。块级作用域是指变量只在声明它的代码块内可见。
{
    let localVar = 'local'; // localVar 只在这个块级作用域内可见
    var globalVar = 'global'; // globalVar 在整个函数中都可见
}
console.log(globalVar); // 'global'
console.log(localVar); // ReferenceError: localVar is not defined
  1. 变量提升:使用 var 声明的变量存在变量提升(hoisting)现象,即变量的声明会被提升到当前作用域的顶部。而使用 let 声明的变量不存在变量提升。
console.log(x); // undefined
var x = 5;

console.log(y); // ReferenceError: y is not defined
let y = 10;
  1. 重新赋值:使用 let 声明的变量可以被重新赋值,而使用 const 声明的常量则不行。
let count = 0;
count = 1; // 合法,重新赋值

const PI = 3.14;
PI = 3.14159; // TypeError: Assignment to constant variable.

在 Node.js 应用程序中,变量用于存储临时数据、控制程序流程、进行计算等。合理使用变量可以使代码更具可读性和可维护性。建议尽可能使用 let 来声明变量,以避免变量提升带来的问题,并将变量的作用范围控制在尽可能小的范围内,提高代码质量。

变量作用域

function test(){
    x = 100; // 全局变量,函数外可引用,不要使用,严格模式下会直接报错
    var y = 200; // 局部变量,仅函数内可用,存在变量提升,不建议使用
    let z = 300; // 局部变量,仅函数内可用,建议使用
    console.log(x, y, z);
}

test();

数据类型

在 Node.js 中,数据类型与标准的 JavaScript 数据类型相同,因为 Node.js 是基于 JavaScript 构建的。以下是 Node.js 中常见的数据类型:

  1. 原始数据类型(Primitive Data Types)

    • 字符串(String):表示文本数据,用单引号(')或双引号(")括起来。
    • 数字(Number):表示数值,可以是整数或浮点数。
    • 布尔值(Boolean):表示真(true)或假(false)。
    • 空值(Null):表示一个空值或不存在的值。
    • 未定义(Undefined):表示一个未赋值的变量。
  2. 复合数据类型(Composite Data Types)

    • 对象(Object):表示一个无序的集合,由键值对组成。
    • 数组(Array):表示一个有序的集合,可以通过索引访问其中的元素。
    • 函数(Function):表示可执行的代码块。
  3. 特殊数据类型(Special Data Types)

    • Symbol:表示唯一的、不可变的值,用于对象属性的键值。

在 Node.js 中,可以使用 typeof 操作符来检查变量的数据类型。例如:

const str = 'Hello';
const num = 42;
const bool = true;
const obj = { key: 'value' };
const arr = [1, 2, 3];
const func = function() { console.log('Function executed'); };

console.log(typeof str);  // 输出: 'string'
console.log(typeof num);  // 输出: 'number'
console.log(typeof bool); // 输出: 'boolean'
console.log(typeof obj);  // 输出: 'object'
console.log(typeof arr);  // 输出: 'object'
console.log(typeof func); // 输出: 'function'

了解这些数据类型以及它们的特性对于在 Node.js 中进行编程非常重要,因为它们构成了程序中存储和操作数据的基础。

字符串

在 JavaScript 和 Node.js 中,有几种方式可以定义字符串:

  1. 单引号:使用单引号(')将字符串括起来。
const str1 = 'Hello, world!';
  1. 双引号:使用双引号(")将字符串括起来。
const str2 = "Hello, world!";
  1. 反引号(模板字符串):使用反引号(`)将字符串括起来。模板字符串可以包含插值表达式(${}),可以方便地插入变量或执行表达式,并支持多行字符串。
const str3 = `Hello,
world!`;

这三种方式都可以用来定义字符串。选择哪种方式通常取决于个人偏好、代码风格和具体情况。

字符串常用方法与函数

以下是 JavaScript 中常用的字符串方法:

  1. length:返回字符串的长度。
const str = "Hello, world!";
console.log(str.length); // 输出: 13
  1. charAt(index):返回指定索引位置的字符。
const str = "Hello, world!";
console.log(str.charAt(0)); // 输出: "H"
  1. charCodeAt(index):返回指定索引位置的字符的 Unicode 编码。
const str = "Hello, world!";
console.log(str.charCodeAt(0)); // 输出: 72
  1. indexOf(searchValue[, fromIndex]):返回指定字符串在原字符串中首次出现的位置,如果没有找到则返回 -1。
const str = "Hello, world!";
console.log(str.indexOf("world")); // 输出: 7
  1. lastIndexOf(searchValue[, fromIndex]):返回指定字符串在原字符串中最后一次出现的位置,如果没有找到则返回 -1。
const str = "Hello, world!";
console.log(str.lastIndexOf("o")); // 输出: 8
  1. slice(start[, end]):提取字符串的一部分,并返回一个新的字符串。
const str = "Hello, world!";
console.log(str.slice(0, 5)); // 输出: "Hello"
  1. substring(start[, end]):提取字符串的一部分,并返回一个新的字符串,与 slice 方法类似,但不支持负数索引。
const str = "Hello, world!";
console.log(str.substring(7)); // 输出: "world!"
  1. substr(start[, length]):从指定位置开始,返回指定长度的子字符串。
const str = "Hello, world!";
console.log(str.substr(7, 5)); // 输出: "world"
  1. toUpperCase():将字符串转换为大写。
const str = "hello";
console.log(str.toUpperCase()); // 输出: "HELLO"
  1. toLowerCase():将字符串转换为小写。
const str = "WORLD";
console.log(str.toLowerCase()); // 输出: "world"
  1. trim():移除字符串两端的空白字符。
const str = "  Hello, world!  ";
console.log(str.trim()); // 输出: "Hello, world!"
  1. replace(searchValue, replaceValue):将字符串中的指定子字符串替换为新的子字符串。
const str = "Hello, world!";
console.log(str.replace("world", "everyone")); // 输出: "Hello, everyone!"
  1. split(separator[, limit]):将字符串分割为子字符串数组。
const str = "Hello, world!";
console.log(str.split(", ")); // 输出: ["Hello", "world!"]
  1. startsWith(searchValue[, position]):判断字符串是否以指定字符串开头。
const str = "Hello, world!";
console.log(str.startsWith("Hello")); // 输出: true
  1. endsWith(searchValue[, length]):判断字符串是否以指定字符串结尾。
const str = "Hello, world!";
console.log(str.endsWith("world!")); // 输出: true

这些是 JavaScript 中常用的字符串方法。可以根据具体需求选择合适的方法来操作字符串。

以下是常用的字符串方法列表,包括 JavaScript 和 Node.js 中常用的字符串方法:

  1. charAt(index):返回指定位置的字符。
  2. charCodeAt(index):返回指定位置字符的 Unicode 编码。
  3. concat(str1, str2, ...):连接两个或多个字符串,并返回新的字符串。
  4. includes(searchString, position):判断字符串是否包含指定的子字符串。
  5. endsWith(searchString, length):判断字符串是否以指定的子字符串结尾。
  6. indexOf(searchValue, fromIndex):返回指定子字符串首次出现的位置。
  7. lastIndexOf(searchValue, fromIndex):返回指定子字符串最后一次出现的位置。
  8. match(regexp):在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
  9. replace(searchValue, replaceValue):在字符串中替换指定值。
  10. search(regexp):检索与正则表达式相匹配的值。
  11. slice(startIndex, endIndex):提取字符串的一部分,并返回一个新的字符串。
  12. split(separator, limit):将字符串分割为子字符串数组。
  13. startsWith(searchString, position):判断字符串是否以指定的子字符串开头。
  14. substr(startIndex, length):从指定位置开始,返回指定长度的子字符串。
  15. substring(startIndex, endIndex):返回字符串的子字符串,取值范围从起始索引到结束索引之间的字符。
  16. toLocaleLowerCase():根据主机的区域设置将字符串转换为小写,只针对特定区域设置有影响。
  17. toLocaleUpperCase():根据主机的区域设置将字符串转换为大写,只针对特定区域设置有影响。
  18. toLowerCase():将字符串转换为小写。
  19. toUpperCase():将字符串转换为大写。
  20. trim():移除字符串两端的空白字符。
  21. valueOf():返回指定对象的原始值。
  22. toString():返回一个字符串,表示指定的对象。
  23. padStart(targetLength, padString):用指定字符串填充字符串,以便产生的字符串达到指定的长度。
  24. padEnd(targetLength, padString):用指定字符串填充字符串,以便产生的字符串达到指定的长度,填充在当前字符串的尾部。

这些方法在处理字符串时非常常用,可以用于字符串的操作、搜索、替换、格式化等等。

数值

在 JavaScript 和 Node.js 中,数值类型包括整数(整型)和浮点数(小数)。这些数值类型用于存储数值数据。

  1. 整数(Integers):整数是没有小数部分的数值,可以是正数、负数或零。在 JavaScript 中,整数被表示为 64 位的二进制补码形式,即 -2^53 <= n <= 2^53
const integer = 42; // 正整数
const negativeInteger = -10; // 负整数
const zero = 0; // 零
  1. 浮点数(Floating-point numbers):浮点数是带有小数部分的数值。在 JavaScript 中,浮点数遵循 IEEE 754 标准,使用双精度 64 位格式表示,即一个浮点数由 64 位中的一部分表示小数,一部分表示指数。
const float = 3.14; // 浮点数
const negativeFloat = -1.5; // 负浮点数
const scientificNotation = 6.022e23; // 科学计数法表示的浮点数

特殊数值

在JavaScript中有三种特殊数值,它们是:

  1. NaN (Not-a-Number)

    • 表示不是一个有效数字,通常在尝试进行无效的数学运算时产生。例如,用一个字符串除以一个数字或者尝试将无法转换的字符串转换为数字时,就会得到NaN。
    • NaN不等于任何值,包括它自己,因此在比较时,NaN === NaNfalse。如果需要检查是否是NaN,可以使用 isNaN() 函数或者 Number.isNaN() 方法。
  2. Infinity

    • 代表正无穷大,在数学上通常用来表示大于任何其他数字的值。可能通过除以零或得到无限大的结果而产生,例如 1/0Math.pow(10, 1000)
    • Infinity等于自身,Infinity === Infinitytrue
  3. -Infinity

    • 表示负无穷大,与Infinity相对。可能通过除以负零或者得到负无穷大的结果而产生,例如 -1/0Math.pow(-10, 1000)
    • -Infinity等于自身,-Infinity === -Infinitytrue

这三者都是JavaScript中的特定数字类型,通常在异常情况下或极端计算时出现。使用它们时,建议采取谨慎措施,确保代码能够正确处理这些特殊值,以避免意外的结果或错误。

数值常用方法与函数

除了这些基本的数值类型外,JavaScript 还提供了一些用于处理数值的内置方法和函数,其中一些常用的包括:

  • parseInt():将字符串转换为整数。
  • parseFloat():将字符串转换为浮点数。
  • isNaN():检查一个值是否为 NaN(非数值)。
  • isFinite():检查一个值是否为有限数。
  • Math 对象:提供了各种数学函数和常量,如 Math.abs()、Math.sqrt()、Math.min()、Math.max() 等等。

在编写 JavaScript 或 Node.js 应用程序时,了解数值类型及其相关方法是非常重要的,因为它们经常用于处理数学计算、数据转换和验证等任务。

以下是一些常用的处理数值的内置方法和函数的示例:

  1. parseInt():将字符串转换为整数。
const numStr = '42';
const num = parseInt(numStr);
console.log(num); // 输出: 42
  1. parseFloat():将字符串转换为浮点数。
const floatStr = '3.14';
const floatNum = parseFloat(floatStr);
console.log(floatNum); // 输出: 3.14
  1. isNaN():检查一个值是否为 NaN(非数值)。
const value = 'abc';
console.log(isNaN(value)); // 输出: true
  1. isFinite():检查一个值是否为有限数。
const number = 42;
console.log(isFinite(number)); // 输出: true
  1. Math 对象方法:提供了各种数学函数和常量。
const absValue = Math.abs(-5); // 绝对值
console.log(absValue); // 输出: 5

const sqrtValue = Math.sqrt(16); // 平方根
console.log(sqrtValue); // 输出: 4

const minValue = Math.min(10, 20, 30); // 最小值
console.log(minValue); // 输出: 10

const maxValue = Math.max(10, 20, 30); // 最大值
console.log(maxValue); // 输出: 30

这些方法和函数在处理数值时非常有用,并且在日常的 JavaScript 和 Node.js 开发中经常会用到。

隐式类型转换

string

都被隐式转换为string类型,然后拼接

console.log(a = 3 + 'azheng', typeof(a)); // 3azheng string
console.log(a = null + 'azheng', typeof(a)); // nullazheng string
console.log(a = undefined + 'azheng', typeof(a)); // undefinedazheng string
console.log(a = true + 'azheng', typeof(a)); // trueazheng string

这些输出的原因是 JavaScript 中的类型转换规则。当一个非字符串的值与字符串相加时,JavaScript 会自动将其转换为字符串类型,并执行字符串拼接操作。

  1. 3 + 'azheng'3 是一个数字,但与字符串相加时,JavaScript 将数字转换为字符串,然后进行字符串拼接,所以结果是 '3azheng',类型为字符串。

  2. null + 'azheng'null 被转换为字符串 'null',然后进行字符串拼接,结果为 'nullazheng',类型为字符串。

  3. undefined + 'azheng'undefined 被转换为字符串 'undefined',然后进行字符串拼接,结果为 'undefinedazheng',类型为字符串。

  4. true + 'azheng'true 被转换为字符串 'true',然后进行字符串拼接,结果为 'trueazheng',类型为字符串。

这种行为是 JavaScript 中的类型转换规则之一,称为隐式类型转换。JavaScript 会尝试根据操作符和操作数的类型进行自动类型转换,以便执行操作。

number

都被隐式转换为数值类型,然后做数值加法

console.log(a = null + 8, typeof(a)); // 8 number
console.log(a = undefined + 8, typeof(a)); // NaN number
console.log(a = true + 8, typeof(a)); // 9 number
console.log(a = false + 8, typeof(a)); // 8 number

这些输出的原因同样是由于 JavaScript 中的类型转换规则引起的。让我们分析每个表达式的执行过程:

  1. null + 8null 被转换为数字 0,然后进行数字相加操作,结果为 8,类型为数字。

  2. undefined + 8undefined 无法转换为有效的数字,因此会得到 NaN(Not a Number),即非数字。当 NaN 与任何数字相加时,结果仍然是 NaN。因此结果是 NaN,类型为数字。

  3. true + 8true 被转换为数字 1,然后进行数字相加操作,结果为 9,类型为数字。

  4. false + 8false 被转换为数字 0,然后进行数字相加操作,结果为 8,类型为数字。

这些行为符合 JavaScript 中的类型转换规则,根据操作符和操作数的类型进行自动类型转换。

boolean

console.log(a = null + true, typeof(a)); // 1 number
console.log(a = null + false, typeof(a)); // 0 number
console.log(a = undefined + true, typeof(a)); // NaN number
console.log(a = undefined + false, typeof(a)); // NaN number
console.log(a = null & true, typeof(a)); // 0 number
console.log(a = undefined & true, typeof(a)); // 0 number

这些输出的原因同样是由于 JavaScript 中的类型转换规则引起的。让我们分析每个表达式的执行过程:

  1. null + truenull 被转换为数字 0true 被转换为数字 1,然后进行数字相加操作,结果为 1,类型为数字。

  2. null + falsenull 被转换为数字 0false 被转换为数字 0,然后进行数字相加操作,结果为 0,类型为数字。

  3. undefined + trueundefined 无法转换为有效的数字,因此会得到 NaN,即非数字。当 NaN 与任何数字相加时,结果仍然是 NaN。因此结果是 NaN,类型为数字。

  4. undefined + false:同样,undefined 无法转换为有效的数字,因此会得到 NaN,即非数字。当 NaN 与任何数字相加时,结果仍然是 NaN。因此结果是 NaN,类型为数字。

  5. null & true& 是按位与操作符,在执行按位与操作时,JavaScript 会将操作数转换为 32 位整数,然后执行按位与操作。因此,nulltrue 都会被转换为数字,分别为 01,然后执行按位与操作,结果为 0,类型为数字。

  6. undefined & true:同样,undefined 会被转换为数字 NaN,而 true 会被转换为数字 1,然后执行按位与操作,由于其中一个操作数为 NaN,按位与操作的结果为 0,类型为数字。

这些行为符合 JavaScript 中的类型转换规则和按位运算规则,根据操作符和操作数的类型进行自动类型转换和按位运算。

按位与:

按位与(Bitwise AND)是一种位运算操作,用于对二进制数字的相应位进行逻辑与操作。在进行按位与操作时,会将两个操作数的每一位进行比较,如果两个相应位都为 1,则结果为 1,否则结果为 0。

让我们通过一个简单的例子来说明按位与的操作:

假设有两个二进制数字:10101100

   1010
& 1100
-------
   1000

在进行按位与操作时,对应位置上的数字分别是 1010。根据按位与的规则,只有当两个操作数的相应位都为 1 时,结果才为 1,否则为 0。因此,按位与的结果是 1000

按位与的使用场景包括:

  1. 清除指定位:通过将某些位设置为 0,可以清除二进制数字中的特定位。

  2. 检查特定位:通过将某些位设置为 0,可以检查二进制数字中的特定位是否为 1。

  3. 数据压缩:通过按位与操作可以将多个数据合并为一个数据。

  4. 加密算法:按位与操作在一些加密算法中被用于混淆数据。

需要注意的是,在进行按位与操作时,操作数会被转换为 32 位的有符号整数,并且操作数的小数部分会被忽略。因此,按位与操作通常用于处理整数数据。

短路(逻辑与)

console.log(a = null && true, typeof(a)); // null object
console.log(a = false && null, typeof(a)); // false boolean
console.log(a = false && 'azheng', typeof(a)); // false boolean
console.log(a = true && 'azheng', typeof(a)); // azheng string
console.log(a = true && '' && 'abc', typeof(a)); // string

这些输出的结果是由于 JavaScript 中的逻辑与(Logical AND)操作符的特性引起的。让我们逐个分析每个表达式:

  1. null && true:逻辑与操作符 && 返回其第一个操作数(称为左操作数)的值,如果左操作数可以被强制转换为 false,则返回左操作数的值;否则,返回右操作数的值。在这个例子中,null 被视为 false,所以结果为 null,类型为对象(这是因为在 JavaScript 中,null 被归类为对象类型,尽管严格来说它是原始值)。

  2. false && null:左操作数 false 可以被视为 false,所以结果为 false,类型为布尔值。

  3. false && 'azheng':左操作数 false 可以被视为 false,所以结果为 false,类型为布尔值。

  4. true && 'azheng':左操作数 true 不会被视为 false,因此返回右操作数 'azheng',类型为字符串。

  5. true && '' && 'abc':逻辑与操作符会从左到右依次判断每个操作数的真假。如果遇到 false 类型的操作数,则直接返回该值,否则继续判断下一个操作数。在这个例子中,第一个操作数 true 不会被视为 false,所以继续判断下一个操作数 ''。由于 '' 被视为 false,因此结果为 '',类型为字符串。

这些行为符合 JavaScript 中逻辑与操作符的规则,根据左操作数的真假来确定返回值,并且返回的值可能是左操作数或右操作数的值。

补充

console.log(a = [] && 'abc', typeof(a)); // abc string
console.log(a = {} && 'abc', typeof(a)); // abc string

在这两个表达式中,&& 是逻辑与(Logical AND)操作符。它会返回它的第一个操作数(左操作数)的值,如果左操作数可以被强制转换为 false,则返回左操作数的值;否则,返回右操作数的值。

在 JavaScript 中,空数组 [] 和空对象 {} 都被视为真值(truthy),因此它们不会被视为 false。因此,对于 [] && 'abc'{},逻辑与操作符返回右操作数 'abc'。因此,结果是 'abc',类型为字符串。

这种行为符合 JavaScript 中逻辑与操作符的规则,根据左操作数的真假来确定返回值,并且返回的值可能是左操作数或右操作数的值。

null

console.log(a = null + undefined, typeof(a)); // NaN number

这个结果是因为在 JavaScript 中,对于 nullundefined 进行数学运算(如加法)时,它们会被转换为数字,而且它们都会被转换为 NaN(Not a Number)。因此,null + undefined 的结果是 NaN,类型为数字。

这种行为符合 JavaScript 中的类型转换规则。当进行数学运算时,JavaScript 会尝试将操作数转换为数字,如果操作数无法转换为有效的数字,则结果为 NaN

Symbol

Symbol 类型用于创建唯一且不可变的标识符

let sym1 = Symbol()
let sym2 = Symbol('key1')
let sym3 = Symbol('key1')

console.log(sym2 == sym3) // false,因为Symbol的值是唯一的。

构建常量

以前的用法

const color = {
    RED: 1,
    BLUE: 2,
    BLACK: 3,
};
  • 这种方式使用数值作为枚举的标识符。这种方法简单直观,但可能存在一些潜在的问题:
    • 可能不小心重复使用数值,从而导致混淆。
    • 数值容易被打印、比较、改变或误解为其他含义。
    • 在对象的键值对上,数值容易被枚举(通过 Object.keys() 等方式)。

现在的用法

const newColor = {
    RED: Symbol(),
    BLUE: Symbol(),
    BLACK: Symbol(),
};
  • 这种方式使用 Symbol 来表示枚举的标识符。它的特性和优点包括:
    • 唯一性:每个 Symbol 都是唯一的,即使具有相同的描述。
    • 不可变性:符号是不可变的,不能被更改或重用。
    • 安全性:与数值相比,符号不会被隐式转换,这样可以防止意外的操作或比较。
    • 不被枚举Symbol 属性不会被 Object.keys()for...inJSON.stringify() 枚举,因此更适合定义私有或保护性的属性。

两者的选择

  • 如果你需要简单的枚举,并且对唯一性或不可变性没有严格要求,数值可能是一个更简单的选择。
  • 如果需要确保唯一性,并且不希望对象的属性被意外地暴露或枚举,那么 Symbol 是更好的选择。

在涉及枚举或一组唯一标识符时,Symbol 可以提供更强的安全性和唯一性,这使得它在更复杂的场景下非常有用。例如,定义某个类的私有属性,或者创建可以与其他库共存的唯一标识符。

作为对象的属性key

示例1

let s = Symbol(); // s指向了一个唯一值

let obj = {
    [s]: 'sss',
    [Symbol()]: 'ttt', // 访问不到,因为[Symbol()]是唯一值
};

console.log(obj[s]) // sss

示例2

let s = Symbol(); // s指向了一个唯一值
let t = 'abc';
let a = {
    [s]: 'xyz', // Symbol()作为key,注意要使用中括号,这个key一定唯一
    t: 'ttt',
    [t]: 'ooo',
};

console.log(a); // { t: 'ttt', abc: 'ooo', [Symbol()]: 'xyz' }
console.log(a[s]); // xyz

a[s] = 2000
console.log(a[s]) // 2000
console.log(a); // { t: 'ttt', abc: 'ooo', [Symbol()]: 2000 }

PS

// 定义字符串变量a
let a = 'abc';

// 创建一个对象obj,并为它赋值了多个属性。
let obj = {
    a: 123, // a: 123,这个属性的键是字面值的字符串a,对应的值是数字123。这是最常见的对象属性定义方式。
    'b': 'xyz', // b: 'xyz',此属性的键是显式字符串'b',对应的值是字符串'xyz'。即使键是显式的字符串,它仍然会在对象中被存储为属性名称。
    c: a, // c: 'abc',这里属性的键是字面字符串c,但它的值来自于变量a。由于a的值是'abc',因此c的属性值就是'abc'。
    [a]: [a], // abc: [ 'abc' ],这是一个计算属性名。键是由变量a的值计算得到的,这意味着键将会是 'abc'。属性的值也是一个数组,数组的唯一元素是变量a的值。
    1: 2 // '1': 2,在 JavaScript 中,数字键会被转换为字符串键,因此这个键实际上是'1',值是数字2。在对象中,数字键和字符串键具有相同的处理方式。
};

JavaScript 中的 Symbol 类型是一种独特且不可变的原始类型。

主要用于创建唯一的标识符,适用于在对象属性和其他场景中防止名称冲突,或者在代码中实现私有或独特的标识。

总之,Symbol 是一种强大的工具,用于防止对象属性的名称冲突,支持定义对象的特定行为,并且可以增强代码的安全性和可维护性。

让我们详细解释一下 Symbol 类型的关键点:

创建 Symbol

  • Symbol() 构造函数创建一个唯一的 Symbol。这个符号是唯一的,并且它与其他 Symbol 值不会冲突,即使它们具有相同的描述。
  • 你可以提供一个可选的描述(description),用于调试和输出时识别符号,但它不影响 Symbol 的唯一性。
const sym1 = Symbol('foo');
const sym2 = Symbol('bar');
const sym3 = Symbol('foo'); // 即使描述相同,它仍然是唯一的

用作对象属性

  • Symbol 常用于对象的键或属性,因为它们不会在常规对象遍历中被列出。这种特性使得 Symbol 适合用于创建私有或保护性的属性。
const myObject = {};
const symKey = Symbol('myKey');
myObject[symKey] = 'secretValue';

// 常规的属性枚举无法看到 Symbol 属性
console.log(Object.keys(myObject)); // []

Symbol 内置符号

  • JavaScript 提供了一些内置的 Symbol,用于定义内置行为的属性。这些内置符号用于扩展对象的原生行为,例如自定义对象的迭代、定义对象的原型等。
// 使用 Symbol.iterator 自定义迭代
const iterable = {
  [Symbol.iterator]() {
    let step = 0;
    return {
      next() {
        return {
          done: step > 3,
          value: step++,
        };
      },
    };
  },
};

for (const value of iterable) {
  console.log(value); // 0, 1, 2, 3
}

通过 Symbol 访问符号属性

  • Symbol 的属性不会被 Object.keys()for...in 枚举,因此要访问对象上的 Symbol 属性,需要使用 Object.getOwnPropertySymbols()Reflect.ownKeys()
const sym1 = Symbol('sym1');
const obj = {
  [sym1]: 'value1',
  normalKey: 'value2',
};

const symbols = Object.getOwnPropertySymbols(obj);
console.log(symbols); // [Symbol(sym1)]

注意事项

  • Symbol 不可被隐式转换为字符串。这可以防止在错误的情况下使用符号,但也意味着你可能需要显式转换以进行调试。
const sym = Symbol('example');
// console.log('Symbol: ' + sym); // 这会抛出错误
console.log(`Symbol: ${sym.toString()}`); // 这是正确的用法

运算符

算数运算符

算数运算符(Arithmetic Operators)是编程语言中用于执行数学运算的基本运算符。在 JavaScript 中,常见的算数运算符包括:

加法运算符 (+)

  • 用于两个数值之间的加法运算或字符串拼接。
  • 例如:
    console.log(5 + 3); // 8
    console.log('Hello ' + 'World'); // 'Hello World'
    

减法运算符 (-)

  • 用于两个数值之间的减法运算。
  • 例如:
    console.log(10 - 3); // 7
    

乘法运算符 (*)

  • 用于两个数值之间的乘法运算。
  • 例如:
    console.log(4 * 2); // 8
    

除法运算符 (/)

  • 用于两个数值之间的除法运算。
  • 结果可能是浮点数。如果除以零,结果是 Infinity-Infinity,而除以零时有可能产生 NaN
  • 例如:
    console.log(1 / 2); // 0.5
    console.log(12 / 4); // 3
    console.log(1 / 0); // Infinity
    

取模运算符 (%)

  • 返回除法运算的余数。注意,余数可能是负数,这取决于被除数和除数的符号。
  • 例如:
    console.log(10 % 3); // 1
    console.log(10 % 4); // 2
    

幂运算符 (**)

  • 用于对数值进行指数运算。
  • 例如:
    console.log(2 ** 3); // 8
    console.log(3 ** 2); // 9
    

递增运算符 (++)

  • 将变量的值加 1。可以在变量之前(前置递增)或变量之后(后置递增)使用。
  • 例如:
    let a = 5;
    a++; // 后置递增,a 变为 6
    ++a; // 前置递增,a 变为 7
    

示例1

let a = 0

console.log(a++) // 0,先用后加,等价于:console.log(a); a++
console.log(a++) // 1,
console.log(++a) // 3,先加后用,等价于:a++; console.log(a)
console.log(a) // 3,

示例2

let i = 0
// 这里初始化了一个变量 i,并将其值设为 0。

let a = i++
// i++ 是后置递增运算符,意味着先返回当前值,再将 i 增加 1。
// 因此,i++ 返回 0,然后 i 被增至 1。
// a 得到了 0。
// 等价于:let a = i; i++

console.log(a, i) // 0 1
// a 的值是 0,而 i 的值在上一行操作后变为 1。
// 因此,输出是 0 1。

console.log(a, i++) // 0 1
// 在这一行中,i++ 再次是后置递增,所以返回 i 的当前值,即 1,然后 i 被增至 2。
// 因此,这里输出的结果是 0 1,因为 a 仍然是 0,而 i 在递增之前是 1。

a = -i++
// 在这一步中,i++ 依然是后置递增,所以返回 i 的当前值,即 2,然后 i 增至 3。
// -i++ 将 2 取反,所以 a 得到 -2。

console.log(a, ++i) // -2 4
// ++i 是前置递增运算符,意味着先将 i 增加 1,然后返回新的值。
// 因此,i 由 3 变为 4。
// 输出的结果是 -2 4L。

总结

  • 后置递增运算符 (i++) 会返回当前值,然后将变量递增。
  • 前置递增运算符 (++i) 会先递增变量,然后返回新值。

理解这些细微差别可以帮助你在编写代码时准确预测变量的变化,特别是在涉及递增和递减操作符的情况下。

递减运算符 (--)

  • 将变量的值减 1。类似地,既可以前置,也可以后置。
  • 例如:
    let b = 5;
    b--; // 后置递减,b 变为 4
    --b; // 前置递减,b 变为 3
    

以上是 JavaScript 中常见的算数运算符,每个运算符都有特定的用法和注意事项。对于表达式中运算符的优先级以及可能的类型转换,开发人员在使用时应保持清晰的认知,以确保运算结果符合预期。

比较运算符

比较运算符(Comparison Operators)用于比较两个值或表达式,以决定它们之间的关系。在 JavaScript 中,这些运算符返回布尔值 truefalse,根据比较结果进行决策。让我们看看 JavaScript 中的常见比较运算符:

等于运算符 (==)

  • 检查两个值是否相等,允许类型转换。这种宽松的比较可能导致意外结果,因为 JavaScript 会尝试将值转换为相同的类型。
  • 例如:
    console.log(1 == '1'); // true (因为 '1' 被转换为数字)
    console.log(null == undefined); // true (因为它们被视为相等)
    

严格等于运算符 (===)

  • 检查两个值是否相等且类型相同。这是最推荐的比较运算符,因为它不会进行类型转换,确保了更明确的比较。
  • 例如:
    console.log(1 === '1'); // false (类型不同)
    console.log(1 === 1); // true
    

不等于运算符 (!=)

  • 检查两个值是否不相等,允许类型转换。
  • 例如:
    console.log(1 != '1'); // false (因为 '1' 被转换为数字)
    console.log(1 != 2); // true
    

严格不等于运算符 (!==)

  • 检查两个值是否不相等或类型不同,不允许类型转换。
  • 例如:
    console.log(1 !== '1'); // true (类型不同)
    console.log(1 !== 1); // false
    

大于运算符 (>)

  • 检查左侧值是否大于右侧值。
  • 例如:
    console.log(5 > 3); // true
    console.log('a' > 'b'); // false (因为 'a' 的字符编码小于 'b')
    

大于等于运算符 (>=)

  • 检查左侧值是否大于或等于右侧值。
  • 例如:
    console.log(5 >= 5); // true
    console.log(5 >= 3); // true
    

小于运算符 (<)

  • 检查左侧值是否小于右侧值。
  • 例如:
    console.log(3 < 5); // true
    console.log('b' < 'a'); // false
    

小于等于运算符 (<=)

  • 检查左侧值是否小于或等于右侧值。
  • 例如:
    console.log(3 <= 3); // true
    console.log(3 <= 5); // true
    

注意事项

  • 对于非数字比较,JavaScript 通常按字典顺序(基于 Unicode 字符集)比较字符串。
  • 当比较不同类型的值时,== 运算符会尝试进行隐式转换,而 === 不会,因此在比较过程中可能产生意外结果。
  • 一般推荐使用严格比较运算符(===!==)以避免意外的隐式类型转换。

这些比较运算符在条件语句(如 ifwhile 等)和迭代语句中非常常见,并且在 JavaScript 中的逻辑和决策处理中扮演重要角色。

逻辑运算符

逻辑运算符(Logical Operators)在 JavaScript 中用于处理布尔逻辑,通过它们可以组合或改变布尔表达式的值。这些运算符常用于条件语句和循环语句,帮助我们创建更复杂的条件逻辑。主要的逻辑运算符包括:

逻辑与运算符 (&&)

  • 当且仅当两个操作数都为真时,结果才为真。如果第一个操作数为假,第二个操作数不会被计算(短路逻辑)。

  • 例如:

    console.log(true && true); // true
    console.log(true && false); // false
    console.log(false && true); // false
    console.log(false && false); // false
    
  • 在实际应用中,它可以用于确保多个条件都成立时再执行某个代码块。

    const x = 5;
    if (x > 0 && x < 10) {
      console.log("x 在 0 和 10 之间");
    }

逻辑或运算符 (||)

  • 只要两个操作数中有一个为真,结果就为真。如果第一个操作数为真,第二个操作数不会被计算(短路逻辑)。

  • 例如:

    console.log(true || false); // true
    console.log(false || true); // true
    console.log(false || false); // false
    
  • 这种运算符通常用于检查是否满足至少一个条件。

    const x = -1;
    if (x < 0 || x > 10) {
      console.log("x 小于 0 或大于 10");
    }

逻辑非运算符 (!)

  • 逻辑非(取反)运算符将布尔值翻转:真变为假,假变为真。

  • 例如:

    console.log(!true); // false
    console.log(!false); // true
    
  • 它可以用于反转条件。

    const x = 10;
    if (!x) {
      console.log("x 为假值");
    } else {
      console.log("x 为真值");
    }

短路逻辑

  • &&|| 运算符具有短路逻辑特性,即当结果可以明确时,后续操作数不会被计算。这一特性可以用于优化性能或避免不必要的运算。

返回非布尔值

  • 在 JavaScript 中,逻辑运算符可以返回非布尔值。例如:
    • a && b 如果 a 为真,返回 b;否则返回 a
    • a || b 如果 a 为真,返回 a;否则返回 b
    • 这在某些场景中可用于返回第一个真值,或实现默认值的逻辑。
const a = null;
const b = "Hello";
console.log(a || b); // "Hello" (返回第一个真值)
console.log(a && b); // null (返回第一个假值)

这些逻辑运算符在编程中非常重要,因为它们允许我们创建复杂的条件和逻辑,控制程序流程,并在布尔逻辑中实现更复杂的行为。

位运算

位运算(Bitwise Operations)是在二进制位上进行操作的运算。JavaScript 中的位运算通常用于处理整数,因为在内部,JavaScript 将所有数字表示为双精度浮点数(64 位),但位运算时会将数字转换为 32 位整数。了解位运算可以帮助你在某些场景下处理低级数据操作,如加密、压缩、通信协议等。JavaScript 中常见的位运算包括:

按位与 (&)

  • 对两个数字的二进制位执行 “与” 操作,只有当两个对应位都为 1 时,结果位才为 1
  • 例如:
    console.log(5 & 3); // 1
    // 5: 0101
    // 3: 0011
    // & : 0001 (1)
    

按位或 (|)

  • 对两个数字的二进制位执行 “或” 操作,任何一个对应位为 1,结果位就为 1
  • 例如:
    console.log(5 | 3); // 7
    // 5: 0101
    // 3: 0011
    // | : 0111 (7)
    

按位异或 (^)

  • 对两个数字的二进制位执行 “异或” 操作,当且仅当两个对应位不同,结果位才为 1
  • 例如:
    console.log(5 ^ 3); // 6
    // 5: 0101
    // 3: 0011
    // ^ : 0110 (6)
    

按位取反 (~)

  • 将每个位取反,即将 1 变成 00 变成 1
  • 因为 JavaScript 使用 32 位整数表示位运算,按位取反会产生 “二的补码” 结果。
  • 例如:
    console.log(~5); // -6
    // 5 : 00000000000000000000000000000101
    // ~  : 11111111111111111111111111111010 (-6)
    

左移 (<<)

  • 将二进制位左移指定的次数,右侧用 0 填充。左移的结果会导致数值乘以 2 的移位次数。
  • 例如:
    console.log(2 << 2); // 8
    // 2 : 00000000000000000000000000000010
    // << : 00000000000000000000000000001000 (8)
    

右移 (>>)

  • 将二进制位右移指定的次数,左侧用符号位(即最高位)填充。这种操作保持了符号。
  • 例如:
    console.log(-8 >> 2); // -2
    // -8: 11111111111111111111111111111000
    // >> : 11111111111111111111111111111110 (-2)
    

无符号右移 (>>>)

  • 将二进制位右移指定的次数,左侧用 0 填充。无符号右移不保留符号位,因此即使是负数,结果也被视为正数。
  • 例如:
    console.log(-8 >>> 2); // 1073741822
    // -8: 11111111111111111111111111111000
    // >>> : 00111111111111111111111111111110 (1073741822)
    

位运算的应用场景

  • 权限控制:用位运算来设置和检查权限标志。
  • 掩码操作:用于提取特定位或设置特定位。
  • 数据压缩和解压:通过位移操作实现。
  • 位域处理:用于二进制数据的解析和操作。

通过位运算,可以直接操作二进制位,从而实现复杂的数据处理任务。然而,因为位运算涉及位的操作,通常需要对二进制表示和相关技术有较好的理解。

三元运算符

三元运算符(Ternary Operator)是 JavaScript 中一种用于条件表达式的运算符,也称为条件运算符。它允许在一个紧凑的语法中进行条件判断,并根据条件的结果返回不同的值或执行不同的操作。

语法

三元运算符的语法为:

condition ? expr1 : expr2
  • condition 是一个布尔表达式,决定了哪个结果会被选中。
  • expr1 是条件为真时的结果。
  • expr2 是条件为假时的结果。

用途

三元运算符常用于代替简单的 if-else 结构,特别是在需要根据条件返回值或执行紧凑的操作时。

示例1

let x = 100

console.log(x < 100? '<100':'>=100') // >=100

示例2

  1. 基础示例 根据 x 是否大于 10,选择返回不同的值:

    const x = 15;
    const result = x > 10 ? 'Greater than 10' : '10 or less';
    console.log(result); // "Greater than 10"
    
  2. 赋值操作 根据某个条件决定变量的赋值:

    let isEven = (x % 2 === 0) ? 'Even' : 'Odd';
    console.log(isEven); // "Even" 或 "Odd"
    
  3. 嵌套三元运算符 在某些情况下,你可能需要多个条件判断,可以通过嵌套三元运算符实现,但要注意代码的可读性:

    const age = 25;
    const category = age < 13
        ? 'Child'
        : age < 20
            ? 'Teenager'
            : 'Adult';
    console.log(category); // "Adult"
    

注意事项

  • 可读性:虽然三元运算符可以简化代码,但过多或过深的嵌套可能导致代码难以阅读和理解。在这种情况下,建议使用 if-else 语句。
  • 避免副作用:由于三元运算符用于表达式而不是语句,尽量避免在其中引入副作用,如修改变量、调用函数等。
  • 明确结构:如果三元运算符比较复杂,建议使用括号来明确优先级和结构,以避免错误的结果。

总体而言,三元运算符是 JavaScript 中一种有用的工具,适用于简化简单条件判断和赋值操作。在使用时,应注意保持代码的清晰和可读,以免增加调试和维护的难度。

逗号操作符

逗号操作符(Comma Operator)在 JavaScript 中用于将多个表达式组合在一起并按顺序执行,然后返回最后一个表达式的结果。它在需要在单一语句中进行多个操作时非常有用,但使用时需要小心,以避免代码复杂或难以理解。

示例

let a = 4+5, b = true, c = a > 20? 't':'f' // 为 a、b、c 分别赋值
console.log(a) // 9
console.log(b) // true
console.log(c) // f

function test() {
    return 3, a + b, c = a++
}

console.log(test()) // 9,逗号表达式值,等于最后一个逗号后表达式的值,因此相当于 return c = a++,return c
console.log(c) // 9
console.log(a) // 10

语法和用法

逗号操作符的语法是通过逗号将多个表达式连接在一起。它的主要用途包括:

  1. 顺序执行 当逗号分隔的表达式被求值时,它们按从左到右的顺序执行,并且逗号操作符的返回值是最后一个表达式的结果。

    const result = (1 + 2, 3 + 4, 5 + 6); // 执行三个加法操作,但返回最后一个
    console.log(result); // 11
    
  2. 多变量赋值 逗号操作符可以用于在单个语句中执行多次赋值。

    let a, b, c;
    a = (b = 2, c = 3, b + c); // b 被赋值为 2,c 被赋值为 3,返回 b + c 的结果
    console.log(a); // 5
    
  3. 控制结构中的逗号操作符 逗号操作符可以在控制结构中使用,以便在一个表达式中执行多项操作。例如,在 for 循环中,逗号操作符可以用于初始化和迭代多个变量。

for (let i = 0, j = 10; i < j; i++, j--) {
    console.log(`i: ${i}, j: ${j}`);
}

// 输出:
// i: 0, j: 10
// i: 1, j: 9
// i: 2, j: 8
// ...

注意事项

  • 逗号操作符的返回值:逗号操作符的返回值是最后一个表达式的结果,这在复杂的表达式中可能导致意想不到的行为。确保你理解逗号操作符返回值的含义。
  • 代码可读性:逗号操作符可以在一个语句中做很多事情,但这种简洁性可能会降低代码的可读性。在许多情况下,使用分号将表达式分隔开会更加清晰。
  • 适度使用:在单个语句中加入太多逗号操作符可能导致代码难以理解和维护。在复杂逻辑下,考虑使用更清晰的控制结构或多行语句。

典型用途

  • 简化赋值:在同一行中对多个变量进行赋值。
  • 简化循环:在 for 循环中初始化或更新多个变量。
  • 多项表达式:在表达式中同时执行多个操作,并仅返回最后一个结果。

总体而言,逗号操作符是一种强大的工具,在某些情况下可以使代码更简洁。但由于潜在的可读性问题和可能的错误使用,最好谨慎使用,确保代码在结构和逻辑上是清晰的。

instanceof

判断是否属于指定类型

instanceof 运算符用于检测一个对象是否属于某个构造函数的实例。在 JavaScript 中,构造函数通常是指那些创建对象的函数,例如类或内置构造函数。instanceof 主要用于检查原型链,以确定一个对象是否继承自某个特定的构造函数。

基本语法

object instanceof Constructor
  • object 是要检测的对象。
  • Constructor 是构造函数。

如果 object 是由 Constructor 构造函数创建的,或者它的原型链上继承自 Constructor.prototype,则 instanceof 返回 true,否则返回 false

示例1

console.log(1 instanceof Number); // false
console.log('abc' instanceof String); // false

let x = new Number(1);
console.log(x) // [Number: 1]
console.log(x instanceof Number); // true

let y = new String('abc');
console.log(y) // [String: 'abc']
console.log(y instanceof String); // true

console.log([] instanceof Array); // true
console.log([] instanceof Object); // true

console.log({} instanceof Array); // false
console.log({} instanceof Object); // true

示例2

class Animal {}
class Dog extends Animal {}

const dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true (因为 Dog 继承自 Animal)
console.log(dog instanceof Object); // true (因为所有对象都继承自 Object)

检查原型链

instanceof 运算符的运作基于原型链。如果 object 的原型链中有一部分是 Constructor.prototype,则 instanceof 会返回 true

function MyConstructor() {}

const myObject = new MyConstructor();

console.log(myObject instanceof MyConstructor); // true
console.log(myObject instanceof Object); // true (因为所有对象都继承自 Object)

与类/构造函数结合使用

在使用类和继承时,instanceof 运算符很有用,可以用于检查一个对象是否是特定类的实例。

class Person {}
class Employee extends Person {}

const emp = new Employee();
console.log(emp instanceof Employee); // true
console.log(emp instanceof Person); // true (因为 Employee 继承自 Person)

与内置构造函数结合使用

instanceof 还可以用于检查对象是否是某个内置类型,如 ArrayDateRegExp 等。

const arr = [1, 2, 3];
console.log(arr instanceof Array); // true

const date = new Date();
console.log(date instanceof Date); // true

注意事项

  • 原型被改变:如果原型链被改变,instanceof 的结果可能会意想不到。例如,如果你更改了对象的 __proto__,或将 Constructor.prototype 设置为不同的对象,instanceof 可能不再返回预期的结果。
  • 不同全局环境:在某些情况下(如跨 iframe 或跨不同的 JavaScript 环境),由于 Constructor 在不同环境中可能是不同的引用,即使它们具有相同的名称,instanceof 可能返回错误结果。
  • 用于检测类型instanceof 是检查对象类型的有用工具,但要确保它适用于需要的场景,特别是涉及复杂原型链的情况。

总结

instanceof 运算符在 JavaScript 中用于检测对象是否是某个构造函数的实例。这在类、继承、原型链等场景中非常有用,但使用时需注意可能的副作用和特定情形下的错误结果。

typeof

返回类型字符串,有点像python中的type()

typeof 运算符用于返回一个值的类型,是 JavaScript 中用于检测变量或表达式类型的常用工具。它是一个单目运算符,操作数在运算符的右侧。typeof 返回的结果是一个字符串,表示值的类型。

基本语法

typeof operand
  • operand 是要检测的值、变量、表达式等。

示例

let x = new Number(1);
console.log(x) // [Number: 1]
console.log(typeof x); // object,本质上是'object'
console.log(typeof 1); // number,本质上是'number'
console.log((typeof x) === 'object') // true

typeof 返回的类型

typeof 可以返回以下几种类型的字符串表示:

  1. “undefined”

    • 当变量未定义时。
    • 例如:
      let a;
      console.log(typeof a); // "undefined"
      
  2. “boolean”

    • 当值是布尔类型时。
    • 例如:
      const isTrue = true;
      console.log(typeof isTrue); // "boolean"
      
  3. “number”

    • 当值是数字类型时,包括整数和浮点数。
    • 例如:
      const x = 42;
      console.log(typeof x); // "number"
      
  4. “string”

    • 当值是字符串类型时。
    • 例如:
      const str = "Hello, World!";
      console.log(typeof str); // "string"
      
  5. “symbol”

    • 当值是 Symbol 类型时。
    • 例如:
      const sym = Symbol("mySymbol");
      console.log(typeof sym); // "symbol"
      
  6. “function”

    • 当值是函数时。
    • 例如:
      function myFunction() {}
      console.log(typeof myFunction); // "function"
      
  7. “object”

    • 当值是对象、数组、null、正则表达式等。
    • 例如:
      const obj = {};
      console.log(typeof obj); // "object"
      

特殊情况

  • null

    • 尽管 null 表示 “无对象”,但 typeof null 返回 “object”。这是一个已知的错误行为,但为了向后兼容,未被修改。
    • 例如:
      console.log(typeof null); // "object"
      
  • 数组

    • 数组也是一种对象,因此 typeof 返回 “object”。
    • 例如:
      const arr = [1, 2, 3];
      console.log(typeof arr); // "object"
      

用途

  • 类型检查:在处理不同类型的变量时,可以使用 typeof 确认变量的类型。
  • 防止错误:在访问属性或调用函数之前,确认类型可以避免某些类型错误。
  • 动态类型:在 JavaScript 中,由于变量是动态类型的,typeof 可以帮助在运行时确认变量的类型。

注意事项

  • typeof 返回的结果是一个字符串,注意与字符串比较时要确保精确匹配。
  • 它在某些情况下可能不会给出预期的结果,例如对 null 或数组使用时返回 “object”。
  • 对于检测数组,可以使用 Array.isArray();对 null 的检测可以直接比较 value === null

总结

typeof 是一种方便的工具,用于在 JavaScript 中检查变量和表达式的类型。理解它的工作原理以及特殊情况可以帮助你在代码中更好地使用它,避免潜在的陷阱。

delete

delete 是 JavaScript 中的运算符,用于删除对象的属性。如果成功删除,delete 返回 true;如果无法删除(比如对象的属性是不可配置的),则返回 falsedelete 常用于移除对象中的属性,而不是变量或数组元素。

基本用法

delete object.property;
delete object['property'];
  • object 是要操作的对象。
  • property 是要删除的属性的名称。

示例1

x = 42;
var y = 43;
let z = 60;

myobj = new Number();
myobj.h = 4

console.log(delete x) // true
console.log(delete y) // false
console.log(delete z) // false
console.log(delete Math.PI) // false
console.log(delete myobj.h) // true
console.log(delete myobj) // true

delete 运算符用于删除对象的属性。在某些情况下,删除操作可能失败,具体取决于变量、属性的定义方式,以及对象的可配置性。让我们逐一解释这些 delete 操作的结果和原因:

delete x

  • x 是未使用 varletconst 声明的全局变量。在非严格模式下,全局变量被视为全局对象的属性(例如,在浏览器中,全局对象是 window),可以使用 delete 删除。
  • 在严格模式下,这种用法会报错。
  • 在非严格模式下,删除全局变量的结果取决于环境。在浏览器中,删除一个隐式创建的全局变量通常会成功。
console.log(delete x); // 在非严格模式下通常返回 true

delete y

  • y 是用 var 声明的变量。在 JavaScript 中,用 var 声明的变量不能被 delete 删除,因为它们是作用域的固有部分。
  • deletevar 声明的变量返回 false
console.log(delete y); // 返回 false

delete z

  • z 是用 let 声明的变量。与 var 相同,用 letconst 声明的变量也不能被 delete 删除。
  • 对于 let 声明的变量,delete 返回 false
console.log(delete z); // 返回 false

delete Math.PI

  • Math.PI 是一个不可配置的属性。在 JavaScript 中,内置对象的某些属性是不可删除的,这些属性往往是只读的或者不可配置的。
  • delete Math.PI 返回 false
console.log(delete Math.PI); // 返回 false

delete myobj.h

  • myobj.h 是自定义对象的属性。自定义对象的属性通常可以被 delete 删除。
  • delete myobj.h 成功删除了 h 属性,返回 true
console.log(delete myobj.h); // 返回 true

delete myobj

  • delete 运算符不能删除对象本身,它只能删除对象的属性。如果要删除对象引用,应将变量设为 nullundefined
  • delete myobj 返回 false,因为 myobj 是一个变量,而不是对象的属性。
console.log(delete myobj); // 返回 false

总结

  • delete 运算符通常用于删除对象的属性,但不能删除用 varletconst 声明的变量。
  • 内置对象的某些属性是不可配置的,不能被删除。
  • 要删除对象引用,应将其设置为 nullundefined,而不是使用 delete

示例2

let arr = ['red', 'blue', 'black']
// let arr = new Array('red', 'blue', 'black') // 等价于上面的写法

for  (let i=0; i<arr.length; i++){
    console.log(i, arr[i])
}
/*
0 red
1 blue
2 black
*/

delete arr[1] // 只有数组中的元素被删除,空着的位置是undefined

for  (let i=0; i<arr.length; i++){
    console.log(i, arr[i])
}
/*
0 red
1 undefined
2 black
*/

console.log(arr.length) // 3

示例3

const person = {
  name: 'John',
  age: 30,
};

console.log(person.name); // 'John'

delete person.name;

console.log(person.name); // undefined

在这个例子中,使用 delete 删除了 person 对象的 name 属性,之后访问这个属性返回 undefined,表明属性已经被删除。

删除属性 vs. 设置为 undefined

delete 会删除属性,使它不再出现在对象上;而将属性设置为 undefined 只是将属性的值设为 undefined,但属性仍然存在。

const person = {
  name: 'John',
  age: 30,
};

delete person.name; // 属性被删除
person.age = undefined; // 属性仍存在,但值为 undefined

console.log('name' in person); // false (属性被删除)
console.log('age' in person); // true (属性仍然存在)

使用场景

  • 删除对象属性delete 可以用于移除对象中的属性。
  • 对象清理:在不再需要某些属性时,可以使用 delete 进行清理。
  • 动态对象:在某些动态场景下,使用 delete 删除属性可能是一种方便的操作。

注意事项

  • 删除数组元素:虽然可以用 delete 删除数组的特定索引,但这会留下一个空位,数组的长度不会改变。通常删除数组元素应该使用 Array.splice()

    const arr = [1, 2, 3];
    delete arr[1]; // 数组变成 [1, , 3]
    console.log(arr.length); // 3
    
  • 删除变量delete 无法删除由 varletconst 声明的变量。它主要用于对象属性。

    var x = 10;
    delete x; // 无法删除变量
    console.log(x); // 10
    
  • 不可配置属性:如果属性是不可配置的,delete 将返回 false。这可能发生在 Object.defineProperty 中设置了不可配置的属性,或者内置对象的某些属性。

    const obj = Object.freeze({ a: 1 }); // 冻结对象,所有属性不可配置
    console.log(delete obj.a); // false
    

总结

delete 运算符用于删除对象的属性,它在删除对象中不再需要的属性时非常有用。然而,使用时需要注意它的局限性,如不能删除变量、删除数组元素会留下空位,以及处理不可配置属性的情况。理解这些特性有助于正确使用 delete,避免潜在的问题。

in

如果指定的属性在对象内,则返回true

in 运算符是 JavaScript 中用于检查对象是否具有指定属性的运算符。它返回一个布尔值,根据对象是否包含指定属性返回 truefalse

基本语法

property in object
  • property 是要检查的属性名称,通常是字符串或符号。
  • object 是要检查的对象。

用途

in 运算符可以用于:

  • 检查对象是否包含特定属性,无论是直接存在还是继承自原型链。
  • 在操作属性前,确认其存在性。
  • 确定数组是否包含指定索引。

示例1

let myobj = {
    a: 100,
    b: 'abc'
};

console.log('a' in myobj) // true
console.log('b' in myobj) // true
console.log('c' in myobj) // false
console.log(100 in myobj) // false,查询的是对象属性的key,而不是value

示例2

  • in判断的是属性在不在,对于数组来说 索引和length都是属性
let arr = new Array('red', 'blue', 'black')

console.log('red' in arr) // false
console.log( 0 in arr) // true
console.log( 'length' in arr) // true

in 运算符用于检查对象是否包含特定属性。在 JavaScript 中,数组是一种特殊的对象,其索引实际上是对象的属性名,且有一个特殊的属性 length 表示数组的长度。

在你的代码示例中,创建了一个包含 'red''blue''black' 的数组,然后使用 in 运算符来检查各种属性的存在性。

console.log('red' in arr) // false

  • 'red' 这个值虽然在数组中存在,但 in 运算符检查的是属性名,而不是数组中的元素。
  • 因此,'red' in arr 检查的是数组对象是否有一个属性名为 'red',而不是数组中是否包含该值。
  • 结果是 false,因为数组没有属性名为 'red' 的属性。

console.log(0 in arr) // true

  • 数组的索引是从 0 开始的,0 in arr 检查的是数组是否包含索引 0
  • 由于数组包含第一个元素 'red',因此索引 0 存在。
  • 所以结果是 true

console.log('length' in arr) // true

  • 数组对象具有一个内置的 length 属性,表示数组的长度。
  • 'length' in arr 检查的是数组对象是否包含 length 属性。
  • 因为所有数组都具有 length 属性,结果是 true

总结

  • in 运算符用于检查对象是否包含特定的属性名。
  • 在数组中,in 运算符用于检查索引的存在性,而不是数组中的值。
  • 数组的索引从 0 开始,因此检查 0 或其他索引时,返回 true 表示该索引存在。
  • 数组都有一个 length 属性,因此检查 length 的存在性通常会返回 true

如果你想检查数组中是否包含特定的值,应该使用 Array.includes() 方法,而不是 in 运算符。

console.log(arr.includes('red')); // true (检查数组中是否包含 'red')

这更直接地表明数组是否包含特定的元素,而不是检查索引或属性名。

示例3

  1. 检查对象属性

    const person = {
      name: 'Alice',
      age: 25,
    };
    
    console.log('name' in person); // true
    console.log('gender' in person); // false
    
  2. 检查继承属性 in 运算符不仅检查对象自身的属性,还会检查原型链上的属性。

    class Person {
      constructor(name) {
        this.name = name;
      }
    }
    
    const person = new Person('Bob');
    console.log('name' in person); // true
    console.log('toString' in person); // true (因为继承自 Object.prototype)
    
  3. 检查数组索引 in 运算符也可以用于检查数组是否包含特定索引。

    const arr = [1, 2, 3];
    console.log(1 in arr); // true (索引 1 存在)
    console.log(5 in arr); // false (索引 5 不存在)
    

注意事项

  • in 运算符会检查整个原型链,因此即使属性不在对象本身上,它仍可能返回 true。这与对象自身的属性检查方法(如 Object.hasOwnProperty)有所不同。
  • 当检查数组时,in 运算符检查索引的存在性,而不是数组的值。这一点与 Array.includes() 不同,后者检查数组中是否包含特定的值。
    const arr = [1, 2, 3];
    console.log('2' in arr); // false (因为 '2' 不是索引)
    console.log(arr.includes(2)); // true (因为数组包含值 2)
    

in 运算符和对象属性

in 运算符适用于检查对象的属性,但在某些情况下,你可能希望只检查对象自身的属性,而不考虑原型链。这种情况下,Object.hasOwnProperty 或者 Object.keys() 是更好的选择。

const obj = { x: 10 };

console.log('x' in obj); // true (检查整个原型链)
console.log(obj.hasOwnProperty('x')); // true (只检查对象自身)

总结

in 运算符是 JavaScript 中用于检查对象属性和数组索引的有用工具。它的灵活性和对原型链的支持使其成为检查属性是否存在的一个方便方法。在使用时,应注意它与其他属性检查方法的区别,以确保代码行为符合预期。

运算符优先级

运算符优先级(Operator Precedence)决定了在表达式中多个运算符同时出现时,它们的计算顺序。理解运算符优先级对编写正确且可预测的代码至关重要。较高优先级的运算符将在较低优先级的运算符之前被求值。

以下是 JavaScript 中运算符优先级的一个简要概览,优先级从高到低排列,并且在同一优先级内,运算符的结合方向决定了运算顺序。

高优先级运算符

  1. 成员访问和函数调用

    • (), [], .
    • 成员访问和函数调用的优先级最高。
    • 例如:obj.propertyfunc(args)arr[1]
  2. 新建对象

    • new
    • 当创建对象时,new 运算符优先于其他运算符。
    • 例如:new MyClass()
  3. 单目运算符

    • +, -, !, ~, typeof, void, delete
    • 单目运算符包括正负号、逻辑非、按位取反、类型判断、无返回、删除属性等。
    • 例如:-x, !true, typeof x
  4. 幂运算

    • **
    • 幂运算符用于执行指数运算。
    • 例如:2 ** 3

中优先级运算符

  1. 乘法、除法和取模

    • *, /, %
    • 这些运算符的优先级高于加法和减法。
    • 例如:6 / 2 * 3
  2. 加法和减法

    • +, -
    • 用于执行加法、减法或字符串拼接。
    • 例如:5 + 3, 'Hello' + ' World'
  3. 移位运算

    • <<, >>, >>>
    • 这些运算符用于执行位移操作。
    • 例如:4 << 2, -8 >> 2
  4. 比较运算

    • <, <=, >, >=, instanceof, in
    • 用于比较值的大小或判断对象实例。
    • 例如:5 > 3, x instanceof MyClass
  5. 等于和不等于

    • ==, !=, ===, !==
    • 用于检查值是否相等或不等。
    • 例如:x === 10, y != 'text'
  6. 按位运算

    • &, ^, |
    • 包括按位与、按位异或、按位或。
    • 例如:5 & 3, 2 | 1

低优先级运算符

  1. 逻辑运算

    • &&, ||
    • 用于逻辑与、逻辑或。
    • 例如:true && false, x || y
  2. 条件(三元)运算符

    • ? :
    • 根据条件返回不同的值。
    • 例如:x > 5 ? 'yes' : 'no'
  3. 赋值运算

    • =, +=, -=, *=, /=, %=, **=, <<=, >>=, >>>=, &=, ^=, |=
    • 赋值及复合赋值运算符。
    • 例如:x = 10, x += 5
  4. 逗号运算符

    • ,
    • 用于组合表达式,返回最后一个表达式的结果。
    • 例如:(1 + 2, 3 + 4)

结合方向

  • 多数运算符从左到右结合,即从左到右求值。
  • 赋值、条件和幂运算符从右到左结合,即从右到左求值。

示例

const a = 3 + 4 * 2; // 11,因为乘法优先级高于加法
const b = (3 + 4) * 2; // 14,括号改变了优先级

了解运算符优先级和结合方向对于编写准确和清晰的代码很重要。当不确定优先级时,使用括号明确表达式的计算顺序,确保代码的意图明确。

生成器函数

示例1

function * inc() {
    let x = 0, y = 7; // 初始化两个变量 x 和 y
    while (true) { // 进入无限循环
        yield x++; // 返回 x 的当前值,然后将 x 增加 1
        if (!y--) return 100; // 将 y 减 1,然后检查其是否为 0,如果是,则结束循环并返回 100
    }
}

let gen = inc(); // 创建生成器对象
// 生成器函数调用后,返回的是一个生成器对象,它是一个惰性迭代器。生成器不会在创建时立即执行,而是等到调用 gen.next() 时才开始执行。

console.log(gen); // Object [Generator] {},查看生成器对象的结构

for (let i=0; i<10; i++) {
    console.log(gen.next())
}
/*
{ value: 0, done: false }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: 6, done: false }
{ value: 7, done: false }
{ value: 100, done: true }
{ value: undefined, done: true }
*/

生成器函数 inc()

生成器函数的语法使用星号 * 表示。

yield x++; 是一种特殊的语法,它暂停生成器并返回当前的 x 值给调用者,之后将 x 增加 1。每次调用 gen.next(),生成器从上次暂停的位置继续执行。

if (!y--) return 100; 是个条件语句。y-- 会首先返回 y 的当前值,然后将 y 减 1。如果 y 的值在减 1 之前是 0,这个条件会返回 true,于是生成器退出并返回 100,同时将 done 标记为 true

  • if (!y--) return 100; 这段代码中,! 是逻辑非运算符,通常称为 “取反” 或 “逻辑否定”。它将布尔值翻转,即将 true 变为 false,将 false 变为 true。在这段代码中,!y-- 的含义与 y--(后置减运算符)结合在一起需要详细说明。

  • !y--

    • 这个组合首先会执行 y--,得到 y 的当前值,然后取反。

    • 如果 y-- 的结果是 0,则 !y-- 返回 true,因为 !0true

    • 如果 y-- 的结果不是 0,则 !y-- 返回 false,因为非零值被 ! 转换为 false

  • if (!y--) return 100;

    • 这一行代码首先执行 y--,并返回 y 的当前值,然后将它与 ! 运算符一起使用。

    • 如果 y-- 的结果是 0,则 !y-- 返回 true,这意味着 if 条件成立,return 100 被执行,生成器结束,返回 100

    • 如果 y-- 的结果不是 0,则 !y-- 返回 falseif 条件不成立,继续生成器中的循环。

  • 解释整个流程:

    • y = 1 时,y-- 的结果是 1,因此 !y--false,循环继续。

    • y = 0 时,y-- 的结果是 0,因此 !y--true,触发 return 100,生成器终止。

  • 这段代码的逻辑依赖于 y-- 逐渐减小,并在 y 变为 0 时使条件成立,导致生成器返回 100 并结束循环。

块作用域

if

if (true) {
    x = 100;
    var y = 200;
    let z = 300; // let关键字定义的是块级别,不能离开大括号
    console.log(111, x, y, z); // 111 100 200 300
    if (true) {
        console.log(222, x, y, z); // 222 100 200 300,向内穿透,因此在内部可以访问
    }
}

console.log(x); // 100
console.log(y); // 200
console.log(z); // z is not defined

function

function a() {
    x = 100; // 严格模式下禁止这种定义方式
    var y = 200;
    let z = 300;
    console.log(x, y, z); // 100 200 300
}

a()

console.log(x); // 100
console.log(y); // y is not defined
console.log(z); // z is not defined

if 语句

if 语句是 JavaScript 中用于条件控制的一种结构。它允许根据布尔条件来决定是否执行某段代码,或者在多条件下选择执行哪段代码。

基本语法

if (condition) {
    // code to execute if condition is true
} else {
    // code to execute if condition is false
}
  • condition: 这是一个布尔表达式,决定 if 块是否执行。如果条件为 true,则执行 if 块;如果为 false,执行 else 块。
  • {}: 花括号用于定义代码块。在简单的 if 语句中,花括号是可选的,但强烈建议使用它们以确保代码的可读性和维护性。

if-else 语句

else 是可选的,它可以用于在 if 条件为 false 时执行另一段代码。

const age = 20;

if (age >= 18) {
    console.log("Adult");
} else {
    console.log("Minor");
}

在这个例子中,age 是否大于或等于 18 决定了要打印的信息。如果 age 大于或等于 18,输出 “Adult”;否则,输出 “Minor”。

else if 语句

else if 允许在多条件下执行不同的代码块。

const score = 75;

if (score >= 90) {
    console.log("Grade A");
} else if (score >= 80) {
    console.log("Grade B");
} else if (score >= 70) {
    console.log("Grade C");
} else {
    console.log("Grade D");
}

在这个例子中,根据 score 的值,决定执行哪段代码。多个 else if 可以创建一系列条件判断。

注意事项

  • 布尔条件: if 条件必须是布尔表达式。JavaScript 中的一些值在布尔上下文中被视为 false,这些值包括 false0""(空字符串)、nullundefined、和 NaN。其他所有值都被视为 true
  • 花括号: 在简单的 if 语句中,花括号是可选的,但如果省略它们,代码可能变得难以阅读和维护。最好始终使用花括号来避免潜在的问题。
  • 嵌套if 语句: 嵌套 if 语句可能导致代码难以阅读。在复杂条件下,最好考虑其他结构,例如使用函数或 switch 语句。
  • 条件表达式的类型转换: JavaScript 中的条件表达式可能涉及隐式类型转换。例如,数字、字符串等在布尔上下文中会自动转换为布尔值。这可能导致意外行为,因此需要谨慎。

总结

if 语句在 JavaScript 中是条件控制的基础结构。它的使用允许程序根据不同的条件执行不同的代码块。理解 if 语句的基本用法、elseelse if 的功能,以及布尔条件的处理,有助于编写清晰和正确的代码。

switch 语句

switch 语句是 JavaScript 中一种多分支条件控制结构,用于根据不同的条件值执行不同的代码块。它通常用于替代复杂的多个嵌套 if-else if-else 结构,使代码更易读和维护。

基本语法

switch (expression) {
  case value1:
    // 执行代码块 1
    break;
  case value2:
    // 执行代码块 2
    break;
  ...
  default:
    // 默认情况下执行的代码块
}
  • expression 是一个要被评估的表达式或值。
  • 每个 case 表达式后面跟着一个值(或表达式),如果 expression 的结果与某个 case 的值匹配,则执行相应的代码块。
  • break 关键字用于跳出 switch 语句。如果省略 break,则执行完匹配的 case 后会继续执行下一个 case 中的代码。
  • default 关键字用于在 expression 的值与任何 case 的值都不匹配时执行的代码块。default 是可选的。

示例

let day = 2;
let dayName;

switch (day) {
  case 1:
    dayName = "Monday";
    break;
  case 2:
    dayName = "Tuesday";
    break;
  case 3:
    dayName = "Wednesday";
    break;
  case 4:
    dayName = "Thursday";
    break;
  case 5:
    dayName = "Friday";
    break;
  default:
    dayName = "Unknown";
}

console.log(dayName); // 输出 "Tuesday"

在这个示例中,switch 语句根据 day 的值选择不同的分支,并将对应的 dayName 赋值。

注意事项

  • 每个 case 后的值可以是任意类型(字符串、数字、布尔值等),甚至是表达式。
  • 如果省略了 break,则会从匹配的 case 开始,执行后续所有 case 直到遇到 breakswitch 语句结束。
  • 如果没有匹配的 case,则会执行 default 分支中的代码(如果存在)。
  • switch 语句是严格相等比较(===)。
  • if-else 相比,switch 语句更适用于具有多个等价值的条件判断。

使用场景

switch 语句适用于以下情况:

  • 当有多个条件需要根据不同的值执行不同的代码块时。
  • 当条件值是确定的且不需要复杂的逻辑计算时。

总结

switch 语句是 JavaScript 中一种方便的多分支条件控制结构。它使代码更清晰、更易读,并且在某些情况下比嵌套的 if-else 结构更具可读性。使用时应注意每个 case 后的 break,以及默认情况的处理。