《深入理解ES6》读书笔记 【1~10章】

文章目录

  • 1. 块级绑定
    • var声明与变量提升
    • 块级声明
    • let声明
    • const常量声明
    • 循环内的let与const
    • 全局块级绑定
    • 最佳实践
  • 2. 字符串
    • 识别子串
    • repeat()方法
    • 模板字面量
      • 多行字符串
      • 替换位
      • 标签化模板
  • 3. 正则表达式
    • y 标志
    • 复制正则表达式
    • flags属性
  • 4. 函数
    • 参数默认值
    • 扩展运算符
      • 剩余参数
      • 函数构造器增强
    • 名称属性
    • 双重用途
      • new.target属性
    • 块级函数
    • 尾调用优化
  • 5. 箭头函数
    • 创建立即调用函数表达式
    • 操作数组
  • 6. 扩展的对象功能
    • 对象字面量简化
    • 新的方法
      • Object.is()方法
      • Object.assign()方法
    • 对象属性
      • 重复的对象字面量属性
      • 自有属性的枚举顺序
    • 原型
      • 修改原型
      • 使用super引用
    • “方法”的定义
  • 7. 解构
    • 对象解构
    • 数组解构
    • 参数解构
  • 8. Symbol符号
  • 9. Set 与 Map
    • ES5模拟Set与Map
    • Set
    • Map
  • 10、迭代器与生成器
    • 迭代器
    • 生成器
    • 可迭代对象与for-of循环
    • 集合的迭代器

1. 块级绑定

var声明与变量提升

var关键字声明的变量会被视为声明于所在作用域的顶部,即变量提升

function getvalue(condition) {
     
	if (condition) {
     
		var value = "blue";
		return value;
	} else {
     
		// value在此处可访问,值为undefined
		return nu1l;
	}
// value在此处可访问,值为undefined
}

事实上value会进行变量提升,相当于:

function(){
     
	var value;
	if(){
     
		value = "blue";
	}
}

value的声明提升到顶部,而初始化保留在原处。因此在else分支内访问value值为undefined(未初始化)。

块级声明

ES6引入块级作用域,所声明的变量在指定块的作用域外无法被访问。块级作用域被创建的情况:

  1. 在函数内部
  2. 在一个代码块内部(一对花括号包裹中)

let声明

  • let将变量的作用域限定在当前代码块中
  • let声明不会进行变量提升,需要手动将其声明放置在顶部以便在整个代码块内部可用。

const常量声明

  1. const声明的变量会被认为是常量(constant),值设定后不能再改变,因此const变量需要在声明时进行初始化。对const变量的再次赋值会报错(严格与非严格模式都会报错)。
  2. const声明也是块级声明,在声明的代码块外部无法访问,变量不会被提升。
  3. const声明阻止对于变量绑定与变量自身值的修改,但是不包含对其成员的修改,因此对于Object引用类型,其属性值可以修改:
const person = [
	name: "Nicholas"
};
//工作正常
person.name = "Greg";
//抛出错误
person = {
     
	name: "Greg"
};

修改成员并没有修改person的绑定值(对象的引用),而当person自身赋值(字面量)会改变绑定导致报错。

即:const阻止对于变量绑定的修改,而非对成员值的修改。

遇到letconst时会将声明放在暂时性死区内,访问其中的变量会报错runtime error,直到执行到声明语句时才会从暂时性死区中移除安全使用,typeof也会受此影响(当typeof在该代码块外时应当报错undefined

循环内的let与const

var funcs = [];
for (let i= 0; i< 10; 1++) {
     
	funcs. push(function() {
     
		console .10g(i);
	});
}
funcs.forEach(function(func) {
     
	func();
})

每次迭代时都会创建新的绑定:每一个循环都是新的代码块,会保存新的 i 变量副本(var声明会提升而let声明不会,但是let在循环内部的行为是特别定义的,与变量提升关系不大)。

如果在循环中使用const,只要不改变值就没有问题(显然(const i = 0;i<10;i++)在第二次迭代时会报错):

var funcs = [],
	object = {
     
		a: true,
		b: true,
		C: true
	};
//不会导致错误
for (const key in object) {
     
	funcs.push( function() {
     
		console .1og(key);
	});
}
funcs.forEach(function(func) {
     
	func();
	// 依次输出 "a"、"b"、 "C"
});

全局块级绑定

在全局作用域上使用var,会创建一个全局变量,作为全局对象的一个属性(浏览器中是window),则可能会无意覆盖window的原有的属性:

//在测览器中
var RegExp = "Hello!";
console.log( window.RegExp);
// "Hello!"

事实上应当是保存正则表达式的构造函数:
在这里插入图片描述
而使用letconst时,会在全局作用域创建新的绑定,创建的变量的绑定屏蔽了全局的RegExp:

//在浏览器中
let RegExp = "He1lo!";
console.log(RegExp);// "Hello!"
console .log(window.RegExp === RegExp); // false

let与const不会在全局对象上添加属性。全局作用域不会被污染。(如果需要代码在全局对象中可以被访问则使用var声明)

最佳实践

块级绑定中的最佳实践:在默认情况下使用const,当变量值需要更改使用let

2. 字符串

识别子串

  • includes():存在子串返回true;
  • startsWith():子串在起始位置返回true;
  • endsWith():子串在结束位置返回true;

都接收两个参数:子串与搜索起始位置,前二者从索引位置开始正向匹配,后者反向查找(起始位置为长度减索引位置)。

indexOf()不同的是,它们返回是否存在而非具体查找位置,并且不可以接收正则表达式作为参数(会报错)。

repeat()方法

接收参数作为字符串的重复次数,返回新串:

console.log("abc".repeat(3));
//"abcabcabc"

模板字面量

反引号包裹字符串:

let	message	= `Hello world!`;

在字符串中包含反引号需要反斜杠 \ 转义

多行字符串

使用反斜杠\:延续下一行的字符串:

var	message	= "Multiline \
string";
console.log(message);	 //	"Multiline	string"

为了在输出中能够换行:使用模板字符串:

let	message	= `Multiline 
string`;
console.log(message);
//"Multiline
//string" 
console.log(message.length);//	16

注意:模板字符串中的空白字符会视为自身的一部分,记入长度。

此外,当然可以使用\n

替换位

在模板字符串中嵌入任何有效的JS表达式,使用$与一对花括号{}

替换位变量名:

let name = "nico",
	message = `Hello, ${
       name}`;
console.log(message);//"Hello, nico"

嵌入函数、表达式:

let count = 10,
	price = 0.25,
	message = `${
       count} items cost $${
       (count * price).toFixed(2)}.`;
console.log(message);//"10 items cost $2.50."

同时,模板字符串也是表达式,可以进行嵌套。

标签化模板

模板标签可以对模板字符串进行转换并返回字符串值,标签指定与模板字符串的起始处:

let	message	= tag`Hello world`;

tag是会应用到模板字面量上的标签。

标签是处理模板字面量的函数,接收的数据被划分为片段:它接收的参数:第一个是字符串数组,包含模板字符串中除去替换位的部分(如果由替换位开始,则第一个数组元素为空串以确保字符串的起始),随后的参数依次是替换位的解释值:

let count = 10,
	price = 0.25,
	message = pass`${
       count} items cost $${
       (count * price).toFixed(2)}.`;

此处,pass()函数接收到三个参数,第一个字符串数组为:

["", " items cost $", "."]

随后接收的两个参数分别是count值为数值10与计算结果字符串“2.50”。

pass()函数:

function passthru(literals, ...substitutions) {
     
let result ="";

//避免另一个数组越界
for (let i = 0; i < substitutions.1ength; i++) {
     
	result += literals[i]; 
	result += substitutions[i];
}

//添加最后一个字面量
result += literals[literals.1ength -1];
return result;
}

console.1og(message);
// "10 items cost $2.50."

使用内置的String.raw标签访问模板字符串的原始形式:

1et message1 =‘Multiline\nstring ,
	message2 = string.raw`Multiline\nstring`;
console .log(message1);
// "Multiline
// string"
console.log(message2);
// "Multiline\\nstring"

3. 正则表达式

y 标志

y标志影响正则表达式搜索时的粘连sticky属性,表示从lastIndex属性值的位置开始检索。

var text = "hel101 hello2 he1lo3",

pattern = /hello\d\s?/,
result = pattern. exec(text),

globalpattern = /hello\d\s?/g,
globalResult = globalpattern. exec(text),

stickyPattern = /hello\d\s?/y,
stickyResult = stickypattern. exec(text); 

console .log(resu1t[0]);
console .log(globa1Resu1t[0]);
console. log(stickyResu1t[0]);
// "hello1"

pattern.1astIndex = 1;
globalpattern.1astIndex = 1;
stickypattern.lastIndex = 1;

result = pattern. exec(text);
g1obalResult = globalPattern. exec(text);
stickyResult = stickyPattern. exec(text);

console.log(resu1t[0]);
// "he1lo1"
console.log(globalResu1t[0]);
// "hello2"
console.log(stickyResu1t[0]);
// Error! stickyResult is null

注意:只有在正则表达式对象上的方法(exec()test()),lastIndex属性才会生效;而将正则表达式作为参数传给字符串的方法(match())不会生效(无粘连特性)

检测y标志是否存在:

var pattern = /hello/y;
console.log(pattern.sticky);//true

存在时sticky属性值为true(由y标志存在与否决定)。

复制正则表达式

将正则表达式传给RegExp构造器进行复制:

var re1 = /ab/i,
	//re2 = new RegExp(re1);
	//ES5会报错,ES6可行
	re2 = new RegExp(re1,"g");

ES6中允许使用第二个参数,并使其覆盖第一个参数中的标志。

flags属性

正则表达式的source属性返回其文本,对应地添加flags属性返回其标志(由所有标志组成的字符串)

4. 函数

参数默认值

function request(url, timeout, callback){
     
	timeout = timeout || 2000;
	callback = callback || function(){
     };
	//更安全的方法:
	timeout = (typeof timeout !== "undefined")? timeout : 2000;
}

ES6使用初始化的形式简化了参数默认值的设置:

function request(url, timeout = 2000, callback = function(){
     }){
     
}

未传递的、传入undefined的参数将使用默认值。(null值是有效的)

ES6中,arguments对象表现与ES5严格模式一致,参数默认值触发了arguments与命名参数的分离。arguments此时仅包含被正确传入的参数,使用默认值的参数在对应的arguments中是undefined;更改命名参数不会对arguments产生影响。

当使用其他参数来为参数进行默认赋值是,仅允许使用前面的参数,前面的参数不能访问后面的参数。对应参数在暂时性死区内:

function add(first = second, second) {
     
	return first + second;
}
console.log(add(1, 1));// 2
console.log(add(undefined, 1)); //抛出错误

扩展运算符

找到数组中的最大值:

let values = [25,56,82,955,1025];

console.log(Math.max.apply(Math, values));

ES6中使用扩展运算符...来传入整个数组,JS引擎会将数组分隔为独立参数传入:

Math.max(...values);

扩展运算符可以与其他参数混用:使Math.max()返回的最小值为0,将0单独传入:

console.log(Math.max(...values, 0));

剩余参数

JS函数不要求参数的数量等于已定义的参数数量,提供arguments对象来查看传入的参数。

剩余参数(rest parameter)由三个点...与紧跟的命名参数指定,是包含传递给函数的其余参数的数组。

剩余参数在一个函数中只能有一个,且必须在传参的最后(first,...keys),形如(first, ...keys, last)的参数会导致语法错误。

setter属性不可使用剩余参数,因为setter限定只能使用单个参数:

let object = {
     
	//报错
	set name(...value){
     
	}
}

函数构造器增强

Function构造器可以使用字符串动态创建新函数:

var add = new Function("first", "second", "return first + second");

ES6允许Function构造器使用默认参数与剩余参数:


var add = new Function("first = 0", "...args", "return first + args[0]");

确保构造函数与函数声明形式具有相同的所有能力。

名称属性

ES6中所有函数都有适当的name属性值

函数声明的优先级高于函数赋值目标的变量名。

getter与setter函数会在名称属性值前带有get/set前缀,bind()创建的函数会有bound前缀,Function构造器创建的函数会有anonymous前缀。

name属性是为了在调试时获得相关信息,不能用它来获取对函数的引用。

双重用途

ES5及之前,函数使用new调用时,其内部的this是一个新对象,并作为函数的返回值:

function Person(name){
     
	this.name = name;
}
var person = new Person("nicho"); //[Object object]
var notPerson = Person("nichi"); //undefined

其中,JS为函数提供了两种内部方法:[[Call]][[Construct]],当函数未使用new调用,[[Call]]方法执行,运行函数体;使用new调用,[[Construct]]方法执行,负责创建一个新对象,并使用它作为this去执行函数体。拥有[[Construct]]方法的函数被称作构造器。

一般使用instanceof判断是否为构造器,对this值检查,判断是否为构造器的一个实例;但是在使用Person.call(new Person("nico"), "Mico");时,内部的this被设置为Person的实例,使用instanceof无法区分。

new.target属性

ES6引入new.target元属性,元属性指非对象上的属性,提供关联它的目标的附加信息。当函数的[[Construct]]方法被调用,new.target被填入new运算符的作用目标(通常是新创建的对象实例的构造方法),并作为函数体内部的this值;如果[[Call]]被执行则其值为undefined

function Person(name){
     
	if(typeof new.target === Person){
     
		this.name = name;
	}else{
     
		throw new Error("please use new");
	}
}

function AnotherPerson(name){
     
	Person.call(this, name);
}

var person = new Person("nico");
//报错,因为new.target值为undefined
var anotherPerson = new AnotherPerson("nocho");

在函数外使用new.target会有语法错误。

块级函数

ES5的严格模式不允许在代码块内部声明函数:

"use strict"

if(true){
     
	function foo(){
     
	}
}

在ES6中,foo()函数被视为块级声明,在所定义的代码块内可以被访问,函数声明提升至当前代码块的顶部,使用let声明的函数表达式不会提升(let foo = function(){})。

ES6在非严格模式下也允许使用块级函数,但是其作用域会被提升至所在函数或全局环境的顶部,而非当前代码块的顶部。

尾调用优化

尾调用指在函数最后语句中调用函数。

ES5中尾调用的处理与其他函数一样:新栈帧被创建并推入调用栈来表示该次调用。

ES6在严格模式下为特定尾调用减少调用栈,需要满足:

  1. 尾调用不能引用当前栈帧中的变量(不为闭包)
  2. 进行尾调用的函数在尾调用返回结果后不能做额外操作
  3. 尾调用的结果作为当前函数的返回值立即返回
"use strict";

function dosth(){
     
	return other();
	//不满足的情况:
	//return 1+other();有其他操作
	//other();不作为返回值
	
}

此时,尾调用优化会清除当前栈帧并再次利用,而非创建新的栈帧。

尾递归优化的主要用例在递归中:考虑阶乘:

function factorial(n){
     
	if(n <= 1){
     
		return 1;
	}else{
     
		return n * factorial(n-1);
	}
}

随n增长,调用栈大小增长,可能导致堆栈溢出。

考虑优化,则需要最后的语句不发生乘法运算,将乘法移至函数内:

function factorial(n, p = 1){
     
	if(n <= 1){
     
		return 1 * p;
	}else{
     
		let result = n * p;
		return factorial(n-1, result);
	}
}

此时,p参数保存前一次乘法的结果。

尾调用优化提供显著的性能提升,尤其是在计算复杂度高时。

5. 箭头函数

箭头函数(arrow function),使用=>定义:

  • 没有自己的thissuperargumentsnew.target:这些值由所在的最靠近的非箭头函数决定。
  • 不能使用new调用:没有[[Construct]]方法,不能作为构造函数,否则会报错。
  • 没有原型:没有prototype属性。
  • 不能更改this:this值在函数内部不能修改,在整个生命周期内保持不变。使用call、apply、bind方法时this值不受影响。
  • 没有arguments对象:必须使用具体的命名参数或者剩余参数来传参。
  • 不允许重复的命名参数:不论是否是在严格模式下(传统函数只有在严格模式下才禁止这种重复)

常规函数作为构造器时this异变不利于优化。

箭头函数也拥有name属性,规则与常规函数相同。

即使不使用return也会将函数体内的变量返回:

var reflect = value => value;
//等价于:
var reflect = function(value){
     
	return value;
};

单个参数不需要圆括号包裹;无入参则为:

var get = () => "nico";

函数体包含多个语句则使用花括号包裹:

var sum = (num1 + num2) => {
     
	return num1 + num2;
};

当需要返回对象字面量时:函数体的花括号需要变成圆括号:

var getItem = id => ({
     id: id, name: "nico"});

创建立即调用函数表达式

创建立即调用函数表达式,immediately-invoked function expression,IIFE。

当需要创建作用域并隔离在程序其他部分之外:

let person = function(name){
     
	return {
     
		getName: function(){
     
			return name;
		}
	};
}("Nico");

console.log(person.getName());//"Nico"

此时IIFE用于创建一个包含getName方法的对象,在箭头函数中:

let person = ((name) => {
     
	return {
     
		getName: function(){
     
			return name;
		}
	};
})("Nico");

传统函数中,(function(){}());(function(){})();都是有效的,在箭头函数中只有后者有效:括号仅包裹箭头函数的定义。

操作数组

传统的数组排序:

var result = values.sort(function(a,b){
     
	return a-b;
});

使用箭头函数使之简洁:

var result = values.sort((a,b) => a-b);

6. 扩展的对象功能

对象字面量简化

function Person(name, age){
     
	return {
     
		name: name,
		age: age
	};
}

简化为:

return {
     
	name,
	age
};

当对象字面量中属性只有名称时。JS引擎会在周边作用域查找同名变量

let person= {
     
	name: "name",
	//sayName: function(){alert(this.name);}
	sayName(){
     
		alert(this.name);
	}
};

函数简写

注意:方法简写可以使用super,非简写的方法不能使用。

计算属性名:使用方括号表示法:

let suffix = " name";
let person = {
     
	["first" + suffix]: "noco",
	["last" + suffix]: "zakas"
};
//属性名结果为字符串

新的方法

Object.is()方法

严格等于中,+0-0相等,NaN === NaN会返回false

ES6引入Object.is()方法,接收两个参数,比较二者值与类型相等返回true。一般与严等相同,除了上述两个例子结果相反。

Object.assign()方法

浅拷贝方法,接收参数为一个接收者与任意数量的供应者(依次拷贝属性,若相同则后接收的属性会覆盖先接收的值)。使用赋值运算符,供应者的访问器属性会变成接收者的数据属性。

对象属性

重复的对象字面量属性

ES5中严格非严格模式都会检查重复的属性,ES6不会。当存在重复属性时,后面的值会覆盖成为属性的实际值。

自有属性的枚举顺序

数字升序,字符串、符号类型按照添加顺序。定义顺序优先于动态添加。

原型

修改原型

ES5添加Object.getPrototypeOf()方法用于获取对象原型;ES6添加Object.setPrototypeOf()方法来修改对象原型,接收两个参数:被修改原型的对象与成为原型的对象。

使用super引用

super是指向当前对象的原型的指针,即等同于Object.getPrototypeOf(this)的值。

当使用super来调用原型的方法时,则该方法必须位于简写的方法内,否则使用具名方法会导致语法错误:

let f = {
     
	greet(){
     
		return super.greet(); 
	}
	/* 错误的用法: 
	greet: function(){
		return super.greet();
	}*/

另外,由于this值引发的问题可以由super更好地解决,因为super的引用非动态不受影响,总是指向正确的原型对象。

“方法”的定义

此前方法指对象的函数属性(区别于数据属性)。

ES6正式定义方法为:拥有[[HomeObject]]内部属性的函数,该属性指向方法所属的对象,由此区别对象内外的函数。

对super的引用会判断[[HomeObject]]属性的值,在其上调用Object.getPrototypeOf()方法获取原型的引用,然后查找同名函数,最后创建this绑定并调用方法。

7. 解构

对象解构

对象解构左侧使用对象字面量,右侧必须有初始化器:

let node= {
     
	type: "def",
	name: "doc"
};

let {
     type, name}= node;

解构赋值:

let node= {
     
	type: "def",
	name: "doc"
},
	type= "chl",
	name= 56;

( {
     type, name}= node);

使用圆括号是因为暴露的花括号视为代码块,而其右侧不允许有等号。圆括号代表其中的语句非代码块,而是解释为表达式。

**解构赋值表达式的值为其等号右侧的值。**若右侧值为undefined/null会报错runtime error

当未找到同名变量时,解构赋值的默认值undefined。也可以指定默认值:

let node= {
     
	type: "def",
	name: "doc"
};

let {
     type, name, color = "red"}= node;

则在对应属性缺失或为undefined时赋值为指定默认值。

当使用不同名的变量赋值时,则使用扩展语法:
类似于属性初始化:

let node= {
     
	type: "def",
	name: "doc"
};

let {
     type: localType, name: localName}= node;
//对应赋值给localType与localName变量

嵌套的对象解构:

let node= {
     
	type: "def",
	loc: {
     
		start: {
     
			line: 2,
			column: 1
		},
		end: {
     
			line: 1,
			column: 1
		}
	}
};

let {
     type, loc: {
     start} }= node;

使用花括号表示在属性内部寻找嵌套的属性。
使用非同名变量的情况:

let node= {
     
	type: "def",
	loc: {
     
		start: {
     
			line: 2,
			column: 1
		},
		end: {
     
			line: 1,
			column: 1
		}
	}
};

let {
     type, loc: {
     start: localStart} }= node;

数组解构

使用数组字面量,根据位置进行解构(而非对象的同名使用变量名):

let color= ["red", "blue", "green"];

let [fir, sec]= color;
//"red", "blue"

let [, , last]= color;
//"green"

//解构赋值
[fir, sec, last]= color;

逗号占位使变量获取对应位置的值。

解构赋值在交换变量值的应用:

let a=1, b=2;
[a, b]= [b, a];

右侧为临时创建的数组,左侧为对应解构的赋值,完成了两个变量值的互换。

默认值的情况:

let color= ["red"];

let [fir, sec= "green"]= color;

嵌套的情况:

let color= ["red", ["green", "blue"], "white"];

let [fir, [sec]]= color;//"red", "green"

使用剩余项(剩余参数)将剩余的项目赋值给指定的变量:

let color= ["red", "green", "white"];

let [fir, ...sec]= color;
//"red"
//["green", "white"]

复制数组时往往使用concat方法(本意为合并,当无参数时为复制):

let color= ["red", "green", "white"];
let clone= color.concat();

使用剩余项可以达到相同效果:

let color= ["red", "green", "white"];
let [...clone]= color;

剩余项必须是解构模式的最后一部分,否则语法错误。

数组和对象的结构可以同时混合使用。

参数解构

在函数接收可选参数时,常用模式为:将附加的参数赋给options对象作为具名属性,将options对象传入函数:

function setCookies(name, value, options){
     
	options= options || {
     };
	let secure = options.secure,
		path = options.path,
		domain= options.domain,
		expires= options.expires;
}

参数解构提供了更明确的方案:使用对象或数组的结构模式替代了具名参数,当未传入时参数值为undefined

function setCookies(name, value, {
     secure, path, domain, expires}){
     }

需要注意的问题:解构的右侧值为null/undefined时会报错,也就是当右侧未传值时会出错。也就是当参数为可选参数时需要提供参数默认值来处理:

function setCookies(name, value, {
     secure, path, domain, expires}={
     }){
     } 

也可以使用解构默认值:

function setCookies(name, value, {
     secure=false, path="/", domain="example.com", expires="new Date(Date.now() + 360000000)} = {
     }){
     } 

8. Symbol符号

新的基本类型:符号Symbol

创建:没有字面量形式,使用全局Symbo()函数创建:
注意:使用new Symbol()会报错,因为Symbol是基本类型。

let fir = Symbol();
let person = {
     };
person[fir] = "nico";

可以使用typeof运算符判断Symbol值(返回"symbol")。

共享符号值:跨文件或代码访问Symbol相对困难,因此提供了“全局符号注册表”:使用Symbol.for()接收单个字符串参数,作为其标识符与描述信息。

Symbol.for()方法会先搜索全局符号注册表,判断是否存在该字符串为键的符号值,若是则返回该符号值,否则创建新的符号值并记录注册表,然后返回。

对应Symbol.keyFor()方法在全局注册表中根据符号值Symbol返回其键值String。(使用Symbol()函数创建的值没有在全局注册所以不会查询到结果会返回undefined

在全局注册时应当使用命名空间前缀来避免命名冲突。

符号值不可以直接转换为字符串(使用加号连接空串报错)、数字(数学运算报错),逻辑运算中作为非空值等价于true

console.log(symbol)会调用了Symbol的String()方法来进行输出,即等于String(symbol),而该方法调用symbol.toString()来获取其字符串描述信息。

Object.keys()/getOwnPropertyNames()方法(可枚举/不论是否可枚举)都不返回符号类型属性,可以使用Object.getOwnPropertySymbols()方法来检索对象的符号类型属性。

(待追加的部分:Symbol属性,因为对下一章比较感兴趣所以先康康好玩的)

9. Set 与 Map

ES5模拟Set与Map

//作为容器,确保没有继承属性
let ves = Object.create(null);

//作为set
ves.foo = true;

if(ves.foo){
     ...}

//作为map
ves.foo = "nib";

局限性

需要保证任意两个键不会被转换为相同的字符串:由于对象的属性只能是字符串,数值类型、对象类型键会在内部转换为字符串,因此ves[5]ves["5"]引用相同的属性。

且,若值存在且为假值,会在if条件中混淆其存在性。

Set

无重复值的有序列表

使用new Set()创建,add()方法添加项目,size属性查看项数。

let set = new Set();
set.add(5);
set.add("5");
console.log(set.size);//2

Set不会强制类型转换(内部使用Object.is()来判断重复的项,+0与-0是相等的)。

可以使用 数组 来初始化Set(可接受任意可迭代对象作为参数)

let set= new Set([1,2,3,4,4,4,5,6,8,8,8,8,8,]);
console.log(set.size);//7

使用has()方法来查询某个值是否在Set中:

console.log(set.has(5));//true

使用delete()方法来移除值,使用clear()方法移除其中所有值(清空)。

迭代
使用forEach()方法,由于该方法的三个参数:值、键、容器本身,而Set没有键,所以其前两个参数值是一样的(每一项同时认定为键值):

let set = new Set([1,2]);
set.forEach(function(value, key, ownerset){
     
	console.log(value+ " "+ key);
});
//1 1
//2 2

若需要在回调函数中使用this,则需要给forEach()传入this作为第二个参数:

let set = new Set([1,2]);

let processor = {
     
	output(value){
     
		console.log(value);
	},
	process(dataSet){
     
		dataSet.forEach(function(value){
     
			this.output(value);
		}, this);
	}
	//使用箭头函数而无需传入this:
	process(dataSet){
     
		dataSet.forEach((value)=>this.output(value));
	}
};

processor.process(set);

转换为数组
使用扩展运算符将可迭代对象(例如Set)转换为数组:

let set = new Set([1,2,3,3]);
let array = [...set];//[1,2,3]

Weak Set

let set = new WeakSet();

与Set相同,拥有addhasdelete方法,构造器可传入数组;

与Set不同:

  • WeakSet不接受基本类型的值构造器add方法传入非对象的参数会报错deletehas方法则对基本类型返回false
  • Set在变量(实例)中存储对象,即使Set外的引用释放,由于Set内的变量引用仍然使存储的对象无法被垃圾回收机制回收。而Weak Set中,外部的引用释放,内部就会随之删除(因为已被删除而无法通过has来证实)
  • WeakSet不可迭代,不暴露迭代器keys、values方法),无法在for-of循环中使用,没有forEach方法
  • WeakSet没有size属性

一般在追踪对象的引用时应当使用WeakSet。

Map

键值对的有序列表。

键、值可为任意类型,因此5"5"可以同时作为键,可以使用Object.is()方法比较键。

使用set()get()来设置和获取键值:

let map = new Map();

map.set("name", "nico");

console.log(map.get("name"));//"nico"

当获取键不在Map中则get方法返回undefined

Map有与Set相似的方法:has(key)判断指定键是否存在、delete(key)删除对应键值、clear()清除所有键值。

Map拥有size属性,包含其中键值对的数量。

使用数组初始化Map

let map = new Map([["name","nico"],["age",13]]);

传入参数数组的每一项都是数组,为了保证在传入Map之前不会被强制转换为其他类型。

Map的ForEach方法

与Set与数组类似,接受回调函数包含三个参数:值、键、Map自身:

map.forEach(function(value, key, map){
     
	console.log(key+" "+value);
});

Weak Map
Weak Set相似,键为对象,弱引用不会干扰垃圾回收
(需要注意键是弱引用而值不是,值中存储对象会阻止垃圾回收)

Weak Map是键值对的无序列表,初始化与Weak Set类似。不可枚举,没有size、clear()

往往在对应DOM元素与自定义对象时使用,也用于私有变量的保存与获取。 如果只想用对象作为键则应当使用Weak Map,确保数据不可用后被销毁。

10、迭代器与生成器

迭代器

专用于迭代的对象。

每个对象都有next()方法(返回一个结果对象,包含下一个值value与布尔类型的done,值为true时代表无值可用)。
若在最后一个值返回后使用next()方法则其done属性为truevalue属性为迭代器自身返回值(往往是undefined)。

在ES5中创建迭代器:

function createIterator(items){
     
	var i= 0;
	
	return {
     
		next: function(){
     
			
			var done= (i >= items.length);
			var value= !done? items[i++]: undefined;
			
			return {
     
				done:done;
				value:value;
			};
		}
		
	};
}

生成器

生成器generator函数返回一个迭代器。生成器函数由function关键字后的*星号表示,可使用新的yield关键字(指定迭代器next()方法调用时按顺序返回的值)

每个yield语句执行后会停止执行。

可迭代对象与for-of循环

可迭代对象,即包含Symbol.iterator属性对象,所有的集合(数组、Set、Map)与字符串都是可迭代对象。

for-of循环执行时会调用next()方法:

let values = [1,2,3];
for(let num of values){
     
	console.log(num);

访问默认迭代器的方法:

let iterator = values[Symbol.iterator]();

console.log(iterator.next());

可以检测一个对象是否可以进行迭代:

function isIterable(obj){
     
	return typeof obj[Symbol.iterator] === "function";
}

isIterable(new Map());//true
isIterable(new WeakMap());//false

创建可迭代对象:

let collection = {
     
	items: [],
	*[Symbol.iterator](){
     
		for(let item of this.items){
     
			yield item;
		}
	}
};

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

集合的迭代器

三种集合对象类型:数组、Map、Set;都具有以下迭代器:

  • entries():返回包含键值对的迭代器
  • values():返回包含值的迭代器
  • keys():返回包含键的迭代器

entries()迭代器的next()方法返回双项数组包含键与值,数组的键为索引,Set的键与值相同。

let a = [1,2,3];

for(let entry of a.entries()){
     
	console.log(entry);
}
//[0,1]...

若数组包含添加的具名属性,for-of会迭代其索引而for-in会迭代其索引与所有属性。

字符串、NodeList也可以使用for-of迭代其所有字符。

(扩展部分:生成器的高级用法)

你可能感兴趣的:(前端,javascript,es6,前端)