JavaScript红宝书第6章:集合引用类型

简介: 集合引用类型

Object对象


创建对象的两种方式


new+构造函数


let obj=new Object({name:'bob',age:'age'})
console.log(obj);


使用new操作符加构造函数可以创建一个对象


使用对象字面量{}


let obj1={name:'Tom',age:'20'}


使用{}包含可以创造一个对象,{表示一个表达式开始,}则代表这个表达式结束.

注: 在所有现代浏览器中,对象字面量最后一个属性加,不报错,但在一些老的浏览器中,会发生报错。


在上述两小节中,我们可以得出new Object在一定程度上等于{},区别是前者是用构造函数来创建对象,后者是用对象字面量来创建。{}是ES3语法,在较旧的js版本中不支持,而new Object在兼容所有js环境。


存取对象属性两种方式


点语法和中括号语法


但在特殊情况下,如属性名中有空格,那么可以通过[]来进行存取。 举例:

let obj1={age:'20'}
    obj1.name='Tom'
    obj1['My Cat']='Jack'
    console.log(obj1.name,obj1['My Cat']);//Tom Jack


且[]可以获取到动态属性值,而点语法只能获取到静态属性值,举例:

let obj={name:'Tom'}
const nickName='name'
console.log(obj.nickName,obj.name);//undefined 'Tom'
console.log(obj[nickName],obj['name']);//undefined 'Tom'


nickName的值等于name,中括号如果里面不加’'则会提取nickname的值来作为属性名,而点语法则只有静态,不能进行提取。


数组


创建数组方式


Array构造函数


构造函数和Object构造函数类似,不同的是,它如果输入一个数字,且只输入该数字,则该数字就是数组长度。

let arr=new Array(20);
let arr1=new Array(20,'Tom')
let arr2=new Array('Jack','Tom','John')
console.log(arr.length,arr1.length,arr2.length)//20 2 3


数组字面量[ ]


let arr=[];
let arr1=[1,3]
let arr2 =[1,2,3,4,,,,,]
console.log(arr,arr1,arr2)//[] [1,3] (8) [1, 2, 3, 4, empty × 4]
console.log(arr.length,arr1.length,arr2.length);//0 2 8



和对象字面量类似,数组字面量实现和构造函数一样的效果,在上述代码的arr2中我们发现加了很多空,这些,被用作占位,打印出来是undefined(未定义的值),有占位符的数组也叫稀疏数组,为了代码整洁和增强可读性一般不建议使用。


创建数组方法(form&of)

of方法


of方法可以将一组参数转换成数组实例,举例

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


from方法


From方法可以把任何可迭代的结构从类数组对象,转换成数组实例。js当中大多数都是可迭代对象,这里举几个不是迭代对象的例子:


不可迭代数据类型number、boolean、Math、symbol、null、undefined、JSON

注:ES6中引入的迭代器协议,我们也可以通过实现Symbol.iterator方法来对一个不可迭代对象实现可迭代。那么它就可以被from方法所调用并转换成数组实例。


from方法是在获取到一个可迭代结构后,新建一个数组,并把这个结构返回一个新数组,那么这个数组的地址的新的,但值还是可迭代结构转换成数组后的引用。举例:

    let str='123'
    let ma=new Map().set(1,2)
              .set(3,4)
    let se=new Set().add(1).add(3).add(5)
    console.log(str,ma,se);//123 Map(2) {1 => 2, 3 => 4} Set(3) {1, 3, 5}
    console.log(Array.from(str),Array.from(ma),Array.from(se));
    //(3) ['1', '2', '3'] (2) [Array(2), Array(2)] [1, 3, 5]


也可以通过对arguments对象进行改变。

arguments 对象是 JavaScript 中的一个特殊对象,它在函数内部自动创建,并包含了函数被调用时传递的参数信息。



a数组复制给b数组,=和from的区别是什么?



from实现过程:

1.获取可迭代对象,2.遍历可迭代对象中的元素,可迭代对象转换成新数组 3.返回新数组

转换后的数组是被创建出来的新数组,和原数组堆地址不同,引用值相同。所以它虽然也是浅拷贝,但是它俩改变非对象索引的值,不会发生一变都变的效果。



等于=实现过程:

1.获取原始数组

2.复制引用值,引用堆地址

所以说=被复制和复制的数组都用一个引用堆地址,就会出现一变都变的情况,不管它们是不是对象索引值。


数组空位(稀疏数组)


let arr=[,]

这个就被称为稀疏数组,ES6中,打印出来为undefined。

正常来说遍历这种未定义的值数组时,会跳过,但使用ES6新增的from方法,则会返回undefined,未定义的值。


数组索引(length)


数组可以通过length属性来判断长度,同时也可以通过改变长度修改数组。

let arr=[1,2,3]
console.log(arr);//(3) [1, 2, 3]
arr.length=2;
console.log(arr);//(3) [1, 2, 3]


如果想在数组末尾添加元素也可以通过**arr[arr.length]=‘元素值’**进行添加。


数组检测(instanceof&isArray)


常用的两种方法:instanceof、isArray()

instanceof: value instanceof Array

isArray: Array.isArray(value)


前者在遭遇多个全局执行上下文|框架时,会出现报错,因为主页面的Array构造函数和子页面的Array构造函数不同,每个框架或页面都有自己原生的Array构造函数,所以就可能出现报错问题。举例:

我有一个页面,有用到iframe子页面框架


<body>
  <iframe src="./angular-01.html"></iframe>
  <script>
    // 在全局作用域中定义一个值
    var value = [1, 2, 3];
    // 判断 value 是否为数组
    if (value instanceof Array) {
      console.log("Value is an arrayS");  // 在主页面输出
    }
  </script>
</body>


子页面angular-01.html

<body>
  <script>
    // 在 frame1.html 的全局作用域中重新定义 Array
    var Array = "I'm not an array!";
    // 使用与主页面相同的代码来判断 value 是否为数组
    if (value instanceof Array) {
      console.log("Value is an array");  // 不会输出
    }
  </script>
</body>


如果有多个执行上下文或多个iframe框架,则可以使用isArray来检测数组。


迭代器方法(keys、values、entries)

这三种方法都是实现对数组迭代,区别是获得的内容不同

keys:数组索引(键)

values:数组各个属性值(值)

entries:数组索引和值(键值对)

  let arr=['apple','banana','orange']
  console.log(Array.from(arr.keys()));// (3) [0, 1, 2]
  console.log(Array.from(arr.values()));//(3) ['apple', 'banana', 'orange']
  console.log(Array.from(arr.entries()));//(3) [Array(2), Array(2), Array(2)]


复制和填充方法(copyWithin&fill)


fill填充方法


语法格式:

一个参数:数组名.fill(填充数)

两个参数:数组名.fill(填充数,最小索引值)

三个参数:数组名.fill(填充数,最小索引值,最大索引值)



举例:

      let oneFill = [0, 1, 2, 3, 4]
      let twoFill = [0, 1, 2, 3, 4]
      let threeFill = [0, 1, 2, 3, 4]
      oneFill.fill(5);
      console.log(oneFill); //(5) [5, 5, 5, 5, 5]
      twoFill.fill(6, 1)
      console.log(twoFill); //(5) [0, 6, 6, 6, 6]
      threeFill.fill(5, 2, 4)
      console.log(threeFill); //(5) [0, 1, 5, 5, 4]



copyWithin复制方法


浅复制自身数组索引,一共有三个参数,语法格式如下:

let arr=[0,1,2,3,4,5,6,7,8,9]
arr.copyWithin(5);//复制从索引为0的值开始,复制的位置在索引为5的值//0,1,2,3,4,0,1,2,3,4
let arr1=[0,1,2,3,4,5,6,7,8,9]
console.log(arr1);
arr1.copyWithin(2,4)//开始复制的位置是2,起始位置索引
console.log(arr1);//0,1,4,5,6,7,8,9,8,9
let arr2=[0,1,2,3,4,5,6,7,8,9]
console.log(arr2)
arr2.copyWithin(2,4,8)
console.log(arr2);//0,1,4,5,6,7,6,7,8,9
//被复制值,起始位置:4,结束位置:8,复制地点:2,如果没有结束位置,则一直复制到数组末尾。



转换方法(toLocalString,toString,join)


toLocalString和toString


它俩都是用于数组转换成字符串的方法。

toString() 方法返回一个表示数组元素的字符串,并使用逗号分隔每个元素。

默认情况下,toString()方法不会对数组元素进行任何格式化或本地化处理。

例如,对于数组 [1, 2, 3],调用 toString() 方法将返回字符串 “1,2,3”。


toLocaleString() 方法将数组转换为一个本地化后的字符串,根据不同的语言环境可能有不同的输出。 与 toString()

不同,toLocaleString() 对数组元素进行了本地化处理,根据语言环境应用相应的格式。

例如,在某些语言环境下,日期、时间和数字可能以特定的方式显示。 toLocaleString()

方法还接受一些可选参数,用于指定语言环境和格式选项。 例如,对于日期数组 [new Date()],调用 toLocaleString()

方法将返回日期的本地化字符串表示。



join

实现数组转字符串,还可以往每个元素中添加参数,来进行隔断,如join(‘,’)等

let arr=['red','green','yellow']
let str1=arr.join('')
let str2=arr.join(',')
console.log(str1+'\n'+str2);
//greenyellow
// red,green,yellow



栈方法(pop、push)

首先讲一下堆栈概念: 堆栈是一种后进先出(Last-In-First-Out,LIFO)的数据结构。 元素在堆栈的顶部进行插入和删除操作。

最后插入的元素始终位于堆栈的顶部,即最新的元素可被首先访问或移除。 类似于现实生活中的一叠盘子,只能从顶部放入或取出。

常见的应用场景包括函数调用、表达式求值、撤销操作等。 例如,浏览器的历史记录就可以看作是一个堆栈。.


js这两个方法类似于堆栈进行操作,pop就是释放最外层元素,push就是在最外层加一个元素。

let arr=['red','green','yellow']
arr.pop()
arr.push('blue')
console.log(arr);
// [
//     "red",
//     "green",
//     "blue"
// ]


队列方法(shift、push)

队列概念·


队列是一种先进先出(First-In-First-Out,FIFO)的数据结构。

元素在队列的尾部(末尾)进行插入,而从队列的头部(开头)进行删除。 新元素被添加到队列的末尾,而最早添加的元素位于队列的开头。

类似于现实生活中排队等候的概念,先来先服务。 常见的应用场景包括任务调度、消息传递等。 例如,打印机队列就是一个典型的队列。


shift方法是在数组头部,进行释放动作,push是在数组末尾进行添加动作,这两个方法组合就变成了队列方法。

let arr=['red','green','yellow','pink']
arr.push('blue','black')
arr.shift()
console.log(arr);
// [
//     "green",
//     "yellow",
//     "pink",
//     "blue",
//     "black"
// ]


unshift

除了shift方法外,还有一个unshift方法,和shift方法相反,unshift方法主要做了往数组头部添加元素的动作。


排序方法(sort、reverse)

reverse翻转数组


通俗来说,把数组反过来,12345变成54321(,省略了)

let arr=['red','green','yellow','pink']
arr.reverse()
console.log(arr);
// [
//     "pink",
//     "yellow",
//     "green",
//     "red"
// ]


sort排序

sort默认是升序,把数组从小到大进行排序。

let arr=[5,4,2,6,3]
arr.sort()
console.log(arr);//(5) [2, 3, 4, 5, 6]



也可以通过箭头函数或者普通函数来进行降序或者复杂的动作。

let arr=[5,4,2,6,3]
arr.sort((a,b)=>b-a)
console.log(arr);//(5) [6, 5, 4, 3, 2]



操作方法(concat、slice、splice)

concat

concat主要是用于新数组的拼接以及数组拍平操作。

拍平有一个数组属性:

Symbol.isConcatSpreadAble


,这个属性可以控制数组是否被强制拍平,默认是true。

数组拼接

let arr1=[1,2,3]
let arr2=[9,8]
let arr3=arr1.concat(arr2)
console.log(arr3);//(5) [1, 2, 3, 9, 8]
arr2=arr2.concat(1,2)
console.log(arr2);//(4) [9, 8, 1, 2]
arr1=arr1.concat('yes','no')
console.log(arr1);//(5) [1, 2, 3, 'yes', 'no']
arr1=[4,5,6].concat(1)
console.log(arr1);//(4) [4, 5, 6, 1]
//拍平
      let arr=[1,2,3,4]
      let arrs=[2,3,4]
      let arra=[3,4,5]
      arrs[Symbol.isConcatSpreadable]=false;
      arra[Symbol.isConcatSpreadable]=true;
      console.log(arr.concat(arrs));//(5) [1, 2, 3, 4, Array(3)]
      console.log(arr.concat(arra));//(7) [1, 2, 3, 4, 3, 4, 5]



slice

切割原数组得到新数组的一个方法,可以传正负值。

let arr=[0,1,2,3,4]
console.log(arr.slice(1));//得到索引>1的新数组
//(4) [1, 2, 3, 4]
console.log(arr.slice(1,2));//得到索引>=1同时<2的新数组
//[1]


传负值,一般会从结束索引反推,比如上述数组,传值-2,-1,如果负数过大,则会返回空数组。

console.log(arr.slice(-2,-1));//[2]



splice(删除、添加、替换)

删除
let arr=[0,1,2,3,4];
//删除 语法格式[删除开始位置,删除截止位置]
console.log(arr.splice(0,2),arr);//(2) [0, 1] (3) [2, 3, 4]




添加
let arr=[0,1,2,3,4];
//添加 语法格式[开始位置,删除数量,添加内容....]
console.log(arr.splice(0,1,'red','blue'),arr);//[0] (6) ['red', 'blue', 1, 2, 3, 4]


替换

替换和添加是一样的语法格式,只不过替换是要先删除原有索引下的内容,然后替换成新的。

let arr=[0,1,2,3,4];
//添加 语法格式[开始位置,删除数量,替换内容....]
console.log(arr.splice(0,1,6),arr);//[0] (5) [6, 1, 2, 3, 4]




在上述代码中,0就被替换成6。

显式调用和隐式调用的区别



显式调用是指直接调用函数,如fun().隐式调用是指用对象下的方法,用对象进行调用,a.fun()等,属于隐式调用。

6. 显式调用要求直接使用函数名来调用函数,而隐式调用则需要通过对象的方法或属性来调用函数。

7. 显式调用可以在任何地方进行,而隐式调用必须通过对象来进行,因为函数和对象之间有关联。

8. 在隐式调用中,函数内部的 this 关键字会绑定到调用该函数的对象上,而显式调用则可以自由地指定 this 的值。


搜索和位置方法

按严格相等搜索是基于值的相等性进行比较,使用严格相等运算符(===)来查找数组中与指定值严格相等的元素,返回第一个或最后一个匹配项的索引。


按断言函数搜索是基于自定义条件进行比较,使用传入的断言函数对每个元素进行判断,返回满足条件的第一个元素或其索引。它允许根据特定的需求编写复杂的条件来搜索数组中的元素


严格相等搜索(indexOf、lastIndexOf、includes)


前两者为所有版本都可以使用,includes为ES7新出现的搜索方法。

lastIndexOf是从后往前搜索,而indexOf和includes是从前往后搜索。

includes查询Object或数组返回都是true\false,indexOf和lastIndexOf返回的都是索引位置或-1、


举例:

let arr=[0,1,2,3,4,4,3,2,1,0];
console.log(arr.indexOf(3),arr.indexOf(5));//3 -1
console.log(arr.lastIndexOf(3),arr.lastIndexOf(5));//6 -1
console.log(arr.includes(3),arr.includes(5));//true false



断言函数搜索(find、findIndex)

ES6新出现的断言函数.举例

let person=[
  {'name':'Jack',age:20},
  {'name':'Tom',age:21},
  {'name':'John',age:10}
]
console.log(person.find((item,index,array)=>item.age>18))
//{name: 'Jack', age: 20}
console.log(person.findIndex((item,index,array)=>item.age<18));
//2


前者返回值,后者返回索引,都是从数组头部开始判断,每个索引都调用这个函数,只返回第一个匹配的值。



迭代方法(every、filter、forEach、map、some)


every方法与some方法

    同:两个方法都返回true、false。
    异:every方法需要所有值都满足条件,才会返回true。
        some方法需要至少有一个值满足条件,返回true。


举例:

let person=[
  {'name':'Jack',age:20},
  {'name':'Tom',age:21},
  {'name':'John',age:10}
]
console.log(person.every((item)=>item.age>18));//false
console.log(person.some((item)=>item.age>18));//true


filter、map方法

同: 新数组为返回值,原数组不发生改变。 时间复杂度:O(n)

异: filter为条件筛选后的新数组。map为把数组中元素操作后的新数组。


举例:

let person=[
  {'name':'Jack',age:20},
  {'name':'Tom',age:21},
  {'name':'John',age:10}
]
let fil=person.filter(item=>{
  return item.age>18;
})
let ma=person.map(item=>{
  if(item.age>18){
    return item.age*2;
  }else return item.age;
})
console.log(fil,ma);
// {name: 'Jack', age: 20}
// {name: 'Tom', age: 21}
//(3) [40, 42, 10]


forEach方法

特点:

无返回值,时间复杂度O(n),遍历数组并执行指定操作。


let person=[
  {'name':'Jack',age:20},
  {'name':'Tom',age:21},
  {'name':'John',age:10}
]
person.forEach(item=>{
  item.age++;
})
console.log(person);//age都加1


归并方法(reduce、reduceRight)

归并方法,用于将数组的元素聚合成单个值。

它接受一个回调函数作为参数,在每次迭代中将累加器和当前元素组合起来。

回调函数的返回值将成为下一次迭代的累加器值。最后将这个值作为返回值。

区别:reduce从第一项开始遍历,reduceRight从最后一项开始遍历

let red=[1,2,3,4,5];
console.log(red.reduce((rev,rep)=>rev-rep));//-13
//1-2-3-4-5=-13
console.log(red.reduceRight((rev,rep)=>rev-rep));//-5
//5-4-3-2-1=-5


Map、WeakMap、Set 类型


Map基本语法

通常使用 Map 数据结构来表示键值对的集合。


四大方法

get:获取map集合中的属性。

set:添加map集合中属性。

has:判断map集合是否有某个属性。

delete:删除map集合中的属性。


举例:

let a=new Map([
  ['name','key'],
  ['age',18]
])
console.log(a.get('name'));//key
a.set('animal','cat')
console.log(a.get('animal'));//cat
console.log(a.has('animal'));//true
console.log(a.delete('animal'),a.has('animal'));//true false



Map特性、Map与Object区别

特性


键可以是任何数据类型:在Map中,键可以是任何JavaScript的数据类型,包括原始类型(如字符串、数字等)和引用类型(如对象、函数等)。这使得Map在处理复杂数据结构时非常有用。

保持插入顺序:Map会记住元素插入的顺序,并按照插入顺序进行迭代。这在需要按顺序访问元素时非常有用。

动态大小:与数组不同,Map没有固定的长度限制。它可以根据需要自动扩展以容纳更多的元素。



区别

1.Map在内存占用可以比Object多存储50%的键值对。

2.插入性能Map较Object快。(Map使用哈希表)。

3.查找速度Object较快。

4.删除性能Map较Object强。因为Object需要用到垃圾回收机制。


weakMap

因为Map即使没有被引用也不会被垃圾回收。会出现内存泄漏的问题,但weakMap解决了这个问题,它如果没被引用,则会被回收,从而预防了内存泄漏的问题。

因为是Map的子集,所有Map的API它大多都可以调用。

它出现的价值有三点:

1.预防内存泄漏。

2.保持数据的隐私性:因为外部无法直接访问weakMap内部的键值。

缺点:

1.无法迭代 2.无法获取大小长度。3.键必须是对象类型,不能是原始类型。


Set

set是一种具有无序和唯一性的集合。无序是指,它的属性没有先后顺序之分,唯一是指,它定义的值,不能再次添加,接下来简单讲解一下它的四个方法和size属性。



语法格式:

let se=new Set();


add、delete、has方法|size属性

      let se=new Set([
        'val1','val2'
      ])
      se.add('val3').add('val4').add('val2')
      console.log(se);//Set(4) {'val1', 'val2', 'val3', 'val4'}
      let boo=se.has('val1')
      console.log(boo,se.size);//true
      console.log(se.delete('val1'),se.has('val1'),se.size);//true false 3
      se.clear();
      console.log(se.size);//0



上述代码中,add是用来添加set元素,可以多次添加,但是不能添加已有的属性值。

add添加,delete删除,has查询,clear删除所有元素,size查询当前集合长度。


顺序和迭代

使用keys、values或者[Symbol.iterator]属性可以迭代。

举例:

let se=new Set([
  'val1','val2'
])
for(item of se.values()){
  console.log(item);//val1 val2
}
for(item of se[Symbol.iterator]()){
  console.log(item);//val1 val2
}
for(item of se.keys()){
  console.log(item);//val1 val2
}



扩展操作符

[…]


let arr1 = [1, 2, 3]; 
let arr2 = [0, ...arr1, 4, 5]; 
console.log(arr2);//(6) [0, 1, 2, 3, 4, 5]


总结

林林总总写了一星期,map和set那里写的很少,只讲了基本用法。那么好,本期内容到此结束。

相关文章
|
6月前
|
资源调度 JavaScript 前端开发
Day.js常用方法集合
Day.js常用方法集合
209 0
|
6月前
|
JavaScript 前端开发 定位技术
JavaScript 中如何代理 Set(集合) 和 Map(映射)
JavaScript 中如何代理 Set(集合) 和 Map(映射)
107 0
|
12天前
|
存储 JavaScript 前端开发
js的基础类型和引用类型
【10月更文挑战第29天】理解 JavaScript 中的基础类型和引用类型的区别对于正确地编写代码和理解程序的行为非常重要。在实际开发中,需要根据具体的需求合理地选择和使用不同的数据类型,以避免出现一些意想不到的错误和问题。同时,在处理引用类型数据时,要特别注意对象的引用关系,避免因共享引用而导致的数据不一致等问题。
|
5月前
|
存储 JavaScript 前端开发
JavaScript进阶-Map与Set集合
【6月更文挑战第20天】JavaScript的ES6引入了`Map`和`Set`,它们是高效处理集合数据的工具。`Map`允许任何类型的键,提供唯一键值对;`Set`存储唯一值。使用`Map`时,注意键可以非字符串,用`has`检查键存在。`Set`常用于数组去重,如`[...new Set(array)]`。了解它们的高级应用,如结构转换和高效查询,能提升代码质量。别忘了`WeakMap`用于弱引用键,防止内存泄漏。实践使用以加深理解。
77 3
|
29天前
|
存储 JavaScript 前端开发
JavaScript 数据类型详解:基本类型与引用类型的区别及其检测方法
JavaScript 数据类型分为基本数据类型和引用数据类型。基本数据类型(如 string、number 等)具有不可变性,按值访问,存储在栈内存中。引用数据类型(如 Object、Array 等)存储在堆内存中,按引用访问,值是可变的。本文深入探讨了这两种数据类型的特性、存储方式、以及检测数据类型的两种常用方法——typeof 和 instanceof,帮助开发者更好地理解 JavaScript 内存模型和类型检测机制。
67 0
JavaScript 数据类型详解:基本类型与引用类型的区别及其检测方法
|
2月前
|
JavaScript 前端开发 索引
JavaScript HTML DOM 集合(Collection)
JavaScript HTML DOM 集合(Collection)
28 4
|
4月前
|
JavaScript API 索引
JS【详解】Set 集合 (含 Set 集合和 Array 数组的区别,Set 的 API,Set 与 Array 的性能对比,Set 的应用场景)
JS【详解】Set 集合 (含 Set 集合和 Array 数组的区别,Set 的 API,Set 与 Array 的性能对比,Set 的应用场景)
65 0
|
4月前
|
存储 缓存 JavaScript
浅谈JS的映射和集合
浅谈JS的映射和集合
30 0
|
6月前
|
存储 JavaScript
JS中原始类型与引用类型
JS中原始类型与引用类型