# ESPRO **Repository Path**: an-katg/espro ## Basic Information - **Project Name**: ESPRO - **Description**: es6学习笔记 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-09-13 - **Last Updated**: 2023-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ES6 ## 一、语法基础 ### 1.1、变量声明 **var 类型** var类型变量为函数级作用域,存在以下问题 - 变量覆盖 ```js var v1 = 1 if(true) { var v1 = 2 } console.log(v1) // 结果为 2 ``` - 循环变量泄露(在非立即执行函数(声明但未立即调用,如在 setTimeout 中或下面的情况)) ```js var funcs = []; for (var i = 0; i < 3; i++) { funcs[i] = function() { console.log(i) //此处定义而没有执行 } } console.log(nums1[0]()) //执行函数 //结果为 3 而非预期的 0 ``` **let 变量** let 变量作用域为块级,类似Java 中的变量作用域,相比于 var 变量有以下特点 - 不存在变量提升 ```js console.log(var1) var var1 = 1; // 结果:undefine console.log(let1) let let1 = 1; // 结果: 报错 ``` - 同一块内不可重复声明 ```js let let1 = 1; let let1 = 2; var let1 = 3; // 检查时异常 ``` - 不自动增加到 Global 对象上 ``` 使用var 在函数之外声明变量,会自动挂载到Global 对象上,造成全局变量污染,而 let 和 let 和 const 不会,他们存在于 TDZ 区域中 ``` **const 变量** - 作用域与 let 一致 - 常量类型,一旦赋值不能改变变量值,且必须在声明时赋初始值。 ```js const num1 = 10 num1 = 10 //报错 ``` - 如果是复杂数据类型,可以改变对象的值,注意区别对象这种复杂数据类型 ```js var nums1 = [1,2] const nums2 = nums1 //nums2 = [] // 报错,不能重新赋值 nums1 = [1,1,1] // nums2 的结果仍为 [1,2] var obj = { haha: "11", hehe: "22" } const ooo = obj obj.haha = "haha" //ooo 的结果为 { haha: 'haha', hehe: '22' } ``` ### 1.2、模板字面量 ​ 模板字面量就是使用 反引号 `` 包裹字符串 **模板字面量的功能** - 保留原格式(保留换行等) ```js let str = `你 好` ``` - 支持插值表达式 ```js let str1 = 'world' let str2 = `hello ${str1} ` ``` - 嵌入式表达式 ```js let str1 = 'world' let str2 = `hello ${str1+'!'} ` ``` **标记模板字面量** 规则: - 定义函数时,两个参数,一个数组,另一个不定变量参数 - 调用函数时,参数在 ``里面而不是 () fun_name - 嵌入表达式的参数被放在不定变量参数里面,是一个数组,其他参数被放在另一个参数数组里面 - 第一个数组长度比第二个长一个,变量的分隔是以 ${} 来定的。 ```js function myFun(arr1,...arr2){ console.log(arr1); //[ '亻尔', '王', '' ] 不够长用 '' 补足 console.log(arr2); //[ '元', '神' ] } var v1 = "亻" var v2 = '尔' var v3 = '王' var v4 = '元' var v5 = '神' myFun `亻尔${v4}王${v5}`; ``` ### 1.3、对象字面量 **属性和方法的扩展语法** - 对于同名的变量和属性,可以简写 ```js let uname = 'ha' let age = 20 let user={ uname, age, getInfo(){ return `Helleo, my name is ${uname}, i'm ${age} years old .` } } console.log(user.uname, user.age, user.getInfo()) ``` **属性名表达式** - ​ 使用变量值作为属性名,支持动态生成对象属性 ```js let username = 'heiha' let user1={ [username]:'hi', age, getInfo(){ return `my name is `+ this.heiha } } console.log(user1.getInfo()) //输出:my name is hi ``` ### 1.4、For-of 循环 **for-in、forEach、for-of 的缺陷** ```js let nums=[1,2,3] //1. for in 循环的下标是字符串变量 for( var i in nums){ console.log(nums[i]+" : "+typeof(i)) } console.log('----------------------') //2. for each 里面不能使用 continue、break、return nums.forEach((e)=>{ if(e===1){ break //无效 } console.log(e+" : "+typeof(e)) }) console.log('----------------------') //3. for of 不能改变原数组的值,相当于把数组的值赋值给了n,该变n 的值不能改变原数组的值;支持迭代器 for(let n of nums){ n = n+10; console.log(n+" : "+typeof(n)) } ``` **for-of 的优势** - 最简洁直接的数组遍历语法 - 避开for-in 的所有缺陷 - 正确响应break、continue、return - 支持数组、字符串、集合、类数组对象、生成器 - 只要对象有迭代器方法,就可以使用for-of 循环 ### 1.5、解构 ​ 按一定的模式从数组或者对象中提取值,并对变量进行复制的操作称为解构 **数组解构** ​ 数组解构允许您将数组的元素分配给变量,元素的位置决定了它们将被分配给哪个变量。 ```JS ""// 声明一个数组 const numbers = [1, 2, 3, 4, 5]; // 使用数组解构 const [first, second, ...rest] = numbers; console.log(first); // 输出 1 console.log(second); // 输出 2 console.log(rest); // 输出 [3, 4, 5] ``` **对象解构** ​ 对象解构允许您从对象中提取属性值并将它 们分配给相应的变量,属性的名称用于匹配 变量名。 ```JS ""// 声明一个对象 const person = { firstName: 'John', lastName: 'Doe', age: 30, }; // 使用对象解构 const { firstName, lastName, age } = person; console.log(firstName); // 输出 'John' console.log(lastName); // 输出 'Doe' console.log(age); // 输出 30 ``` **嵌套解构** ​ 嵌套解构使您能够从复杂的数据结构中提取所需的数据,通过适当嵌套解构,可以访问深层次的属性或元素。 - 数组嵌套访问 ```js const nestedArray = [1, [2, 3], 4]; const [first, [second, third], fourth] = nestedArray; console.log(first); // 输出 1 console.log(second); // 输出 2 console.log(third); // 输出 3 ``` - 对象嵌套访问 ```js const person = { name: 'Alice', address: { city: 'New York', zip: '10001', }, }; const { name, address: { city, zip } } = person; console.log(name); // 输出 'Alice' console.log(city); // 输出 'New York' console.log(zip); // 输出 '10001' ``` ### 1.6、函数参数 **函数参数可以是常量、函数返回值、默认值、变量** ```js function test(num1,num2=0){ console.log(num1+num2) } test(1) function returnNum(){ return 2 } test(2,returnNum()) ``` **传变量注意,使用前必须定义了,不能再参数列表赋值之后定义,不能再函数体和函数后定义** ```js // 错误示范:num3未定义使用 function fun3(num1,num2=num3,num3=0){ } ``` **不定参数 不定后面不能跟参数,不定参数不计入参数个数** ```js function fun4(num1,...nums){ } fun4(1,2,3) ``` **结构数组或对象** ```js function fun5([num1,num2]){ console.log(num1,num2) } fun5([1,2]) ``` ### 1.7、箭头函数 ​ 箭头函数在语法上更简洁,在多层回调中使用 this 更加方便 **箭头函数的使用,几种常见的形式** ```js let fn0=function(){} let fn1=()=>{} //只有一个参数并且函数体只有 return 语句 let fn2=para=>para*2 //返回值是一个对象,fn3、fn4两者等价 let fn3=()=>({name:"name1"}) let fn4=()=>{return {name:"name1"}} ``` - 关于箭头函数中的 this,this 向外指到最近的含有 this 对象的 this , 省去了逐级将 this 带入的麻烦。下面是一些示例代码: ```js //2、箭头函数的 this let user = { uname:"yuanyuan", //2.1 普通函数 getINfo: function(){ console.log(this) console.log("this uname :: "+this.uname) console.log("普通函数------------------") }, //2.2 箭头函数 getName:()=>{ console.log(this) console.log(this.user.uname) console.log("箭头函数------------------") }, //2.3 普通函数里面的箭头函数 getAll:function(){ return ()=>{ console.log(this) console.log(this.uname) console.log("普通函数里面的箭头函数------------------") } } } module.exports.user=user console.log("user 的 this 箭头-------》"+this.user.uname) user.getINfo() user.getName() user.getAll()() let fn5 = function(){ console.log("fn5 全局普通函数 this=============------------------") console.log(this) } let fn6 = ()=>{ console.log("fn6 全局箭头函数 this=============------------------") this.abcd=100 console.log(this.abcd) console.log(module.exports.abcd) } fn5() fn6() ``` - 关于 js 中的 this 指向: ``` //1、全局函数 // this 指向 global , 在全局函数中定义变量相当于给 global 对象加了一个属性 //2、对象内函数 // this 指向 对象本身,对象内的箭头函数指向 Global 对象 //3、构造函数 // this 指向 实例本身 //4、箭头函数 // this 向外指到最近的含有 this 对象的 this , 省去了逐级将 this 带入的麻烦 ``` ### 1.8、Symbol 对象 **语法特点** - 每个 Symbol 变量都是唯一的 - 对于 Symbol 对象可以使用 typeof 查看类型为 Symbol, 但是使用 instanceof Symbol 得到的结果为 false **使用 Symbol** ```js // 创建一个 Symbol 变量 const uniqueKey = Symbol('unique'); // 使用 Symbol 作为对象属性的键 const obj = {}; obj[uniqueKey] = 'Some value'; // 访问属性 console.log(obj[uniqueKey]); // 输出 "Some value" ``` **内置 Symbol 对象** 在 JavaScript 中,有一些内置的 Symbol 值,它们用于标识对象的内部属性和行为,如 Symbol.iterator、Symbol.toStringTag 等。 - Symbol.iterator:用于指定对象的默认迭代器,可以通过定义一个 Symbol.iterator 方法来使对象可迭代。 ```js const myArray = [1, 2, 3]; const iterator = myArray[Symbol.iterator](); console.log(iterator.next()); // 输出 { value: 1, done: false } ``` - Symbol.toStringTag:用于自定义对象的字符串描述。 ```js class MyObject { [Symbol.toStringTag] = 'My Custom Object'; } const obj = new MyObject(); console.log(obj.toString()); // 输出 "[object My Custom Object]" ``` - 其他内置 Symbol 值用于自定义对象的行为,例如 Symbol.hasInstance 用于自定义 instanceof 运算符的行为。 ## 二、内置对象增强 ### 2.1、Object 对象 **Object.is(v1, v2)**: - `Object.is()` 方法用于比较两个值是否相同,与 `===` 运算符不同的是,它可以正确处理特殊值,例如 `NaN` 和 `-0`。 - 如果 `v1` 和 `v2` 值相同,返回 `true`,否则返回 `false`。 ```js Object.is(5, 5); // true Object.is(0, -0); // false Object.is(NaN, NaN); // true ``` **Object.getOwnPropertySymbols(obj)**: - `Object.getOwnPropertySymbols()` 方法用于获取对象 `obj` 中的所有 Symbol 属性的键。 - 这对于访问对象的 Symbol 属性非常有用,因为 Symbol 属性不会出现在 `for...in` 循环或 `Object.keys()` 中。 ```js const obj = { [Symbol('x')]: 1, [Symbol('y')]: 2, }; const symbols = Object.getOwnPropertySymbols(obj); console.log(symbols); // 输出 [Symbol(x), Symbol(y)] ``` **Object.setPrototypeOf(obj, proto)**: - `Object.setPrototypeOf()` 方法用于设置对象 `obj` 的原型(`__proto__`)为 `proto` 对象。 - 这种方法通常不推荐使用,因为它会改变对象的原型链,可能会导致不可预测的行为。推荐使用 `Object.create()` 或类继承来创建对象。 ```js ""const obj = {}; const proto = { x: 10 }; Object.setPrototypeOf(obj, proto); console.log(obj.x); // 输出 10 ``` **Object.assign(target, ...sources)**: - `Object.assign()` 方法用于将一个或多个源对象的属性复制到目标对象 `target`。 - 它是一种浅拷贝,如果属性具有相同的键,后面的源对象会覆盖前面的源对象。 ```js const target = { a: 1, b: 2 }; const source1 = { b: 3, c: 4 }; const source2 = { d: 5 }; Object.assign(target, source1, source2); console.log(target); // 输出 { a: 1, b: 3, c: 4, d: 5 ``` ### 2.2、Number 对象和 Math 对象 #### **Number** **Number.isFinite(value)**: - `Number.isFinite()` 方法用于检查传入的值是否是一个有限数字。 - 返回一个布尔值,如果是有限数字返回 `true`,否则返回 `false`。 **Number.isNaN(value)**: - `Number.isNaN()` 方法用于检查传入的值是否是 NaN(非数字)。 - 返回一个布尔值,如果是 NaN 返回 `true`,否则返回 `false`。 **Number.isInteger(value)**: - `Number.isInteger()` 方法用于检查传入的值是否是整数。 - 返回一个布尔值,如果是整数返回 `true`,否则返回 `false`。 **Number.isSafeInteger(value)**: - `Number.isSafeInteger()` 方法用于检查传入的值是否是一个"安全整数"。 - 安全整数是在 JavaScript 中可以精确表示的整数范围内的整数。 - 返回一个布尔值,如果是安全整数返回 `true`,否则返回 `false`。 **Number.EPSILON**: - `Number.EPSILON` 属性表示 JavaScript 中可表示的最小间隔,通常用于比较浮点数的精确性。 #### **Math** **Math.trunc(x)**: - `Math.trunc()` 方法用于将传入的数值去除小数部分,返回整数部分。 - 这对于处理数字取整非常有用,而不是四舍五入。 **Math.sign(x)**: - `Math.sign()` 方法用于获取传入数值的符号,返回 1、-1、0 或 NaN。 - 返回 1 表示正数,-1 表示负数,0 表示零,NaN 表示非数值。 **Math.cbrt(x)**: - `Math.cbrt()` 方法用于计算传入数值的立方根。 **Math.log10(x)**: - `Math.log10()` 方法用于计算以 10 为底的对数。 **Math.log2(x)**: - `Math.log2()` 方法用于计算以 2 为底的对数。 **Math.hypot(...values)**: - `Math.hypot()` 方法用于计算传入值的平方和的平方根,通常用于计算直角三角形的斜边长度。 ### 2.3、String 和 RegExp #### String **`startsWith(str; pos=0)`**: - `startsWith()` 方法用于检查一个字符串是否以指定的子字符串 `str` 开头。 - `pos` 参数是可选的,用于指定搜索的起始位置,默认值为 0。 - 返回一个布尔值,如果字符串以给定的子字符串开头,返回 `true`,否则返回 `false`。 ```js const text = 'Hello, world!'; console.log(text.startsWith('Hello')); // true console.log(text.startsWith('world', 7)); // true (从索引 7 开始检查) ``` **`endsWith(str; endPos=str.length)`**: - `endsWith()` 方法用于检查一个字符串是否以指定的子字符串 `str` 结尾。 - `endPos` 参数是可选的,用于指定搜索的结束位置,默认值为字符串的长度。 - 返回一个布尔值,如果字符串以给定的子字符串结尾,返回 `true`,否则返回 `false`。 ```js const text = 'Hello, world!'; console.log(text.endsWith('world!')); // true console.log(text.endsWith('Hello', 5)); // true (在索引 5 结束检查) ``` **`includes(str; pos=0)`**: - `includes()` 方法用于检查一个字符串是否包含指定的子字符串 `str`。 - `pos` 参数是可选的,用于指定搜索的起始位置,默认值为 0。 - 返回一个布尔值,如果字符串包含给定的子字符串,返回 `true`,否则返回 `false`。 ```js const text = 'Hello, world!'; console.log(text.includes('world')); // true console.log(text.includes('world', 7)); // true (从索引 7 开始检查) ``` **repeat(count)**: - `repeat()` 方法用于复制一个字符串 `count` 次,并返回新的字符串。 - `count` 参数是一个整数,指定复制的次数。 - 如果 `count` 小于 0 或为非整数,会抛出一个范围错误(RangeError)。 ```js const text = 'Hello'; console.log(text.repeat(3)); // 'HelloHelloHello' ``` **`String.raw`**: - `String.raw` 是一个字符串的属性,用于获取模板字符串的原始字符串,不会进行转义。 - 通常与模板字符串一起使用,以保留原始字符串中的反斜杠转义序列。 ```js const str = String.raw`First line\nSecond line`; console.log(str); // 'First line\\nSecond line' ``` #### RegExp **`flags` 属性**: - `flags` 是 RegExp 对象的一个只读属性,用于返回正则表达式的修饰符标志字符串。 - 正则表达式的修饰符标志指定了正则匹配的方式,如全局匹配(g)、不区分大小写匹配(i)、多行匹配(m)等。 - `flags` 返回的是一个字符串,包含了正则表达式的所有修饰符。 示例: ```js const regex = /abc/gim; console.log(regex.flags); // 输出 "gim" ``` 这允许您在运行时访问正则表达式的修饰符,以便更轻松地了解正则表达式的行为。 **构造函数可以得到 RegExp 对象的拷贝**: - 在 ES6 中,可以使用正则表达式的构造函数 `RegExp()` 创建一个新的 RegExp 对象,并且它会产生原 RegExp 对象的拷贝。 - 这可以用来克隆正则表达式对象,以便在运行时修改或操作而不影响原始正则表达式。 示例: ```js const originalRegex = /abc/g; const clonedRegex = new RegExp(originalRegex); console.log(originalRegex.source); // 输出 "abc" console.log(clonedRegex.source); // 输出 "abc" // 修改 clonedRegex 不会影响 originalRegex clonedRegex.lastIndex = 2; console.log(originalRegex.lastIndex); // 输出 0 console.log(clonedRegex.lastIndex); // 输出 2 ``` ### 2.4、Array #### 静态方法 **Array.from(arrayLike[, mapFn[, thisArg]])**: - `Array.from()` 静态方法用于从类似数组的对象或可迭代对象创建一个新数组。 - 可以提供一个可选的 `mapFn` 函数,用于映射原始元素到新数组。 - 可以提供一个可选的 `thisArg` 参数,用于指定映射函数中的 `this` 上下文。 ```js const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; const newArray = Array.from(arrayLike); console.log(newArray); // 输出 ["a", "b", "c"] ``` **Array.of(...elements)**: - `Array.of()` 静态方法用于创建一个包含指定元素的新数组,无论元素的数量如何。 - 它消除了数组构造函数的不一致性,因为传递单个数字参数给 `Array()` 构造函数会创建具有指定长度的数组,而不是包含该数字的数组。 ```js const newArray = Array.of(1, 2, 3, 4, 5); console.log(newArray); // 输出 [1, 2, 3, 4, 5] ``` #### 实例方法 **array.find(callback[, thisArg])**: - `find()` 方法用于查找数组中满足条件的第一个元素。 - `callback` 函数在每个数组元素上调用,直到找到一个返回 `true` 的元素。 - 可以提供可选的 `thisArg` 参数,用于指定回调函数中的 `this` 上下文。 **array.findIndex(callback[, thisArg])**: - `findIndex()` 方法用于查找数组中满足条件的第一个元素的索引。 - `callback` 函数在每个数组元素上调用,直到找到一个返回 `true` 的元素。 - 可以提供可选的 `thisArg` 参数,用于指定回调函数中的 `this` 上下文。 ```js const numbers = [10, 20, 30, 40, 50]; const foundNumber = numbers.find(number => number > 25); const foundIndex = numbers.findIndex(number => number > 25); console.log(foundNumber); // 输出 30 console.log(foundIndex); // 输出 2 ``` **array.includes(searchElement[, fromIndex])**: - `includes()` 方法用于检查数组是否包含指定的元素。 - 返回一个布尔值,表示是否包含该元素。 - 可以提供可选的 `fromIndex` 参数,用于指定搜索的起始位置。 ```js const fruits = ['apple', 'banana', 'cherry']; const includesBanana = fruits.includes('banana'); const includesGrapes = fruits.includes('grapes'); console.log(includesBanana); // 输出 true console.log(includesGrapes); // 输出 false ``` **array.flat([depth])**: - `flat()` 方法用于将嵌套数组扁平化为一个新数组。 - 可以提供可选的 `depth` 参数,用于指定扁平化的深度。 ```js const nestedArray = [1, [2, [3, 4], 5]]; const flatArray = nestedArray.flat(); console.log(flatArray); // 输出 [1, 2, [3, 4], 5] ``` **array.flatMap(callback[, thisArg])**: - `flatMap()` 方法结合了 `map()` 和 `flat()`,首先映射每个元素,然后将结果扁平化为一个新数组。 - `callback` 函数在每个数组元素上调用,然后返回的结果会被扁平化。 ```js const numbers = [1, 2, 3, 4, 5]; const doubledAndSquared = numbers.flatMap(number => [number * 2, number ** 2]); console.log(doubledAndSquared); // 输出 [2, 1, 4, 4, 6, 9, 8, 16, 10, 25] ``` ## 三、处理二进制数据 ### 3.1、二进制数组 **概述** - 二进制数组是JavaScript操作二进制数据的一个接口 - 允许开发者以数组形式,直接操作内存可以极大的提升性能 - 建立在ArrayBuffer对象的基础上 - 来源于WebGL技术 **数据创建和转换** ```js /** 创建*/ // 创建一个包含 4 字节的 ArrayBuffer const buffer = new ArrayBuffer(4); // 使用不同字节长度的 TypedArray 视图 const int8View = new Int8Array(buffer); // 1 字节 // 向视图中写入数据 int8View[0] = 127; // 输出视图中的数据 console.log(int8View[0]); // 127 /** 转换*/ // 创建一个包含 4 字节的 ArrayBuffer const buffer = new ArrayBuffer(4); // 使用 Int32Array 视图写入数据 const int32View = new Int32Array(buffer); int32View[0] = 2147483647; // 将 Int32Array 视图转换为 Int8Array 视图 const int8View = new Int8Array(buffer); // 输出 Int8Array 视图中的数据 console.log(int8View[0]); // -1 console.log(int8View[1]); // -1 console.log(int8View[2]); // -1 console.log(int8View[3]); // 127 ``` ### 3.2、TypedArray视图 **TypedArray 视图的特点** - **固定的数据类型**:每个 TypedArray 视图具有固定的数据类型,因此无法在同一个视图中混合不同的数据类型。 - **无需手动计算偏移量**:TypedArray 视图自动处理字节偏移,因此您无需手动计算偏移量。 - **数据是连续存储的**:TypedArray 视图中的数据是连续存储的,因此它们非常适合处理连续的二进制数据,例如文件读取和网络通信。 **创建 TypedArray 视图** ​ 使用 TypedArray 视图时,首先需要创建一个 ArrayBuffer 对象,然后将该对象传递给 TypedArray 视图的构造函数。下面是创建 TypedArray 视图的示例: ```js // 创建一个包含 4 个字节的 ArrayBuffer const buffer = new ArrayBuffer(4); // 使用 Int32Array 视图来操作 ArrayBuffer const int32View = new Int32Array(buffer); ``` **读写数据** ​ 一旦创建了 TypedArray 视图,您可以使用索引访问和修改其中的数据。不同类型的 TypedArray 视图允许您以其特定的数据类型来读取和写入数据。 ```js const buffer = new ArrayBuffer(4); const int32View = new Int32Array(buffer); // 写入数据 int32View[0] = 42; // 读取数据 const value = int32View[0]; console.log(value); // 输出 42 ``` ### 3.3、DataView视图 **DataView视图特点** - **指定字节偏移**:您可以指定字节偏移来访问和操作不同部分的二进制数据。 - **支持大端和小端序**:DataView 视图支持大端序(Big Endian)和小端序(Little Endian)的处理方式,因此它适用于多种二进制数据格式。 - **无需考虑数据类型**:与 TypedArray 视图不同,DataView 视图不关心数据类型,因此它非常灵活。 **创建 DataView 视图** 要创建一个 DataView 视图,您需要有一个 ArrayBuffer 对象,然后将该对象传递给 DataView 构造函数。您还可以指定字节偏移和数据类型。以下是创建 DataView 视图的示例: ```js // 创建一个包含 8 个字节的 ArrayBuffer const buffer = new ArrayBuffer(8); // 使用 DataView 视图来操作 ArrayBuffer const dataView = new DataView(buffer); ``` **指定字节偏移和数据类型** DataView 视图允许您在读取和写入数据时指定字节偏移和数据类型。您可以使用不同的方法来读写不同类型的数据,例如 `getInt8()`, `getUint8()`, `getInt16()`, `getUint16()`, `getInt32()`, `getUint32()`, `getFloat32()`, `getFloat64()` 等。以下是一个示例: ```js const buffer = new ArrayBuffer(8); const dataView = new DataView(buffer); // 写入数据到 DataView 视图,指定字节偏移 dataView.setInt32(0, 42); dataView.setFloat64(4, Math.PI); // 读取数据,指定字节偏移 const int32Value = dataView.getInt32(0); const float64Value = dataView.getFloat64(4); console.log(int32Value); // 输出 42 console.log(float64Value); // 输出 3.141592653589793 ``` ## 四、集合 ### 4.1、Set **Set 的概念** - Set 是一种集合数据结构,用于存储唯一的值,它不允许包含重复的元素。 - Set 不是按照插入顺序来维护元素的顺序,它是无序的,但可以通过遍历操作来访问其中的元素。 - Set 提供了一种简单的方式来存储和管理不同类型的数据,如数字、字符串、对象等。 **Set 的构造函数** ​ 要创建一个 Set 集合,可以使用 Set 构造函数,如下所示: ```js const mySet = new Set(); ``` **Set 的属性及方法** - **`size` 属性**:Set 集合的 size 属性用于返回集合中元素的数量。 ```js const mySet = new Set(); mySet.add(1); mySet.add(2); console.log(mySet.size); // 输出 2 ``` - **`add(value)` 方法**:add 方法用于向 Set 集合中添加一个值,如果值已存在,不会重复添加。 ```js const mySet = new Set(); mySet.add(1); mySet.add(2); mySet.add(1); // 不会重复添加 console.log(mySet.size); // 输出 2 ``` - **`delete(value)` 方法**:delete 方法用于从 Set 集合中删除一个值。 ```js const mySet = new Set(); mySet.add(1); mySet.add(2); mySet.delete(1); console.log(mySet.size); // 输出 1 ``` - **`has(value)` 方法**:has 方法用于检查 Set 集合中是否包含特定值,如果包含则返回 true,否则返回 false。 ```js const mySet = new Set(); mySet.add(1); console.log(mySet.has(1)); // 输出 true console.log(mySet.has(2)); // 输出 false ``` - **`clear()` 方法**:clear 方法用于清空 Set 集合中的所有元素。 ```js const mySet = new Set(); mySet.add(1); mySet.add(2); mySet.clear(); console.log(mySet.size); // 输出 0 ``` **Set 中判断重复的依据** ​ Set 集合中判断重复的依据是值的唯一性。如果一个值已经存在于 Set 中,再次添加相同的值不会生效,因为 Set 只会包含唯一的值。 **Set 转换为数组** ​ 您可以使用扩展运算符(`...`)或 `Array.from()` 方法将 Set 集合转换为数组: ```js const mySet = new Set(); mySet.add(1); mySet.add(2); const myArray = [...mySet]; // 使用扩展运算符 // 或 const myArray2 = Array.from(mySet); // 使用 Array.from() console.log(myArray); // 输出 [1, 2] ``` ### 4.2、Map **Map 的概念**: - Map 是一种集合数据结构,用于存储键值对,其中键是唯一的,而值可以重复。 - Map 与对象相似,但不同之处在于 Map 的键可以是任何数据类型,包括原始数据类型、对象、函数等,而对象只能使用字符串或符号作为键。 - Map 保持键值对的插入顺序,与插入的顺序一致。 **Map 的构造函数**: 要创建一个 Map 集合,可以使用 Map 构造函数,如下所示: ```js const myMap = new Map(); ``` **Map 的属性和方法**: - **`size` 属性**:Map 集合的 size 属性用于返回集合中键值对的数量。 ```js const myMap = new Map(); myMap.set('name', 'Alice'); myMap.set('age', 30); console.log(myMap.size); // 输出 2 ``` - **`set(key, value)` 方法**:set 方法用于向 Map 集合中添加键值对,如果键已存在,则更新值。 ```js const myMap = new Map(); myMap.set('name', 'Alice'); myMap.set('age', 30); myMap.set('name', 'Bob'); // 更新值 console.log(myMap.get('name')); // 输出 "Bob" ``` - **`get(key)` 方法**:get 方法用于根据键获取对应的值。 ```js const myMap = new Map(); myMap.set('name', 'Alice'); console.log(myMap.get('name')); // 输出 "Alice" ``` - **`delete(key)` 方法**:delete 方法用于从 Map 集合中删除指定键值对。 ```js const myMap = new Map(); myMap.set('name', 'Alice'); myMap.delete('name'); console.log(myMap.get('name')); // 输出 undefined ``` - **`has(key)` 方法**:has 方法用于检查 Map 集合中是否包含特定键,如果包含则返回 true,否则返回 false。 ```js const myMap = new Map(); myMap.set('name', 'Alice'); console.log(myMap.has('name')); // 输出 true console.log(myMap.has('age')); // 输出 false ``` - **`clear()` 方法**:clear 方法用于清空 Map 集合中的所有键值对。 ```js const myMap = new Map(); myMap.set('name', 'Alice'); myMap.set('age', 30); myMap.clear(); console.log(myMap.size); // 输出 0 ``` **Map 中判断键重复的依据**: Map 集合中判断键重复的依据是键的唯一性。如果尝试使用相同的键来添加新的键值对,那么它将覆盖先前的键值对,保持唯一性。 Map 集合提供了一种方便的方式来存储和管理键值对数据,特别适用于需要键和值之间的映射关系的场景。与对象不同,Map 允许使用不同数据类型作为键,提供更大的灵活性。 ### 4.3、weakset 和 weakMap **WeakSet 的特点**: 1. **弱引用**:WeakSet 中的元素是弱引用的,这意味着它们不会阻止元素被垃圾回收,即使它们仍然存在于 WeakSet 中。 2. **无法迭代**:WeakSet 不具备 Set 中的迭代方法,因为其中的元素没有强引用。这意味着您无法遍历 WeakSet 中的元素。 3. **只能包含对象**:WeakSet 只能包含对象,不能包含原始数据类型(如数字、字符串)。 4. **主要用途**:WeakSet 的主要用途是在需要跟踪对象引用而不阻止垃圾回收的情况下,比如处理对象之间的循环引用。 ```js const weakSet = new WeakSet(); const obj1 = { name: 'Alice' }; const obj2 = { name: 'Bob' }; weakSet.add(obj1); weakSet.add(obj2); console.log(weakSet.has(obj1)); // 输出 true // obj1 不再被引用 obj1 = null; // obj1 从 WeakSet 中自动移除 console.log(weakSet.has(obj1)); // 输出 false ``` **WeakMap 的特点和使用**: 1. **弱引用**:与 WeakSet 类似,WeakMap 中的键是弱引用的,这意味着它们不会阻止键对象被垃圾回收。 2. **键可以是任何对象**:WeakMap 的键可以是任何对象(包括原始数据类型、函数等),而值可以是任何数据类型。 3. **无法迭代**:WeakMap 也不具备 Map 中的迭代方法,因为其中的键没有强引用。 4. **主要用途**:WeakMap 的主要用途是将附加数据与对象关联起来,同时不阻止这些对象被垃圾回收。它在某些场景下非常有用,如存储对象的私有数据。 ```js const weakMap = new WeakMap(); const key1 = { id: 1 }; const key2 = { id: 2 }; const value1 = 'Value for key 1'; const value2 = 'Value for key 2'; weakMap.set(key1, value1); weakMap.set(key2, value2); console.log(weakMap.get(key1)); // 输出 'Value for key 1' // key1 不再被引用 key1 = null; // key1 从 WeakMap 中自动移除 console.log(weakMap.get(key1)); // 输出 undefined ``` WeakSet 和 WeakMap 在需要管理对象引用、防止内存泄漏和处理临时关联数据时非常有用。它们的主要优势在于不会阻止被引用对象被垃圾回收,因此适用于特定的使用场景。 ## 五、类 ### 5.1、类的声明和实例化 **类的概念**: - 类是一种用于创建对象的模板或蓝图,它定义了对象的结构和行为。 - 类是一种面向对象编程(OOP)的概念,它允许将属性和方法封装在一个单独的实体中,以创建对象的实例。 **类的声明**: ES6 中声明一个类使用关键字 `class`,类名通常使用大写字母开头,如下所示: ``` class Person { constructor(name, age) { this.name = name; this.age = age; } sayHello() { console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`); } } ``` 在上述示例中,我们声明了一个名为 `Person` 的类,它具有一个构造函数和一个 `sayHello` 方法。 **类的组成部分**: 1. **构造函数(Constructor)**:构造函数是类的一个特殊方法,用于创建和初始化类的实例。它通常包含对象的属性初始化逻辑。 2. **方法(Methods)**:方法是类中的函数,用于定义对象的行为。方法在类的原型上定义,因此它们在所有类的实例之间共享。 3. **属性** **类的构造函数与实例化**: - 构造函数是类的一个特殊方法,它用于创建和初始化类的实例。在构造函数中,您可以定义对象的属性和初始化逻辑。 - 要创建类的实例,可以使用 `new` 关键字,如下所示: ```js const person1 = new Person('Alice', 30); const person2 = new Person('Bob', 25); person1.sayHello(); // 输出 "Hello, my name is Alice and I am 30 years old." person2.sayHello(); // 输出 "Hello, my name is Bob and I am 25 years old." ``` ### 5.2、类的属性和方法 #### 类的属性 在 ES6 类中,属性可以分为实例属性和静态属性。 **实例属性**是每个类实例独有的属性,它们在类的构造函数中通过`this`关键字定义。实例属性必须在实例化对象后才能访问。 **静态属性**是类本身的属性,它们不依赖于类的实例。静态属性是通过`static`关键字定义的,可以直接通过类本身访问。 以下是一个更详细的示例: ```js class Person { constructor(name, age) { this.name = name; // 实例属性 this.age = age; // 实例属性 } sayHello() { console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`); } // 静态属性,通过类本身访问 static species = 'Homo sapiens'; } const person1 = new Person('Alice', 30); console.log(person1.name); // 访问实例属性 console.log(person1.age); // 访问实例属性 person1.sayHello(); // 调用实例方法 console.log(Person.species); // 访问静态属性 ``` #### 类的方法 **实例方法**: - 实例方法是在类的原型上定义的,每个类的实例都可以调用这些方法。 - 实例方法可以访问实例的属性,因为它们可以通过 `this` 关键字访问实例的上下文。 **静态方法**: - 静态方法是直接与类相关联的方法,而不是与类的实例相关联。 - 静态方法使用 `static` 关键字定义,可以通过类本身来调用,而不能通过实例调用。 以下是一个类方法的示例: ```js class Person { constructor(name, age) { this.name = name; this.age = age; } // 实例方法 sayHello() { console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`); } // 静态方法 static getSpecies() { return 'Homo sapiens'; } } const person1 = new Person('Alice', 30); person1.sayHello(); // 调用实例方法 console.log(Person.getSpecies()); // 调用静态方法 ``` ### 5.3、类的继承 **继承的概念**: - 继承是面向对象编程的一个基本原则,它允许一个类(子类或派生类)继承另一个类(父类或基类)的属性和方法。 - 子类继承父类的特性,这意味着子类可以重用父类的代码,同时可以添加自己的特有属性和方法。 - 继承有助于提高代码的重用性和可维护性,减少冗余代码。 **继承的使用**: 在 ES6 中,可以使用 `extends` 关键字来创建类的继承关系。子类继承父类的属性和方法,并可以自定义自己的属性和方法。以下是一个继承的示例: ```js class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a sound.`); } } class Dog extends Animal { // 子类构造函数 constructor(name, breed) { super(name); // 调用父类构造函数 this.breed = breed; } bark() { console.log(`${this.name} barks.`); } } const myDog = new Dog('Buddy', 'Golden Retriever'); myDog.speak(); // 调用父类方法 myDog.bark(); // 调用子类方法 ``` 在上述示例中,`Dog` 类继承了 `Animal` 类,`super` 关键字用于调用父类的构造函数。子类可以调用父类的方法,也可以定义自己的方法。 **方法重写**: 方法重写是指子类可以重新定义父类中已存在的方法,以适应自己的需求。如果在子类中定义了与父类相同名称的方法,子类的方法将覆盖父类的方法。以下是方法重写的示例: ```js class Animal { speak() { console.log('Animal makes a sound.'); } } class Dog extends Animal { speak() { console.log('Dog barks.'); } } const myDog = new Dog(); myDog.speak(); // 调用子类方法,输出 "Dog barks." ``` 在这个示例中,`Dog` 类重写了 `Animal` 类的 `speak` 方法,所以在创建 `myDog` 实例并调用 `speak` 方法时,会执行子类的方法。 **super 的使用**: `super` 关键字用于在子类中调用父类的构造函数和方法。它允许子类访问父类的属性和方法,同时可以扩展或修改它们。在构造函数中,`super` 用于调用父类的构造函数,以便初始化父类的属性。在方法中,`super` 用于调用父类的方法。以下是示例: ```js class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a sound.`); } } class Dog extends Animal { constructor(name, breed) { super(name); // 调用父类构造函数 this.breed = breed; } speak() { super.speak(); // 调用父类方法 console.log(`${this.name} barks.`); } } const myDog = new Dog('Buddy', 'Golden Retriever'); myDog.speak(); // 调用子类方法,输出父类方法和子类方法的结果 ``` 在这个示例中,`super` 用于在子类构造函数中调用父类构造函数,并在子类方法中调用父类方法。 类继承、方法重写和`super` 关键字是面向对象编程中非常重要的概念,它们允许您构建复杂的类层次结构并扩展类的功能。通过合理使用继承,您可以更好地组织和重用代码,同时实现更高的可维护性。 ## 六、模块 ### 6.1、模块的基本使用 **模块概述**: - 模块是一种代码组织和封装的方法,它将相关的功能和数据封装在一个独立的单元中。 - 模块提供了一种机制,用于控制命名空间,避免全局命名冲突,并允许将代码拆分成可管理的部分。 - 模块化编程有助于提高代码的可维护性,降低复杂性,促进代码的重用。 **传统的模块实现及流行的模块规范**: 传统的 JavaScript 中,并没有内置的模块系统,因此开发者通常使用全局对象或立即执行函数来模拟模块化。这导致了命名空间污染和其他问题。为了解决这些问题,一些流行的模块规范出现了,包括: 1. **CommonJS**:CommonJS 规范是用于服务器端 JavaScript 的模块规范,它定义了模块的导入和导出方式。Node.js 是一个使用 CommonJS 规范的平台。 2. **AMD (Asynchronous Module Definition)**:AMD 规范用于浏览器中的异步模块加载,允许在页面加载过程中异步加载模块。 3. **UMD (Universal Module Definition)**:UMD 是一种通用的模块规范,旨在支持多种模块环境,包括浏览器和 Node.js。 **ES6 中模块导出与导入的基本使用**: ES6 引入了一种原生的模块系统,它提供了模块的导入和导出机制,使用 `import` 和 `export` 关键字。 - **导出(export)**:要在模块中导出变量、函数、类或对象,可以使用 `export` 关键字。例如,可以在一个名为 `myModule.js` 的模块中导出一个函数: ```js // myModule.js export function greet(name) { return `Hello, ${name}!`; } ``` - **导入(import)**:要在另一个模块中使用导出的内容,可以使用 `import` 关键字。例如,可以在另一个模块中导入上述的 `greet` 函数: ```js // anotherModule.js import { greet } from './myModule'; console.log(greet('Alice')); // 输出 "Hello, Alice!" ``` **在浏览器中使用 ES6 中的模块**: 在浏览器中使用 ES6 模块时,您需要在 HTML 文件中使用 ` ``` 在上述示例中,`main.js` 是一个使用 ES6 模块的 JavaScript 文件。浏览器会自动解析和加载模块。 需要注意的是,浏览器支持 ES6 模块的程度可能因浏览器版本而异,但现代浏览器一般都支持它。 ES6 模块提供了一种标准化的模块化解决方案,有助于更好地组织和管理代码。 ### 6.2、模块导出详解 **导出单个变量或函数**: 您可以使用 `export` 关键字将单个变量或函数导出。导出的变量或函数可以在其他模块中使用。 ```js // module.js export const name = 'Alice'; export function greet(name) { return `Hello, ${name}!`; } ``` 在其他模块中导入和使用: ```js // anotherModule.js import { name, greet } from './module.js'; console.log(name); // 输出 'Alice' console.log(greet(name)); // 输出 'Hello, Alice!' ``` **导出多个变量或函数**: 您可以使用 `export` 关键字一次导出多个变量或函数,它们被封装在对象中。 ```js // module.js const name = 'Alice'; function greet(name) { return `Hello, ${name}!`; } export { name, greet }; ``` 在其他模块中导入和使用: ```js // anotherModule.js import { name, greet } from './module.js'; console.log(name); // 输出 'Alice' console.log(greet(name)); // 输出 'Hello, Alice!' ``` **导出类**: 您可以使用 `export` 关键字导出类,以便在其他模块中实例化和使用它。 ```js // module.js export class Person { constructor(name) { this.name = name; } greet() { return `Hello, my name is ${this.name}.`; } } ``` 在其他模块中导入和使用: ```js // anotherModule.js import { Person } from './module.js'; const person = new Person('Alice'); console.log(person.greet()); // 输出 'Hello, my name is Alice.' ``` **默认导出**: 除了命名导出,您还可以使用默认导出。一个模块只能有一个默认导出,它是模块的主要导出。默认导出不需要使用大括号。 ```js // module.js const name = 'Alice'; export default name; ``` 在其他模块中导入和使用默认导出: ```js // anotherModule.js import name from './module.js'; console.log(name); // 输出 'Alice' ``` 注意,命名导出可以与默认导出一起使用。 ### 6.3、模块导入详解 **命名导入**: 命名导入允许您从一个模块中引入特定的变量、函数、类或对象。您需要使用大括号 `{}` 包围要导入的标识符。 ```js // anotherModule.js import { name, greet } from './module.js'; ``` 在这个示例中,`name` 和 `greet` 是从 `module.js` 模块中导入的标识符。 **导入整个模块**: 您也可以导入整个模块,而不是导入特定的标识符。在这种情况下,您可以使用 `* as` 关键字来指定一个别名,以便在本地使用模块的所有导出。 ```js // anotherModule.js import * as myModule from './module.js'; console.log(myModule.name); console.log(myModule.greet('Alice')); ``` 在这个示例中,`myModule` 是一个别名,它包含了整个 `module.js` 模块的导出。 **默认导入**: 默认导入允许您导入模块的默认导出,一个模块只能有一个默认导出。不需要使用大括号,而是将默认导出赋值给一个本地变量。 ```js // anotherModule.js import name from './module.js'; ``` 在这个示例中,`name` 变量是从 `module.js` 模块的默认导出导入的。 **导入和重命名**: 您可以使用 `as` 关键字来重命名导入的标识符,以避免与现有的标识符冲突。 ```js // anotherModule.js import { name as myName, greet as sayHello } from './module.js'; ``` 在这个示例中,`name` 和 `greet` 被重命名为 `myName` 和 `sayHello`。 **条件导入**: 有时,您可能希望根据条件导入不同的模块。您可以在导入语句中使用条件表达式。 ```js // anotherModule.js if (someCondition) { import { foo } from 'moduleA'; } else { import { bar } from 'moduleB'; } ``` 在这个示例中,`foo` 或 `bar` 将根据 `someCondition` 的值而被导入。 **动态导入**: ES6 模块还支持动态导入,允许在运行时根据需要加载模块。 ```js // anotherModule.js const moduleName = 'moduleA'; import(moduleName).then((module) => { console.log(module.default); // 访问默认导出 }); ``` 动态导入使用 `import()` 函数,可以根据需要异步加载模块。 ### 6.4、模块依赖 **导入依赖**: 依赖关系的一种主要方式是使用导入语句,这在 ES6 模块中是标准的方法。通过导入语句,一个模块可以明确引用另一个模块的导出内容。例如: ```js // moduleA.js export const variableA = 'Value from module A'; // moduleB.js import { variableA } from './moduleA.js'; console.log(variableA); // 输出 'Value from module A' ``` 在这个示例中,`moduleB` 依赖于 `moduleA`,因为它导入了 `moduleA` 中的 `variableA`。 **模块的顺序和异步加载**: 模块的依赖关系通常由导入语句的顺序定义。例如,如果 `moduleB` 先导入 `moduleA`,则 `moduleA` 中的内容将在 `moduleB` 中可用。这意味着模块的加载顺序非常重要,您需要确保所有依赖模块在它们被引用之前已经加载。 另外,模块也支持异步加载,您可以使用 `import()` 函数来在运行时异步加载模块,这对于按需加载模块非常有用。 ```js // 在需要的时候异步加载模块 import('./moduleA.js').then((module) => { console.log(module.variableA); // 输出 'Value from module A' }); ``` **解决循环依赖**: 循环依赖是指两个或多个模块相互依赖,这可能会导致问题。要解决循环依赖,一种方法是重新组织模块以消除循环依赖。另一种方法是确保依赖于其他模块的代码位于导入语句之后,这可以避免循环依赖。 **默认导出**: 模块还可以使用默认导出,一个模块只能有一个默认导出。默认导出允许一个模块作为整体被导入,而不需要使用大括号。 ```js // moduleA.js export default 'Default export from module A'; // moduleB.js import variableA from './moduleA.js'; console.log(variableA); // 输出 'Default export from module A' ``` **导出全部内容**: 模块还可以使用 `export *` 语法导出另一个模块的全部内容。这将导出目标模块的所有导出内容。 ```js // moduleA.js export const variableA = 'Value from module A'; export const variableB = 'Another value from module A'; // moduleB.js export * from './moduleA.js'; ``` 在这个示例中,`moduleB` 导出了 `moduleA` 中的所有变量。 ## 七、迭代器与生成器 ### 7.1、迭代器 **迭代器概述**: 迭代器是一种设计模式,它提供了一种用于按顺序访问集合中的元素的通用方式。在 JavaScript 中,迭代器通常用于遍历数组、对象、Map、Set、字符串等数据结构,以访问它们的每个元素。 迭代器是一种抽象的概念,它提供了两个基本方法:`next()` 和 `return()`,以及一个属性 `done` 和一个属性 `value`。 **迭代器的组成**: 1. **next() 方法**:`next()` 方法用于在迭代过程中获取下一个元素。它返回一个包含两个属性的对象:`value` 表示当前迭代的值,`done` 表示是否已经遍历完了容器中的所有元素。 2. **return() 方法**:`return()` 方法用于提前终止迭代。它返回一个包含两个属性的对象:`value` 通常是 undefined,`done` 表示迭代是否已经结束。 3. **done 属性**:`done` 是一个布尔值,用于表示迭代是否已经完成。如果已经遍历完所有元素,`done` 为 true;否则,`done` 为 false。 4. **value 属性**:`value` 是迭代器当前指向的元素的值。 **迭代器的实现**: 在 JavaScript 中,迭代器通常通过对象的 `Symbol.iterator` 属性和一个 `next()` 方法来实现。下面是一个简单的示例,演示了如何手动实现一个迭代器: ```js const myArray = [1, 2, 3, 4, 5]; const myIterator = { array: myArray, index: 0, next: function () { if (this.index < this.array.length) { return { value: this.array[this.index++], done: false }; } else { return { done: true }; } } }; for (const item of myIterator) { console.log(item); // 依次输出 1, 2, 3, 4, 5 } ``` 在这个示例中,`myIterator` 对象实现了 `next()` 方法,它可以按顺序返回数组中的元素。使用 `for...of` 循环来迭代 `myIterator` 对象。 ### 7.2、生成器 **生成器概述**: 生成器是一种特殊类型的函数,它与普通函数不同,可以在函数体内部多次暂停和恢复执行。生成器使用 `function*` 声明,包括 `yield` 关键字,这个关键字用于暂停函数的执行并返回一个值。 生成器的主要优点在于它提供了一种简单的方式来处理异步编程任务,例如迭代异步操作或处理大型数据集。 **生成器的声明**: 生成器函数的声明使用 `function*` 语法,如下所示: ```js function* myGenerator() { yield 1; yield 2; yield 3; } ``` 生成器函数内部使用 `yield` 关键字定义生成器的迭代步骤。在每次调用 `yield` 时,生成器会返回一个对象,其中包括 `value` 属性,表示产生的值,以及 `done` 属性,表示生成器是否已经完成。 **生成器的调用**: 要创建一个生成器对象,您只需要调用生成器函数,而不执行它,如下所示: ```js const gen = myGenerator(); ``` 这将创建一个生成器对象,但不会执行函数内的代码。您可以使用 `next()` 方法来控制生成器的执行,如下所示: ```js const result1 = gen.next(); // 第一次调用,返回 { value: 1, done: false } const result2 = gen.next(); // 第二次调用,返回 { value: 2, done: false } const result3 = gen.next(); // 第三次调用,返回 { value: 3, done: false } const result4 = gen.next(); // 第四次调用,返回 { done: true } ``` **作为迭代器使用**: 生成器可以用作迭代器,因为它们遵循迭代器协议,包括 `next()` 方法和 `value`、`done` 属性。这使得生成器非常适合用于遍历数据集或执行异步操作。 以下是一个使用生成器作为迭代器的示例: ```js function* generateNumbers() { let number = 1; while (number <= 5) { yield number; number++; } } const numbers = generateNumbers(); for (const num of numbers) { console.log(num); // 依次输出 1, 2, 3, 4, 5 } ``` 在这个示例中,`generateNumbers` 生成器函数用于生成数字序列,而 `for...of` 循环用于迭代生成器并输出每个生成的数字。 生成器是 JavaScript 中非常强大和灵活的功能,它们使异步编程更容易,并可以用于处理各种迭代需求。通过使用 `yield` 关键字,生成器允许您将函数的执行状态保存下来,以后可以在需要时恢复执行。 **生成器接收参数**: 生成器函数可以接收参数,这些参数可以在生成器的执行过程中使用。参数是通过生成器函数的参数列表传递的。在生成器内部,您可以访问这些参数,就像访问普通函数的参数一样。 以下是一个示例,演示了如何在生成器中接收参数: ```js function* greetGenerator(name) { yield `Hello, ${name}!`; } const generator = greetGenerator("Alice"); const result = generator.next(); // 返回 { value: 'Hello, Alice!', done: false } ``` 在这个示例中,`greetGenerator` 生成器函数接收一个 `name` 参数,然后在生成器内部使用它。 **`return` 方法**: 生成器对象上有一个特殊的方法,称为 `return(value)`,它用于提前终止生成器的执行并返回一个指定的值。这个值将被包装在生成器对象的 `done` 属性为 `true` 的对象中。 以下是一个示例,演示了如何使用 `return` 方法: ```js function* numberGenerator() { yield 1; yield 2; yield 3; } const generator = numberGenerator(); const result1 = generator.next(); // 返回 { value: 1, done: false } const result2 = generator.return("Stopped"); // 返回 { value: 'Stopped', done: true } ``` 在这个示例中,我们使用 `return` 方法提前终止了生成器,并返回了指定的值。 **`throw` 方法**: 生成器对象上还有一个特殊的方法,称为 `throw(error)`,它用于在生成器内部抛出异常。当在生成器内部抛出异常时,生成器会进入异常状态,然后您可以使用 `try...catch` 块来捕获异常。 以下是一个示例,演示了如何使用 `throw` 方法: ```js function* errorGenerator() { try { yield 1; yield 2; } catch (error) { yield `Error: ${error.message}`; } } const generator = errorGenerator(); const result1 = generator.next(); // 返回 { value: 1, done: false } try { generator.throw(new Error("Something went wrong")); } catch (error) { console.log(error); // 输出 'Error: Something went wrong' } ``` 在这个示例中,我们使用 `throw` 方法在生成器内部抛出异常,然后在 `catch` 块中捕获异常。 这些特性使生成器更加强大,允许您在生成器的执行中接收参数、提前终止执行并返回值,以及在生成器内部抛出异常并进行处理。 ## 八、Promise ### 8.1、Promise 基础 **JavaScript 的回调地狱**: 回调地狱是一种异步编程中的常见问题,它出现在多个嵌套的回调函数中,导致代码难以理解和维护。这种情况经常发生在处理多个异步操作时,如网络请求或文件读取。回调地狱使代码变得难以阅读、难以调试,容易出错。 ```js asyncFunction1(function (result1) { asyncFunction2(result1, function (result2) { asyncFunction3(result2, function (result3) { // ... }); }); }); ``` **Promise 的概念**: Promise 是一种用于处理异步操作的对象,它提供了更清晰的异步代码结构。Promise 可以处于三种状态之一:待定(pending)、已解决(resolved,也称为成功)、已拒绝(rejected,也称为失败)。Promise 对象可以被异步操作解决为一个值或被拒绝为一个错误。 **Promise 的状态**: Promise 有以下三种状态: 1. **待定(pending)**:初始状态,既不是成功状态也不是失败状态。 2. **已解决(resolved)**:意味着操作成功完成。 3. **已拒绝(rejected)**:意味着操作失败。 一旦 Promise 的状态从待定变为已解决或已拒绝,就不会再改变。 **Promise 的创建及基本使用**: 您可以使用 `Promise` 构造函数来创建一个 Promise 对象。该构造函数接受一个函数参数,该函数包含两个参数,`resolve` 和 `reject`,它们是用来解决或拒绝 Promise 的函数。基本的 Promise 使用示例如下: ```js const myPromise = new Promise((resolve, reject) => { // 异步操作 if (operationSucceeded) { resolve("Operation succeeded"); } else { reject("Operation failed"); } }); myPromise .then((result) => { console.log(result); // 输出 "Operation succeeded" }) .catch((error) => { console.error(error); // 输出 "Operation failed" }); ``` 在这个示例中,`myPromise` 是一个 Promise 对象,它在构造函数中执行了一个模拟的异步操作,然后根据操作成功或失败使用 `resolve` 或 `reject` 来解决或拒绝 Promise。使用 `then` 方法处理成功状态,使用 `catch` 方法处理失败状态。 Promise 的优势在于它使异步代码更易于理解和维护,避免了回调地狱的问题。通过使用 Promise,您可以更清晰地处理异步操作的成功和失败,而不需要多层嵌套的回调函数。 ### 8.2、Promise 的方法 **then(onFulfilled, onRejected)** - `then` 方法是 Promise 对象上的主要方法,用于处理 Promise 的成功和失败状态。它接受两个参数,`onFulfilled` 和 `onRejected`,分别是成功状态和失败状态的回调函数。 - `onFulfilled` 函数在 Promise 被成功解决时调用,接收成功的结果作为参数。 - `onRejected` 函数在 Promise 被拒绝时调用,接收拒绝的原因作为参数。 - `then` 方法返回一个新的 Promise 对象,允许链式调用多个 `then` 方法。 ```js const myPromise = new Promise((resolve, reject) => { // 异步操作 resolve("Operation succeeded"); }); myPromise .then((result) => { console.log(result); // 输出 "Operation succeeded" }) .catch((error) => { console.error(error); }); ``` **catch(onRejected)** - `catch` 方法是 `then` 方法的一个简化版本,用于处理 Promise 的失败状态。它接受一个 `onRejected` 回调函数,该函数在 Promise 被拒绝时调用,接收拒绝的原因作为参数。 - `catch` 方法返回一个新的 Promise 对象,允许处理失败状态。 ```js const myPromise = new Promise((resolve, reject) => { // 异步操作 reject("Operation failed"); }); myPromise.catch((error) => { console.error(error); // 输出 "Operation failed" }); ``` **resolve(value)** - `resolve` 方法是 Promise 构造函数的静态方法,用于创建一个已解决的 Promise 对象。 - 它接受一个 `value` 参数,表示 Promise 的成功结果。 - 这个方法通常用于将普通的值转化为一个 Promise 对象。 ```js const resolvedPromise = Promise.resolve("Resolved value"); resolvedPromise.then((result) => { console.log(result); // 输出 "Resolved value" }); ``` **reject(reason)** - `reject` 方法是 Promise 构造函数的静态方法,用于创建一个已拒绝的 Promise 对象。 - 它接受一个 `reason` 参数,表示 Promise 的拒绝原因。 - 这个方法通常用于将一个错误或异常转化为一个拒绝的 Promise 对象。 ```js const rejectedPromise = Promise.reject("Rejection reason"); rejectedPromise.catch((error) => { console.error(error); // 输出 "Rejection reason" }); ``` **all(iterable)** - `all` 方法是 Promise 构造函数的静态方法,用于处理多个 Promise 对象,等待它们全部完成。 - 它接受一个可迭代的对象(通常是数组),包含多个 Promise 对象。 - `all` 返回一个新的 Promise 对象,当所有输入的 Promise 对象都成功完成时,这个新的 Promise 才会成功。如果任何一个 Promise 被拒绝,那么新的 Promise 也会被拒绝。 ```js const promise1 = Promise.resolve(1); const promise2 = Promise.resolve(2); const promise3 = Promise.resolve(3); Promise.all([promise1, promise2, promise3]) .then((values) => { console.log(values); // 输出 [1, 2, 3] }) .catch((error) => { console.error(error); }); ``` race(iterable) - `race` 方法是 Promise 构造函数的静态方法,用于处理多个 Promise 对象,等待任何一个完成。 - 它接受一个可迭代的对象,包含多个 Promise 对象。 - `race` 返回一个新的 Promise 对象,只要有一个输入的 Promise 对象成功完成,新的 Promise 就会成功。如果第一个成功的 Promise 被拒绝,新的 Promise 也会被拒绝。 ```js const promise1 = new Promise((resolve) => setTimeout(resolve, 1000, 'one')); const promise2 = new Promise((resolve) => setTimeout(resolve, 2000, 'two')); Promise.race([promise1, promise2]) .then((value) => { console.log(value); // 输出 'one' }) .catch((error) => { console.error(error); }); ``` ### 8.3、Promise 链 **Promise 链的概念**: - Promise 链是一种将多个异步操作按顺序连接在一起的模式,以确保它们以有序的方式执行。 - 每个异步操作都返回一个 Promise,该 Promise 代表操作的成功或失败状态。 - 使用 `.then()` 方法将一系列操作链接在一起,以依次执行它们。 - 当前一个 Promise 完成(成功或失败)后,`.then()` 方法中的回调函数将触发执行下一个 Promise 操作。 **Promise 链中的数据传递**: - 在 Promise 链中,您可以在每个 `.then()` 方法中访问前一个操作的结果,并将其传递到下一个操作中。 - `.then()` 方法的回调函数可以返回一个新的 Promise 对象,以实现数据传递。 - 当前一个操作成功时,将前一个操作的结果传递给下一个操作的方式如下: ```js promise1.then((result) => { return result * 2; // 传递 result 的加工值 }); ``` - 在链中,前一个操作的结果将成为后一个操作的输入,依此类推。这样的链式数据传递非常有用,因为它允许您在多个异步操作之间传递和转换数据。 - Promise 链中的多次数据传递 ```js promise1.then((result) => { return result * 2; // 传递 result 的加工值 }).then((doubledResult) => { return doubledResult + 10; // 继续处理数据 }).then((finalResult) => { // finalResult 是前一个操作返回的结果 // 在此进行最终处理 }); ``` **Promise 链中的异常处理**: - 在 Promise 链中,异常处理是通过 `.catch()` 方法或 `.then()` 方法的第二个参数来处理的。 - 如果前一个操作失败(Promise 被拒绝),`.catch()` 方法将捕获该异常,并您可以在其中执行错误处理。 ```js promise1.then((result) => { return asyncOperation(result); // 异步操作 }).catch((error) => { console.error(error); // 处理错误 }); ``` - 或者,您可以在 `.then()` 方法的第二个参数中处理异常: ```js promise1.then((result) => { return asyncOperation(result); // 异步操作 }, (error) => { console.error(error); // 处理错误 }); ``` - 在链中,异常会向下传递,直到遇到一个 `.catch()` 方法或最后一个 `.then()` 方法,其中异常会被捕获。 ```js promise1.then((result) => { return asyncOperation(result); // 异步操作 }).then((result) => { return anotherAsyncOperation(result); // 另一个异步操作 }).catch((error) => { console.error(error); // 处理错误 }); ``` ### 8.4、Promise 的应用 **在 Ajax 中应用 Promise**: Promise 在处理 Ajax 请求中非常有用,因为它们可以改善异步代码的可读性和可维护性。通常,Ajax 请求是异步的,而 Promise 允许您更清晰地处理这些请求。以下是使用 Promise 处理 Ajax 请求的示例: ```js function makeAjaxRequest(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onload = () => { if (xhr.status === 200) { resolve(xhr.response); // 请求成功,将响应传递给成功处理函数 } else { reject(new Error('Request failed')); // 请求失败,将错误传递给失败处理函数 } }; xhr.onerror = () => { reject(new Error('Network error')); // 网络错误,将错误传递给失败处理函数 }; xhr.send(); }); } // 使用 makeAjaxRequest 函数 makeAjaxRequest('https://api.example.com/data') .then((response) => { console.log('Data received:', response); }) .catch((error) => { console.error('Error:', error); }); ``` 在这个示例中,`makeAjaxRequest` 函数返回一个 Promise,它封装了一个 Ajax 请求。如果请求成功,Promise 被解决,并将响应数据传递给 `.then()` 方法的成功处理函数。如果请求失败或出现网络错误,Promise 被拒绝,并将错误传递给 `.catch()` 方法的失败处理函数。 **与生成器配合使用**: Promise 可以与生成器(Generator)一起使用,以实现更复杂的异步控制流。这种组合允许您编写异步代码,就像同步代码一样顺序执行,而不需要回调函数或嵌套。您可以使用 `yield` 将异步操作包装成一个 Promise,并使用生成器函数来控制异步操作的顺序。 以下是一个示例,演示如何在生成器函数中使用 Promise 控制异步操作的顺序: ```js function fetchData(url) { return fetch(url) .then((response) => response.json()); } function* dataGenerator() { try { const data1 = yield fetchData('https://api.example.com/data1'); console.log('Data 1:', data1); const data2 = yield fetchData('https://api.example.com/data2'); console.log('Data 2:', data2); } catch (error) { console.error('Error:', error); } } function runGenerator(generator) { const iterator = generator(); function iterate(iteration) { if (iteration.done) { return Promise.resolve(iteration.value); } return Promise.resolve(iteration.value) .then((x) => iterate(iterator.next(x))) .catch((error) => iterator.throw(error)); } return iterate(iterator.next()); } runGenerator(dataGenerator); ``` 在这个示例中,`fetchData` 函数返回一个 Promise,用于获取数据。`dataGenerator` 生成器函数使用 `yield` 关键字暂停执行,等待每个异步操作完成。`runGenerator` 函数用于运行生成器函数,依次执行每个步骤,并处理成功和失败状态。 ## 九、元数据编程 ### 9.1、反射 **元数据编程概述**: 元数据编程是一种编程范式,它关注的是程序中数据和结构的信息,而不仅仅是程序的运行时行为。元数据是描述数据和程序的数据,允许程序在运行时对自身进行检查和修改。元数据编程通常用于动态配置、自动化和反射等编程任务。 在 JavaScript 中,元数据通常包括对象、函数、类、属性和方法的描述信息,以及它们的注释、类型信息、名称和结构。通过访问和操作这些信息,可以实现高度动态的编程任务。 **反射概述**: 反射是元数据编程的核心概念,它允许程序在运行时检查、分析和修改代码的结构和行为。在反射中,程序可以动态地获取有关对象、函数、类、属性和方法的信息,并可以根据这些信息执行操作。 在 JavaScript 中,反射可以用于以下任务: - 检查对象的属性和方法。 - 动态创建、修改和删除属性。 - 枚举对象的属性和方法。 - 动态调用方法。 - 检查对象的原型链。 - 创建实例并设置原型。 **Reflect 对象的方法**: Reflect 是一个 JavaScript 内置对象,它提供了许多用于反射和元数据编程的方法。以下是一些常用的 Reflect 方法: **Reflect.get(target, property, [receiver])**: - 用于获取目标对象(`target`)的属性(`property`)的值。 - `receiver` 参数是可选的,如果提供,它将作为 `this` 值绑定到 getter 方法。 **Reflect.set(target, property, value, [receiver])**: - 用于设置目标对象的属性的值。 - `value` 是要设置的值。 - `receiver` 参数是可选的,如果提供,它将作为 `this` 值绑定到 setter 方法。 ```js // 获取和设置对象属性 const person = { name: "Alice", age: 30 }; const propertyName = "age"; const propertyValue = Reflect.get(person, propertyName); console.log(`Person's ${propertyName}: ${propertyValue}`); const newAge = 32; Reflect.set(person, propertyName, newAge); console.log(`Updated age: ${person.age}`); ``` **Reflect.has(target, property)**: - 用于检查目标对象是否具有指定属性,类似于 `property in object` 运算符。 ```js // 检查对象属性是否存在 const car = { make: "Toyota", model: "Camry" }; const propertyToCheck = "color"; if (Reflect.has(car, propertyToCheck)) { console.log(`Car has a ${propertyToCheck} property.`); } else { console.log(`Car does not have a ${propertyToCheck} property.`); } ``` **Reflect.deleteProperty(target, property)**: - 用于删除目标对象的属性,类似于 `delete object[property]`。 **Reflect.ownKeys(target)**: - 返回一个包含目标对象自身属性(不包括继承属性)的数组。 ```js //枚举对象自身属性 const animal = { type: "Dog", name: "Buddy", sound: "Woof" }; const ownProperties = Reflect.ownKeys(animal); console.log("Own properties:", ownProperties); ``` **Reflect.getPrototypeOf(target)**: - 返回目标对象的原型对象。 **Reflect.setPrototypeOf(target, prototype)**: - 用于设置目标对象的原型对象。 **Reflect.apply(func, thisArg, args)**: - 用于调用目标函数(`func`)。 - `thisArg` 是函数的上下文,`args` 是函数的参数。 ```js //调用函数 function greet(name) { return `Hello, ${name}!`; } const args = ["Alice"]; const result = Reflect.apply(greet, null, args); console.log(result); ``` **Reflect.construct(constructor, args, [newTarget])**: - 用于创建一个新的对象,类似于使用 `new` 关键字。 - `args` 是构造函数的参数。 - `newTarget` 参数是可选的,它用于指定新对象的构造函数。 ```js //创建对象实例 class Rectangle { constructor(width, height) { this.width = width; this.height = height; } getArea() { return this.width * this.height; } } const args = [5, 10]; const rectangle = Reflect.construct(Rectangle, args); console.log("Rectangle area:", Reflect.get(rectangle, "getArea")()); ``` ### 9.2、代理 **代理的概念**: 代理是一种对象,它允许您在访问其他对象之前添加自定义的行为。代理对象包装了另一个对象,并允许您截获并定义一系列操作,以控制和干预对被代理对象的访问。这允许您实现各种用途,例如拦截和验证属性访问、添加日志记录、实现数据绑定等。 代理的核心思想是将对象的访问和操作抽象成陷阱(Traps),这些陷阱是拦截器函数,可以截获和处理操作。Proxy 对象是元编程的工具,它为开发人员提供了更多的控制权,以定义对象的行为。 **Proxy 对象的使用**: Proxy 对象的基本语法如下: ```js const proxy = new Proxy(target, handler); ``` - `target` 是要代理的目标对象,即需要添加自定义行为的对象。 - `handler` 是一个包含陷阱方法的对象,每个陷阱对应一个操作,例如 `get` 用于属性访问、`set` 用于属性赋值、`apply` 用于函数调用等。 以下是一些 Proxy 对象的用例: **拦截属性访问和赋值**: ```js const target = { name: "Alice" }; const handler = { get: function (target, property) { console.log(`Accessing property: ${property}`); return target[property]; }, set: function (target, property, value) { console.log(`Setting property: ${property} to ${value}`); target[property] = value; }, }; const proxy = new Proxy(target, handler); console.log(proxy.name); // 通过代理访问属性 proxy.age = 30; // 通过代理设置属性 ``` **拦截函数调用**: ```js const target = { greet: function (name) { return `Hello, ${name}!`; }, }; const handler = { apply: function (target, thisArg, argumentsList) { console.log(`Calling function: ${argumentsList[0]}`); return target.apply(thisArg, argumentsList); }, }; const proxy = new Proxy(target, handler); console.log(proxy.greet("Bob")); // 通过代理调用函数 ``` **验证和过滤属性**: ```js const target = { age: 25 }; const handler = { get: function (target, property) { if (property in target) { return target[property]; } else { throw new Error(`Property "${property}" not found.`); } }, }; const proxy = new Proxy(target, handler); console.log(proxy.age); // 访问属性 console.log(proxy.name); // 触发异常 ``` **数据绑定**: ```js function createObservable(target) { const handlers = { get: function (target, property) { console.log(`Accessing property: ${property}`); return target[property]; }, set: function (target, property, value) { console.log(`Setting property: ${property} to ${value}`); target[property] = value; // 触发数据更新通知 }, }; return new Proxy(target, handlers); } const data = createObservable({ age: 30 }); data.age = 35; // 自动触发数据更新通知 ``` Proxy 对象提供了一种强大的方式来自定义和监视对象的行为,这对于元编程、拦截操作和实现高级功能非常有用。