JavaScript高级程序设计学习笔记(四) — 集合引用类型

集合引用类型

Object

1.对象

有两种创建对象的方式

let user = new Object(); // “构造函数” 的语法

let user = {};  // “字面量” 的语法

文本和属性

可以在创建对象的时候,立即将一些属性以键值对的形式放到 {...} 中。

let user = {     // 一个对象
  name: "John",  // 键 "name",值 "John"
  age: 30        // 键 "age",值 30
};

访问属性值:

alert( user.name ); // John
alert( user.age ); // 30

属性的值可以使任意类型:

user.age = true;

移除属性:

delete user.age;

我们也可以用多字词语来作为属性名,但必须给它们加上引号:

let user = {
  name: "John",
  age: 30,
  "likes birds": true  // 多词属性名必须加引号
};

注意:

使用 const 声明的对象是可以被修改的

const user = {
  name: "John"
};

user.name = "Pete"; // (*)

alert(user.name); // Pete

const 声明仅固定了 user 的值,而不是值(该对象)里面的内容。

仅当我们尝试将 user=... 作为一个整体进行赋值时,const 会抛出错误。

方括号

// 这将提示有语法错误
user.likes birds = true

点符号要求 key 是有效的变量标识符。这意味着:不包含空格,不以数字开头,也不包含特殊字符(允许使用 $_)。

此时需要使用方括号来处理对象的属性

let user = {};

// 设置
user["likes birds"] = true;

// 读取
alert(user["likes birds"]); // true

// 删除
delete user["likes birds"];

另外:还可以这样使用

let user = {
  name: "John",
  age: 30
};

let key = prompt("What do you want to know about the user?", "name");

// 访问变量
alert( user[key] ); // John(如果输入 "name")

但是点符号不能这样使用

计算属性

当创建一个对象时,我们可以在对象字面量中使用方括号。这叫做 计算属性。

let fruit = prompt("Which fruit to buy?", "apple");

let bag = {
  [fruit]: 5, // 属性名是从 fruit 变量中得到的
};

alert( bag.apple ); // 5 如果 fruit="apple"

计算属性的含义很简单:[fruit] 含义是属性名应该从 fruit 变量中获取

let fruit = 'apple';
let bag = {
  [fruit + 'Computers']: 5 // bag.appleComputers = 5
};

属性名称限制

任何关键字都可以当做属性名称,因为最后都会被转换为字符串

属性存在性检测

相比于其他语言,JavaScript 的对象有一个需要注意的特性:能够被访问任何属性。即使属性不存在也不会报错!

读取不存在的属性只会得到 undefined。所以我们可以很容易地判断一个属性是否存在:

let user = {};

alert( user.noSuchProperty === undefined ); // true 意思是没有这个属性

可以使用in来检测属性是否存在

let user = { name: "John", age: 30 };

alert( "age" in user ); // true,user.age 存在
alert( "blabla" in user ); // false,user.blabla 不存在。

in 的左边必须是 属性名。通常是一个带引号的字符串。

如果我们省略引号,就意味着左边是一个变量,它应该包含要判断的实际属性名。例如:

let user = { age: 30 };

let key = "age";
alert( key in user ); // true,属性 "age" 存在

正常来说,想要判断属性是否存在只需要将属性 === undefined即可,但是在一种情况下会判断失败:属性定义了,但是属性值是undefined,此时需要用in来判断。

let obj = {
  test: undefined
};

alert( obj.test ); // 显示 undefined,所以属性不存在?

alert( "test" in obj ); // true,属性存在!

检测对象类型

使用instaceof来检测对象的类型

result = variable instanceof constructor;

console.log(person instanceof Object);
console.log(colors instanceof Array);

遍历对象的所有属性

我们可以使用for…in来循环遍历一个对象的所有属性

let user = {
  name: "John",
  age: 30,
  isAdmin: true
};

for (let key in user) {
  // keys
  alert( key );  // name, age, isAdmin
  // 属性键的值
  alert( user[key] ); // John, 30, true
}

属性排列问题

整数属性的排列为从小到大,而其余属性会根据创建的顺序依次显示

这里的“整数属性”指的是一个可以在不做任何更改的情况下与一个整数进行相互转换的字符串。

所以,“49” 是一个整数属性名,因为我们把它转换成整数,再转换回来,它还是一样的。但是 “+49” 和 “1.2” 就不行了

如果不想让整数属性进行排列,而是想要正常的按照创建顺序排列的话,可以再整数属性的前面加上+。

2.对象拷贝,引用

对象与原始类型其中一个基本的区别是:对象“通过引用的形式”被存储和拷贝。

变量存储的不是对象自身,而是该对象的“内存地址”,换句话说就是一个对该对象的“引用”。

换句话说:创建对象时会放在内存地址中,变量存储的就是对这个地址的引用。

当一个对象变量被拷贝 —— 引用则被拷贝,而该对象并没有被复制。

let user = { name: "John" };

let admin = user; // 拷贝引用

admin存储的也是对对象user的引用,即两个变量存储的都是相同对象的引用,

一个变量(对象的引用)改变对象的属性值,另一个引用也能看到这个改变,两个引用都可以改变对象的属性。

let user = { name: 'John' };

let admin = user;

admin.name = 'Pete'; // 通过 "admin" 引用来修改

alert(user.name); // 'Pete',修改能通过 "user" 引用看到

比较

对于对象来说,=====都是一样的,都是判断是否为同一个对象,

let a = {};
let b = a; // 拷贝引用

alert( a == b ); // true,都引用同一对象
alert( a === b ); // true

而两个空对象并不是同一个对象,因为内存地址并不是一个地方,

let a = {};
let b = {}; // 两个独立的对象

alert( a == b ); // false

浅拷贝(克隆与合并)

如果想要完全的克隆一个对象,而不是引用,那么可以:

  • 创建一个新对象,并通过遍历现有属性的结构,在原始类型值的层面,将其复制到新对象,以复制已有对象的结构
let user = {
  name: "John",
  age: 30
};

let clone = {}; // 新的空对象

// 将 user 中所有的属性拷贝到其中
for (let key in user) {
  clone[key] = user[key];
}

// 现在 clone 是带有相同内容的完全独立的对象
clone.name = "Pete"; // 改变了其中的数据

alert( user.name ); // 原来的对象中的 name 属性依然是 John
  • 利用Object.assign方法
Object.assign(dest, [src1, src2, src3...])

1.第一个参数 dest 是指目标对象。

2.更后面的参数 src1, ..., srcN(可按需传递多个参数)是源对象。

3.该方法将所有源对象的属性拷贝到目标对象 dest 中。换句话说,从第二个开始的所有参数的属性都被拷贝到第一个参数的对象中。

4.调用结果返回 dest

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// 将 permissions1 和 permissions2 中的所有属性都拷贝到 user 中
Object.assign(user, permissions1, permissions2);

// 现在 user = { name: "John", canView: true, canEdit: true }

如果拷贝时遇到相同属性,则会覆盖原来的属性

简单克隆:

let user = {
  name: "John",
  age: 30
};

let clone = Object.assign({}, user);  //将所有属性拷贝到了一个空对象中,并返回这个新对象

深拷贝(深层克隆)

当属性是对其他对象的引用时,需要用到深拷贝

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

alert( user.sizes.height ); // 182

此时不能通过简单地将属性sizes拷贝来实现

结构化拷贝算法:我们应该使用会检查每个 user[key] 的值的克隆循环,如果值是一个对象,那么也要复制它的结构。这就叫“深拷贝”。

或者使用javascript库中的lodash中的_.cloneDeep(obj)来实现,避免重复造轮子

4.对象方法—this

举例:

let user = {
  name: "John",
  age: 30
};

user.sayHi = function() {
  alert("Hello!");
};

user.sayHi(); // Hello!

作为对象属性的函数被称为 方法

得到了 user 对象的 sayHi 方法。

简写一下

user = {
  sayHi: function() {
    alert("Hello");
  }
};

// 方法简写看起来更好,对吧?
let user = {
  sayHi() { // 与 "sayHi: function()" 一样
    alert("Hello");
  }
};

方法中的this

通常,对象方法需要访问对象中存储的信息才能完成其工作。

例如,user.sayHi() 中的代码可能需要用到 user 的 name 属性。

为了访问该对象,方法中可以使用 this 关键字。

this 的值就是在点之前的这个对象,即调用该方法的对象。

let user = {
  name: "John",
  age: 30,

  sayHi() {
    // "this" 指的是“当前的对象”
    alert(this.name);
  }

};

user.sayHi(); // John

JavaScript 中的 this 可以用于任何函数。

this 的值是在代码运行时计算出来的,它取决于代码上下文。

在没有对象的情况下调用this

在没有对象的情况下调用:this == undefined

我们甚至可以在没有对象的情况下调用函数:

function sayHi() {
  alert(this);
}

sayHi(); // undefined

在这种情况下,严格模式下的 this 值为 undefined。如果我们尝试访问 this.name,将会报错。

在非严格模式的情况下,this 将会是 全局对象

箭头函数没有自己的this

箭头函数有些特别:它们没有自己的 this。如果我们在这样的函数中引用 thisthis 值取决于外部“正常的”函数。

let user = {
  firstName: "Ilya",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // Ilya

5,构造器和操作符new

构造函数和new用于创造多个类似的对象

构造函数

构造函数有两个约定-

  • 它们的命名以大写字母开头。
  • 它们只能由 "new" 操作符来执行。
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

当一个函数被使用 new 操作符执行时,它按照以下步骤:

  1. 一个新的空对象被创建并分配给 this
  2. 函数体执行。通常它会修改 this,为其添加新的属性。
  3. 返回 this 的值。
function User(name) {
  // this = {};(隐式创建)

  // 添加属性到 this
  this.name = name;
  this.isAdmin = false;

  // return this;(隐式返回)
}

new function() { … }

如果我们有许多行用于创建单个复杂对象的代码,我们可以将它们封装在构造函数中,像这样

let user = new function() {
  this.name = "John";
  this.isAdmin = false;

  // ……用于用户创建的其他代码
  // 也许是复杂的逻辑和语句
  // 局部变量等
};

构造器不能被再次调用,因为它不保存在任何地方,只是被创建和调用。因此,这个技巧旨在封装构建单个对象的代码,而无需将来重用。

new.target

在一个函数内部,我们可以使用 new.target 属性来检查它是否被使用 new 进行调用了。

对于常规调用,它为空,对于使用 new 的调用,则等于该函数:

function User() {
  alert(new.target);
}

// 不带 "new":
User(); // undefined

// 带 "new":
new User(); // function User { ... }

构造器的return

通常,构造器没有 return 语句。它们的任务是将所有必要的东西写入 this,并自动转换为结果。

  • 如果 return 返回的是一个对象,则返回这个对象,而不是 this
  • 如果 return 返回的是一个原始类型,则忽略。

换句话说,带有对象的 return 返回该对象,在所有其他情况下返回 this

例如,这里 return 通过返回一个对象覆盖 this

function BigUser() {

  this.name = "John";

  return { name: "Godzilla" };  // <-- 返回这个对象
}

alert( new BigUser().name );  // Godzilla,得到了那个对象
function SmallUser() {

  this.name = "John";

  return; // <-- 返回 this
}

alert( new SmallUser().name );  // John

构造器中的方法

构造函数中还可以定义函数

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "My name is: " + this.name );
  };
}

let john = new User("John");

john.sayHi(); // My name is: John

Array

  • 数组可以存储任意类型的数据
  • 数组的大小是动态的,会随着添加数据而自动增长

创建数组

1.使用Array构造函数

let colors = new Array();
//如果知道数组的大小,可以传入一个数值,然后length属性就会被自动创建并设置成这个值
let colors1 = new Array(2);
//也可以传入几个要保存的数据
let colors2 = new Array("red","blue",3);
//也可以省略new关键字
let name = Array();

2.数组字面量创建

let colors = ["red","blue","green"];
//注意:在使用数组字面量表示法创建数组不会调用Array构造函数

3.from()方法创建

from()方法是es6新增的静态方法,用于将类数组结构转换为数组实例

Array().from()的第一个参数是一个类数组对象(任何可迭代的结构),或者有一个length属性和课索引元素的结构

console.log(Array.from("hello"))  //["h","e","l","l","o"]
//也可以将集合和映射转换为一个新数组

Array.from()对现有数组进行浅复制

const a1 = [1,2,3,4];
const a1 = Array.from(a1);
console.log(a1);  //[1,2,3,4]
alert(a1 === a2);

在symbol.iterator也可以使用

arguments对象也可以被转换为数组

function getArgsArray(){
	return Array.from(arguments);
}
console.log(getArgsArray(1,2,3,4))  //[1,2,3,4]

from()也能转换带有必要属性的自定义对象

const arrayLikeObject = {
    0:1,
    1:2,
    2:3,
    3:4
    length:4
}
console.log(Array.from(arrayLikeObject));  //[1,2,3,4]

from()方法的第二个参数是可选的映射函数参数,这个函数可以直接增强新数组的值,

第三个可选的参数用于指定映射函数中this的值,但是在箭头函数中不适用

const a1 = [1,2,3];
cosnt a2 = Array.from(a1,x =>x**2);
const a3 = Array.from(a1,function(x) { return x**this.exponent },{exponent:2});
console.log(a2);  //[1,4,9]
console.log(a3);  //[1,4,9]

4,Array.of()方法创建

Array.of()可以把一组参数转换为数组,此方法用于替代之前的Array.prototype.slice.call(arguments)

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

数组空位

const options = [,,,,,]; //创建包含五个元素的数组
console.log(options.length)  //5

es6中将这个空位当成存在的元素,值为undefined

数组索引

要获取数组某一项的值,需要使用中括号

let colors = [1,2,3,4];
alert(colors[1]);  //显示第二项
colors[1] = 1;  //修改第二项
colors[4] = 5;  //添加第五项

如果索引小于数组包含的元素数,则返回相应位置的元素,如果索引大于数组包含的元素数,则数组长度会自动扩充到改索引值+1的大小,且新增的元素值为undefined

数组的length并不是只读的,可以改变length的值,可以从数组末尾删除或添加元素,

let colors = ["red","blue","green"];
colors.length = 2;
alert(colors[2]);  //undefined
//添加元素
let colors = ["red","blue","green"];
colors.length = 4;
alert(colors[3]);  //undefined
//向数组末尾添加元素
let colors = ["red","blue","green"];
colors[colors.length] = "black";
colors[colors.length] = "brown";
//此时length会自动添加1

检测数组

如果在单网页中检测数组可以使用instanceof

if(value instanceof Array){
    //操作数组
}

但是使用instanceof的前提是只有一个全局执行上下文

Array.isArray()方法可以用来检测是否为数组

if(Array.isArray(value)){
    //操作数组
}

迭代器方法

es6中,array原型暴露了检索数组的方法:

  • keys():返回数组索引的迭代器
  • values():返回数组元素的迭代器
  • entries():返回索引/值对的迭代器

利用.from()方法可以很轻松的构建数组实例

const a = ["foo","bar","baz","qux"];
const aKeys = Array.from(a.keys());  //[0,1,2,3]
const aValues = Array.from(a.values());  //["foo","bar","baz","qux"]
const aEntries = Array.from(a.entries());  //[[0,"foo"],[1,"bar"],[2,"baz"],[3,"quz"]]

拆分键值对:

const a = ["foo","bar","baz","qux"];
for(const [idx,element] of a.entries()){
    alert(idx);
    alert(element);
}

复制和填充方法

es6新增:

  • copyWidth()

copyWidth()方法会按照指定范围浅复制数组中的部分内容,然后将他们插入到指定索引开始的位置,开始索引和结束索引与fill相同(参数:复制元素的索引,开始索引,结束索引)

const ins = [0,1,2,3,4,5,6,7,8,9];
ins.copyWidthin(5);
console.log(ins);  //[0,1,2,3,4,0,1,2,3,4]

ins.copyWithin(4,0,3);
console.log(ins);  //[0,1,2,3,0,1,2,7,8,9]
  • fill()

fill()方法可以向一个已经存在的数组中插入全部或部分相同的值,开始索引用于指定开始填充的位置,他是可选的,如果不提供结束索引,则一直填充到数组末尾,复制索引从数组末尾开始计算,也可以将福索引想象成数组长度加上它得到的一个正索引

const zeros = [0,0,0,0,0];
//用5填充整个数组
zeros.fill(5);
console.log(zeros);  //[5,5,5,5,5]
zeros.fill(0);  //重置

zeros.fill(7,1,3);  //用7填充索引>=1,且<3的元素

两种方法索引规则:默认忽略超出数组边界,零长度和方向相反的索引范围,(什么都不操作),如果部分索引可用,则操作可用索引部分

转换方法

  • toLocaleSreing():会调用数组中每个值的toLocaleString()方法
  • toString():返回数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串
  • valueOf():返回的是数组本身

栈方法

栈是一种后进先出的结构,栈的插入和删除都在栈顶发生,es提供了push()和pop()方法

  • push()方法接受任意数量的参数,并将它们添加到数组末尾,返回数组的新长度

  • pop()方法则用于删除数组的最后一项,同时减少长度值,返回删除的项

let colors = new Array();
colors.push("red","blue");
console.log(colors);  //[ 'red', 'blue' ]
let result = colors.pop();
console.log(result);  //blue
console.log(colors.length);  //1

队列方法

队列是先进先出的结构,在列表末尾添加数据,在头部获取数据

  • push():在末尾添加数据
  • shift():删除数组的第一项并返回
  • unshift():在数组头目添加任意个值,然后返回数组长度

一般使用push(),shift()模拟队列,还可以使用unshift()和pop()在相反方向上模拟队列

let colors = new Array();
colors.push("red","blue","white");
let first = colors.shift();
console.log(first);  //red
let addLength = colors.unshift("1","2");
console.log(addLength);  //4
console.log(colors);  //[ '1', '2', 'blue', 'white' ]

排序方法

  • reverse():使数组反序排列
  • sort():正常使用时,sort函数会将每一项都转换为字符串进行排序,所以在很多情况下都会出现排序错误

解决方法是:在sort内传入一个函数参数,例如:(如果想要降序排序,可以将1和-1调换)

let a = [0,1,5,10,15];
a.sort((a,b)=>{ a<b? 1:a>b? -1:0 });
console.log(a);

//或者传入下列函数
function compare(val1,val2){
    return val2-val1;
}

reverse()和sort()函数都返回调用他们数组的引用(即数组本身)

操作方法

  • concat():传入参数后,先创建一个原数组的副本,然后会将参数的内容添加到原数组的后面,最后返回这个新数组,如果参数是数组,会将参数数组中的每一项都添加到数组末尾
let nums = [1,2,3,4];
let nums2 = nums.concat(5,[6,7]);
console.log(nums2);

在方法内部,有一个符号位Symbol.isConcatSpreadable,这个符号能够阻止concat打平参数数组,把这个值设置为true可以强制打平类数组对象,值为true代表打平数组,值为false代表不打平按照原来的结构合并到原数组

let colors = ["red","green","blue"];
let newColors = ["black","white"];
let moreColors = {
    length:2,
    0:"pink",
    1:"cyan"
}
// newColors[Symbol.isConcatSpreadable] = false;
let colors2 = colors.concat("yellow",newColors);
let colors3 = colors.concat(moreColors);
console.log(colors);  //[ 'red', 'green', 'blue' ]
console.log(colors2);  //
console.log(colors3);
  • slice():用于创建一个包含原有数组中一个或多个元素的新数组,接受一个或两个参数,返回元素的开始索引和结束索引(不包含结束索引的元素),如果只有一个参数,则是该参数到数组末尾的所有元素,此方法不影响原数组:
    • 删除:两个参数splice(0,2)删除前两个元素
    • 插入:三个参数,开始位置,要删除的元素数量,要插入的元素(可以放多个参数)
    • 替换:三个参数,开始位置,要删除元素的数量,要插入的任意多个元素

搜索和位置方法

两种搜索方法:按严格相等搜索和按断言函数搜索

严格搜索:都接受两个桉树:要查找的元素和一个可选的起始搜索位置

  • indexOf():从前往后搜索,返回要查找的元素在数组中的位置,没有返回-1
  • lastIndexOf():从后往前搜索。返回要查找的元素在数组中的位置,没有就返回-1
  • includes() (es7新增):返回布尔值,表示是否找到一个预指定元素匹配的项,在比较元素时,会使用===全等,必须严格相等才会返回true

断言搜索:允许按照定义的断言函数搜索数组,每个索引都会调用这个函数,断言函数的返回值表示是否匹配

断言函数三个参数:元素(数组中当前搜索的元素),索引(当前元素的索引),数组本身,

  • find():从数组的最小索引开始,返回第一个匹配的元素,第二个可选参数指定断言函数内部的this的值
  • findIndex():从数组的最小索引开始,返回第一个匹配元素的索引,第二个可选参数指定断言函数内部的this的值
const people = [
    {
        name:"Matt",
        age:27
    },
    {
        name:"Nicholas",
        age:29
    }
];
console.log(people.find((element,index,array)=>element.age<28));
//{name:"Matt",age:27}

迭代方法

五个迭代方法:每个方法都接受两个参数:以每一项为参数运行的函数,可选的作为函数运行上下文的作用域对象(影响函数中this的值)。传给每个方法的函数接受3个参数:数组元素,元素索引,数组本身

  • every():对数组的每一项都运行传入的函数,如果对每一项函数都返回true,则这个方法返回true
  • filter():对数组的每一项都运行传入的函数,函数返回true的项会组成数组返回
  • forEach():对数组每一项都运行传入的函数,没有返回值
  • map():对数组的每一项都运行传入的函数,返回由每次函数调用的结果构成的数组
  • some():对数组每一项都运行传入的函数,如果有一项函数返回true,则这个方法返回true

这些方法都不改变调用他们的数组

归并方法

  • reduce:从数组的第一项遍历到最后一项
  • reduceRight():从数组的最后一项遍历到第一项

这两个方法都会迭代数组的所有项,并在此基础上构建一个最终的返回值,

两个参数:对每一项都会运行的归并函数,可选的以之为归并起点的初始值

传给reduce和reduceright的函数接受四个参数:上一个归并值,当前项,当前项的索引,数组本身。

let values = [1,2,3,4,5];
let sum = values.reduce((prev,cur,index,array)=>prev+cur);
console.log(sum);  //15

函数返回的任何值都会作为下一次调用相同函数的第一个参数(上一个归并值),如果没有指定可选的第二个参数,默认第一次迭代从数组的第二项开始,因此传给归并函数的第一个参数是数组的第一项,第二个参数是数组的第二项

定型数组

用得不多,暂时省略

Map

映射:可以实现键值的结构

使用构造函数创建一个空映射:

const m = new Map();
//如果想要在创建的同事初始化实例,可以给map构造函数传入一个可迭代的对象,需要包含键值对数组
const m1 = new Map([
    ["key1":"val1"],
    ["key2":"val2"],
    ["key3":"val3"],
]);
console.log(m1.size);  //3
//使用自定义迭代器初始化映射
const m2 = new Map({
    [Symbol.iterator]:function*() {
        yield ["key1","val1"];
        yield ["key2","val2"];
        yield ["key3","val3"];
    }
});
console.log(m2.size);  //3
  • set():初始化之后,添加键值对
  • get():查询,参数为键,返回值
  • has():查询是否有这个键值对,参数为键
  • size:为长度

map可以使用任何数据类型作为键,map内部使用SameValueZero(相当于使用严格对象相等的标准来检查)

Map实例会维护键值对的插入顺序,实例提供一个迭代器(iterator),能以插入顺序生成[key,value]形式的数组,可以通过entries()方法,(或者Symbol.iterator属性,他引用entries)取得这个迭代器

const m = new Map([
    ["key1","val1"],
    ["key2","val2"],
    ["key3","val3"]
])
console.log(m.entries === m[Symbol.iterator])  //true
for(let pair of m.entries()){
    console.log(pair);
}
//[key1,val1]
//[key2,val2]
//[key3,val3]
for(let pair of m[Symbol.iterator]()){
    console.log(pair);
}

因为entries是默认迭代器,可以直接进行拓展操作,将映射转换为数组

const m = new Map([
    ["key1","val1"],
    ["key2","val2"],
    ["key3","val3"]
]);
console.log([...m]);  //[["key1","val1"],["key2","val2"],["key3","val3"]]

还可以使用回调函数进行迭代:forEach(callback,opt_thisArg) opt_thisArg用于重写this

const m = new Map([
    ["key1","val1"],
    ["key2","val2"],
    ["key3","val3"]
]);
m.forEach((val,key)=>alert(`${key}->${val}`));

key()和m.values()和entries一样,都是迭代器,可以迭代键和值

键和值在内部是无法修改的,即使修改了也不会改变遍历的结果

WeakMap(更新中)

WeakMap是弱映射

const m = new WeakMap();

弱映射中的键只能是Object或者继承自Object的类型,使用其他类型会抛出错误

初始化之后可以使用:

  • set()添加键值对
  • get()查询是否存在,并返回值
  • has()查询是否存在,返回布尔值
  • delete()删除

弱映射中的weak意思是这些键不属于正事的引用,所以不会阻止垃圾回收,但是只要键存在,键值对就会存在于映射中,并被当做对值的引用,因此就不会被当做垃圾回收

当作为键的对象没有其他引用时,这个对象键就会被当做垃圾回收,最后映射变成一个空映射

weakmap不能够迭代,因为可能随时被销毁

Set

集合,es6新增,可以包含任何数据类型作为值

//初始化
const s = new Set();
//如果想同时创建实例,可以传入一个可迭代的对象
const s1 = new Set(["val1","val2","val3"]);
  • add():添加值
  • has():查询
  • size():获取元素数量
  • delete():删除元素,会返回布尔值,是否存在想要删除的值
  • clear():清空所有值

用作值的对象和其他“集合”类型在自己的内容被修改时也不会被改变

const s = new Set();
const objval = {};
const arrval = [];
s.add(objval);
s.add(arrval);
objval.bar = "bar";
arrval.push("bar");
console.log(s.has(objval));  //true
console.log(s.has(arrval));  //true

set会维护值插入时的顺序,因此支持按顺序迭代

提供迭代器和values()

const s = new Set(["val1","val2","val3"]);
for(let value of s.values()) {
    alert(value);
}
for(let value of s[Symbol.iterator]()) {
    alert(value);
}

还可以使用拓展操作

const s = new Set(["val1","val2","val3"]);
console.log([...s]);  //["val1","val2","val3"]

set的entries()方法会返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现

const s = new Set(["val1","val2","val3"]);
for(let pairs of s.entries()) {
    console.log(pair);
}
//["val1","val1"]
//["val2","val2"]
//["val3","val3"]

也可以类似Map一样,使用回调函数迭代

WeakSet(更新中)

弱集合

const ws = new WeakSet();

弱集合中的值只能是Object或者继承自Object的类型,

  • add():添加新值,并返回弱集合实例
  • has():查询
  • delete():删除

和weakmap一样,没有被引用时会被当做垃圾回收

不能迭代

迭代与拓展操作

有四种原生集合类型定义了默认迭代器:

  • Array
  • 所有定型数组
  • Map
  • Set

意味着上述的类型都支持顺序迭代,都可以传入for-of循环

都可以兼容拓展操作符,拓展操作符在对可迭代对象执行浅复制时很有用(浅复制只会复制对象引用)

也都支持Array.of()和Array.from()

你可能感兴趣的:(JavaScript,javascript)