【前端基础】2 - 2 数组与对象

§2-2 数组与对象

2-2.1 数组

JavaScript 数组属于引用数据类型。

数组:声明方法与 C/C++ 和 Java 略有不同,但使用方法基本一致。

// 使用 [] 包围数组元素
let names = ['李华', '韩梅梅', '罗宾', '小明'];
// 使用 new Array 构造函数
let arr = new Array('语文', '数学', '英语', '物理', '化学', '生物', '政治', '历史', '地理');

// 打印全数组元素
console.log(names);

// 打印数组指定元素:使用索引访问(从 0 开始)
console.log(names[0]);	// 李华
console.log(names[3]);	// 小明

// 获取数组长度
console.log(names.length);	// 4

注意,由于 JavaScript 是弱类型语言,数组可以存储任意类型的数据。但尽量在实际中避免在数组中存放类型不一致的数据,避免可能导致意外的问题。

对数组的使用离不开增删改查这四大操作,JavaScript 提供了针对数组的这四种基本操作的操作方法。

2-2.1.1 查找元素

查找元素的方法有许多种,其中最为常用的方法是直接使用索引访问数组中的元素,如 arr[0]。此外,还可以使用方法访问数组。

方法 说明
Array.prototype.every() 测试数组中所有元素是否都能通过指定函数测试
Array.prototype.find() 返回数组中满足测试函数的第一个元素的值
Array.prototype.findIndex() 返回数组中满足测试函数的第一个元素的索引
Array.prototype.findLast() 反向迭代数组,返回数组中满足测试函数的第一个元素的值
Array.prototype.findLastIndex() 反向迭代数组,返回数组中满足测试函数的第一个元素的索引
Array.prototype.includes() 测试数组的条目中,是否包含指定的值
Array.prototype.indexOf() 返回数组中第一次出现给定元素的索引
Array.prototype.lastIndexOf() 反向迭代数组,返回数组中第一次出现给定元素的索引
Array.prototype.some() 测试数组中是否至少有一个元素通过了给定测试函数的测试

2-2.1.2 修改元素

常用的修改方法是通过索引访问数组元素,结合赋值语句修改数组中的元素。也可使用方法修改。

方法 说明
Array.prototype.fill() 使用一个固定值,填充数组中从指定索引到终止索引处的全部元素
Array.prototype.reverse() 就地反转数组中的元素,并返回同一数组的引用
Array.prototype.sort() 就地对数组中的元素升序排序,并返回同一数组的引用
Array.prototype.toSorted() sort() 的复制方法版本,返回新数组

2-2.1.3 新增元素

JavaScript 提供了一系列方法用于向数组新增元素。

方法 说明
Array.prototype.concat() 合并两个或多个数组,返回新数组
Array.prototype.push() 将新元素添加到数组的末尾,返回新的数组长度
Array.prototype.unshift() 将新元素添加到数组的开头,返回新的数组长度

2-2.1.4 删除元素

JavaScript 提供了一系列用于删除数组中元素的方法。

方法 说明
Arrray.prototype.pop() 删除数组中最后一个元素,返回该元素,并修改数组长度
Array.prototype.shift() 删除数组中第一个元素,返回该元素,并修改数组长度
Array.prototype.splice() 移除或替换已存在的元素,和/或添加新元素,就地修改数组

2-2.1.5 其他操作

JavaScript 还提供了其他用于操作数组的 API。

方法 描述
Array.from() 静态方法,从可迭代或类数组对象创建一个新的浅拷贝数组实例
Array.prototype.filter(callbackfn)
Array.prototype.filter(callbackfn, thisArg)
创建给定数组的浅拷贝,包含通过所提供函数实现测试的元素
Array.prototype.flat()
Array.prototype.flat(depth)
创建一个新数组,根据指定深度,递归地将所有子元素拼接到新的数组中
Array.prototype.forEach(callbackFn)
Array.prototype.forEach(callbackfn, thisArg)
对数组中的每一个元素执行一次给定的函数
Array.prototype.join()
Array.prototype.join(seperator)
创建并返回由该数组中所有元素连接而成的字符串
Array.prototype.map(callbackFn)
Array.prototype.map(callbackFn, thisArg)
创建新数组,该新数组由原数组中每个元素都调用一次提供的函数后的返回值组成
Array.prototype.reduce(callbackFn)
Array.prototype.reduce(callbackFn, initialValue)
按顺序在数组的每一个元素上依次执行用户传入的回调函数,其返回值作为再下一个元素上调用回调函数的一个传入参数

注意:

  • Array.prototype.filter() 方法:该方法适用于过滤数组中符合条件的元素。
  • Array.prototype.flat() 方法:该方法适用于扁平化多维数组(多维数组降维处理)。
  • Array.prototype.forEach() 方法:该方法适用于遍历数组。
  • Array.prototype.map() 方法:该方法适用于重映射数组,类似于 Java 所提供的 Stream API 中的 map() 方法。
  • Array.prototype.reduce() 方法:该方法适用于累计数组中的元素,常用于求和等。

2-2.1.6 解构赋值

数组解构赋值是将数组的单元值快速批量赋值给一系列变量的简洁语法。

示例:我们考虑以下的数组。

const arr = [100, 60, 20];	// 设数组的三个单元分别表示数组的最大值、平均值和最小值
// 按照一般方法,若要访问数组中的这三个特殊值,应当使用索引访问
console.log(arr[0]);		// 访问最大值	100
console.log(arr[1]);		// 访问平均值	60
console.log(arr[2]);		// 访问最小值	20

我们可以使用解构赋值,将数组中的这些元素赋值给一系列变量便于访问。

const arr = [100, 60, 20];
const [max, avg, min] = arr;	// 这里声明为常量,则这些变量不允许被修改
console.log(max);			// 100
console.log(avg);			// 60
console.log(min);			// 20

数组解构赋值也可用于快速交换两个变量。

let a = 1;
let b = 2;
[b, a] = [a, b];
console.log(a);		// 2
console.log(b);		// 1

同理,这可推广至多变量交换。

注意:

  • 使用数组解构赋值必须保证前一语句使用分号标志语句结束。

  • 若变量多于数组元素,则多余的变量的值为 undefined。

    const arr = [1, 2, 3, 4];
    const [a, b, c, d, e] = arr;
    console.log(a);		// 1
    console.log(b);		// 2
    console.log(c);		// 3
    console.log(d);		// 4
    console.log(e);		// undefined
    
  • 若变量少于数组元素,则数组中未与变量关联的元素将会被忽略。

    const arr = [1, 2, 3, 4];
    const [a, b] = arr;
    console.log(a);		// 1
    console.log(b);		// 2
    
  • 支持选取数组中的指定元素。

    const arr = [1, 2, 3, 4];
    const [a, b, ,c] = arr;
    console.log(a);		// 1
    console.log(b);		// 2
    console.log(c);		// 4
    
  • 支持指定默认值。

    const arr = [1, 2];
    const [a = 0, b = 1, c = 2] = arr;
    console.log(a);		// 1
    console.log(b);		// 2
    console.log(c);		// 2
    
  • 支持剩余参数。

    const arr = [1, 2, 3, 4, 5];
    const [a, b, ,,,c] = arr;
    console.log(a);			// 1
    console.log(b);			// 2
    console.log(...c);		// 3 4 5
    
  • 支持多维数组结构。

    const arr = [1, 2, [3, 4]];
    const [a, b, [c, d]] = arr;
    console.log(a);		// 1
    console.log(b);		// 2
    console.log(c);		// 3
    console.log(d);		// 4
    

2-2.2 类与对象

JavaScript 本身是一门支持面向对象的编程语言,任何事物都是对象。对象实际上是一类事物的实例,用于描述某一事物的属性集合。

2-2.2.1 声明对象与成员表示方法

对象是一种引用数据类型,需要先声明后使用。对象的成员可以是属性,也可以是方法,属性间使用 , 分隔。

const person = {
    // 成员可以是属性
    name: 'Zebt',
    age: 18,
    scores: [90, 85, 95],
    
    // 成员也可以是方法
    grades: function () {
        // 使用 this 关键字指向当前代码运行时的对象
        console.log(`I got ${this.scores[0] + this.scores[1] + this.scores[1]} points in total.`);
    },
    // 方法可以简写为:
    introduce() {
        console.log(`I am ${this.name} and I am ${this.age}.`);
    }
}

person.grades();	// I got 270 points in total.
person.introduce();	// Hello. I am Zebt, and I am 20.

在上述示例代码中,我们使用 . 访问对象的成员,用法是先使用对象的命名空间,然后输入一个 .,紧接着对象的成员。一个对象的成员可能是另一个对象,若要访问对象的子对象,则还需要在容器对象的命名空间的基础上,紧接着子对象的命名空间。

除了使用点表示访问,还可使用括号表示法:

person["name"];
person["scores"];

若要移除对象中的某一属性,可以使用关键字 delete。若我们要删除上述示例代码中的 scores 属性:

delete person.scores;

若要为对象新增属性,使用点表示直接为对象新增属性。若我们要为上述示例代码中的 person 对象添加 sex 属性:

person.sex = 'male';

对象中存储的数据是无序的,无法像遍历数组一样,在已知数组长度的情况下遍历。但我们可以使用 for...in 遍历对象中的数据属性。

for (let k in person) {
    console.log(`${k}: ${person[k]}`);
    // k:属性名称,person[k]:属性值
}

for...in 也可以用于遍历数组,但不推荐使用。

2-2.2.2 构造函数与原型对象

若我们需要反复创建多个对象时,重复使用上述代码就显得有些冗长。我们可以将对象的这些特征抽取出来,声明为一个函数,每当需要创建一个对象时,调用该函数即可。

一般地,我们会使用大写字母开头明明构造函数。若要使用构造函数创建对象,应当使用关键字 new。

function Person(name, age) {
    // 为对象添加实例属性,供实例调用
    this.name = name;
    this.age = age;
    this.introduceSelf = function () {
        console.log(`Hello, I am ${this.name}, and I am ${this.age}.`)
    };
    this.farewell = function () {
        console.log('Goodbye!');
    }
}

const zebt = new Person('Zebt', 20);
zebt.introduceSelf();	// Hello, I am Zebt, and I am 20.
zebt.farewell();		// Goodbye!

使用 new 关键字实例化对象,会经历以下过程:

  • 构造新的空对象;
  • 构造函数中的 this 指向新建的空对象;
  • 执行构造函数中的语句,通过 this 为对象添加属性;
  • 返回新的对象(无需 return);

构造函数可用于实例化拥有一致属性但不同值(也可以相同)的对象。对象中所拥有的属性和方法称为实例成员,可由对象访问。构造函数本身也支持拥有自己的成员(属性和方法),称为静态成员,仅能由构造函数访问。

// 以上述的构造函数为例
// 静态方法
Person.greet = function () {
    cnosole.log(`${this}: Hello!`);	// 此处的 this 指向构造函数
}
// 静态属性
Person.species = 'Homo Sapiens';
console.log(Person.species);		// Homo Sapiens

JavaScript 提供了大量的标准内置对象,这些对象大部分都有自身的构造函数(称为构造器)。

使用构造函数便于我们对具有相同属性的对象进行实例化。但是,若实例化的对象数量较多,这些对象中的每一个成员(属性和函数)都相互独立,占用一定的内存空间。因此,使用构造函数实例化对象会较为浪费内存空间。使用原型,并将这些共有的函数(方法)挂载到原型对象上,让这些对象都继承自原型,就可以节省空间。

有关对象原型的内容请详见下文,这里,我们通过构造函数访问原型对象,将公共方法挂载到原型对象上。

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.introduceSelf = function () {
    console.log(`Hello, I am ${this.name} and I am ${this.age}.`);
}

Person.prototype.farewell = function () {
    console.log('Goodbye!');
}

这样,由构造函数创建的对象都具有该公共方法,且这些方法都指向原型中的方法。若需要定义多个公共方法,将这些方法一个一个地以这样的方式追加到原型上略显繁琐。我们可以将这些公共方法封装为一个对象,使用 Object.assign() 方法将该对象复制到原型中(见下文对象原型)。当然也可以将对象赋值到原型中,但不能够直接这样做:

Person.prototype = { ... };			// 不能这么做

则是因为如果我们向控制台打印 Peron.prototype,注意到该对象拥有一个 constructor 属性:

constructor f Person(name, age)

实际上,原型中的 constructor 属性就是构造器,它指向我们的所定义的构造函数。如果直接将对象的原型直接通过赋值的方式修改,这会导致原型中存储的构造器信息丢失。因此,若要传入对象直接赋值的方法修改原型,以追加公共方法,应当在传入的对象添加 constructor 属性重新指明构造函数。

2-2.2.3 对象原型

原型是 JavaScript 对象相互继承特性的机制。我们可以以字面量的形式声明一个对象,然后在控制台输入对象名称,使用点,控制台将会列出可用于该对象的一系列属性。以上文的 zebt 为例,在控制台输入:

robin.

然后,就会看到控制台列出了一系列属性:

age
introduceSelf
name
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
constructor
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOf

其中,除了 age 和 introduceSelf 成员是由我们自行定义的之外,该对象还具有一系列并非由我们定义的成员,并且这些成员都能由我们调用。

JavaScript 中所有的对象都有一个内置属性,称为其原型(prototype)。原型本身也是一个对象,也有其自身的原型,构成了一条原型链(prototype chain),它将终止于以 null 为原型的对象上。

Person.prototype --> Object.prototype ---> null

实际上,当我们尝试调用一个对象的属性时,会经历下列步骤:

  • 查看对象本身(本例为 zebt)是否具有该属性;
  • 在对象中找不到属性时,会在其原型对象中寻找;
  • 原型对象拥有属性,调用之。

若要访问一个对象的原型,可以使用 Object.getPrototypeOf() 函数。

Object.getPrototypeOf(zebt);	// Object {}

最基础的原型是 Object.prototype,其原型为 null,它位于原型链的终点。由于继承关系可以多重层叠,因此,一个对象的原型可能并不是 Object.prototype。

若原型链上,有多个对象具有重名属性,此时会发生属性遮蔽,根据属性调用的顺序,会优先尝试调用该对象的重名属性。

另外,我们也可以通过调用 Object.create() 方法创建对象,并为其指定一个新的原型。

const personPrototype = {
    farewell() {
        console.log('Googbye!');
    }
}

const robin = Object.create(personPrototype);
robin.farewell();		// Goodbye

这样,我们就为新对象 robin 制定了原型 personPrototype。我们就可以在 robin 上使用方法 farewell() 了。

同样地,若我们希望所创建的对象都具有统一的指定原型,我们可以通过调用 Object.assign() 方法指定构造函数所创建出的对象的原型。

JavaScript 中的所有函数都有一个名为 prototype 的属性(在浏览器显示为 __proto__),这个属性指定了新构造对象的原型。

const personPrototype = {
    introduceSelf() {
        console.log(`Hello, I am ${this.name} and I am ${this.age}.`);
    },
    farewell() {
        console.log('Googbye!');
    }
}

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Object.assign(Person.prototype, personPrototype);
// Person.prototype.farewell = personPrototype.farewell;
// Person.prototype.introduceSelf = personPrototype.introduceSelf;

这时,我们会看到这样的一种模式:方法定义在原型中,而数据属性定义在构造函数中。我们称定义在对象中的属性为自有属性,可用 Object.hasOwn() 检查一个属性是否为自有属性。

const zhangsan = new Person("Zhang San", 20);

console.log(Object.hasOwn(zhangsan, "name"));		// true
console.log(Object.hasOwn(zhangsan, "farewell"));	// false

我们可以使用 instanceof 运算符检测构造函数的 prototype 属性是否出现在某个对象的原型链上。

const zebt = new Person('Zebt', 20);
cnosole.log(zebt instanceof Object);	// true

2-2.2.4 面向对象

JavaScript 本身是一门面向对象的编程语言。谈到面向对象,我们一般都是指基于类的面向对象。JavaScript 也支持类。

可以使用关键字 class 声明一个类。不同于函数声明,类声明并不具有函数声明提升的特性,类声明必需先于类调用。

// 类声明
class Person {
    // 公共字段,可选
    // 构造函数中的赋值语句会在初始化属性前自动创建数据属性
    // 但为了可读性,建议保留数据属性声明
    name;
    age;
    
    // 静态字段
    static staticProperty = 'Homo Sapiens';
    
    // 使用 constructor 关键字声明构造器
    // 若缺省,则生成一个默认的空构造器
	// 构造器不能为私有的
    constructor(name, age) {
        // 构造器中使用 this 关键字指向新的对象
        this.name = name;
    }
    
    // 成员方法
    introduceSelf() {
        console.log(`Hi, I am ${this.name} and I am ${this.age}.`);
    }
    
    // 静态方法:无需实例化即可调用,常用于创建工具函数
	// 应当通过类本身调用
    static staticMethod() {
        console.log('This is a static method.');
    }
}

// 也可以使用类表达式,可用于声明未命名/匿名类
let RectangleA = class {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
}

// 类表达式声明命名类
let RectangleB = class Rectangle {
    constructor(height, width) {
        this.height = height;
        this.weight = weight;
    }
}

面向对象的继承特性由关键字 extends 声明。

class Student extends Person {
    major;
    
    constructor(name, age, major) {
        super(name, age);	// 使用 super 关键字调用超类构造器,必须为首句
        this.major = major;
    }
    
    // 支持方法重写(覆盖)
    introduceSelf() {
        console.log(`Hi, I am ${this.name} and I major in ${this.major}.`);
    }
}

在一些经典的面向对象编程语言中(如 Java),类的属性可以由访问修饰符修饰,以限制属性的访问权限。在 JavaScript 中,类的属性默认是公有的。可以在共有属性前加上 # 前缀以声明私有属性。

除了构造器之外,大多数类属性都有其对应的私有项。若需要防止在类之外构造类对象,应当使用私有标记。

// 私有属性示例
class PrivateFields {
    #x;
    
    constructor(x) {
        this.#x = x;
    }

	static getX(obj) {
        // 使用 in 运算符检查对象是否拥有一个私有属性
        if (#x in obj)	return obj.#x;
        else {
            return 'obj 必须为 PrivateFields 的实例';
        }
    }
}

共有属性参与继承,而私有属性不参与原型继承。私有属性不能够被 delete 运算符删除,不能够通过括号表示法访问。

2-2.2.5 标准内置对象

JavaScript 提供了大量的标准内置对象,它们可分类为:

  • 值属性:globalThis, Infinity, NaN, undefined。返回一个简单值,没有自己的属性和方法。
  • 函数属性:eval(), isFinite(), isNaN(), parseFloat(), parseInt(), decodeURI(), decodeURIComponent(), encodeURI(), encodeURIComponent()。这些函数可全局调用。
  • 基本对象:Object, Function, Boolean, Symbol。定义或使用其他对象的基础。
  • 错误对象:Error, AggregateError, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError。这些对象拥有基本的 Error 类型,也支持多种具体的错误类型。
  • 数字和日期对象:Number, BigInt, Math, Date。用于表示数字、日期和执行数学计算的对象。
  • 字符串:String, RegExp。用于表示和操作字符串。
  • 可索引的集合对象:Array, Int8Array, Unit8Array, Unit8ClampedArray, Int16Array, Unit16Array, Int32Array, Unit32Array, BigInt64Array, BigUnit64Array, Float32Array, Float64Array。
  • 使用键的集合对象:Map, Set, WeakMap, WeakSet。
  • 结构化数据:ArrayBuffer, SharedArrayBuffer, Atomics, DataView, JSON。
  • 内存管理对象:WeakRef, FinalizationRegistry。
  • 控制抽象对象:Iterator, AsyncIterator, Promise, GeneratorFunction, AsyncGeneratorFunction, Generator, AsyncGenerator, AsyncFunction。
  • 反射:Reflect, Proxy。
  • 国际化:Intl, Intl.Collator, Intl.DateTimeFormat, Intl.DisplayNames, Intl.DurationFormat, Intl.ListFormat, Intl.Locale, Intl.NumberFormat, Intl.PluralRules, Intl.RelativeTimeFormat, Intl.Segmenter。

这些标准内置对象为开发者提供了一系列实用工具。实际上,我们向文档、控制台写出数据时所使用的 document 和 console 都属于内置的对象。

2-2.2.6 对象解构

和数组一样,JavaScript 的对象也支持解构。

示例一:使用原始对象的属性名,解构对象

const { name, age } = { name: 'Zhang San', age: 20 };
console.log(name);		// Zhang San
console.log(age);		// 20

示例二:将解构所得数据赋予新的变量名

const { name: username, age } = { name: 'Zhang San', age = 20 };
console.log(username);	// Zhang San
console.log(age);		// 20

示例三:结合数组解构,解构数组对象

const arr = [
    {
        name: 'Zhang San',
        age: 20
    }
]

const [{ name, age}] = arr;
console.log(name);		//  Zhang San
console.log(age);		// 20
const people = [
    {
        name: 'Zhang San',
        info: {
            marital: false,
            level: "senior",
            dept: "development"
        }
    }
]

const [{name, info: { marital, level, dept }}] = people;
console.log(name);		// Zhang San
console.log(marital);	// false
console.log(level);		// senior
console.log(dept);		// development

2-2.3 部分常用的标准内置对象

这里列出了部分常用的标准内置对象。

2-2.3.1 Object

Object 表示 JavaScript 中的一种数据类型。Object 对象可通过 Object() 构造器,或对象初始化器、字面量语法获得。

几乎所有 JavaScript 中的对象都是 Object 的实例,除了 null.prototype 和继承自其他 null.prototype 对象的对象。

Object 支持许多方法,这些方法绝大多数都属于静态方法。

静态方法 描述
Object.assign(target)
Object.assign(target, source)
Object.assign(target, source1, source2, /* ... */, sourceN)
从一个或多个源对象中复制所有可枚举的自有属性到目标对象中
Object.create(proto)
Object.create(proto, propertiesObject)
使用给定对象作为原型对象创建新的对象
Object.entries(obj) 返回所给对象的属性键值对数组
Object.getPrototypeOf(obj) 返回对象的原型对象
Object.keys(obj) 返回所给对象的属性键数组
Object.values(obj) 返回所给对象的属性值数组

注意:

  • Object.assign() 方法:方法会修改目标对象,同时也会返回这个被修改的对象。
  • Object.entries() 方法:返回的键值对数组是一个二维数组,每一个内层数组都是一个键值对向量 [key, value]。

2-2.3.2 Number

Number 的值表示一个浮点数,如 37 或 -9.25,是基本数据类型 number 的包装类。

静态方法 描述
Number.isFinite(value) 判断传入的参数是否为有穷
Number.isInteger(value) 判断传入的参数是否为整数
Number.isNaN(value) 判断传入的参数是否为非数
Number.parseFloat(string) 将传入的字符串解析为浮点数
Number.parseInt(string) 将传入的字符串解析为整数

注意:

  • Number.isFinite() 方法:方法在传入参数既不为正负无穷,也不为非数时返回 true。
实例方法 描述
Number.prototype.toExponetial()
Number.prototype.toExponetial(fractionDigits)
返回传入数字的指数表示
Number.prototype.toFixed()
Number.prototype.toFixed(digits)
使用定点数表示格式
Number.prototype.toPrecision()
Number.prototype.toPrecision(precision)
将传入的数字格式化为指定精度的有效数字

2-2.3.3 Symbol

Symbol 是 JavaScript 的一个标准内置对象,其构造器返回一个 Symbol 值,这是一个基本数据类型 symbol ,且能保证每一个 Symbol 类型都是唯一的。Symbol 常用于向对象中添加唯一的属性键,而不会与其他代码为其添加的键重叠,且对于其他代码常用的访问对象的机制隐藏。这相当于启用了某种弱封装,或一种弱信息绑定。

每一个 Symbol() 调用都保证返回的是一个唯一的 Symbol。每一个 Symbol.for('key') 调用总是会在给定相同的 key 返回相同的 Symbol。调用 Symbol.for('key') 时,若能够在全局 Symbol 注册表中找到给定的 key,则返回对应的 Symbol。否则,则创建新的 Symbol,并将其添加至全局 Symbol 注册表中,然后返回之。

构造器:

Symbol();
Symbol(description)

注意:Symbol() 仅能够在无 new 运算符的情况下被调用,任何使用 new 运算符的尝试都会抛出 TypeError。

参数 description 是一个字符串,用于描述该 Symbol 变量,用于调试而不能访问 Symbol 本身。构造器返回的是存储在局部 Symbol 注册表中的一个 Symbol。

静态方法:

静态方法 描述
Symbol.for(key) 在运行时 Symbol 注册表中搜索给定键的 Symbol,若找到,则返回之,否则创建之
Symbol.keyFor(sym) 在全局 Symbol 注册表中搜索给定 Symbol 的键

注意事项:

  • Symbol 的类型转换一般而言都会抛出 TypeError 异常。
  • 使用 Symbol.prototype.toString() 将 Symbol 转换为字符串并返回。
  • 对象中的 Symbol 是不可枚举的,这意味着使用 for...in 迭代时,Symbol 变量将会被忽略。
  • 属性名为 Symbol 类型的键值对也会被 JSON.stringify() 方法忽略。

2-2.3.4 日期与时间

Date 是日期与时间对象。若要使用该对象,应当适用 new 实例化对象。

const date = new Date();						// 无参构造,指向实例化时的时间
const someDate = new Date('2024-1-1 08:00:00');	// 传入时间字符串构造指定时间

获得日期对象后,我们可以调用对象的方法,将日期打印在控制台上。

// 一般的输出
console.log(date);
// Mon Jan 01 2024 08:30:15 GMT+0800 (中国标准时间)

// 针对不同地区的时间格式输出完整时间与日期
console.log(date.toLocaleString());
// 2024/1/1 08:30:15

// 针对不同地区的时间格式仅输出日期
console.log(date.toLocaleDateString());
// 2024/1/1

// 针对不同地区的时间格式仅输出时间
console.log(date.toLocaleTimeString());
// 08:30:15

除了以人类可读形式打印到控制台或输出到页面中,也可以单独获取该日期事件对象所指的部分时间。

// 获得完整年份(四位数年份)
console.log(date.getFullYear());	// 2024

// 获得月份(注意:月份范围为 0 ~ 11)
console.log(date.getMonth());		// 0

// 获得日
console.log(date.getDate());		// 1

// 获得所在周(范围 0 ~ 6,0 为周日)
console.log(date.getDay());			// 1

// 获得小时
console.log(date.getHours());		// 8

// 获得分钟
console.log(date.getMinutes());		// 30

// 获得秒
console.log(date.getSeconds());		// 15

// 获得毫秒
console.log(date.getMilliseconds());	// 00

这些方法都有对应的 setter 方法,用于设置该日期对象的某一时间字段。

使用时间日期对象还可用于作时间计算。每个日期事件对象都存储了该对象自身的 UNIX 时间戳(自 1970-1-1 起所经历的毫秒数)。可通过类型转换,将时间日期对象转换为 number(表示该对象的时间戳)以计算时间差。

// 显示该对象的时间戳
console.log(+date);			// 1704069015000

// 计算时间差
const sometime = new Date('2024-1-1 10:45:30');
const elapsedMillis = sometime - date;	// 蕴含类型转换,计算时间差(毫秒值)
const elapsedSecs = Math.floor(elapsedMillis / 1000);	// 转换为秒

console.log(elapsedMillis);		// 8115000
console.log(elapsedSecs);		// 8115

2-2.3.5 字符串

字符串 String 对象用于表示和操作字符序列。字符串本身支持许多用于操作自身的方法。

字符串具有一个表示自身长度的属性 length。

下面列出一些常用的方法:

方法 描述
String.prototype.at(index) 返回字符串中指定索引处的字符。支持相对索引,若索引为负,则返回相对于字符串末位置的字符。
String.prototype.concat(str1)
String.prototype.concat(str1, st2)
String.prototype.concat(str1, /**/, strN)
拼接字符串
String.prototype.endsWith(searchString)
String.prototype.endsWith(searchString, endPosition)
检测字符串末尾是否具有搜索串内容,或指定期望发现搜索串的末位置(默认为 str.length)
String.prototype.includes(searchString)
String.prototype.includes(searchString, position)
判断字符串是否包含指定子串(大小写敏感),可指定搜索起始索引(默认为 0)
String.prototype.match(regexp) 使用正则表达式匹配字符串,返回满足表达式的结果数组
String.prototype.matchAll(regexp) 返回所有满足正则表达式匹配结果的迭代器(含捕获组)
String.prototype.repeat(count) 构建并返回一个重复指定串指定次数的新字符串
String.prototype.replace(pattern, replacement) 将字符串中满足模式串的子串替换为指定内容。若替换串为字符串,只替换首次满足匹配的子串;若为函数,将会为每一个满足匹配的子串调用该函数。原串不改变
String.prototype.replaceAll(pattern, replacement) 将字符串中给所有满足模式串的子串替换为指定内容,原串不改变
String.prototype.search(regexp) 返回字符串中首次满足模式串的主串索引
String.prototype.slice(indexStart)
String.prototype.slice(indexStart, indexEnd)
从原串中提取指定区域的子串作为新串返回,不修改原串(左闭右开区间)
String.prototype.split(seperator)
String.prototype.split(seperator, limit)
从主串中搜索模式串,并以模式串为分割点将主串切割为若干子串且保存在数组中
String.prototype.startWith(searchStrig)
String.prototype.startsWith(searchString, position)
判断字符串起始处是否包含指定串,可指定搜索起始索引,支持相对索引,默认为 0
String.prototype.toLowerCase() 将字符串转换为全小写
String.prototype.toUpperCase() 将字符串转换为全大写
String.prototype.trim() 移除字符串首尾的空格,返回新串,不修改原串
String.prototype.trimEnd() 移除字符串末尾的空格,返回新串,不修改原串
String.prototype.trimStart() 移除字符串首部的空格,返回新串,不修改原串

2-2.3.6 正则表达式

正则表达式是字符串模式匹配的一个强有力的工具,在 String 的大量与搜索/匹配相关的方法中都能够使用正则表达式作为模式串匹配主串。JavaScript 提供了专用于正则表达式的特殊标准内置对象 RegExp。

有关正则表达式的详细用法和语法,详见 MDN 的正则表达式参考(见底部链接),或参考[Java 常用类:正则表达式](./../../JavaSE 进阶/1 - 常用类/1 - 10 正则表达式.md)。

构建方式:有两种方式在 JavaScript 中创建正则表达式对象。

  • 字面量表示:表达式由两个正斜杠包围,如 /hello/i,可在斜杠后使用可选标志启用高级搜索;
  • 使用构造器:使用 new 运算符调用构造器构造正则表达式对象,构造器首参为正则表达式,其后为可选标志,如const regex = new RegExp("hello", "i");

一般而言,我们优先选择使用字面量的形式构造正则表达式对象,这会更为方便。倘若需要根据程序运行时动态输入创建正则表达式,则考虑使用构造器构造正则表达式对象。

编写表达式:正则表达式由一些简单的字符组成(如 /abc/),或由简单字符和特殊字符的组合构成。简单字符常用于查询直接匹配,若不仅需要直接匹配,则需要使用特殊字符。特殊字符有断言、字符类、捕获组和反向引用、量词、转义等等。

使用标志启用高级查询:正则表达式有六个可选参数,这些参数既可以单独使用,也可以以任意顺序混合使用。

标志 描述
d 为匹配的子串生成索引
g 全局搜索
i 不区分大小写
m 多行搜索
s 允许 . 匹配换行符
u 使用 Unicode 码的模式进行匹配
y 执行粘性搜索,匹配从目标字符串的当前位置开始

若要使用标志,使用 /regexp/flags。

使用正则表达式匹配:String 的许多方法都支持使用正则表达式匹配以操作字符串,RegExp 本身也具有两个用于匹配和查询的方法。

方法 描述
exec(str) 若找到匹配项,方法返回一个包含受匹配的项和受匹配文本的捕获组,并更新表达式对象的 lastIndex;若失败,方法返回 null;
test(str)

注意:exec() 方法返回的数组具有额外的属性:

  • index:受匹配文本在主串中的起始索引;
  • input:主串;
  • groups:命名捕获组的 null 原型对象,对象的键为捕获组名称,值为捕获组,若未定义任何捕获组,则为 null;
  • indices:仅在 d 标志存在时生效。这是一个每一个条目表示匹配子串的索引范围。

2-2.3.7 JSON

JavaScript 对象标记语言(JavaScript Object Notation, JSON)是一种基于 JavaScript 对象语法表示结构化数据的标准文本格式,通常用于在网页应用程序里传输数据。JavaScript 本身内置了专用于处理 JSON 的标准对象,即 JSON。

虽然 JSON 的语法与 JavaScript 的对象语法十分相似,但可以独立于 JavaScript 使用,许多编程环境都提供了读取(解析)与生成 JSON 的功能。

JSON 以字符串的形式存储数据,这在网络中传输数据时十分有用(在本地存储中也十分实用,在后面的章节中会提到)。一个 JSON 字符串可以存储在一个文本文件中,文件扩展名为 .json,MIME 类型为 application/json。

JSON 结构:JSON 的语法十分类似于 JavaScript 的对象字面格式。在一个标准 JSON 对象中,可以插入基础的数据类型,如字符串、数字、数组、布尔值,以及其他对象字面量。这允许你构建具有层级结构的数据:

{
    "className": "Starter Class",
    "yearAdmitted": 2020,
    "active": true,
    "students": [
        {
            "name": "Zhang San",
            "age": 18,
            "sex": "male",
            "hobbies": ["Computer Science", "Data Structure and Algorithms"]
        }, 
        {
            "name": "Li Hua",
            "age": 19,
            "sex": "male",
            "hobbies": ["Operating System", "Computer Network"]
        }, 
        {
            "name": "Han Meimei",
            "age": 20,
            "sex": "female",
            "hobbies": ["Artificial Intelligence", "Machine Learning"]
        }
    ]
}

我们一旦将上述数据使用 JSON 解析到内存当中,我们就获得了该 JSON 文件所表示的对象,接着,我们就能够通过该对象访问其中的数据。

JSON 对象本身并不具有构造器,因此不能够使用 new 操作符实例化对象,但它提供了以下静态方法以供使用:

静态方法 描述 参数
JSON.parse(text)
JSON.parse(text, reviver)
将给定字符串解析为一个 JavaScript 对象。 text - 待解析字符串
reviver - 一个函数,指定解析得到的值在被返回前如何被转换,具有两个参数 key 和 value 分别指向当前正在处理的属性的键和值,深度优先
JSON.stringify(value)
JSON.stringify(value, replacer)
JSON.stringify(value, replacer, space)
将一个 JavaScript 值转换为一个 JSON 字符串 value - 待转换的值
replacer - 若为数组,则数组中的字符串和数字决定了结果串中出现的属性;若为函数,this 指代当前正在被处理的属性的 key,返回值决定了结果串属性中的值
space - 控制最终结果串中的间距,若为数字,子层级将会包含指定空格字符缩进;若为字符串,子层级将由该字符串缩进

注意:

  • reviver 参数:this 关键字指向正在被处理的属性(若使用箭头函数,this 未绑定)。若该函数返回 undefined,该属性会被删除;
  • replacer 参数:若该参数既不是数组也不是函数,结果串将包含所有的键值对属性。通常配合 reviver 使用;

2-2.3.8 Function

Function 对象为函数提供方法。在 JavaScript 中,每一个函数实际上都是一个 Function 对象。

这意味着,一个对象当中的每一个函数(含构造函数)实际上都是一个个 Function 对象。

构造函数 说明
Function(functionBody)
Function(arg1, /* ... */, argN, functionBody)
动态构造一个函数

注意:

  • Function() 构造器可以在没有 new 运算符的情况下调用,无论是否携带 new 运算符,都会返回一个函数。
  • Function() 所创建的函数只能够在全局范围内执行。
  • 构造器中的 functionBody 是包含了 JavaScript 语句的字符串,用作函数体。

一个 Function 对象中含有以下属性:

属性 说明
length 该函数期望的参数个数
name 该函数在创建时的名称,若为匿名函数,则为 anonymous 或 ``(空白字符串)
prototype 当该函数结合 new 运算符一起被调用时,将该函数视作构造函数,并且将该属性设为新建对象的原型

注意:并不是所有的函数都有 prototype 的属性,

一个 Function 对象含有以下方法(部分):

方法 描述
Function.prototype.apply(thisArg)
Function.prototype.apply(thisArg, argsArray)
用指定 this 值和以数组形式传递的 arguments 调用该函数
Function.prototype.bind(thisArg)
Function.prototype.bind(thisArg, arg1, /* ... */, argN)
创建一个被绑定函数。调用时,向目标函数传入指定的 this 值,以及往前追加的参数
Function.prototype.call(thisArg)
Funciton.prototype.call(thisArg, arg1, /* ... */, argN)
调用该函数,指定函数的 this 值,并分别提供函数的参数

注意:

  • Funcion.prototype.bind() 方法:若被绑定函数由 new 运算符构建,则传入的 this 值会被忽略。得到的函数不具有 prototype 属性。
  • Function.prototype.call() 和 Function.prototype.apply():二者本质上并没有太大区别,只是在为被调用函数传参时方式有所不同。前者将形参作为列表分别传入,后者将参数包装为一个数组(对象)传入。

2-2.X 外部链接

Standard built-in objects - JavaScript | MDN (mozilla.org)

Array - JavaScript | MDN (mozilla.org)

instanceof - JavaScript | MDN (mozilla.org)

Classes - JavaScript | MDN (mozilla.org)

Object - JavaScript | MDN (mozilla.org)

Number - JavaScript | MDN (mozilla.org)

Symbol - JavaScript | MDN (mozilla.org)

Date - JavaScript | MDN (mozilla.org)

String - JavaScript | MDN (mozilla.org)

RegExp - JavaScript | MDN (mozilla.org)

Regular expressions - JavaScript | MDN (mozilla.org)

Regular expression syntax cheat sheet - JavaScript | MDN (mozilla.org)

Working with JSON - Learn web development | MDN (mozilla.org)

JSON - JavaScript | MDN (mozilla.org)

Function - JavaScript | MDN (mozilla.org)

posted @ 2024-03-09 21:17  Zebt  阅读(72)  评论(0)    收藏  举报