0%

前端进击笔记总结

数据结构与算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
把一个数组旋转k步 例如[1,2,3,4,5,6,7]=>[5,6,7,1,2,3,4]

第一种方法
第二个思路
function rotate(arr,k){
const length = arr.length
if(!k||length===0)return arr
const step = Math.abs(k%length)
const arr1 = arr.slice(-step) //O(1)因为原数组没有发生改变
const arr2 = arr.slice(0,length-step)
const arr3 = arr1.concat(arr2)
return arr3
}
时间复杂度为O(1) 空间复杂度为为O(n) 因为定义了3个还是为O(n)

第二种方法
function rotate1(arr){
const length = arr.length
if(!k||length===0) return arr
const step = Math.abs(k%length)
for(let i = 0;i<length;i++){ O(n) 空间复杂度为O(1),因为没有定义和其它数组相关的变量
const n = arr.pop()
if(n!=null){
arr.unshift(n)//数组是一个有序结构,unshift操作非常慢 你把最后一个元素添加到第一位需要移动所有元素 因此为O(n) //可以想象成在教室上课 最后一个学生移动第一排所有的学生都要移动
}
}
return arr
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2、匹配括号  栈
function matchLetter(s){
const stack = []
const left = '({['
const right = ')}]'
for(let i =0 ;i<s.length;i++){
const q = s[i]
if(left.includes(q)){
stack.push(q)
}else if(right.includes(q)) {
const top = stack[stack.length-1]
if((top==='('&&q===')')||(top==='{'&&q==='}')||(top==='['&&q===']')){
stack.pop()
}else{
return false
}
}
}
return stack.length===0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
队列 链表和数组可以实现队列  链表和数组都是有序结构 Set是无序结构
链表查询慢O(n) ,新增和删除快O(1)
数组查询快O(1) ,新增和删除慢O(n)
入队的时间复杂度是O(1)
但是出队(是使用栈来时间队列 两个栈首先先入栈然后出栈到另外一个栈之后移除栈顶元素再归还第一个栈)的时间复杂度为O(n)
用两个栈来实现队列
反转链表
1、反转两个节点只需要把n+1个节点的next指针指向n
2、反转多个节点:双指针遍历链表,重复上述操作
输入:1->2->3->4->NULL
输出:4->3->2->1->NULL
双指针一前一后遍历链表
反转双指针
let p1=head;
let p2=NULL;
while(p1){
//后写
const temp = p1.next
p1.next = p2
//先写双指针的遍历
p2=p1
p1=temp

}
return p2 //最后p1指向NULL p2才是那个头节点

连环问 链表和数组,哪个实现队列更快? 空间复杂度都是O(n)
数组是连续存储,push很快,shift 很慢 (删除第一个元素O(n))
链表是非连续存储,add和delete都很快(但查找很慢)
结论∶链表实现队列更快

用链表实现队列
单向链表 但要同时记录head和tail
从tail(尾部入队) head(头部出队)
length 要实时记录,不能遍历链表获取

image-20220224210751586

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
用JS实现二分查找
递归-代码逻辑更加清晰
非递归-性能更好
时间复杂度是O(logn)
binarySearch=function(arr,item){
const length = arr.length
if(lengtg===0)return -1
let left = 0
let right = arr.length-1
while(left<=right){
const mid = Math.floor((left+right)/2)
const element = arr[mid]
if(element<item){
left = mid+1
}else if(element>item){
right = mid-1
}else{
return mid
}
}
return -1
}
如果使用递归就需要把left和right加到function的参数里

找到一个数组中为n的两个数 =>嵌套循环
function sum(arr,n){
const res = []
for(let i=0;i<arr.length-1;i++){
const n1 = arr[i]
let flag = false
for(let j=i+1;j<arr.length;j++){ //O(n^2)
const n2 =arr[j]
if(n1+n2===n){
res.push(n1)
res.push(n2)
flag = true
break
}
}
if(flag)break;
}
return res
}

利用双指针来解决 =>利用递增的特性
第一个指针在开头 第二个指针在结尾 如果两个数之和大于目标是 就第二个指针左移 如果小于就第一个指针右移
function sum(arr,n){
const length= arr.length
const res = []
let i = 0
let j = length-1
//O(n)
while(i<j){
const n1 = arr[i]
const n2 = arr[j]
const n3 = n1+n2
if(n3<sum){
i++
}else if(n3>sum){
j--
}else{
res.push(n1)
res.push(n2)
break
}
}
return res
}
1
2
3
4
5
6
7
8
二叉搜索数 左边小于根小于右边
第k小就相当于中序遍历后的数组 arr[k-1] 因为下标从0开始
二叉搜索数BST的价值可使用二分法进行快速查找
平衡二叉树为BBST 增删查时间复杂度都是O(logn) ,即树的高度 让整体最优

堆栈模型 值类型变量存储在栈 引用类型变量存储在堆
堆物理上是一个数组 逻辑上是一个二叉树
查询慢 删除快 时间复杂度为O(logn)

image-20220224222323816

1
2
3
4
斐波那契函数
if(n<=0)return 0
if(n===1)return 1
return fibonacci(n-1)+fibonacci(n-2)

image-20220224222858341

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
时间复杂度不过关就要优化 不用递归用循环 记录中间结果
if(n<=0)return 0
if(n===1)return 1
let n1 = 1 //记录n-1的结果
let n2 = 0 //记录n-2的结果
let res = 0
for(let i = 2 ;i<=n,i++){
res = n1+n2
n1=n2
n2=res
}
return res
此方法引出动态规划 =>大问题
连环问 青蛙跳台阶和斐波那契数列完全一样

将数组中的0移动末尾
splice 的时间复杂度就是O(n) 外加遍历O(n) 总的为O(n^2)
function moveZero(arr){
const length = arr.length
if(length===0)return;
let zerolength = 0
for(let i = 0;i<length-zerolength;i++){
if(arr[i]===0){
arr.push(0)
arr.splice(i,1) //本身就O(n)
i-- //数组截取了一个元素,i就要递减,否则连续0就会有错误
zerolength++
}
}
}
const arr= [1,0,3,4,0,0,11,0]
moveZero(arr)
console.log(arr)
时间复杂度为O(n^2) 算法不可用

优化:双指针
定义j指向第一个0,i指向j后面的第一个非O
交换i和j的值,继续向后移动
只遍历一次,所以时间复杂度是O(n)
function moveZero(arr){
const length = arr.length
if(length===0)return;
let i
let j = -1 //指向第一个0
for(let i = 0;i<length;i++){
if(arr[i]===0){ //第一个0
if(j<0){ //j还没开始赋值
j = i
}
}
if(arr[i]!==0&&j>=0){
//交换
const n =arr[i]
arr[i] = arr[j]
arr[j]=n
j++
}
}
}
const arr= [1,0,3,4,0,0,11,0]
moveZero(arr)
console.log(arr)
总结 数组是连续存储,要慎用splice unshift等API

image-20220225195645925

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
字符创中连续最多的字符以及次数
只需要输出最多字符的次数
var maxPower = function(s) {
let slow = 0;
let fast = 0;
let max = 0;
if(s.length===1)return 1;
while(slow<s.length&&fast<s.length){
if(s[slow]!==s[fast]){
max = Math.max(max,fats-slow) //赋值
slow++
}else{
fast++
}
}
return Math.max(max,fats-slow)
}

使用双指针
定义指针i和j。j不动,i继续移动
如果i和j的值一直相等,则i继续移动
直到i和j的值不相等,记录处理,让j追上i。继续第一步
时间复杂度为O(n)
var maxPower = function(s) {
const res ={char:'',max:0}
const length = s.length
if(length===0) return res
let tempLength = 0 //临时记录当前连续字符的长度
let i = 0;
let j = 0;
for(let i = 0 ;i<length;i++){
if(s[i]===s[j]){
tempLength++
}
if(s[i]!==s[j] || i === length-1){ //不相等或者i到了字符串的末尾
if(tempLength>res.max){
res.char = s[j] //i是往前跑的 等于j
res.max = tempLength
}
tempLength = 0
if(i<length-1){ //没有到末尾
j=i //让j追上i
i-- //因为for循环里有一个i++ 细节
}
}
}
return res
}

const s = 'aabbcccddeeeee'
console.log(maxPower(s)) //{ char: 'e', max: 5 }

第二种方法 正则表达式 效率低 慎用

image-20220225203958930

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
对称数 求1-10000之间的所有对称数
**数组反转**
function findPalindrome(max){
const res = []
if(max<=0) return res
for(let i=0; i<=max;i++){
const s = i.toString() //转换为字符串
if(s===s.split('').reverse().join('')){//转换为数组 再反转 比较 看似是O(n) 但是转换也需要时间
res.push(i)
}
}
return res
}

**字符串前后比较**
function findPalindrome(max){
const res = []
if(max<=0) return res
for(let i=0; i<=max;i++){
const s = i.toString() //转换为字符串
const length= s.length
let flag = true
let star = 0
let end = length-1
while(star<end){
if(s[star]!==s[end]){
flag = false
break
}else{
star++
end--
}
}
if(flag) res.push(i)
}
return res
}

**生成翻转数**
function findPalindrome(max){
const res = []
if(max<=0) return res
for(let i=0; i<=max;i++){
let n = i
let rev =0 //存储翻转数
while(n>0){ //带一个数进去验证
rev = rev*10+n%10
n = Math.floor(n/10)
}
if(i = nav)res.push(i)
}
return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
高效的字符串前缀匹配
第一,遍历单词库数组
第二,indexOf 判断前缀
实际时间复杂度超过了O(n),因为要考虑indexOf的计算量


输入一个字符串,切换其中字母的大小写
如,输入字符传12aBc34,输出字符串12AbC34
正则表达式
export function switchLetterCase(a) {
let res = '.
const length = s.length
if ( length === 0)return res
const reg1 = /[a-z]/
const reg2 =/[A-Z]/
for (let i= 0;i< length; i++){
const c = s[i]
if (reg1.test(c)){
res += c.toUpperCase()
}else if (reg2.test(c)){
res t= c.toLowerCase()
}else
res += c
}
}
return res
}
ASCII 编码来做
export function switchLetterCase(a) {
let res = '.
const length = s.length
if ( length === 0)return res
for (let i= 0;i< length; i++){
const c = s[i]
const code = c.charCodeAt(0)
if (code>=65&&code<=90){ //大写
res += c.toLowerCase()
}else if (code>=97&&code<=122){
res t= c.toUpperCase()
}else
res += c
}
}
return res
}
使用正则表达式,性能较差
使用ASCII 码判断,性能较好——推荐答案
1
2
为什么0.1+0.2!==0.3 false
是因为小数存储二进制进行转换的时候 没法准备表达造成的

前端基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
1、Ajax Fetch Axios的区别?
三者都用于网络请求、但是不同维度
Ajax一种技术统称 使用XMLHttpRequest实现简易ajax
Fetch是一个原生API 浏览器原生API 用于网络请求 和XMLHttpRequest一个级别 语法更加简洁易用,支持Promise
Axios是第三方库 库是第三方工具
第三方库需要API来实现 实际项目中,使用现成的lib(第三方库)
function ajax2(url){
return fetch(url).then(res =>res.json())
}

2、节流和防抖
防抖:一个搜索输入框,等输入停止之后,再触发搜索
const input1 = document.getElementById('input1')
input1.addEventListener('keyup' , debounce(() =→>{
console.log('发起搜索",input1.value)
}))
节流:节省交互沟通 按节奏来 插队无效 拖拽drag 和scroll滚动 期间触发某个回调 要设置一个时间间隔
隔一段时间执行一次

节流︰限制执行频率,有节奏的执行
防抖∶限制执行次数,多次密集的触发只执行一次
节流关注“过程”,防抖关注“结果”

3、
vw屏幕宽度的1%
vh屏幕高度的1%
vmin两者的最小值,vmax两者的最大值

4、箭头函数的缺点 ,哪里不能用箭头函数?
缺点:
1、没有arguments
const fn1 =()=>{
console.log( 'arguments', arguments)//arguments is not defined
}
function fn1(){
console.log( 'arguments' , arguments) 100 200
}
fn1(100,200)
2、无法通过apply call bind改变this
不适用对象方法 原型方法 this不会指向当前对象也不会指向对象原型,指向父作用域window
箭头函数不能当构造函数
要熟练应用箭头函数,也要对函数 this arguments敏感
5、TCP三次握手和四次挥手(原理)
握手是连接
挥手是断开
连接和断开都是我发起的
握手:
先建立连接(确保双方都有收发消息的能力)
再传输内容(如发送给一个get请求)
网络连接是TCP协议,传输内容是HTTP协议
三次握手就相当于客户端和服务端 客户端问你在家嘛 服务端回答在家 客户端说那你等我我去找你 这3次相当于3次握手
Client 发包,Server接收。Server:有Client要找我
Server发包,Client 接收。Client : Server已经收到信息了
Client发包,Server 接收。Server : Client 要准备发送了
中间建立连接数据集传输
挥手:
Client 发包,Server接收。Server : Client已请求结束
Server 发包,Client接收。Client : Server已收到,我等待它关闭
Server 发包,Client接收。Client : Server此时可以关闭连接了
Client发包,Server接收。Server :可以关闭了(然后关闭连接)
6、for...in和for...of的区别
for..in遍历得到key
for...of 遍历得到value
const arr = [10,20,30]
for (let key in arr) {
console.log( key)//0 1 2
}

for...of 遍历得到value
const arr = [10,20,30]
for ( let val of arr){
console. log(val)//10 20 30
}
适用于不同的数据类型
遍历对象: for...in 可以,for...of 不可以
遍历Map Set : for...of可以,for...in不可以
遍历generator : for...of可以,for...in不可以
for...in用于可枚举数据,如对象、数组、字符串 得到key
for...of用于可迭代数据、如数组、字符串、Map、Set 得到value

7、for await...of有什么作用

就是promise.all的替代品 只不过promise.all是一个API的形式.then去获取所有的结果
for await...of循环的形式快速的在列表把所有结果取出来
for await...of作用能够遍历多个promise,遍历多个异步
(async function(){
const p1 = createPromise( 100)
const p2 = createPromise( 200)
const p3 = createPromise( 300)
//const res1 = await p1
//console.log(res1) 100
// const res2 = await p2
//console.log( res2) 200
//const res3 = await p3
// console.log(res3) 300

const list = [p1,p2,p3]
// Promise.all(list).then(res => console.log(res)) 100 200 300

// for await ( let res of list) {
// console.log(res) 100 200 300
// }
-----------------------分割线-------------------------
如果是按顺序慢慢的出来
//const res1 = await createPromise( 100)
//console.log(res1) 100
// const res2 = await createPromise( 200)
//console.log( res2) 200
//const res3 = await createPromise( 300)
// console.log(res3) 300
循环 和上面一样
// const arr = [10,20,30]
// for ( let res of arr) {
// const res = await createPromise(num)
// console.log(res)
// }

8、 offsetHeight scrollHeight clientHeight区别
offsetHeight offsetWidth : border + padding + content
clientHeight clientWidth : padding + content
scrollHeight scrollWidth : padding +实际内容尺寸 //例如父盒子里有子盒子且有内容
scrollTo就是滚动条滑动的距离
9、HTMLCollection和NodeList区别 (是类数组)
Node和Element DOM是一棵树,所有节点都是Node //Node是Element的基类
HTMLCollection是Element的集合
NodeList是Node集合

p标签里有em和b标签 也有文字
console.log(p1.children instapceof HTMLCollection) //true
console.log(p1.children instapceof NodeList) //false
如果用childNodes 就相反
console.log(p1.childNodes instapceof HTMLCollection) //false
console.log(p1.childNodes instapceof NodeList) //true
10、JS严格模式有什么特点
"use strict"
全局变量必须先声明 var n = 10
禁止使用with
创建eval作用域
禁止this指向window 就是undefined
'use strict'function fn() {
console.log( 'this ', this) //undefined
}
fn.call({x: 100}) //可以使用call来改变this的指向
函数参数不能重名
11、HTTP跨域请求时为何发送options请求
跨域请求:;浏览器同源策略 JSONP解决跨域
CORS配置允许跨域(服务端)
答案:
options请求,是跨域请求之前的预检查
浏览器自行发起的,无需我们干预
不会影响实际的功能
12、如何检测JS内存泄漏?JS内存泄漏场景有哪些?
垃圾回收:JS引擎要做的事

13、闭包是内存泄露
早期的 V8 中,由于闭包引用的变量被挂载了全局的大对象 windows 中,所以这一变量由老生代区采用标记清除算法进行回收。频繁的 垃圾回收会生成大量的内存碎片,所以也会导致内存泄漏问题。后来 v8 又采用了标记清除整理算法,以及增量回收、并行回收、并发回收等 垃圾回收技术,所以在新一代浏览器中,使用闭包几乎不会出现内存泄漏问题。

14、浏览器和nodejs的事件循环有什么区别?
JS是单线程的
浏览器中JS执行和DOM渲染共用一个线程
异步分为:
宏任务,如setTimeout setInterval 网络请求
微任务,如promiseasyn.cLawait
微任务在下一轮DOM渲染(内容绘制到页面让用户看到)之前执行,宏任务在之后执行
先执行微任务的异步 再执行宏任务的异步
15、Node.js异步
Nodejs 同样使用ES语法,也是单线程,也需要异步
异步任务也分∶宏任务+微任务
但是,它的宏任务和微任务,分不同类型,有不同优先级

执行同步代码
执行微任务( process.nextTick 优先级更高)
按顺序执行6个类型的宏任务(每个结束时都执行当前的微任务)

答案:
浏览器和nodejs的event loop流程基本相同
nodejs宏任务和微任务分类型,有优先级
16、vodm
用JS对象模拟DOM节点数据 由React最新推出
Vue Reacct框架的价值:1、组件化 2、数据视图分离,数据驱动视图 ---核心
vdom并不快,JS直接操作DOM才是最快的
但是是为了服务与“数据驱动视图”的开发思路,要有合适的技术方案,不能全部 DOM重建
vdom 就是目前最合适的技术方案(并不是因为它快,而是合适)

vdom是数据驱动视图技术方案的技术实现而已
万一真有土豪呢!!!