总结:
-
函数式编程其实是一种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;
-
它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
Tips:
其实我们很难也不需要在面试过程中去完美地阐述出整套思想,这里也只是浅尝辄止,一些个人理解而已。博主也是初级小菜鸟,停留在表面而已,只求对大家能有所帮助,轻喷🤣;
我个人觉得: 这些编程范式之间,其实并不矛盾,各有各的 优劣势。
理解和学习它们的理念与优势,合理地 设计融合,将优秀的软件编程思想用于提升我们应用;
所有设计思想,最终的目标一定是使我们的应用更加 解耦颗粒化、易拓展、易测试、高复用,开发更为高效和安全;
1、存入的元素唯一,不重复
成员的值都是唯一的,没有重复的值
Map特性
1、键名可以是任意类型数据
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
我觉得很尴尬,堂堂ES6专栏提出的两大新类型,两大新数据结构,它们对比Array和Object的差异只有这么一点。如果说只理解到这个层面的话,我相信大部分人在存储数据时,依旧会先想到Array和Object,而不是Set和Map。事实上确实如此,现在用的最多的依旧是Array和Object。
此时我们就需要深入学习Map和Set
对比Set和Array
首先我们需要理解Set如何实现元素不重复?Array能不能实现元素不重复?
我们先思考下如何实现Array存入元素不重复
class ArrayUnique {
constructor(){
this.container = []
}
has(ele) {
// 这里不使用indexOf和some,而使用原生for来让大家体验下数组查找值时的效率有多低,改进可以用二分
let flag = false;
for (let i = 0; i < this.container.length; i++) {
if(this.container[i] === ele || Object.is(ele, this.container[i])){
flag = true
break
}
}
return flag
}
add(ele) {
if(this.has(ele)) return false
this.container.push(ele)
return true
}
delete(ele) {
if(this.has(ele)) {
let index = this.container.indexOf(ele)
return this.container.splice(index,1)
}
return undefined
}
}
可以发现,Array只要稍加封装就能实现Set的特性。尴尬…
但是ArrayUnique有一种致命的缺点,低效。为什么说他低效呢,我们可以看看add功能实现,由于要保证add进的元素必须唯一,所以每次add前都需要判断该元素是否在数组中已存在,
而判断数组在元素中是否已存在,现有数组直接方法有indexOf,lastIndexOf,includes,间接方法有some,forEach,而我们需要知道这些方法的底层实现都是基于for循环遍历每一个数组元素来和要查找的元素进行对比,所以时间复杂度为O(n),导致数组基于值查找的低效性的原因是数组自身数据结构。
JS中Array的数据结构是啥呢?其实直白点说,Array也是Object。即Array也是键值对形式
可以发现:Array对象的键名就是整数值,键值就是添加进数组的元素,另外Array对象还有一个length属性来记录元素个数。
那么Array为什么要和Object区别开来呢?Object并不是很难实现一个Array形式的对象,比如类数组对象arguments,几乎具有了Array的所有特点,且可以借用Array原型上方法,所以几乎可以认为它们在形式上是一致的。
原因是:底层数据结构不同,数组的底层数据结构是一个线性表,即在内存上体现为一段连续的内存,假设内存被分为大小相同的块,一段连续的内存可以理解为一段紧挨着的多个内存块。我们知道数据存在内存中都是有内存地址的,而数据内存地址的计算和内存块的大小有关,
那么其实我们知道了内存块大小,数组起始位置内存地址,就可以获得数组中所有元素的起始内存地址了
比如创建一个数组长度为9的数组arr,arr的第一个元素位置是0x000,内存块大小是10(假设,真实计算很复杂),那么第一个元素的起始位置就是 0x000 + 0*10 第二个元素的起始位置就是 0x000 + 1*10,第三个元素的起始位置就是 0x000 + 2*10 ,最后一个元素的起始位置就是 0x000 + 8*10,而这个的0,1,2,…,8就是我们熟知的数组元素索引
arr[0],arr[1],arr[2],…,arr[8]
所以,说到现在,我们发现数组的优势是:根据索引号来查找数组元素,速度块,效率高。但是根据数组元素,查找数组元素的索引,那就没有捷径可言了,只能根据索引遍历出每一个数组元素,然后和要查找的数据元素进行对比,再优化一点就需要使用算法了,比如二分查找,排序后二分查找。
我们在回头看Array对象的整数值属性,你就不会觉得它是一个单纯的Object对象的属性了,首先它不是程序员设置的,而是数组底层根据内存块大小和数组对象在堆中起始内存地址计算得到的索引号。所以Array表面看上去是一个Object,平平无奇,但是底层内存层面上,人家有独到之处。
那么Set的底层实现也是数组吗?就像我们上面代码实现的那样?
答:肯定不是,如果是的话,那么Set就没有必要被创造出来了。
Set去重机制,准确来说是底层数据结构决定的,Set的底层数据结构是哈希表,什么是哈希表?
我们通常将哈希表称为数组升级版,或者说是数组“值”查询的救星。
数组在“值”查询上表现拙劣,性能不佳,而哈希表在”值“查询上表现良好,性能不错。
那么哈希表是如何实现”值“查询的呢?
哈希表在内存上,也是开辟一段连续内存用来存储数据,但是有别于数组,哈希表不会将元素按照添加顺序依次存入连续内存中,元素在内存中存储位置是由哈希算法计算得出的。
哈希算法:将元素值 和 哈希表长度 经过一种算法 计算出 元素 在哈希表中的存储位置
最简单的哈希算法,就是 元素值的哈希值 对 哈希表长度求模,得到模值,就是元素在哈希表中的存储位置
假设哈希表长度 5,现在要存入元素a,a的哈希值假设为123,那么123%5 = 3,那么a就存在哈希表的第3块内存上
这样计算元素的存储位置的好处是什么呢?
当我们需要在哈希表中查找某元素位置时,不需要遍历出所有元素再对比,而是直接将要查找的元素 放入计算哈希值的算法中,得到哈希值,然后对哈希表长度求模,就得到了元素在哈希表中的位置。
即哈希表查找元素位置,不是找出来的,是算出来的,算比找快,也就是典型的动脑子比动手快。
根据以上哈希表的工作原理,我们已经可以知道哈希表在内存上虽然也是一段连续内存,但是哈希表不使用索引,也不会产出索引,而是直接根据元素计算出其内存地址。
这也是ES6的Set没有get(index)方法的原因,我们可以发现Set的查找操作has(ele)全部是基于元素本身的,没有基于索引。
那么哈希表真的就完美了吗?
答:不完美
因为哈希表有一个致命缺点,它计算元素存储位置的哈希算法,会发生哈希冲突
什么叫哈希冲突呢?
比如我们有一个元素 ‘abc’,还有一个元素 ‘bca’,如果我们是按照字符串每个字符的哈希值之和的方式得到字符串元素的哈希值,那么上面两个字符串的哈希值相同,而哈希值相同就会导致它们对哈希表长度取模相同,即最终存储在哈希表的同一个位置上,那么是要发生覆盖吗?那肯定不行,因为’abc’和’bca’明显是两个不同的字符串,所以此时就发生了哈希冲突,哈希表处理哈希冲突的方式是拉链法,即哈希表每一个存储位置可以看出一个桶,当需要在一个存储位置上存储多个值的话,就相当于像桶中放数据,而在数据结构上,这个桶就是单向链表。所以哈希表数据结构可以看出是(数组+单向链表),每个数组元素位置上可以拉一条单向链表存储多个值,一次来解决哈希冲突的元素的存放问题。
当哈希表某一个位置发生的哈希冲突次数很多时,会导致该位置拉出来的单向链表越来越长。
当我们在哈希表上查找的值,刚好在某个位置的单向链表上的话,那么此时该值的查询效率就会骤降,因为单向链表的长处在于元素的插入删除,而不在于元素的查询,我们可以将单向链表的查询操作的时间复杂度看成O(n),差不多和数组一样。
而ES6的Set的一个地方的设计也体现了其对于哈希冲突的现象的担忧:
那就是Set没有length,只有size属性,这说明了什么呢?
我们知道数组的length就是数组存储的元素的个数
那么我们敢说哈希表的length,就是哈希表存储的元素个数吗?
答:不敢,因为哈希冲突的原因,哈希表存储元素的个数可能会大于哈希表的长度
所以发现减少哈希冲突的哈希算法,一直是哈希表数据结构的首要目的。
另外,Set去重机制,就是计算插入元素的哈希值 通过哈希算法得到它在哈希表中的位置,得到位置检查该位置是否已有元素,若已有,则对比是否元素相同,若相同,则不插入,达到去重目的,若不同,则说明哈希冲突,需要拉链。
通过以上对于Array和Set底层数据结构的探究,我们再来回顾Set特性
成员的值都是唯一的,没有重复的值
就会发现,这是很浅的理解,Set比较Array的最大不同就是:Set的值查找效率要比Array快的多,原因就是Set底层是哈希表,它查找元素,不是真的找,而是根据哈希算法,算出元素在哈希表中位置。
此时我们再去理解Set的实例方法和实例属性,就会发现它们设计的多么美妙
web浏览器中的javascript
- 客户端javascript
- 在html里嵌入javascript
- javascript程序的执行
- 兼容性和互用性
- 可访问性
- 安全性
- 客户端框架
- 开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
window对象
-
计时器
-
浏览器定位和导航
-
浏览历史
-
浏览器和屏幕信息
-
对话框
-
错误处理
-
作为window对象属性的文档元素