深入理解JavaScript核心概念:迭代器、闭包与this关键字

深入理解JavaScript核心概念:迭代器、闭包与this关键字

You-Dont-Know-JS A book series on JavaScript. @YDKJS on twitter. You-Dont-Know-JS 项目地址: https://gitcode.com/gh_mirrors/yo/You-Dont-Know-JS

前言

在探索JavaScript的深度时,我们需要理解几个核心概念:迭代器、闭包和this关键字。这些概念构成了JavaScript编程的基础,理解它们对于编写高效、可维护的代码至关重要。

迭代器与可迭代对象

迭代器模式

迭代器模式是一种标准化处理数据的方式,它允许我们逐个"块"地消费数据源。这种模式比一次性处理整个数据集更为常见和实用。

想象一个代表数据库查询结果的数据结构,通常组织为行。如果查询只有少量行,可以一次性处理整个结果集。但对于成百上千行的结果,就需要迭代处理(通常是循环)。

ES6迭代协议

ES6标准化了迭代器协议,定义了一个next()方法,返回一个包含valuedone属性的迭代器结果对象。done是一个布尔值,在遍历完数据源之前为false

消费迭代器

ES6提供了几种消费迭代器的标准化方式:

  1. for..of循环:
for (let val of it) {
    console.log(`迭代器值: ${val}`);
}
  1. 展开运算符...
    • 数组展开:
    var vals = [...it];
    
    • 函数调用展开:
    doSomethingUseful(...it);
    

可迭代对象

可迭代对象是可以被迭代的值。协议会自动从可迭代对象创建迭代器实例,并消费该实例直到完成。这意味着一个可迭代对象可以被多次消费,每次都会创建新的迭代器实例。

JavaScript中的基本数据结构/集合类型都是可迭代的,包括字符串、数组、Map、Set等。

示例
// 数组是可迭代的
var arr = [10, 20, 30];
for (let val of arr) {
    console.log(`数组值: ${val}`);
}

// 使用展开运算符浅拷贝数组
var arrCopy = [...arr];

// 迭代字符串字符
var greeting = "Hello world!";
var chars = [...greeting];
Map的迭代

Map数据结构使用对象作为键,将值(任何类型)与该对象关联。Map的默认迭代不是仅遍历值,而是遍历其条目(键值对的元组)。

var buttonNames = new Map();
buttonNames.set(btn1, "Button 1");
buttonNames.set(btn2, "Button 2");

for (let [btn, btnName] of buttonNames) {
    btn.addEventListener("click", function onClick() {
        console.log(`点击了 ${btnName}`);
    });
}
特定迭代

大多数内置可迭代对象提供三种迭代形式:

  • keys():仅键
  • values():仅值
  • entries():条目(键值对)

闭包

闭包的定义

闭包是函数记住并继续访问其作用域外变量的能力,即使函数在不同的作用域中执行。

闭包有两个定义特征:

  1. 闭包是函数本质的一部分(对象没有闭包,函数有)
  2. 要观察闭包,必须在不同于函数定义的作用域中执行函数

闭包示例

function greeting(msg) {
    return function who(name) {
        console.log(`${msg}, ${name}!`);
    };
}

var hello = greeting("Hello");
hello("Kyle"); // Hello, Kyle!

在这个例子中,内部函数who()闭包了外部函数greeting()msg参数。即使greeting()执行完毕,msg变量仍然被保留在内存中,因为内部函数实例仍然存活。

闭包不是快照

闭包不是变量值的快照,而是变量本身的直接链接和保留。这意味着闭包可以观察(或制造)这些变量随时间的更新。

function counter(step = 1) {
    var count = 0;
    return function increaseCount() {
        count += step;
        return count;
    };
}

var incBy1 = counter(1);
incBy1(); // 1
incBy1(); // 2

异步代码中的闭包

闭包在异步代码(如回调)中最为常见:

function getSomeData(url) {
    ajax(url, function onResponse(resp) {
        console.log(`响应 (来自 ${url}): ${resp}`);
    });
}

即使getSomeData()立即完成,url参数变量也会在闭包中保持活动状态,直到Ajax调用返回并执行onResponse()

循环中的闭包

for (let [idx, btn] of buttons.entries()) {
    btn.addEventListener("click", function onClick() {
        console.log(`点击了按钮 (${idx})!`);
    });
}

使用let声明,每次迭代都会获得新的块作用域idxbtn变量。内部onClick()函数闭包了idx,保留它直到点击处理程序设置在btn上。

this关键字

this的误解

关于this有两个常见误解:

  1. 认为函数的this指向函数本身
  2. 认为this指向方法所属的实例

这两种理解都是错误的。

执行上下文

函数除了通过闭包附加到其封闭作用域外,还有另一个影响其访问能力的特性:执行上下文,通过this关键字暴露给函数。

作用域是静态的,包含定义函数时可用的固定变量集。而函数的执行上下文是动态的,完全取决于如何调用它(无论在哪里定义或调用)。

this示例

function classroom(teacher) {
    return function study() {
        console.log(`${teacher} says to study ${this.topic}`);
    };
}

var assignment = classroom("Kyle");
assignment(); // Kyle says to study undefined

assignment()作为普通函数调用时,this默认为全局对象(非严格模式下),没有topic属性,所以this.topic解析为undefined

不同调用方式

  1. 作为方法调用:
var homework = {
    topic: "JS",
    assignment: assignment
};
homework.assignment(); // Kyle says to study JS
  1. 使用call()指定上下文:
var otherHomework = { topic: "Math" };
assignment.call(otherHomework); // Kyle says to study Math

相同的上下文感知函数以三种不同方式调用,每次this引用不同的对象。

总结

理解JavaScript的这些核心概念对于成为高效开发者至关重要。迭代器提供了处理数据的标准化方式,闭包允许函数记住其词法环境,而this关键字提供了动态执行上下文。掌握这些概念将帮助你编写更清晰、更灵活的代码。

You-Dont-Know-JS A book series on JavaScript. @YDKJS on twitter. You-Dont-Know-JS 项目地址: https://gitcode.com/gh_mirrors/yo/You-Dont-Know-JS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

缪阔孝Ruler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值