你并不了解 JavaScript:入门 - 第二版 - 第三章:寻根究底

本文深入探讨JavaScript的核心概念,包括迭代器协议、闭包和原型链。阐述了迭代器模式如何提高代码可读性,闭包如何在不同作用域中持久化变量,以及原型如何实现对象属性的委托访问。通过实例解析了函数的this关键字在不同调用方式下的动态上下文。最后鼓励读者养成探究「为什么?」的习惯,以深化对JavaScript的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第三章:寻根究底

如果你已经阅读了第一和第二章,并花时间消化和思考,希望你对 JS 的理解有更多的收获。如果你跳过/略过它们(尤其是第二章),我建议你回去花更多的时间阅读这些材料。

在第二章中,我们在高层次上解释了语法、模式和行为。在这一章中,我们的注意力转移到 JS 的一些低层次的根本特征上,这些特征几乎是我们所写的每一行代码的基础。

请注意:这一章的内容比你可能习惯于思考的编程语言要深得多。我的目标是帮助你理解 JS 工作的核心,是什么让它运转。这一章应该开始回答一些你在探索 JS 时可能出现的「为什么?」的问题。然而,这些内容仍然不是对该语言的详尽阐述;这是本系列书的其他部分的目标,我们在这里的目标仍然是入门,并且更加适应 JS 的感觉,了解它是如何起伏的。

不要这么快就读完这些资料,以至于你迷失了方向。我已经说过十几次了,跬步千里。即使如此,你在读完这一章后可能还会有其他问题。这没关系,因为还有一整个系列的书在等着你继续探索呢!

迭代

由于程序本质上是为了处理数据(并对这些数据做出决定),用于浏览数据的模式对程序的可读性有很大影响。

迭代器模式已经存在了几十年,它提出了一种「标准化」的方法,从一个源头一次的消费数据。这个想法是,对数据源进行迭代 。通过处理第一部分,然后是下一部分,以此类推,逐步处理数据集合,而不是一下子处理整个数据集,这样做更常见,更有帮助。

想象一下一个数据结构,它代表了一个关系型数据库的 SELECT 查询,它通常将结果组织成行。如果这个查询只有一条或几条记录,你可以一次处理整个结果集,并将每条记录分配给一个局部变量,然后对这些数据进行任何适当的操作。

但如果查询有 10 或 1000(或更多!)行,你就需要迭代处理来处理这些数据(通常是一个循环)。

迭代器模式定义了一个叫做「迭代器」的数据结构,它有一个对底层数据源(如查询结果行)的引用,它暴露了一个像 next() 的方法。调用 next() 返回下一个数据(即数据库查询的「记录」或「行」)。

你并不总是知道你需要遍历多少数据,所以一旦你遍历整个集合并超过终点,该模式通常以一些特殊的值或异常来表示完成。

迭代器模式的重要性在于坚持以标准的方式来迭代处理数据,这就创造了更干净、更容易理解的代码,而不是让每个数据结构/源都定义自己处理数据的自定义方式。

经过多年来 JS 社区围绕共同商定的迭代技术所做的各种努力,ES6 在语言中直接为迭代器模式规范了一个特定的协议。该协议定义了一个 next() 方法,其返回值是一个被称为 iterator result 的对象;该对象有 valuedone 属性,其中 done 是一个布尔值,在对底层数据源的迭代完成之前为 false

消费迭代器

有了 ES6 的迭代协议,每次消费一个数据源的值是可行的,在每次 next() 调用后检查 done 是否为 true 来停止迭代。但这种方法是相当手动的,所以 ES6 也包括了几个机制(语法和 API),用于标准化地消费这些迭代器。

其中一个机制是 for..of 循环:

// 给定某个数据源的迭代器:
var it = /* .. */;

// 循环处理其结果,一次一个
for (let val of it) {
   
   
    console.log(`Iterator value: ${
     
      val }`);
}
// Iterator value: ..
// Iterator value: ..
// ..
注意:
这里我们将省略等效的手动循环,但它的可读性肯定不如 for..of 循环!

另一个经常用于消费迭代器的机制是 ... 操作符。这个操作符实际上有两种对称的形式: 扩展剩余扩展形式是一个迭代器消费器。

扩展一个迭代器,你必须要有东西来传递它。在 JS 中有两种可能性:一个数组或一个函数调用的参数列表。

一个数组的传递:

// 将一个迭代器分散到一个数组中,
// 每个迭代的值都占据一个数组元素的位置。
var vals = [...it];

一个函数调用传递:

// 将一个迭代器分散到一个函数中,
// 每个迭代的值占据一个参数位置。
doSomethingUseful(...it);

在这两种情况下,扩展运算符形式的 ... 遵循 iterator-consumption 协议(与 for..of 循环相同),从迭代中获取所有可用的值,并将它们放入(又称,扩展)接收上下文中(数组,参数列表)。

迭代器

迭代器消费 (iterator-consumption) 协议在技术上是为消费迭代器 (iterables) 而定义的;迭代器是一个可以被迭代的值。

该协议自动从一个可迭代的程序中创建一个迭代器实例,并且只消费该迭代器实例,直到其完成。这意味着一个迭代器可以被消费一次以上;每次都会创建并使用一个新的迭代器实例。

那么,我们在哪里可以找到可迭代项?

ES6 将 JS 中的基本数据结构/集合类型定义为 iterables。这包括字符串、数组、maps、sets 等。

假设以下代码:

// 数组是一个可迭代的对象
var arr = [10, 20, 30];

for (let val of arr) {
   
   
    console.log(`Array value: ${
     
     val}`);
}
// Array value: 10
// Array value: 20
// Array value: 30

由于数组是可迭代的,我们可以通过 ... 扩展运算符,使用迭代器消费浅层复制一个数组:

var arrCopy = [...arr];

我们也可以在一个字符串中一个一个地遍历字符:

var greeting = "Hello world!";
var chars = [...greeting];

chars;
// [ "H", "e", "l", "l", "o", " ",
//   "w", "o", "r", "l", "d", "!" ]

一个 Map 数据结构使用对象作为键,将一个值(任何类型)与该对象相关联。Map 有一个不同于这里的默认迭代,因为迭代不仅仅是在 map 的值上,而是在它的 entries 上。一个 entry 是一个元组(两个元素数组),包括一个键和一个值。

假设以下代码:

// 给定两个DOM元素,`btn1`和`btn2`。

var buttonNames = new Map();
buttonNames.set(btn1, "Button 1"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值