Skip to content

数据结构

动态和弱类型

JavaScript 是一种有着动态类型的动态语言。JavaScript 中的变量与任何特定值类型没有任何关联,任何变量都可以被赋予(和重新赋予)各种类型的值:

js
let foo = 42; // foo 现在是一个数值
foo = "bar"; // foo 现在是一个字符串
foo = true; // foo 现在是一个布尔值

JavaScript 也是一个弱类型语言,这意味着当操作涉及不匹配的类型时,它允许隐式类型转换,而不是抛出类型错误。

js
const foo = 42; // foo 现在是一个数值
const result = foo + "1"; 
// js 将 foo 强制转换为字符串,因此可以将其与另一个操作数连接起来
console.log(result); // 421

隐式强制转换是非常方便的,但当转换发生在预期之外的地方,或发生在预期的另一个方向(例如,字符串转换为数值,而不是数值转换为字符串)时,就会产生一些微妙的错误。对于 symbol 和 BigInt,JavaScript 有意禁止了某些隐式类型转换

原始值

除了 Object 以外,所有类型都定义了表示在语言最低层面的不可变值。我们将这些值称为原始值。

除了 null,所有原始类型都可以使用 typeof 运算符进行测试。typeof null 返回 "object",因此必须使用 === null 来测试 null。

除了 null 和 undefined,所有原始类型都有它们相应的对象包装类型,这为处理原始值提供可用的方法。例如,Number 对象提供像 toExponential() 这样的方法。当在原始值上访问属性时,JavaScript 会自动将值包装到相应的包装对象中,并访问对象上的属性。然而,在 null 或 undefined 上访问属性时,会抛出 TypeError 异常,这需要采用可选链运算符。

类型typeof 返回值对象包装器
Null"object"不适用
Undefined"undefined"不适用
Boolean"boolean"Boolean
Number"number"Number
BigInt"bigint"BigInt
String"string"String
Symbol"symbol"Symbol

对象包装器类的参考页面包含关于每个类型可用方法和属性类型的更多用法,以及原始类型本身的详细描述。

Object

在计算机科学中,对象(object)是指内存中的可以被标识符引用的一块区域。在 JavaScript 中,对象是唯一可变的值。事实上,函数也是具有额外可调用能力的对象。

属性 在 JavaScript 中,对象可以被看作是一组属性的集合。用对象字面量语法来定义一个对象时,会自动初始化一组有限的属性;然后,这些属性还可以被添加和移除。对象属性等价于键值对。属性键要么是字符串类型,要么是 symbol。属性值可以是任何类型的值,包括其他对象,从而可以构建复杂的数据结构。

有两种对象属性的类型:数据属性和访问器属性。每个属性都有对应的特性(attribute)。JavaScript 引擎可在内部访问每个属性,但是你可以通过 Object.defineProperty() 设置它们,或通过 Object.getOwnPropertyDescriptor() 读取它们。你可以在 Object.defineProperty() 页面阅读更多有关各种细微差别的信息。

数据属性 数据属性将键与值相关联。它可以通过以下属性来描述:

js
value
// 通过属性访问器获取值。可以是任意的 JavaScript 值。

writable
// 一个布尔值,表示是否可以通过赋值来改变属性。

enumerable
// 一个布尔值,表示是否可以通过 for...in 循环来枚举属性。另请参阅枚举性和属性所有权,以了解枚举属性如何与其他函数和语法交互。

configurable
// 一个布尔值,表示该属性是否可以删除,是否可以更改为访问器属性,并可以更改其特性。

访问器属性 将键与两个访问器函数(get 和 set)相关联,以获取或者存储值。

备注: 重要的是,意识到它是访问器属性——而不是访问器方法。我们可以将函数作为值来提供给 JavaScript 对象的访问器,使得对象表现得像一个类——但这并不能使该对象成为类。

一个访问器属性有着以下的特性:

js
get
// 该函数使用一个空的参数列表,以便有权对值执行访问时,获取属性值。参见 getter。可能是 undefined。

set
// 使用包含分配值的参数调用的函数。每当尝试更改指定属性时执行。参见 setter。可能是 undefined。

enumerable
// 一个布尔值,表示是否可以通过 for...in 循环来枚举属性。另请参阅枚举性和属性所有权,以了解枚举属性如何与其他函数和语法交互。

configurable
//  一个布尔值,表示该属性是否可以删除,是否可以更改为访问器属性,并可以更改其特性。

对象的原型(prototype)指向另一个对象或者 null——从概念上讲,它是对象的隐藏属性,通常表示为 [[Prototype]]。对象的 [[Prototype]] 属性也可以在对象自身访问。

对象是临时的键值对,因此经常被用作映射。不过,这可能存在人体工程学、安全性和性能方面的问题。请使用 Map 来存储任意数据。Map 参考页面更详细地讨论了使用普通对象和使用 map 存储键值之间的利弊。

Date 当表示日期时,最好的选择是使用在 JavaScript 内置的 Date 工具类。

索引类集合:数组和类型化数组

数组是一种以整数为键(integer-keyed)的属性并与长度(length)属性关联的常规对象。

此外,数组对象还继承了 Array.prototype 的一些操作数组的便捷方法。例如,indexOf()(搜索数组中的一个值)或 push()(向数组中添加一个元素),等等。这使得数组成为表示有序列表的理想选择。

类型化数组表示底层二进制缓冲区的类数组视图,并且提供了与数组相对应的类似语义的方法。“类型化数组”是一系列数据结构的总话术语,包括 Int8Array、Float32Array 等等。获取更多细节,请查看类型化数组页。类型化数组通常与 ArrayBuffer 和 DataView 一起使用。

带键的集合:Map、Set、WeakMap、WeakSet

这些数据结构把对象的引用当作键。Set 和 WeakSet 表示唯一值的集合,而 Map 和 WeakMap 表示键值相关联的集合。

你也可以自己实现 Map 和 Set。然而,因为对象不能被比较(例如,在 <“小于”的意义上),另一方面,引擎也没有暴露出它的哈希函数,因此查找性能必定是线性的。它们的原生实现(包括 WeakMap)可以达到近似对数到常数时间的查找性能。

通常,要将数据绑定到 DOM 节点,可以直接在对象上设置属性,或使用 data-* 属性。这样做的缺点是,在同一上下文中运行的任何脚本都可以使用这些数据。而 Map 和 WeakMap 则可以轻松地将数据隐蔽地绑定到对象上。

WeakMap 和 WeakSet 只允许将可垃圾回收的值作为键,这些键要么是对象,要么是未注册的 symbol,即使键仍在集合中,也可能被收集。它们专门用于优化内存使用。

结构化数据:JSON

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,来源于 JavaScript,同时也被多种语言所使用。JSON 构建了通用数据结构,可以在不同环境之间传输,甚至可以跨语言传输。更多细节,请参见 JSON。

标准库中的更多对象 JavaScript 有一个内置对象的标准库。请阅读参考页面,了解有关内置对象的更多信息。

强制类型转换

如上所述,JavaScript 是一个弱类型语言。这意味着你经常可以使用与预期类型不同类型的值,并且该语言将为你转换它为正确的类型。为此,JavaScript 定义了少数强制转换规则。

原始值强制转换

原始值强制转换用于得到一个期望的原始值,但对实际类型应该是什么并没有强烈的偏好。通常情况下可以接受字符串、数值或 BigInt。例如:

js
Date() // 构造函数,当它收到一个不是 Date 实例的参数时——字符串表示日期字符串,而数值表示时间戳。
+ // 运算符——如果运算对象是字符串,执行字符串串联;否则,执行数值相加。
== // 运算符——如果一个运算对象是原始值,而另一个运算对象是对象(object),则该对象将转换为没有首选类型的原始值。

如果值已经是原始值,则此操作不会进行任何转换。对象将依次调用它的 [@@toPrimitive]()(将 default 作为 hint 值)、valueOf() 和 toString() 方法,将其转换为原始值。注意,原始值转换会在 toString() 方法之前调用 valueOf() 方法,这与数字类型强制转换的行为相似,但与字符串类型强制转换不同。

[@@toPrimitive]() 方法,如果存在,则必须返回原始值——返回对象,会导致 TypeError。对于 valueOf() 和 toString(),如果其中一个返回对象,则忽略其返回值,从而使用另一个的返回值;如果两者都不存在,或者两者都没有返回一个原始值,则抛出 TypeError。例如,以下代码:

JS Copy to Clipboard

console.log({} + []); // "[object Object]" {} 和 [] 都没有 [@@toPrimitive]() 方法。{} 和 [] 都从 Object.prototype.valueOf 继承 valueOf(),其返回对象自身。因为返回值是一个对象,因此它被忽略。因此,调用 toString() 方法。{}.toString() 返回 "[object Object]",而 [].toString() 返回 "",因此这个结果是它们的串联:"[object Object]"。

在强制转换为任意的原始类型时,[@@toPrimitive]() 方法总是优先调用。原始值的强制转换的行为通常与 number 类型的强制转换类似,因为优先调用了 valueOf();然而,有着自定义 [@@toPrimitive]() 方法的对象可以选择返回任意的原始值。Date 和 Symbol 对象是唯一重写 [@@toPrimitive]() 方法的对象。Date.prototype[@@toPrimitive]() 将 "string" 视为 "default" hint,而 Symbol.prototype[@@toPrimitive]() 忽略 hint 并始终返回一个 symbol。

数字类型强制转换 有两种数字类型:number 和 BigInt。有时候,该语言期望使用 number 或 BigInt(例如 Array.prototype.slice(),其中索引必须是一个数字);其他时候,它可能容忍并且根据运算对象的类型不同执行不同的运算。有关不允许从其他类型隐式转换的严格强制转换过程,请参阅 number 强制转换和 BigInt 强制转换。

数字类型强制转换与 number 类型强制转换几乎相同,只是 BigInt 会按原样返回,而不是引起 TypeError。强制数字类型转换用于所有算术运算,因为它们重载了 number 和 BigInt 类型。唯一例外的是一元加,它总是实施 number 强制类型转换。

其他类型强制转换

所有除了 null、undefined 以及 Symbol 的数据类型,都有它们各自的强制转换过程。更多细节,请参见字符串强制转换、布尔值强制转换以及对象强制转换。

你可能已经注意到,

有三种不同的路径可以将对象转换为原始值:

  • 原始值强制转换:[@@toPrimitive]("default") → valueOf() → toString()
  • 数字类型强制转换、number 类型强制转换、BigInt 类型强制转换:[@@toPrimitive]("number") → valueOf() → toString()
  • 字符串类型强制转换:[@@toPrimitive]("string") → toString() → valueOf() 在所有情况下,[@@toPrimitive]() 如果存在,必须可调用并返回原始值,而如果它们不可调用或返回对象,valueOf 或 toString 将被忽略。在过程结束时,如果成功,结果保证是原始值。然后,由此产生的原始值会进一步强制类型转换,具体取决于上下文。

面试题

1. (a == 1 && a == 2 && a ==3) 为true

js
const a = {
  value: 0,
  valueOf: function () {
    return this.value += 1
  }
}

2. ['1', '2', '3'].map(parseInt)

语法 parseInt(string, radix); 参数 string 要被解析的值。如果参数不是一个字符串,则将其转换为字符串 (使用 ToString抽象操作)。字符串开头的空白符将会被忽略。

radix_ 可选_ 从 2 到 36 的整数,表示进制的基数。例如指定 16 表示被解析值是十六进制数。如果超出这个范围,将返回 NaN。假如指定 0 或未指定,基数将会根据字符串的值进行推算。注意,推算的结果不会永远是默认值 10!文章后面的描述解释了当参数 radix 不传时该函数的具体行为。

返回值 从给定的字符串中解析出的一个整数。

或者 NaN,当

radix 小于 2 或大于 36,或 第一个非空格字符不能转换为数字。 parseInt('123', 5) // 将'123'看作 5 进制数,返回十进制数 38 => 15^2 + 25^1 + 3*5^0 = 38

js
// arr.map(function callback(currentValue[, index[, array]])
parseInt('1', 0) //radix为0时,且string参数不以“0x”和“0”开头时,按照10为基数处理。这个时候返回1
parseInt('2', 1) //基数为1(1进制)表示的数中,最大值小于2,所以无法解析,返回NaN
parseInt('3', 2) //基数为2(2进制)表示的数中,最大值小于3,所以无法解析,返回NaN
// 答案 [1, NaN, NaN]

一: 在JavaScript中,常见的数据结构有很多

以下是一些常见的数据结构及其在解决问题时的优势和劣势:

1. 数组(Array):

  • 优势:

    • 快速随机访问:通过索引可以在O(1)时间内访问元素。
    • 动态大小:JavaScript中的数组可以动态增长或缩小。
  • 劣势:

    • 插入和删除:在中间插入或删除元素可能需要移动大量元素,时间复杂度为O(n)。
    • 固定大小:JavaScript的标准数组是动态大小的,但TypedArray是固定大小的。

在JavaScript中,TypedArray 是一组与特定数值类型关联的类数组结构。这些数组提供了一种在底层二进制数据缓冲区上直接操作二进制数据的方式,而无需通过JavaScript对象。

TypedArray 包括多个子类,每个子类都与特定的数值类型相关联,例如:

js
1. `Int8Array`: 8 位有符号整数数组
2. `Uint8Array`: 8 位无符号整数数组
3. `Int16Array`: 16 位有符号整数数组
4. `Uint16Array`: 16 位无符号整数数组
5. `Int32Array`: 32 位有符号整数数组
6. `Uint32Array`: 32 位无符号整数数组
7. `Float32Array`: 32 位浮点数数组
8. `Float64Array`: 64 位浮点数数组

这些 TypedArray 类型提供了一种高效的方式来处理二进制数据,尤其是在与WebGL、Web Audio API等底层API交互时。与普通的JavaScript数组相比,TypedArray 具有更好的性能,因为它们在底层直接表示二进制数据,而不需要通过对象进行封装。

以下是一个简单的示例,演示如何使用 TypedArray

javascript
// 创建一个32位有符号整数数组
const intArray = new Int32Array([1, 2, 3, 4, 5]);

// 访问和修改数组元素
console.log(intArray[0]); // 输出: 1
intArray[1] = 10;

// 输出整个数组
console.log(intArray); // 输出: Int32Array [ 1, 10, 3, 4, 5 ]

TypedArray 提供了一些与数组相似的方法,同时也支持一些额外的操作,使其更适用于处理底层的二进制数据。

2. 链表(Linked List):

  • 优势:

    • 动态大小:可以根据需要动态增长或缩小。
    • 插入和删除:在中间插入或删除元素相对较快,时间复杂度为O(1)。
  • 劣势:

    • 随机访问:访问元素需要从头开始遍历,时间复杂度为O(n)。
    • 非连续存储:JavaScript中的链表是通过对象引用实现的,不像数组那样在内存中连续存储。

3. 栈(Stack):

  • 优势:

    • 后进先出(LIFO):对于某些问题,使用栈可以提供简洁的解决方案。
    • 内存管理:常用于跟踪函数调用、表达式求值等。
  • 劣势:

    • 无法随机访问:只能访问栈顶元素。
    • 有限容量:基于数组或链表实现的栈有一定的容量限制。

4. 队列(Queue):

  • 优势:

    • 先进先出(FIFO):在需要按照顺序处理数据的场景中有很好的应用。
  • 劣势:

    • 无法随机访问:只能访问队列头和尾。
    • 有限容量:基于数组或链表实现的队列有一定的容量限制。

5. 集合(Set):

  • 优势:

    • 唯一性:保证元素的唯一性。
    • 常用操作:提供并集、交集、差集等常用集合操作。
  • 劣势:

    • 无序性:集合中的元素是无序的,不能通过索引访问。

6. 字典/映射(Map):

  • 优势:

    • 键值对存储:可以使用任意类型的值作为键。
    • 查找效率高:根据键直接获取值的效率较高。
  • 劣势:

    • 内存占用:相比于对象,Map占用的内存空间相对较大。

7. 树(Tree):

  • 优势:

    • 层级结构:适用于表示层级关系的数据,例如文件系统、组织结构等。
    • 搜索:二叉搜索树提供O(log n)的查找时间。
  • 劣势:

    • 平衡问题:不平衡的二叉搜索树可能导致性能下降。
    • 操作复杂性:操作树的某些操作可能相对复杂。

8. 图(Graph):

  • 优势:

    • 表示关系:适用于表示多对多关系的数据。
    • 多种算法:可用于解决路径查找、最短路径、最小生成树等问题。
  • 劣势:

    • 复杂性:图的操作相对较复杂,算法复杂度较高。

每种数据结构都有其适用的场景,根据具体的问题和使用需求选择合适的数据结构是很重要的。在JavaScript中,由于灵活性,可以使用内置的数据结构,也可以通过对象的方式模拟其他数据结构。

二: 在实际项目中选择合适的数据结构通常取决于问题的性质、数据访问模式、复杂性需求以及对性能的要求。

以下是一些在项目中考虑的因素:

  1. 问题性质:

    • 分析问题的本质,确定需要存储和处理的数据的特性。例如,是简单的键值对关系,还是需要支持高效的搜索、排序、过滤等操作。
  2. 数据访问模式:

    • 考虑数据的读写频率以及数据的访问模式,是更多的读取操作还是写入操作。不同的数据结构在读写性能上有差异,选择适合访问模式的数据结构能提高效率。
  3. 复杂性需求:

    • 考虑数据结构的复杂度,包括插入、删除、查找等操作的时间复杂度。有些数据结构适用于特定的操作,但可能在其他操作上效率较低。
  4. 内存占用:

    • 考虑项目对内存的要求,有些数据结构可能占用较多的内存空间,而在受限环境下可能需要更节省内存的数据结构。
  5. 语言和平台特性:

    • 考虑所使用的编程语言和平台对于特定数据结构的支持。某些数据结构可能在特定语言或平台上更容易实现或者有更好的性能。
  6. 可维护性:

    • 考虑项目的长期维护性。选择易于理解、扩展和维护的数据结构,以降低项目的复杂度,提高代码的可读性。
  7. 库和框架支持:

    • 考虑项目中已经使用的库和框架,以及它们对于特定数据结构的支持。有时候选择与项目生态系统更加契合的数据结构能简化开发。

举例来说,如果需要高效地进行查找操作且不关心元素的顺序,可能会选择使用哈希表(MapSet);如果需要有序集合并需要支持范围查找,可能会选择使用平衡二叉搜索树(例如Map在某些JavaScript引擎中的实现)。

总体而言,选择合适的数据结构需要在问题需求和实际场景中进行综合权衡,没有一种数据结构适用于所有情况。在实际项目中,深入理解数据结构的特性,以及在不同场景下它们的表现和适用性,是做出明智选择的关键。

三: 选择合适的数据结构通常取决于具体问题的需求。以下是一些实际项目中选择数据结构的例子:

1. 数组(Array):

  • 场景:

    • 当需要按照顺序存储一组元素,并且需要通过索引进行快速访问时,使用数组是合适的。
  • 例子:

    • 存储列表型数据,例如待办事项列表。
    • 表示时间序列数据,例如传感器每小时的测量值。
javascript
// 示例:待办事项列表
const todoList = ['Task 1', 'Task 2', 'Task 3'];

// 示例:时间序列数据
const hourlyMeasurements = [23.5, 24.0, 22.8, /* ... */];

2. 对象(Object):

  • 场景:

    • 当数据具有键值对的结构,并需要通过键快速访问时,使用对象是合适的。
  • 例子:

    • 存储个人信息,例如姓名、年龄、地址等。
    • 表示配置选项,例如应用程序的设置。
javascript
// 示例:个人信息
const person = {
  name: 'John Doe',
  age: 30,
  address: '123 Main St',
};

// 示例:应用程序配置
const appConfig = {
  theme: 'light',
  language: 'en',
};

3. Map:

  • 场景:

    • 当需要存储键值对,并且键可以是任意数据类型(包括对象、函数等)时,使用Map是合适的。
  • 例子:

    • 缓存函数的计算结果,以提高性能。
    • 存储动态生成的键值对。
javascript
// 示例:缓存函数结果
const cache = new Map();

function calculateSquare(x) {
  if (cache.has(x)) {
    return cache.get(x);
  }

  const result = x * x;
  cache.set(x, result);
  return result;
}

4. Set:

  • 场景:

    • 当需要存储唯一值的集合时,使用Set是合适的。
  • 例子:

    • 存储应用程序中唯一的用户标识符。
    • 去重数组中的重复元素。
javascript
// 示例:唯一用户标识符集合
const uniqueUserIds = new Set();

function addUser(userId) {
  uniqueUserIds.add(userId);
}

// 示例:去重数组
const arrayWithDuplicates = [1, 2, 2, 3, 4, 4, 5];
const uniqueValues = new Set(arrayWithDuplicates);
const uniqueArray = [...uniqueValues];

在实际项目中,通常会根据数据的特性、访问模式以及业务需求来选择合适的数据结构。深入理解JavaScript中不同数据结构的特点和性能表现,可以更好地应对实际项目中的各种场景。

四: 数组和链表是两种常见的数据结构

它们在内部组织数据的方式和性能特点上有很大的区别。以下是数组和链表的主要区别以及它们在不同场景中的应用:

数组(Array):

  1. 内部结构:

    • 数组是由一组连续的内存单元组成,元素在内存中是紧密相邻的。
    • 元素通过索引直接访问,索引可以是整数或字符串。
  2. 随机访问:

    • 数组支持通过索引在O(1)时间内直接访问任意位置的元素。
  3. 大小固定:

    • 大多数数组是静态的,大小在创建时就确定,并且通常不可改变。
  4. 插入和删除:

    • 在数组中间插入或删除元素可能需要移动其他元素,时间复杂度为O(n)。
  5. 应用场景:

    • 适用于需要随机访问元素、知道索引的情况,例如数组列表、矩阵等。
    • 在元素的数量是已知且固定的情况下使用较为合适。

链表(Linked List):

  1. 内部结构:

    • 链表由一组节点组成,每个节点包含数据和指向下一个节点的引用。
    • 节点在内存中不一定是连续的,通过引用连接起来。
  2. 随机访问:

    • 链表不支持直接通过索引访问元素,需要从头节点开始遍历。
  3. 大小动态:

    • 链表可以动态增长或缩小,不需要预先指定大小。
  4. 插入和删除:

    • 在链表中插入或删除元素相对较快,时间复杂度为O(1),但需要遍历找到相应位置。
  5. 应用场景:

    • 适用于需要频繁插入和删除操作的场景,例如实现栈、队列等。
    • 在元素的数量不确定,需要动态分配内存的情况下使用较为合适。

应用场景对比:

  • 数组应用场景:

    • 需要高效的随机访问元素的情况,例如需要通过索引迅速访问数据的表格、矩阵等。
    • 元素的数量是已知且固定的情况,不需要频繁进行插入和删除操作。
  • 链表应用场景:

    • 需要频繁插入和删除元素的情况,例如实现队列、栈等数据结构。
    • 元素的数量是不确定的,需要动态分配内存的情况。

总体而言,数组和链表各有其适用的场景,选择合适的数据结构取决于具体问题的需求和访问模式。在实际应用中,通常需要根据问题特性综合考虑数据结构的优势和劣势。

五: 如果要在一个已排序的数组中快速查找一个元素,你会选择使用哪种数据结构?

如果要在一个已排序的数组中快速查找一个元素,通常会选择使用二分查找(Binary Search)算法,而不一定需要引入新的数据结构。

  1. 算法思想:

    • 通过将数组分成两半,判断目标元素可能在数组的左半部分还是右半部分。
    • 如果目标元素等于中间元素,则找到了;否则,根据目标元素与中间元素的大小关系,继续在左或右半部分查找。
  2. 时间复杂度:

    • 二分查找的时间复杂度是O(log n),其中n是数组的大小。
  3. 适用条件:

    • 数组必须是有序的。

使用二分查找的步骤:

  1. 初始化:

    • 定义左边界 left 为数组的起始位置,右边界 right 为数组的结束位置。
  2. 循环或递归:

    • 在循环或递归中,计算中间索引 mid,比较中间元素与目标元素。
    • 如果相等,返回中间索引,找到了目标元素。
    • 如果目标元素小于中间元素,缩小搜索范围到左半部分,更新右边界 right
    • 如果目标元素大于中间元素,缩小搜索范围到右半部分,更新左边界 left
  3. 结束条件:

    • 当左边界大于右边界时,表示整个数组已经搜索完毕,目标元素不存在。
javascript
function binarySearch(arr, target) {
  let left = 0;
  let right = arr.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);

    if (arr[mid] === target) {
      return mid; // 找到目标元素,返回索引
    } else if (arr[mid] < target) {
      left = mid + 1; // 在右半部分继续搜索
    } else {
      right = mid - 1; // 在左半部分继续搜索
    }
  }

  return -1; // 目标元素不存在
}

二分查找是一种非常高效的搜索算法,适用于已排序的数组。在这种情况下,它可以在O(log n)的时间内找到目标元素,相比线性搜索算法的O(n)更加快速。

六: 如何判断数据类型?

1. typeof

typeof 运算符返回一个字符串,表示操作数的类型。

js

javascript复制代码typeof 1                           // "number"
typeof 'zhangsan'                  // "string"
typeof true                        // "boolean"
typeof abcd                        // "undefined"
typeof Symbol('lisi')              // "symbol"
typeof function(){}                // "function"
typeof null                        // "object"
typeof {}                          // "object"
typeof []                          // "object"

2. instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,返回布尔值,只用来检测对象数据类型。 javascript复制代码语法: object instanceof constructor 参数: object为某个实例对象 constructor为某个构造函数 以下为window内置对象,类似Array,Object,Function都是内置构造函数(别称: 内置对象 || 函数对象) ​

js
[] instanceof Array                // true
[] instanceof Object               // true
{} instanceof Object               // true
function(){} instanceof Function   // true
function(){} instanceof Object     // true
null instanceof Object             // true

// new 构造函数的举例:
function Person(name, age) {
    this.name = name
    this.age = age
}
const zhangsan = new Person('张三', 18) // Person {name: '张三', age: 18}
zhangsan instanceof Person // true

3. constructor

所有对象(使用 Object.create(null) 创建的对象除外)都将具有 constructor 属性 ini复制代码语法:object instanceof constructor 参数:object为某个实例对象 constructor为某个构造函数 ​

js
const n = 1
const m = '123'
const flag = true
const sym = Symbol('bian')
const obj = {}
const array = []
const fun = function () {}

n.constructor === Number       // true
m.constructor === String       // true
flag.constructor === Boolean   // true
sym.constructor === Symbol     // true
obj.constructor === Object     // true
array.constructor === Array    // true
fun.constructor === Function   // true
array.constructor === Object   // false
fun.constructor === Object     // false

4. Object.prototype.toString()

toString() 方法返回一个表示该对象的字符串。

Object.prototype.toString() 返回 "[object Type]"

js
Object.prototype.toString.call(null)          // "[object Null]"
Object.prototype.toString.call(undefined)     // "[object Undefined]"
Object.prototype.toString.call(1)             // "[object Number]"
Object.prototype.toString.call('str')         // "[object String]"
Object.prototype.toString.call({})            // "[object Object]"
Object.prototype.toString.call([])            // "[object Array]"
Object.prototype.toString.call(Symbol(666))   // "[object Symbol]"
Object.prototype.toString.call(function(){})  // "[object Function]"