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 前端开发 定位技术
JavaScript 中如何代理 Set(集合) 和 Map(映射)
JavaScript 中如何代理 Set(集合) 和 Map(映射)
56 0
|
9月前
|
前端开发
前端学习笔记202306学习笔记第三十六天-js-对象是属性的无序集合1
前端学习笔记202306学习笔记第三十六天-js-对象是属性的无序集合1
38 0
|
6天前
|
缓存 JavaScript 前端开发
从0开始学习JavaScript--JavaScript中的集合类
JavaScript中的集合类是处理数据的关键,涵盖了数组、Set、Map等多种数据结构。本文将深入研究这些集合类的创建、操作,以及实际应用场景,并通过丰富的示例代码,帮助大家更全面地了解和应用这些概念。
|
5月前
|
JavaScript 前端开发 Java
|
6月前
|
JavaScript
vue.js代码开发最常见的功能集合
vue.js代码开发最常见的功能集合
50 0
|
7月前
|
JavaScript
JS 如何快速遍历一个集合
JS 如何快速遍历一个集合
33 0
|
7月前
|
JavaScript 前端开发
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(1)
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(1)
|
7月前
|
JavaScript 前端开发
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(2)
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(2)
|
9月前
|
前端开发
前端学习笔记202306学习笔记第三十六天-js-对象是属性的无序集合3
前端学习笔记202306学习笔记第三十六天-js-对象是属性的无序集合3
50 0
|
9月前
|
前端开发
前端学习笔记202306学习笔记第三十六天-js-对象是属性的无序集合2
前端学习笔记202306学习笔记第三十六天-js-对象是属性的无序集合2
29 0