【数据结构与算法】详解什么是哈希表,并用代码手动实现一个哈希表

在这里插入图片描述

其实当发生冲突时,寻找空白的位置也有三种方法,分别是 线性探测二次探测再哈希法

1. 线性探测

顾名思义,线性探测的意思就是,当某两个元素发生冲突时,将当前索引+1,查看该位置是否为空,是的话就插入数据,否则就继续将索引+1,以此类推……直到插入数据位置。

但这种方法有一个缺点,那就是当数组中连续很长的一个片段都已经插入了数据,此时用线性探测就显得效率没那么高了,因为每次探测的步长都为1,所以这段都已经插入了数据的片段都得进行探测一次,这种现象叫做 聚集。如下图,就是一个典型的聚集现象

在这里插入图片描述

图书8 的下标值为1,与 图书3 冲突,然后进行线性探测,依次经过 图书6、5、1、7 都没有发现有空白位置可以插入,直到末尾才找到空白位置插入,这样挺不好的,所以我们可以选用 二次探测 来缓解 聚集 这种现象。

2. 二次探测

二次探测 在线性探测的基础上,将每次探测的步长改为了当前下标值 index + 1²index + 2²index + 3² …… 直到找到空白位置插入元素为止

还是举一个例子来理解一下 二次探测

假如现在已存入 图书3图书5图书7,如图

在这里插入图片描述

然后此时要存入一个 图书6,通过哈希化以后求得的下标值为2,与 图书5 冲突了,所以就从索引2的位置向后再移动 个位置,但此时该位置上已存有数据,如下面这个动图演示

在这里插入图片描述

所以此时从索引为2的位置向后移动 个位置,此时发现移动后的位置上也已存有数据,所以仍无法插入数据,如下面这个动图演示

在这里插入图片描述

因此,我们继续从索引2的位置向后移动 个位置,此时发现,移动后的位置上有空余位置,于是直接在此插入数据,这样一个二次探测的过程就完成了,如下列动图演示

在这里插入图片描述

我们可以看到,二次探测 在一定程度上解决了 线性探测 造成的 聚集 问题,但是它却在另一种程度造成了一种聚集,就比如 …… 上的聚集。所以这种方式还是有点不太好。

3. 再哈希法

再哈希法 就是再将我们传入的值进行一次 哈希化,获得一个新的探测步数 step,然后按照这个步数进行探测,找到第一个空着的位置插入数据。这在很大的程度上解决了 聚集 的问题。

既然要再进行哈希化获得一个探测的步数,那么这个哈希化的处理过程一定要跟第一次哈希化的处理过程不一样,这样才能确认一个合适的搜索步长,提高查找效率。

这里,我们就不用担心如何写一个不一样的哈希函数了,给大家看一个公认的比较好的哈希函数:step = constant - (key % constant)

其中,constant 是一个自己定的质数常量,且小于数组的容量; key 就是第一次哈希化得到得值。

然后我们再通过这个函数算得的步长来进行查找搜索空位置进行插入即可,这里就不做过多的演示了。

四、哈希表的扩容和减容

====================================================================

在了解哈希表的扩容之前,我们来了解一个概念,叫做填充因子,它表示的是哈希表中的数据个数与哈希表长度的比值。其决定了哈希表的存取数据所需的时间大小。

当我们用第一种解决冲突的办法——拉链法,填充因子最小为0,最大为无限大,这是因为该方法是通过在数组中的某个位置插入一个数组用来存储互相冲突的元素,因此,只要有可能,哈希表的长度可以很小,然后数据都存储在内置的数组中,这样填充因子就可以无限大了。

那当我们用第二种解决冲突的办法——开放地址法,填充因子最小为0,最大只能为1,这是因为开放地址法的实现原理是找哈希表中空位置插入元素,因此哈希表中的数据量不会大于哈希表的长度,从而填充因子最大也只能是1。

在这里插入图片描述

把哈希表比作是个教室,如果教室里坐满了人,然后让你从中找出你的朋友,那密密麻麻的,是不是特别不好找,可能会眼花缭乱;但是如果这个教室里只坐了 2/3 或者 1/2 的人,那么人群看起来就没那么密密麻麻,那让你找到你的朋友也许会相对容易一点。

也正因为这样的情况,我们可以在适当的时候根据填充因子的大小对哈希表的长度进行扩大,从而减小填充因子的大小,降低我们数据存取所耗费的时间。

这里我们就将填充因子等于 0.75 作为哈希表扩容的临界点,同时会在后面封装哈希表的时候实现扩容

当然,填充因子太小也是不合适地,所以我们也会在适当的地方添加减容功能,即将填充因子等于 0.25 作为哈希表减容的临界点。

五、哈希表的方法

=================================================================

老规矩,我们在封装哈希表之前,先来看看哈希表常见的方法都有哪些

| 方法 | 含义 |

| — | — |

| put() | 向哈希表中插入数据或修改哈希表中数据 |

| get() | 获取哈希表中的某个数据 |

| del() | 删除哈希表中某个数据 |

| isEmpty() | 判断哈希表是否为空 |

| size() | 返回哈希表内元素个数 |

| resize() | 改变哈希表容量,并将数据放到正确的位置上 |

| isPrime() | 判断某个数是不是质数 |

| toPrime() | 获取离某个数最近的质数 |

六、用代码实现哈希表

===================================================================

前提:

  1. 本文选用链地址法解决冲突问题

  2. 涉及到常量的地方,都选用质数,例如哈希表容量 、霍纳算法的常量等。因为在数论上,使用质数可以尽可能地使数据在哈希表中均匀分布

(1)创建一个构造函数


首先创建一个大的构造函数,用于存放哈希表的一些属性和方法。

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

}

因为我们是通过数组来实现的哈希表,所以设置了属性 storage 来存储数据;然后定义了属性 count 用于统计哈希表内的数据个数,方便之后用于计算填充因子;最后定义了属性 length 设定了哈希表的初始长度为质数7

(2)封装哈希函数


在文章的开头,我就用霍纳算法讲解了哈希化的过程,因此我们在封装哈希函数时,就也通过霍纳算法的最终化简结构来实现

这里,我放上霍纳算法的化简结果,方便大家观看学习

P(n) = a 0 a_0 a0​ + x( a 1 a_1 a1​+x( a 2 a_2 a2​ +…+ x( a n a_n an​ − 1 + x a n a_n an​) ) )

我们来看一下代码

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

}

这里的霍纳算法中的常数我就随便取了一个质数37,大家也可以随便选别的稍微大一点的质数

哈希函数接收两个参数,第一个参数为 str ,即我们之后传入数据的 key ;第二个参数为 size ,即哈希表的长度,所以可以直接进行调用我们设定的属性 this.length

(3)实现put()方法(不具备扩容功能)


put()方法就是向哈希表插入数据或者修改哈希表中某数据。

该方法接收两个参数,第一个参数为 key;第二个参数为 value 。即相当于传入一个键值对

实现思路:

  1. 通过哈希函数,将 key 哈希化,获取一个索引 index

  2. 判断哈希表 storage 数组的索引 index 上有无数据,若无,则直接在该位置上创建一个空数组 arr,并把我们传入的键值对打包放到一个新的数组中存到 arr

  3. 若有数据,则遍历该索引上的数组每个元素,比对每个元素的 key 是否与我们传入的 key 相等,若有查询到相等的值,则用我们传入的 value 替换查询到的该元素中的 value ,这就实现了修改数据的功能

  4. 若没有查询到相等的值,则直接将我们的键值对打包放到一个新的数组中存储到哈希表 index 索引上的数组中去,此时 this.count + 1

为了方便大家理解,我用动图来给大家演示该方法的实现过程

首先是插入数据操作

在这里插入图片描述

接下来是修改数据操作

在这里插入图片描述

我们来看一下代码

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

// 插入或修改数据

HashTable.prototype.put = function (key, value) {

// 1.哈希化获得下标值

let index = this.hashFunc(key, this.length)

let current = this.storage[index]

// 2.判断该下标值的位置是否有数据

// 2.1无数据

if(!current) {

this.storage[index] = [[key, value]]

this.count ++

return;

}

// 2.2有数据

// 3.遍历对应索引上的数组

for (let i = 0; i < current.length; i ++) {

// 3.1已存在相同数据

if(current[i][0] === key) {

current[i][1] = value

return;

}

}

// 3.2未存在相同数据,直接添加数据

current.push([key, value])

this.count ++

}

}

我们来使用一下该方法

let ht = new HashTable()

// 执行put()方法6次

ht.put(‘abc’, ‘123’)

ht.put(‘hgf’, ‘124’)

ht.put(‘wds’, ‘125’)

ht.put(‘wer’, ‘126’)

ht.put(‘kgl’, ‘127’)

ht.put(‘kmg’, ‘128’)

// 查看哈希表内数据个数

console.log(ht.count) // 6

// 通过storage属性查看一下哈希表的内部结构

console.log(ht.storage)

/* storage打印结果

[

[ [ ‘wds’, ‘125’ ], [ ‘kgl’, ‘127’ ], [ ‘kmg’, ‘128’ ] ],

[ [ ‘wer’, ‘126’ ] ],

<1 empty item>,

[ [ ‘hgf’, ‘124’ ] ],

[ [ ‘abc’, ‘123’ ] ]

]

*/

此时的哈希表内部是这样的

在这里插入图片描述

(4)实现get()方法


get()方法是用于查询哈希表中某个数据。该方法直接收一个参数,即用于查询的 key

实现思路:

  1. 通过哈希函数,将 key 哈希化,获取一个索引 index

  2. 判断哈希表 storage 数组的索引 index 上有无数据,若无,则返回 false

  3. 若有数据,则遍历该索引上的数组每个元素,比对每个元素的 key 是否与我们传入的 key 相等,若有查询到相等的值,则返回该值的 value

  4. 若无数据,则返回 false

思路和代码都比较简单,我们直接来看代码

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

// 获取数据

HashTable.prototype.get = function (key) {

// 1.获取相应的下标值

let index = this.hashFunc(key, this.length)

let current = this.storage[index]

// 2.判断该下标值的位置是否有数据

// 2.1 若该下标值位置不存在任何数据,则查找失败

if(!current) {

return false

}

// 2.2 该下标值位置有数据

// 3. 进行遍历查找

for(let i in current) {

// 3.1 找到对应数据并返回value

if(current[i][0] === key) {

return current[i][1]

}

}

// 3.2 没有找到对应数据,返回false

return false

}

}

我们来使用以下该方法

let ht = new HashTable()

// 执行put()方法 6次

ht.put(‘abc’, ‘123’)

ht.put(‘hgf’, ‘124’)

ht.put(‘wds’, ‘125’)

ht.put(‘wer’, ‘126’)

ht.put(‘kgl’, ‘127’)

ht.put(‘kmg’, ‘128’)

// 执行get()方法,获取 key为 ‘hgf’ 的值

console.log(ht.get(‘hgf’)) // 124

(5)实现del()方法(不具备减少容量功能)


del()方法是删除哈希表中某个数据。该方法接收一个参数 key

实现思路:

  1. 通过哈希函数,将 key 哈希化,获取一个索引 index

  2. 判断哈希表 storage 数组的索引 index 上有无数据,若无,则返回 false ,表示删除失败

  3. 若有数据,则遍历该索引上的数组每个元素,比对每个元素的 key 是否与我们传入的 key 相等,若有查询到相等的值,则直接删除该值,此时 this.count --,并返回被删除元素的 value

  4. 若没有查询到相等的值,则返回 false ,表示删除失败

我们来看一下代码

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

// 删除数据

HashTable.prototype.del = function (key) {

// 1.获取相应的下标值

let index = this.hashFunc(key, this.length)

let current = this.storage[index]

// 2. 判断该索引位置有无数据

// 2.1 该下标值位置没有数据,返回false,删除失败

if(!current) {

return false

}

// 2.2 该下标值位置有数据

// 3. 遍历数组查找对应数据

for (let i in current) {

let inner = current[i]

// 3.1 找到对应数据了,删除该数据

if(inner[0] === key) {

current.splice(i, 1)

this.count –

return inner[1]

}

}

// 3.2 没有找到对应数据,则删除失败,返回false

return false

}

}

我们来使用一下该方法

let ht = new HashTable()

// 执行put()方法 6次

ht.put(‘abc’, ‘123’)

ht.put(‘hgf’, ‘124’)

ht.put(‘wds’, ‘125’)

ht.put(‘wer’, ‘126’)

ht.put(‘kgl’, ‘127’)

ht.put(‘kmg’, ‘128’)

// 删除 key为 'hgf’的元素

ht.del(‘hgf’) // 删除成功,返回 124

// 删除 key为 'ppp’的元素

ht.del(‘ppp’) // 删除失败,返回 false

// 查看哈希表内部结构

console.log(ht.storage)

/* storage打印结果

[

[ [ ‘wds’, ‘125’ ], [ ‘kgl’, ‘127’ ], [ ‘kmg’, ‘128’ ] ],

[ [ ‘wer’, ‘126’ ] ],

<1 empty item>,

[],

[ [ ‘abc’, ‘123’ ] ]

]

*/

此时的哈希表内是这样的

在这里插入图片描述

(6)实现isEmpty()方法


isEmpty()方法是用于判断哈希表是否为空。该方法无需传参

该方法思路比较简单,直接判断属性 count 是否为 0 即可

我们来看一下代码

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

//判断哈希表是否为空

HashTable.prototype.isEmpty = function () {

return this.count === 0

}

}

我们来用一下该方法

let ht = new HashTable()

console.log(ht.isEmpty()) // false,哈希表为空

ht.put(‘abc’, ‘123’)

console.log(ht.isEmpty()) // true,哈希表不为空

(7)实现size()方法


size()方法就是用于返回哈希表中数据个数。该方法也无需传参

该方法实现思路也特别简单,直接返回属性 count 即可

我们来看一下代码

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

// 返回哈希表内元素个数

HashTable.prototype.size = function () {

return this.count

}

}

我们来用一下该方法

let ht = new HashTable()

console.log(ht.size()) // 0,哈希表内没有数据

ht.put(‘abc’, ‘123’)

console.log(ht.size()) // 1,哈希表内有一条数据

(8)实现resize()方法


resize()方法就是用来对哈希表的容量进行改变的,当填充因子过大,我们就对其进行扩容;当填充因子较小,我们就增加其容量。该方法接收一个参数 newLength,表示新的哈希表的容量。

实现思路:

  1. 将原本的属性 storage 赋值给一个新的变量 oldStorage,然后我们创建一个新的空数组赋值给 storage,并将参数 newLength 赋值给属性 length

  2. 遍历 oldStorage,根据哈希表新的容量大小 newLength 将原本哈希表中所有的数据重新哈希化 、插入数据即可

该方法难以用动图演示,所以大家好好理解一下,我们直接用代码来实现

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

//改变哈希表的容量

HashTable.prototype.resize = function(newLength) {

// 1.将旧的哈希表赋值给新变量

let oldStorage = this.storage

// 2.创建新的空数组作为新的哈希表容器

this.storage = []

// 3.修改哈希表容量

this.length = newLength

// 4.遍历旧的哈希表

for(let i = 0; i < oldStorage.length; i++) {

let box = oldStorage[i]

// 4.1 某索引位置上没有数据

if(box === null) {

continue;

}

// 4.2 某索引上有数据

for(let j = 0; j < box.length; j++) {

let inner_box = box[j]

// 4.2.1 将数据重新经过哈希化插入到新的哈希表中

this.put(inner_box[0], inner_box[1])

}

}

}

}

这里就不对该方法做过多的演示了,后面会在 put()方法 和 del() 方法的改写中用到

(9)实现isPrime()方法


isPrime()方法使用于判断某个数是否为质数的,因此也就只需要接收一个数字为参数即可。

因为我们要实现哈希表的自动扩容与减容,所以在每次容量改变的时候,需要判断新的容量是否为质数,以此来保证之后哈希表中的数据均匀地分布,所以我们还是有必要来封装一下这个方法的。

在说方法实现思路之前,我们来回顾一下,质数是只能被 1自身 整除,因此我们来看一下数字 16,显然它不是一个质数,那来看看他能被哪些数整除吧

| 左 | 右 | 等于 |

| — | — | — |

| 1 | 16 | 16 |

| 2 | 8 | 16 |

| 4 | 4 | 16 |

| 8 | 2 | 16 |

| 16 | 1 | 16 |

非常明显地看到,只要一个数能被整除,那么一个数肯定是大于等于该数的算数平方根;另一个数肯定小于等于该数的算数平方根

因此,我们在判断一个数是否为质数时,只需从 2 开始逐个判断该数能否被整除,一直判断到该数的算数平方根即可

那么我们来看一下代码怎么实现的吧

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

//判断是是否为质数

HashTable.prototype.isPrime = function(number) {

// 1.获取算数平方根,并取整

let sqrt = Math.floor(Math.sqrt(number))

// 2.从2开始遍历到算数平方根

for(let i = 2; i <= sqrt; i++) {

// 2.1 能被整除,返回 false

if(number % i === 0) return false;

}

// 2.2 不能被整除,返回 true

return true

}

}

我们来简单验证一下该方法

let ht = new HashTable()

console.log(ht.isPrime(3)); // true

console.log(ht.isPrime(4)); // false

console.log(ht.isPrime(16)); // false

console.log(ht.isPrime(11)); // true

(10)实现toPrime()方法


toPrime()方法就是用于获取离某个数最近的质数并返回,因此只需要接收一个数字作为参数即可。

我们在实现扩容或减容时,初始会简单地 乘以2 或者 除以2,很难保证获得的数是质数,所以我们需要封装这样一个方法来将变换后的值变为质数再进行使用。

实现思路也很简单,就一直 +1 来寻找质数就好了。

我们来看一下代码

function HashTable() {

// 属性

// 用于存储数据

this.storage = []

// 统计哈希表内数据个数

this.count = 0

// 设定哈希表初始长度

this.length = 7

//封装哈希函数

HashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

//获取离某个数最近地质数并返回

HashTable.prototype.toPrime = function(number) {

while(!this.isPrime(number)) {

number ++

}

return number

}

}

我们来简单验证一下该方法

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

最后

整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。

《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》

前端面试题宝典

前端校招面试题详解

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

ashTable.prototype.hashFunc = function (str, size) {

let hashCode = 0

//取一个很大的数

for (let i = 0; i < str.length; i ++) {

hashCode = 37 * hashCode + str.charCodeAt(i)

}

//取余

return hashCode % size

}

//获取离某个数最近地质数并返回

HashTable.prototype.toPrime = function(number) {

while(!this.isPrime(number)) {

number ++

}

return number

}

}

我们来简单验证一下该方法

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-FK7p4YxW-1712879990573)]
[外链图片转存中…(img-MGLL1Y50-1712879990574)]
[外链图片转存中…(img-6X5Jmyeb-1712879990574)]
[外链图片转存中…(img-HPYBgORP-1712879990574)]
[外链图片转存中…(img-dlEpTRDs-1712879990575)]
[外链图片转存中…(img-xQhzkMDq-1712879990575)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-x2r2R7NS-1712879990575)]

最后

整理面试题,不是让大家去只刷面试题,而是熟悉目前实际面试中常见的考察方式和知识点,做到心中有数,也可以用来自查及完善知识体系。

《前端基础面试题》,《前端校招面试题精编解析大全》,《前端面试题宝典》,《前端面试题:常用算法》

[外链图片转存中…(img-2Aw17HWO-1712879990576)]

[外链图片转存中…(img-It4KD9Ar-1712879990576)]

[外链图片转存中…(img-4Kw6OG3k-1712879990577)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-1JLAacWt-1712879990577)]

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值