一、看《JavaScript高级程序设计》笔记

122 阅读19分钟

前言

首先申明, 我只是个喜欢在掘金记笔记的一个菜鸟罢了, 这一篇文章不建议大家看, 因为不完整, 很多知识点和别的编程语言相同, 我就没写出来, 掘金大佬绝对比我写的好, 至于为什么发出来, 是因为草稿箱笔记想要整理整理

这系列笔记是我看《JavaScript高级程序设计》总结

一、标签

通常应该把<script>元素放到页面末尾,介于主内容之后及标签</body>之前。

二、var和let

一句话, 不要使用 varconst(优先使用) 和 let

  1. var message = "hi"; message = 100; // 合法,但不推荐

  2. var使用这个关键字声明的变量会自动提升到函数作用域顶部, let 声明的变量不会在作用域中被提升。

  3. let 声明的范围是块作用域, 而 var 声明的范围是函数作用域

  4. var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声明的变量则会)。

  5. var在for中的问题

for (var i = 0; i < 5; ++i) {
    setTimeout(() => console.log(i), 0)
}

你可能会认为他会输出 0, 1, 2, 3, 4, 但实际上会输出 5, 5, 5, 5, 5 之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时 逻辑时,所有的i都是同一个变量,因而输出的都是同一个最终值。

而在使用let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例, 所以console.log输出的是我们期盼的值, 也就是循环过程中每个迭代的变量值

不使用 var 使用 const 优先,let 次之

三、typeof

typeof 合适使用在基本类型中

  1. typeof在某些情况下返回的结果可能会让人费解,但技术上讲还是正确的。比如,调用typeof null 返回的是object。这是因为特殊值 null 被认为是一个对空对象的引用。

  2. undefined 类型只有一个值,就是特殊值 undefined。当使用 varlet 声明了变量但没有初始化时,就相当于给变量赋予了 undefined

  3. 增加这个特殊值的目的就是为了正式明确空对象指针(null)和未初始化变量的区别。

  4. 包含 undefined 值的变量跟未定义变量是有区别的。

    请看下面的例子:

    let message; // 这个变量被声明了,只是值为 undefined
    // 确保没有声明过这个变量
    // let age;
    console.log(message); // "undefined"
    console.log(age); // 报错
    
  5. 无论是声明还是未声明,typeof 返回的都是字符串"undefined"。

  6. Null 类型同样只有一个值,即特殊值 null。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回"object"的原因

  7. 布尔值字面量 truefalse 是区分大小写的,因此 TrueFalse(及其他大小混写形式) 是有效的标识符,但不是布尔值。

  8. 由于 JavaScript 保存数值的方式,实际中可能存在正零(+0)和负零(-0)。正零和负零在所有情况下都被认为是等同的

  9. ECMAScript 2015 或 ES6 中的八进制值通过前缀 0o 来表示;严格模式下,前缀 0 会被视为语法错误,如果要表示 八进制值,应该使用前缀 0o

四、类型

  1. 永远不要和某个特定的浮点值做比较

  2. 任何无法表示的负数以-Infinity(负无穷大)表示,任何无法表示的正数以 Infinity(正无穷大)表示。

  3. 要确定一个值是不是有限大(即介于 JavaScript 能表示的最小值和最大值之间),可以使用 isFinite()函数

  4. 有一个特殊的数值叫 NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作 失败了(而不是抛出错误)。

  5. console.log(0 / 0); // NaN
    console.log(-0 / 0); // NaN
    console.log(5 / -0); // -Infinity
    console.log(5 / 0); // Infinity
    
  6. 任何涉及 NaN 的操作始终返回 NaN(如 NaN/10

  7. NaN 不等于包括 NaN 在内的任何值

  8. ECMAScript 提供了 isNaN()函数。该函数接收一个参数,可以是任意数据类型,然后判断 这个参数是否“不是数值”。把一个值传给 isNaN()后,该函数会尝试把它转换为数值。某些非数值的 值可以直接转换成数值,如字符串"10"或布尔值。任何不能转换为数值的值都会导致这个函数返回 true

  9. undefined,返回 NaN

  10. parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即 返回 NaN。这意味着空字符串也会返回 NaN(这一点跟 Number()不一样,它返回 0)。如果第一个字符 是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。比如, "1234blue"会被转换为 1234,因为"blue"会被完全忽略。类似地,"22.5"会被转换为 22,因为小数 点不是有效的整数字符。

  11. 假设字符串中的第一个字符是数值字符,parseInt()函数也能识别不同的整数格式(十进制、八 进制、十六进制)。换句话说,如果字符串以"0x"开头,就会被解释为十六进制整数。如果字符串以"0" 开头,且紧跟着数值字符,在非严格模式下会被某些实现解释为八进制整数。

  12. let num1 = parseInt("1234blue"); // 1234 
    let num2 = parseInt(""); // NaN 
    let num3 = parseInt("0xA"); // 10,解释为十六进制整数 
    let num4 = parseInt(22.5); // 22 
    let num5 = parseInt("70"); // 70,解释为十进制值 
    let num6 = parseInt("0xf"); // 15,解释为十六进制整数
    
  13. let num = parseInt("0xAF", 16); // 175
    // 事实上,如果提供了十六进制参数,那么字符串前面的"0x"可以省掉: 
    let num1 = parseInt("AF", 16); // 175 
    let num2 = parseInt("AF"); // NaN
    // 在这个例子中,第一个转换是正确的,而第二个转换失败了。区别在于第一次传入了进制数作为参 数,告诉 parseInt()要解析的是一个十六进制字符串。而第二个转换检测到第一个字符就是非数值字 符,随即自动停止并返回 NaN。 
    // 通过第二个参数,可以极大扩展转换后获得的结果类型。比如: 
    let num1 = parseInt("10", 2); // 2,按二进制解析
    let num2 = parseInt("10", 8); // 8,按八进制解析
    let num3 = parseInt("10", 10); // 10,按十进制解析
    let num4 = parseInt("10", 16); // 16,按十六进制解析
    
  14. 由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。 这样有助于在代码中保持数据类型的完整性。=== or !==

五、字符串

  1. 字符串可以使用双引号(")、 单引号(')或反引号(`)标示,因此下面的代码都是合法的

  2. 如果字符串中包含双字节字符,那么length 属性返回的值可能不是准确的字符数

  3. nullundefined 值没有 toString()方法

  4. 默认情况下,toString()返回数值的十 进制字符串表示。而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基 数的字符串表示,比如:

    let num = 10;
    console.log(num.toString()); // "10"
    console.log(num.toString(2)); // "1010"
    console.log(num.toString(8)); // "12"
    console.log(num.toString(10)); // "10"
    console.log(num.toString(16)); // "a"
    
  5. 如果你不确定一个值是不是 nullundefined,可以使用 String()转型函数,它始终会返回表 示相应类型值的字符串。String()函数遵循如下规则。

    • 如果值有 toString()方法,则调用该方法(不传参数)并返回结果。

    • 如果值是 null,返回"null"。

    • 如果值是 undefined,返回"undefined"。

  6. 用加号操作符给一个值加上一个空字符串""也可以将其转换为字符串

  7. 由于模板字面量会保持反引号内部的空格,因此在使用时要格外注意

  8. 字符串插值通过在${}中使用一个 JavaScript 表达式实现

  9. 所有插入的值都会使用 toString()强制转型为字符串

  10. 模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数 会接收被插值记号分隔后的模板和对每个表达式求值的结果。

  11. let a = 6;
    let b = 9;
    function simpleTag(strings, aValExpression, bValExpression, sumExpression) {
        console.log(strings);
        console.log(aValExpression);
        console.log(bValExpression);
        console.log(sumExpression);
        return 'foobar';
    }
    let untaggedResult = `${ a } + ${ b } = ${ a + b }`;
    let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`;
    // ["", " + ", " = ", ""]
    // 6
    // 9
    // 15
    console.log(untaggedResult); // "6 + 9 = 15"
    console.log(taggedResult); // "foobar"
    
  12. function simpleTag(strings, ...expressions)

  13. 使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转 换后的字符表示。

六、Symbol

  1. Symbol(符号)是 ECMAScript 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。

  2. 符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

  3. 符号就是用来创建唯一记号

  4. 调用 Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通 过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关

  5. 符号没有字面量语法,这也是它们发挥作用的关键。按照规范,你只要创建 Symbol()实例并将其 用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。

  6. 最重要的是,Symbol()函数不能与 new 关键字一起作为构造函数使用。这样做是为了避免创建符 号包装对象,像使用 Boolean、String 或 Number 那样,它们都支持构造函数且可用于初始化包含原 始值的包装对象

  7. 将一元加应用到非数值,则会执行与使用 Number()转型函数一样的类型转换:布尔值 false 和 true 转换为 0和 1,字符串根据特殊规则进行解析,对象会调用它们的 valueOf()和/或 toString() 方法以得到可以转换的值

七、与和或

  1. 逻辑与: 找false, 找到就返回该变量, 找不到就返回最后一个

  2. 逻辑或: 找 true , 找到就返回该变量, 找不到就返回最后一个变量

  3. let result1 = ("55" == 55); // true,转换后相等
    let result2 = ("55" === 55); // false,不相等,因为数据类型不同
    
  4. let result1 = ("55" != 55); // false,转换后相等
    let result2 = ("55" !== 55); // true,不相等,因为数据类型不同
    

八、逗号操作符

  1. 在赋值时使用逗号操作符分隔值,最终会返回表达式中最后一个值

  2. let num = (5, 1, 4, 8, 0); // num 的值为 0

九、循环

  1. for-of 语句是一种严格的迭代语句,用于遍历可迭代对象的元素

对象好像需要实现Symbol.iterator 才能够使用for-of

  1. 由于 with 语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用 with 语句。

  2. switch 语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类 型(比如,字符串"10"不等于数值 10)。

十、原始对象和引用对象

  1. 原始值(primitive value)就是 最简单的数据,引用值(reference value)则是由多个值构成的对象

  2. 原始值:UndefinedNullBooleanNumberStringSymbol

  3. 引用值是保存在内存中的对象

  4. 在操作对象时,实际上操作的是对该对象的引用(reference)而非 实际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的

  5. 在很多语言中,字符串是使用对象表示的,因此被认为是引用类型。ECMAScript 打破了这个惯例。

  6. typeof 虽然对原始值很有用,但它对引用值的用处不大。我们通常不关心一个值是不是对象, 而是想知道它是什么类型的对象

  7. Symbol: 我们使用的 Symbol只能判断对象是否借助构造函数申请的, 如果不是这样, 则无法判断出准确的类型

Number

let num = 10.005; 
console.log(num.toFixed(2)); // "10.01"

let num = 99; 
console.log(num.toPrecision(1)); // "1e+2"
console.log(num.toPrecision(2)); // "99"
console.log(num.toPrecision(3)); // "99.0"

String

let message = "abcde"; 
console.log(message.charAt(2)); // "c"
let message = "abcde"; // Unicode "Latin small letter C"的编码是 U+0063 console.log(message.charCodeAt(2)); // 99  // 十进制 99 等于十六进制 63 console.log(99 === 0x63); // true
console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"

console.log(String.fromCodePoint(0x1F60A)); // 

4 种规范化形式是:NFD(Normalization Form D) 、NFC(Normalization Form C) 、 NFKD(Normalization Form KD)和 NFKC(Normalization Form KC) 。 可以使用normalize()方法对字 符串应用上述规范化形式, 使用时需要传入表示哪种形式的字符串:"NFD"、"NFC"、"NFKD"或"NFKC"。

let stringValue = "hello world"; 
console.log(stringValue.slice(3)); // "lo world"
console.log(stringValue.substring(3)); // "lo world" 
console.log(stringValue.substr(3)); // "lo world" 
console.log(stringValue.slice(3, 7)); // "lo w" 
console.log(stringValue.substring(3,7)); // "lo w" 
console.log(stringValue.substr(3, 7)); // "lo worl"
let stringValue = "hello world"; 
console.log(stringValue.slice(-3)); // "rld" 
console.log(stringValue.substring(-3)); // "hello world"
console.log(stringValue.substr(-3)); // "rld"
console.log(stringValue.slice(3, -4)); // "lo w"
console.log(stringValue.substring(3, -4)); // "hel"
console.log(stringValue.substr(3, -4)); // "" (empty string)
let message = "abc"; 
let stringIterator = message[Symbol.iterator](); 
 console.log(stringIterator.next()); // {value: "a", done: false}
 console.log(stringIterator.next()); // {value: "b", done: false} 
 console.log(stringIterator.next()); // {value: "c", done: false} 
 console.log(stringIterator.next()); // {value: undefined, done: true}
let stringValue = "hello world"; 
console.log(stringValue.toLocaleUpperCase()); // "HELLO WORLD" 
console.log(stringValue.toUpperCase()); // "HELLO WORLD" 
console.log(stringValue.toLocaleLowerCase()); // "hello world"
console.log(stringValue.toLowerCase()); // "hello world"

这是一个比大小的方法

let stringValue = "yellow"; 
console.log(stringValue.localeCompare("brick")); // 1 
console.log(stringValue.localeCompare("yellow")); // 0 
console.log(stringValue.localeCompare("zoo")); // -1

十一、全局单例

URL

encodeURI()encodeURIComponent()方法用于编码统一资源标识符 (URI) , 以便传给浏览器。 有效的 URI 不能包含某些字符,比如空格

encodeURI()encodeURIComponent()相对的是decodeURI()decodeURIComponent()decodeURI()只对使用encodeURI()编码过的字符解码

let url = "https://juejin.cn/editor/drafts/705365#1 2";
let uri = encodeURI(url);
console.log(uri);
let uriComponent = encodeURIComponent(url);
console.log(uriComponent);
console.log(decodeURI(uri));
console.log(decodeURIComponent(uri));
console.log(decodeURI(uriComponent));
console.log(decodeURIComponent(uriComponent));
https://juejin.cn/editor/drafts/705365#1%202
https%3A%2F%2Fjuejin.cn%2Feditor%2Fdrafts%2F705365%231%202
https://juejin.cn/editor/drafts/705365#1 2
https://juejin.cn/editor/drafts/705365#1 2
https%3A%2F%2Fjuejin.cn%2Feditor%2Fdrafts%2F705365%231 2
https://juejin.cn/editor/drafts/705365#1 2

eval

eval()。这个方法就是一个完 整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。来看 一个例子: eval("console.log('hi')");

像undefined、NaN和Infinity等特殊 值都是Global对象的属性。此外,所有原生引用类型构造函数,比如Object和Function,也都是 Global对象的属性。

使用eval()的时候必须 极为慎重,特别是在解释用户输入的内容时。因为这个方法会对 XSS 利用暴露出很大的 攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。

window对象

window.sayHello()就是访问了个全局的函数,而window.color就会声明一个全局对象

像undefined、NaN和Infinity等特殊 值都是Global对象的属性。此外,所有原生引用类型构造函数,比如Object和Function,也都是 Global对象的属性。

Math 对象

Math.ceil() 方法始终向上舍入为最接近的整数。

Math.floor() 方法始终向下舍入为最接近的整数

Math.round() 方法执行四舍五入。

Math.fround() 方法返回数值最接近的单精度(32 位)浮点值表示。

Math.random() 方法返回一个 0~1 范围内的随机数,其中包含 0 但不包含 1

Math.abs(x) 返回x的绝对值

Math.exp(x) 返回Math.E的x次幂

Math.expm1(x) 等于Math.exp(x) - 1

Math.log(x) 返回x的自然对数

Math.log1p(x) 等于1 + Math.log(x)

Math.pow(x, power) 返回x的power次幂

Math.hypot(...nums) 返回nums中每个数平方和的平方根

Math.clz32(x) 返回 32 位整数x的前置零的数量

Math.sign(x) 返回表示x符号的1、0、-0或-1

Math.trunc(x) 返回x的整数部分,删除所有小数

Math.sqrt(x) 返回x的平方根

Math.cbrt(x) 返回x的立方根

Math.acos(x) 返回x的反余弦

Math.acosh(x) 返回x的反双曲余弦

Math.asin(x) 返回x的反正弦

Math.asinh(x) 返回x的反双曲正弦

Math.atan(x) 返回x的反正切

Math.atanh(x) 返回x的反双曲正切

Math.atan2(y, x) 返回y/x的反正切

Math.cos(x) 返回x的余弦

Math.sin(x) 返回x的正弦

Math.tan(x) 返回x的正切

集合引用类型

Object

表达式上下文指的是期待返回值的上下文

左大括如果出现在语句上下文(statement context)中,比如if语句的条件后面,则表示一个语句块的开始。

let person = { "name": "Nicholas", "age": 29, 5: true };

let person = {}; // 与与 new Object()相同相同
person.name = "Nicholas"; 
person.age = 29;

在使用对象字面量表示法定义对象时,并不会实际调用Object构造函数。

console.log(person["name"]); // "Nicholas" 
console.log(person.name); // "Nicholas"

使用中括号的主要优势就是可以通过变量访问属性

let propertyName = "name"; 
console.log(person[propertyName]); // "Nicholas"
person["first name"] = "Nicholas";

Array

let colors = new Array(3); // 创建一个包含 3 个元素的数组 
let names = new Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组
let colors = ["red", "blue", "green"]; // 创建一个包含 3 个元素的数组
let names = []; // 创建一个空数组
let values = [1,2,]; // 创建一个包含 2 个元素的数组

数组字面量方式创建,不会调用new的方式 与对象一样,在使用数组字面量表示法创建数组不会调用Array构造函数

Array构造函数还有两个 ES6 新增的用于创建数组的静态方法:from()和of()

// 字符串会被拆分为单字符数组 console.log(Array.from("Matt")); // ["M", "a", "t", "t"]

console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]

const options = [,,,,,]; // 创建包含 5 个元素的数组
console.log(options.length); // 5
console.log(options); // [,,,,,]
const a = Array.from([,,,]); // 使用 ES6 的 Array.from()创建的包含 3 个空位的数组
for (const val of a) {
   alert(val === undefined); // true // true // true
}

由于行为不一致和存在性能隐患,因此实践中要避免使用数组空位。如果确实需要 空位,则可以显式地用undefined值代替。

数组最多可以包含 4 294 967 295 个元素

value instanceof Array

Array.isArray(value)

数组提供了push()pop()方法

这种方式是后入先出

shift()push()

这种方式是先入先出

slice()会返回该索引 到数组末尾的所有元素

如果slice()的参数有负值, 那么就以数值长度加上这个负值的结果确定位置。 比 如,在包含 5 个元素的数组上调用slice(-2,-1),就相当于调用slice(3,4)。如果结 束位置小于开始位置,则返回空数组

splice()的主要目的是 在数组中间插入元素,但有 3 种不同的方式使用这个方法。

reduce()方法从数组第一项开始遍历到最后一项。 而reduceRight()从最后一项开始遍历至第一项。

把前一个值和后一个值罗列出来给我们计算出一个,记得是一个值

ArrayBuffer

ArrayBuffer()是一个普通的 JavaScript 构造函数,可用于在内存中分配特定数量的字节空间。

Map

// 使用嵌套数组初始化映射 const m1 = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]);

// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]); 
alert(m3.has(undefined)); // true 
alert(m3.get(undefined)); // undefined

选择Map还是Object?

对的, 你没看错, JavaScript可以使用Object 代替 Map, 而且某些时候 Object 比 Map 的效率更高

增删改查

map 的增删改查

// 创建
// let map = new Map();
let map = new Map([["key", "value"]]);
// 增加
map.set("1", "1");
// 删除
map.delete("1");
// 修改
map.set("key", "value1");
// 查询
console.log(map.get("key"));

object的增删改查

// 创建
const person = {
   name: "zhazha"
}
// 添加
person.age = 23;
person.address = "fj";
// 删除
delete person.address;
// 查询
console.log(person.age);
// 修改
person.age = 22;
console.log(person);
  • Map 大约可以比 Object 多存储 50%的键/值对
  • 如果代码涉及大量插入操作,那么显然 Map 的性能更佳。
  • 如果代码涉及大量查找操作,那么某些情况下可能选择 Object 更好一些
  • 在把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局
  • Map 的 delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map

WeakMap

弱引用的key必须是对象,如果对象引用消失,则相应的key/value(entry)都会消失

弱映射中的键只能是Object 或者继承自Object 的类型,尝试使用非对象设置键会抛出 TypeError。值的类型没有限制。

  • 这样设计的原因

WeakMap实例之所以限制只能用对象作为键, 是为了保证只有通过键对象的引用才能取得值。 如果 允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。

只要键存在,键/值 对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。

const wm = new WeakMap(); 
wm.set({}, "val"); 

set()方法初始化了一个新对象并将它用作一个字符串的键

如果调用了removeReference(),就会摧毁键对象的最后一个引用,垃圾回收程序就可以 把这个键/值对清理掉。

因为WeakMap中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力

迭代器

自定义迭代器

[Symbol.iterator]() {
let count = 1, limit = this.limit; 
   return {
      next() {
         if (count <= limit) {
            return { done: false, value: count++ }; 
         } else {
            return { done: true, value: undefined }; 
         }
      }
   };
}

迭代器实现方案,实现了迭代器就可以直接forof了,只要有arr[Symbol.iterator]就能拿到迭代器对象

提前结束迭代器的方式

  • for-of循环通过break、continue、return或throw提前退出
  • 解构操作并未消费所有值
[Symbol.iterator]() {
    let count = 1, limit = this.limit;
    return {
        next() {
            if (count <= limit) {
                return {done: false, value: count++};
            } else {
                return {done: true};
            }
        }, return() {
            console.log('Exiting early');
            return {done: true};
        }
    };
}

书本上说数组迭代器无法关闭, 但是实际上可以, 这是翻译的问题, 实际上是迭代器的状态没有被关闭掉

书本原文: 如果迭代器没有关闭,则还可以继续从上次离开的地方继续迭代。比如,数组的迭代器就是不能关闭

let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
for (let i of iter) {
    console.log(i);
    if (i > 2) {
        console.log("退出了?");
        break
    }
}

但是实际上数组迭代器最终实测他是结束了

1
2
3
退出了?

但是状态没被关闭掉, 看下面

let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
for (let i of iter) {
    console.log(i);
    if (i > 2) {
        break
    }
}
// 1
// 2
// 3

// 继续从上次离开的地方继续迭代 <<<<<<<< 看这个
for (let i of iter) {
    console.log(i);
}
// 4
// 5

他会继续使用上次 break 掉的迭代器

有些迭代器即使你手动给它加上了 return 函数

iter.return = function() {
   console.log("exit")
   return {done: true}
}

比如前面的数组迭代器, 你给他加上上面这段代码, 他还是会存在上面测试的问题

return()方法是可选的

class Counter03 {
   constructor(limit) {
      this.limit = limit
   }
   
   [Symbol.iterator]() {
      let counter = 0, limit = this.limit;
      return {
         next() {
            if (counter <= limit) {
               return {done: false, value: counter++};
            } else {
               return {done: true}
            }
         },
         return() {
            console.log("exit iterator")
            return {done: true}
         }
      }
   }
}

let counter03 = new Counter03(10);
for (let item of counter03) {
   if (item > 3) {
      break // exit iterator
   }
   console.log(item)
}

最终会打印 return 中的exit iterator