0%

React 自定义 antd 主题方法编译失败与解决办法

一、按照官网操作

首先我是按照官网的步骤一步一步操作下来的:

  1. 安装 lessless-loader

    1
    npm install less less-loader
  2. 引入 customize-cra 中提供的 less 相关的函数 addLessLoader 来帮助加载 less 样式,同时修改 config-overrides.js 文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //配置具体的修改规则
    const { override, fixBabelImports,addLessLoader} = require('customize-cra');

    module.exports = override(
    fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: true,
    }),
    addLessLoader({
    javascriptEnabled: true,
    modifyVars: { '@primary-color': 'green' },
    }),
    );
    阅读全文 »

数据结构与算法

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是数据驱动视图技术方案的技术实现而已

CSS

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
1.什么是CSS继承 .CSS选泽器是什么?
就是父级设置了一些属性,子级继承了父级的该属性 // CSS选择器用来对选定的页面元素进行样式修改。
选择器有哪些?
类型选择器
通配选择器 1
类选择器
id选择器
标签属性选择器
伪类选择器:a:link :fist-child 10
伪元素选择器:placeholder ::before,::after 1
后代选择器
子代选择器
伪元素选择器添加了一个页面中没有的元素(只是从视觉效果上添加了,不是在文档树中添加),伪类是给页面中已经存在的元素添加一个类。
2.面试口诀:一绝三香(相)
px: 绝对单位,网页开发基本长度单位
em: 相对单位,相对当前盒子字体大小进行计算
rem: 相对单位,相对根元素html字体大小进行计算
vw+vh: 相对单位,相对当前网页视口宽度和高度进行计算

px em rem之间的转换关系
默认情况下1rem = 1em = 16px
根元素字体修改情况下1rem = 根元素字体大小
当前盒子字体修改情况下1em = 当前盒子字体大小
em和rem的使用场景
o em:通常用于字体缩进
o rem:配合媒体查询处理移动端适配问题 ***
3.css如何实现左边定宽,右边自适应
1、将盒子外面的容器设为flex布局,右边盒子设置flex属性值为1 这样就会自动撑满
2、将盒子外面的容器设为table布局,将左右盒子的display都设置为table-cell
4.css实现绝对居中
1、盒子容器设置相对定位,盒子设置绝对定位 top和left都设置50% 之后再设置margin-left和margin-top为盒子大小的一半
2、盒子容器设置相对定位,盒子设置绝对定位 四个方向(top、left、bottom、right都设置为0),margin设置为auto即可
3、盒子容器设置相对定位,盒子设置绝对定位 top和left都设置50% 设置transform属性,属性值为translate(-50%,-50%)
4、盒子容器flex布局,设置justify-content属性和align-items属性,属性值都设为center(内部盒子水平,垂直方向居中)
justify-content设置主轴上的子元素排列方式
align-items 设置侧轴上的子元素排列方式(单行)
总结:前两种内部的盒子定宽高,后两种不定宽高
5、清除浮动的方法有哪些,各有什么缺点?
如下图 添加clear属性
原理: 设置清除浮动以后,浏览器会自动为元素添加一个上外边距, 以使其位置不受其他元素的影响。
清除浮动布局场景下的知识深度和广度? 深度:我们不仅可以实现清除浮动,还可以知道他们的优点和缺点,广度就是可以使用多种方式实现
6、如何用CSS画一个三角形?
css实现三角形的核心是用无宽高盒子的border
透明边框第一步四个方向全部透明边框,
箭头方向:第二步选择箭头方向,箭头指向和有色边框方向相反
border: 10px solid transparent;
border-left: 10px solid #f40;使用border去画三角形
场景就是淘宝上方导航栏的标题右上角都有一个小三角
border-bottom 代表箭头向上
border-top 代表箭头向下
border-left 代表箭头向右
border-right 代表箭头向左
第二种方法是创建一个盒子 将其设置为相对定位,盒子前添加一个伪元素设置成绝对定位,将其绕着左下顶点进行顺时针旋转45度,父元素设置overflow:hidden属性将超出盒子外的部分进行裁剪就能形成一个三角形
说出为什么要用css画三角形的原因(出于页面性能考虑,避免发送过多的http请求,节省带宽)
7、css提高页面性能的方法有哪些?
1.属性设置使用简写 (给margin设置四个方向的时候可以简写为 margin:上右下左)
2.用CSS替换图片比如用css画一个三角形来替换图片 (避免发送过多的http请求,节省带宽)
3.删除不必要的零和单位 (0.2写成.2 20.0写成20
4.用CSS精灵图替代单个文件加载(利用背景定位在CSS精灵图拿到自己想要的图标(一个请求) ,项目加载的时候就要发送http请求,)

1.属性简写目的:减小生产包体积
2.图标替换目的:减少http请求/节约带宽
3.删除零和单位目的:减小生产包体积
4.背景图使用雪碧图目的:减少http请求/节约带宽
8、BFC解决了哪些问题(BFC:一块独立渲染区域,内部元素的渲染不会影响边界以外的元素)
1.垂直方向外边距重叠的问题。
2.子元素浮动,父元素高度塌陷问题。
触发BFC:
浮动元素:float 除 none 以外的值 有局限性:高度不塌了但是脱离文档流了,宽度也就丢失了,对应特点的第三条 -不推荐
绝对定位元素:position (absolute、fixed)
display 为 inline-block、table-cells、flex -不推荐 设置弹性布局
overflow 除了 visible 以外的值 (hidden、auto、scroll) -推荐
开启BFC后的特点:
1.开启BFC的元素不会被浮动元素所覆盖
2.开启BFC的元素子元素和父元素外边距不会重叠
3.开启BFC的元素可以包含浮动的子元素

image-20211221161602539

img

image-20211221195541177

image-20211221200057153

JavaScript

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
9、为什么把bind,call,apply放在一起?有什么区别(相同点,不同点)  干什么的?:用来改变this的指向的
var name = 'fff'
function say(){
var name = 'ddd'
log('this.name',this.name)
}
say() //输出的是fff 因为say相当于window.say()
相同点:bind,call,apply都可以改变this指向
不同点:call和apply的传参方式不同(前者挨个传,后者传入一个数组)/bind(返回的是绑定this一个函数)和call,apply的返回值不同
apply、call 则是返回函数调用立即执行
#三、实现
call的应用场景
*判断数据类型
const array = [1,2,3,4];
const type = 0bject.prototype.toString.call(array);
console.log('type', type)//[object,Array]
*类数组转数组
const arrayLike={
0:'name',
1:'age',
2:'gender',
length:3
}//以上就是类数组 看似数组,本身不是数组
const res = Array.prototype.slice.call(arrayLike) //['name','age','gender']

apply 应用场景
对给定数组求最大值/最小值
const array = [1,2,3,4,5]
const max = Math.max.apply(null,array)//null为this的指向 输出结果为5 min为最小值

bind应用场景
通常用在react项目的组件中
利用bind函数将this的指向指向组件的实例

10、如何使用多种方式实现数组去重 分普通数组和对象数组
array = [1,2,3,4,5]
indexOf 查找项的下标 没找到-1
filter 返回值[]
sort 返回值[] 接收两个参数 返回a-b 从小到大排序
reduce 返回值[] const res = array.reduce(function(prev,current){return prev+current})//不断更新prev 输出15
push 返回值length
const array = [1,2,3,4,5,2,3]

过滤解决
function unique(array) {
if( !Array.isArray(array)){
throw new Error( "unique function params is not Array")}
return array.filter((item,index)=>{
return array.index0f(item)===index //当拿到倒数第二个2的时候获取的下标是第二个2的下标,就会将其过滤
})
}
const res = unique(array) //输出的是[1,2,3,4,5] 实现去重

排序解决
function unique(array) {
if(!Array.isArray(array)){
throw new Error( "unique function params is not Array")}
array = array.sort() //先进行排序
let res=[];
for(let i=0;i<array.length;i++){
if(array[i]!==array[i-1]){
res.push(array[i])
}
}
return res
}
const res = unique(array) //输出的是[1,2,3,4,5] 实现去重

解构赋值解决 (先转化为集合 集合里的元素就已经是唯一的了 然后再解构赋值)
function unique(array) {
if(!Array.isArray(array)){
throw new Error( "unique function params is not Array")}
return [...new Set(array)]

}
const res = unique(array) //输出的是[1,2,3,4,5] 实现去重



11.怎么对给定数组求最大值
应用场景:数据处理的时候可以用到给定数组求最大值
Math.max()方法
const array = [1,2,3,4,5]
const res = Math.max(...array) ***
或者借用这个函数 const res = Math.max.apply(null,array)

reduce函数方法
function getMax(array){
return array.reduce((prev,current)=>{
return current>prev?current : prev
或者return Math.max(prev,current)
})
}
const res = getMax(array)


sort()排序方法
function getMax(array){
const result = array.sort()
return result[result.length-1] //数组的最后一项
}
const res = getMax(array)
考察的是对基本的数据处理能力

12.JS中判断数据类型的方式有哪些?
typeof
优点:使用简单 也不精确 比如 typeof [] 会输出Object(数组本身就)
缺点:功能残缺,只能用来判断6种数据类型:string,number,boolean,undefined,symbol,function
type of null symbol 的值是通过 Symbol() 函数生成,每一个 symbol 的值都是唯一的

Object.prototype.toString.call
优点:适用于判断所有数据类型
缺点:使用上相对typeof而言比较繁琐

instanceof(排除):instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

js中判断数据类型的场景?
根据接口/返回参数类型/做区别处理。
js中的数据类型有些哪些?
基本数据类型:StringNumberBooleanSymbolundefinedNull
引用数据类型:ObjectArrayFunction

13.如何实现函数节流 (项目优化)
函数节流:规定在一个单位时间内,事件响应函数只能被触发一次。如果这个单位时间内触发多次函数,只有一次生效
使用场景:window.onresize事件 该事件是浏览器的视口不断被放大缩小的时候触发的
mousemove事件,鼠标拖动事件
这两个事件不断别触发 导致性能开销过大 因此要使用函数节流
window.onresize():函数节流
单位时间内触发一次
1.返回值是一个函数
2.开启定时器
3.如果定时器存在,直接返回false
4.定时器内部清空定时器,并且把timer置为null 然后执行我们的事件响应函数
function throttle(fn,interval) {
var timer;
return (event)=>{
if (timer){
return false;
}
timer = setTimeout(()=> {
clearTimeout(timer);
timer = null;
fn(event)},
interval);
}
}
window.onresize = throttle(function(event)){
log(event)
}

//上面的fn 指的就是(function(event)){log(event)} 本身没有返回值 所以上面调用的时候要return一个函数 把fn放在函数里执行的话就是输出log

14.如何实现函数防抖
事件防抖:事件被触发n秒后再执行回调,如果在这n秒执行过程中又被触发,则会把之前的事件响应清除掉,事件函数只执行一次。
使用场景:网站商品搜索框
1.返回值是一个函数
2.事件响应函数式在固定间隔试行的
<input type="text" id = "searchElement">
const SearchElement = document.getElementById("searchElement")
function debounce(fn, delay){
let timer = null
return ()=>{.
clearTimeout(timer)
timer = setTimeout(fn,delay)
}
}
SearchElement.oninput = debounce(function(event)){
const value = searchElement.value
log('value',value)
,1000}

15.如何使用多种方式实现数组拍平
概念 :数组拍平也叫数组扁平化、数组拉平、数组降维。**指的是把多维数组变成一维数组**。
使用场景:复杂场景下的数据处理
实现数组拍平的具体实现:
const array = [1,2,3,4,[5,6,[7,8]]] //多维数组
array [1,2,3,4,5,6,7,8]; //实现的结果
1.使用reduce实现
//reduce
function flatten(array) {
return array.reduce(function(prev,current){
return prev.concat(Array.isArray(current)?flatten(current):current) //如果是就递归调用
},[])
}

const result = flatten(array);
console.log(`result`, result);

//使用ES6的flat函数 在vscode执行不了 在Chorme浏览器(默认支持ES6语法)的控制台可以 ,因为vscode没有兼容ES6的语法
function flatten(array) {
return array.flat(Infinity); //Infinity是长度无限展平
}
const result = flatten(array)
console.log(`result`, result);

//while
function flatten(array) {
while (array.some(Array.isArray)){ //只要数组里是个数组就会进while循环
array = [].concat(...array)
}
// 1 array
// 2 已经被拍平
return array;
}



const result = flatten(array);
console.log(`result`, result);

16.什么情况会使判断成立
let value = 0;
Object.defineProperty(window,'a',{ // Object.defineProperty给目标对象挂载属性并且拿值的操作
get(){
return value += 1; //return的值就是a的值 每获取一次a,value都会加1
}
});
if(a===1&&a===2&&a===3){ //每一次判断都会拿一下a的值,a的值是递加的 是定义在window对象的
console.log(`object`)
}
17.实现new操作符
实际使用场景:封装第三方的工具(通过构造函数进行封装)
const TMap = function(options){
this.name = options.name;
this.address = options.address;
return{
name:'map',
address:'SZ'
}
}

const map = new TMap({
name: 'tmap',
address:"BJ"
});

console.log('map :>> ', map); //返回的是SZ
使用new操作符实例化后的对象有两种情况:如果构造函数里没有返回值的话,实例化对象输出的就是构造函数里的实例属性和方法 BJ
如果构造函数里有返回值的话,实例化对象就是那个返回值 SZ
如何实现new操作符 ->难先不看

18.实现一个bind函数
function origin(a,b){
console.log(this.name);
console.log([a,b]);
}
const obj = {
name:"freemen"
}
const func = origin.bind(obj,2); //改变this的指向 不然是指向window且返回的是一个函数
func(1) // 输出Freeman [2,1]
**模拟一个bind函数** 难先不看
bind函数的实现原理
1. bind 函数改变this指向
2. bind 函数是Function.prototype上的方法
3. bind 函数的返回值也是函数
4. bind 函数调用之后返回的函数的参数同样也接收处理

如何实现call和apply函数
apply入参方式是以数组传递的 实现也很难先不看
call
1.改变this指针
2.返回函数调用
3.参数挨个依次传递
apply
1.改变this指针
2.返回函数调用
3.数组方式传参

19.实现instanceof
function Persion(){
this.name = "freemen"
}
const obj = new Persion

console.log(obj instanceof Persion)//输出true
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

function instance_of(Obj, Constructor){
let implicitPrototype = Obj.__proto__; // 获取实例对象的隐式原型
let displayPrototype = Constructor.prototype; // 获取构造函数的prototype 属性
// while 循环 -> 在原型链上不断向上查找
while(true){
// 直到 implicitPrototype = null 都没找到, 返回false
if(implicitPrototype === null){
return false
// 构造函数的 prototype 属性出现在实例对象的原型链上 返回 true
}else if (implicitPrototype === displayPrototype){
return true
}
// 在原型链上不断查找 构造函数的显式原型
implicitPrototype = implicitPrototype.__proto__
}
}

const has = instance_of(obj,Persion)
console.log(`has`, has)

实现原理:
1. 获取实例对象的隐式原型
2.获取构造函数的prototype属性
3. while 循环->在原型链上不断向上查找
4. 在原型链上不断查找构造函数的显式原型
5,直到实例对象的隐式原型 = null都没找到,返回false
6.构造函数的prototype属性出现在实例对象的原型链上返回true (构造函数的显式原型===实例对象的隐式原型)

20.总结:
1、什么是原型和原型链?
1.什么是原型
在javascript中,函数可以有属性。每个函数都有一个特殊的属性叫作原型(prototype)
每个对象都会在其内部初始化一个属性,就是prototype(原型)

2.什么是原型链
原型链就是当我们访问对象的某个属性或方法时,如果在当前对象中找不到定义,会继续在当前对象的原型对象中查找,如果原型对象中依然没有找到,会继续在原型对象的原型中查找(原型也是对象,也有它自己的原型)如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。可以看出,这个查找过程是一个链式的查找,每个对象都有一个到它自身原型对象的链接,这些链接组件的整个链条就是原型链

3·原型和原型链存在的意义是什么?
**使得实例对象可以共享构造函数原型上属性和方法**,节省内存。构造函数原型上的属性和方法越多,节省内存越大.

2如何理解作用域和作用域链?
讲清楚如下三点:
1.什么是作用域?
作用域是在运行时代码中的某些特定部分中**变量,函数和对象的可访问性**,作用域决定了代码区块中变量和其他资源的可见性。

2.作用域存在的意义是什么? **不同作用域下同名变量不会有冲突。**
作用域存在的最大意义就是变量隔离即:

3.什么是作用域链?
当我们在某个函数的内部作用域中查找某个变量时,如果没有找到就会到他的父级作用域中查找,如果父级也没找到就会接着一层一层的向上寻找,直到找到全局作用域还是没找到的话,就宣布放弃。这种一层一层的作用域嵌套关系,就是作用域链

3、你对闭包怎么理解?
讲清楚如下三点:

1.什么是闭包?
**一个函数作用域能够访问另外一个函数作用域中的变量的函数**
**闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方!!!**
bind方法只有在Function.prototype

2.闭包有哪些实际的使用场景?
1.事件函数的封装
2.用闭包模拟私有方法
3.在循环中给页面元素绑定事件响应函数
使用闭包主要为了设计私有的方法和变量
3.闭包存在什么问题?
好处是可以延伸这个变量的使用范围 缺点:闭包本身会造成内部变量常驻内存,会增大内存使用量,使用不当很容易造成内存泄露
原因就是在函数执行结束之后不会将变量进行销毁

ES6

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
1.letconstvar的区别  
1 不存在变量提升
2 块级作用域:不会像var一样先变量提升显示undefined,会直接拿去全局的变量
3 暂时性死区:在块级作用域里面,声明变量的时候不能提前给它赋值
4 不可重复声明 let a = '10' let a = '20' 语法错误
5 letconst声明的变量不会挂在在全局window对象下面
// var fullName ='freemen';
// console.log(`window`, window.fullName) undefined
let a = 20
{
let a = 30 这是不会报错的 因为不是在同一个作用域
}
var能够对一个变量进行多次声明
存在变量提升
在函数中使用使用var声明变量时候,该变量是局部的 而如果在函数内不使用var,该变量是全局的
**修改声明的变量**
varlet可以 const声明一个只读的常量。一旦声明,常量的值就不能改变

const声明之后必须马上赋值,否则会报错
const声明的简单类型不可更改,复杂类型内部数据可以更改 例如 const fullName = ['freeman'] fullName[1] = 'mkw' 不会报错
能用const的情况尽量使用const,其他情况下大多数使用let,避免使用var

2.箭头函数和普通函数的区别
1、箭头函数的this指向父级作用域的this
2、call()/.apply()/.bind()无法改变箭头函数中this的指向
3、不可以被当作构造函数
4、不可以使用arguments对象 是一个对应于传递给函数的参数的类数组对象。
关于第四点 普通函数
function sayName(){
const args = arguments;
log('args',args);
}
sayName('a','b') // 输出的是args可以接收传来的参数为a,b
换成箭头函数, 报错arguments没有定义 而箭头函数接收args参数 const sayName = (...args)=>{.....}
1、在函数调用中,this的指向指向调用它的对象
2、在构造函数中,this指向指向实例化对象
3、在事件体中,this指向事件源
4、箭头函数的this指向父级作用域的this
5、其他情况(声明式,赋值式的函数),this指向window

3.ES6哪个方法可以实现数组去重
采用ES6中Array.from配合new Set
const array = [1,2,3,4,5,12,1,2]
const result = new Set(array) //返回的是一个{} //数组去重console.log(`result`, [...new Set(array)])
我们需要array.from
const result = Array.from(new Set(array))//将一个类数组对象转化为数组对象 =>只能对基本类型的值的数组进行去重
如果是一个对象是不能实现去重

4.ES6实例对象新增的方法对象新增的方有哪些?
1 Object.is() =>判断两个值是否相同
const numOne = 1;
const numTwo = '1';
const result = Object.is(numOne,numTwo);//false
const result = Object.is(NaN,NaN);
console.log(`result`, result);//true
特点: 1 不仅可以对值类型进行正常处理而且对象类型的值也可以进行判断
2 对于特殊的值 NaN 也可以进行正常的处理
2.Object.assign()=>两个对象进行合并
const obj = {
name:"freemen"
}
const objTwo = {
age: 18
}
const result = Object.assign(obj,objTwo);
console.log(`result`, result)//{ name:"freemen", age: 18}
3.Object.keys()=>返回一个数组,目标对象的所有可遍历属性的键名
const object = {
name:"freemen",
age:18
}
const result = Object.keys(object);
console.log(`result`, result);//['name','age']
4.Object.values()=>返回一个数组,目标对象的所有可遍历属性的键值 ['freemen','18']
5.Object.entries ()=>返回一个键值对数组(keys和values的结合)//['name','freemen'] ['age','18']

5.classfunction的区别
//相同点:
// 都可以用作构造函数
function Persion(){
this.fullName = "freemen";
}
class Persion{
constructor(){
this.fullName = "freemen";
}
}
const object = new Persion
console.log(`object`, object)
返回的都是Persion{
fullname:'freemen',
[Prototype]:Object
}
// 不同点:
// * class 不可以使用 call apply bind 的方式来改变他的执行上下文   
function sayName(){
console.log(`this.fullName`, this.fullName)
}
class Persion {
constructor(){
this.fullName = "freemen";
}
}

const obj = {
fullName: "freemen"
}
Persion.call(obj)// 普通函数this.fullName freemen class函数不可以类型错误

6.你对promise了解多少 Promise对象中的then方法,可以接收构造函数中处理的状态变化
定义:是一种异步编程的一种解决方案。比传统的解决方案--回调函数和事件--更合理和更强大
基本使用
// 3 个状态 pending正在进行中 fulfilled成功 rejected 失败
new Promise(function(resolve,reject){
let fullName = "freemen";
if(fullName==='Vinko'){
resolve(1)
}else{
reject(2)
}
}).then(function(value){
console.log(`resolve - value`, value)
},function(value){
console.log(`reject - value`, value) //输出这个
})

原型方法
**Promise.prototype.then**
new Promise(function(resolve, reject){
let fullName = "freemen";
if(fullName==="freemen") {
resolve(1);
}else{
reject(2)
}
}).then((value)=>{
console.log(`fulfilled value`, value)
return Promise.reject(2);
}).then((fulfilledValue)=>{
console.log(`fulfilled value`, fulfilledValue)
},(rejectedValue)=>{
console.log(`rejectedValue`, rejectedValue)
})

1 可以支持链式调用
2 then 接受两个参数且都是函数
第一个函数是我们promise 状态变成fullfilled 时候的回调函数 成功的回调
第二个函数是我们promise 状态变成rejected时候的回调函数 失败的回调
3 返回值也是Promise(新的)
**Promise.prototype.catch**
捕获Promise错误
new Promise(function(resolve, reject){
let fullName = "freemen";
if(fullName==="Vinko") {
resolve(1);
}else{
reject('promise error')
}
}).then((value)=>{
console.log(`value`, value)
}).catch((error)=>{
console.log(`error`, error)
})

**Promise.prototype.finally**
用于指定**不管 Promise 对象最后状态如何,都会执行的操作*

实例方法
**Promise.all**
//用于将多个Promise实例包装成一个新的Promise实例
const promiseArray = [1,2,3,4,5].map((item)=>{
return new Promise((resolve)=>{
resolve(item);//包含多个promise实例的数组对象
})
})
Promise.all(promiseArray).then(res=>{
console.log(`res`, res) //res [1,2,3,4,5]
});

**Promise.race**
// Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
// Promise.race 和 Promise.all 区别
// Promise.race 继发 只要有一个状态发生改变就拿这个结果作为回调函数的值(res)来执行
// Promise.all 并发 每一个promise状态都发生改变的时候才拿这个结果
const promiseArray = [1,2,3,4,5].map(item=>{
return new Promise((resolve)=>{
resolve(item)
})
})
Promise.race(promiseArray).then(res=>{
console.log(`res`, res)//res 1
})

**Promise.resolve**
// Promise.resolve 将现有对象转化成Promise 对象
new Promise(function(resolve, reject){
console.log(`loading start...`)
let fullName = "freemen";
if(fullName==="freemen") {
resolve(1);
}else{
reject('promise error')
}
}).then(res=>{
console.log(`res`, res) //res为1
})
=>等价于
Promise.resolve(1).then(res=>{
console.log(`res`, res)
})

**Promise.reject**
// Promise.reject 返回一个Promise实例 -> 实例的状态rejected
new Promise(function(resolve, reject){
console.log(`loading start...`)
let fullName = "freemen";
if(fullName==="Vinko") {
resolve(1);
}else{
reject('promise error')
}
}).then(res=>{
console.log(`res`, res)
},rejected=>{
console.log(`rejected`, rejected) //rejected promise error
})
=>等价于
Promise.reject('promise error').then(null,rejected=>{ //reject状态的回调函数是我们then函数的第二个参数
console.log(`rejected`, rejected)//rejected promise error
})
7.扩展运算符的实现原理
const baseArray = [1,2,3,4]
const array = [...baseArray]
==>实现原理
var baseArray = [1,2,3,4]
var array = [].concat(baseArray)
8.forEach, for...in, for...of之间的区别?
1、forEach是数组的方法
1.遍历的时候更加简洁,效率和for循环相同,不用关心集合下标的问题,减少了出错的概率。
2.没有返回值
3.不能使用break中断循环,不能使用return返回到外层函数

2for...in用于可枚举数据,如对象、数组、字符串 得到key
3for...of用于可迭代数据、如数组、字符串、MapSet 得到value

React全家桶

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
1.react父子组件如何通信
父组件=>子组件
1.Props
2.Prototype Methods 原型方法
子组件=>父组件 也是通过props传递,只不过前提是要求父组件提前给子组件传递一个函数
1.Callback Functions 回调函数
2.Event Bubbling 事件冒泡=>浏览器的事件传输机制 点击内部元素的时候一层一层的冒泡 会触发子组件的父元素的事件响应函数
const Child()=>{
return (
<button>click<button> //点击按钮触发下面的div的onClick事件 利用浏览器的事件传输机制 点击内部元素的时候一层一层的 冒泡 会触发子组件的父元素的事件响应函数
);}

class Parent extends React.Component {
handleClick=()=>{
console.log( "clicked " )
}
render(
return (
<div onClick={this. handleClick}>
<Child/>
</div>
)
};

2.react中的setState是同步还是异步 在执行层面和本质层面进行剖析
**执行层面**
同步:原生事件onclick、setTimeout
异步:react合成事件、生命周期钩子函数
**本质层面**
本质:本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致他们没法立马拿到更新后的值,形式了所谓的“异步”

export default class SetStateComponent extends Component {
state= {
count : 0
}
// 同步:原生事件和setTimeout
**setTimeout**
componentDidMount() {
setTimeout(()=>{
this.setState({count: this.state.count+1});
console.log(`this.state.count`, this.state.count);
})
**原生事件**
const ele = document.getElementById('element');
ele.onclick = ()=>{
this.setState({count: this.state.count+1});
console.log(`this.state.count`, this.state.count)
}
}
**生命周期钩子函数**
componentDidMount() {
this.setState({count: this.state.count+1});
console.log(`this.state.count`, this.state.count)
}

** react合成事件 *
handleClick = ()=> {
this.setState({count: this.state.count+1}); //点击的时候输出0 异步的没有立马拿到最新的值
console.log(`this.state.count`, this.state.count)
}
render() {
return (
<div>
<button id="element">add<tton>
<button onClick={this.handleClick}>add<tton>
</div>
)
}
}
3.在同一个事件响应函数中setStata两次,render会执行几次?
setState机制:内部会维护一个updaterQueue, 当我们同时更新一个同名属性的时候 会对同名的state属性进行一个覆盖。
执行后面的一个setState
4.什么是render props?可以用来解决什么问题?
术语“render prop”是指―种在React组件之间使用一个值为函数的prop共享代码的简单技术
使用场景:获取鼠标坐标值(鼠标移动跟随的场景)
路由劫持

5.react中如何进行条件渲染 三元运算符
判断是否登陆渲染相应的组件

6.Router-React中如何获取url的参数
import React, { Component } from 'react'
import { withRouter } from 'next/router'
export default withRouter(class RouteComponent extends Component {
handleClick = ()=>{
const query = this.props.router.query;
console.log(`query`, query);
}
render() {
return (
<button onClick={this.handleClick}>点击我吧<tton>
)
}
})
// 面试官主要考察什么?
// react框架本身的熟悉程度

// 面试中遇到该题目我们该如何回答?
// 以next为例,用withRouter 包装基础组件 基础组件的props中拿到我们路由的信息
7.React-Router有哪几种路由模式?
1、BrowserRouter:浏览器的路由方式,也就是在开发中最常使用的路由方式/
2、HashRouter:在路径前加入#号成为一个哈希值,Hash模式的好处是,再也不会因为我们刷新而找不到我们的对应路径
2.hashRouter和BrowserRouter的实现原理?
hashRouter: window 监听 hashChange 事件来实现路由切换的
BrowserRouter: window 监听popstate事件来实现路由切换的
(实现原理:react-router这个库是依赖history这个库的 history这个库给我们提供了路由跳转的方式)

8.什么是jsx?
jsx是由babel进行编译的
//jsx 本质是一个语法糖
// jsx 通过@babel/presets-react预设(编译),将jsx转换为React.createElement这个函数 这个函数会生成虚拟DOM
// 生成虚拟DOM之后会调用
// render方法来对虚拟DOM编译
9.react的生命周期你了解多少?
面试中遇到该题目我们该如何回答?
1.分阶段(挂载,更新,卸载)讲清楚react的生命周期
挂载:挂载阶段,也可以理解为组件的初始化阶段,就是将我们的组件插入到DOM中,只会发生一次
constructor, 组件构造函数,第一个被执行
getDerivedStateFromProps(让组件在 props 变化时更新 state。),
它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
此生命周期是在 16.3 版本新增的,用于替代 componentWillReceiveProps 生命周期
这个函数有两个参数props和state,分别指接收到的新参数和当前的state对象,这个函数会返回一个对象用来 更新当前的state对象,如果不需要更新可以返回null
render,
componentDidMount 组件挂载之后调用
更新:当组件的props改变了,或组件内部调用了setState或者forceUpdate发生,会发生多次
getDerivedStateFromProps, 无论我们接收到新的属性,调用了setState还是调用了forceUpdate,这个方法都会被调用
shouldComponentUpdate,控制更新的阀门 ,控制 render 是否允许被执行。。
render,渲染render函数是纯函数(同样的输入必定有同样的输出)
getSnapshotBeforeUpdate,这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之 前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate(获取的快照),如果你不想要返回值,请 返回null,一定要和componentDidUpdate一起使用 (这个方法代替了之前的componentWillUpdate)
componentDidUpdate 该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps(之前的属性),prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的

卸载: 我们的组件被卸载或者销毁了
componentWillUnmount:清除一些定时器,取消网络请求。

Webpack

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
1、webpack的理解
1.webpack是一个用于现代JavaScript应用程序的**静态模块打包工具**
2.解决模块依赖问题 :例如A模块依赖B模块,B模块依赖C模块,就会把C模块放在最前面
3.解决代码编写问题 :将less转化为css 将ts转化为js
4.解决开发效率问题 :webpack提供了热加载的功能,当我们项目开发过程中,保存文件后会自动更新
5.解决项目优化问题 :项目完成开发上线的时候,webpack会将代码进行压缩优化,提高性能。
2、使用过webpack中的哪些loader
loader 分为js相关、css相关、文件相关、
js相关
babel-loader:处理javascript文件,提供语法转换(编译过程中class转换成function),语法垫片(对ES的新语法提供支持)
source-map-loader:从现有源文件中提取源映射关系(webpack打出来的包都是一个个bundle.js,bundle.js里面包括webpack打包的代码和源文件,如果要在项目中进行debug调试的话,没有source-map-loader的话,就会debug到webpack打出来的包都是一个个bundle.js中,有的话就会定位到源代码中,在源代码进行调试)
css相关
style-loader:把编译后的css以style标签的格式插入DOM中 执行顺序3
css-loader:处理css中比如url()@import等语法的文件引用路径问题 执行顺序2
less-loader:将less编译为css 执行顺序1 先由less转为css
文件相关
file-loader:处理文件引用路径问题
gzip-loader:加载gzip资源
url-loader:允许有条件地将文件转换为内联的base-64 URL,转化玩之后,减少了http请求的个数,提高了应用的性能
**注意**loader以数组的方式进行配置,执行的顺序是从后往前栈结构的顺序执行。

3.使用过webpack中的哪些plugin分别是用来做什么的
DllPlugin 为了极大减少构建时间,进行分离打包
HotModuleReplacementPlugin 启用模块热替换(Enable Hot Module Replacement -HMR)*
HtmlwebpackPlugin 简单创建HTML文件,用于服务器访问
ProvidePlugin 全局的注入模块,不必通过import/require使用模块
这些插件的执行顺序与配置顺序无关 pligun可以在webpack的整个编译周期执行
4.如何实现一个loader -D是开发依赖 -S 生成依赖
**loader本质上是一个函数,这个函数接收source为参数,函数返回值便是我们最终loader处理的结果。**
使用过uglify-loader来进行代码压缩的
1.首先现在webpack.config.js下配置loader和指定loader编写的目录
2.在指定编写的目录写对应的loader的实现操作
3.返回code实现webpack代码的压缩。
5.如何编写一个plugin 日志loader
1.plugin本质上是一个构造函数,通常用class类来表示,constructor中接受入参,
且必须实现apply方法来接收webpack的主体编译对象compiler
2.讲清楚自己实现过哪些自定义的plugin,分别是用来做什么的?
打印日志的LogPlugin
6.plugin和loader的区别:
1.功能:loader能做的事情,plugin也可以做
2.执行顺序:
pligin 可以在webpack编译的过程中执行,类比react的生命周期钩子
loader 只能在固定的阶段执行
3.本质上的区别
loader 本质上是一个翻译官,对客户端识别不了的源码进行翻译
plugin 执行的是一些副操作,比如输出一个日子,图片资源可以实现插件将图片做一些副操作,上传到cdn里面
7.webpack的构建流程
如下图
8.

image-20211225172445261

数据结构

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
1、JavaScript的数组方法有哪些?
concat连接2个或更多数组,并返回结果
every对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true
const array = [{name:'freemen',age:18},{name:'vinko',age:18}];
const result = array.every((item)=>{
return item.name==="freemen"
})
console.log(result);//false

filter对数组中的每一项运行给定函数,返回该函数会返回true的项组成的数组
forEach对数组中的每—项运行给定函数**这个方法没有返回值** 传入的参数可以使item,和一个索引index
const array = [{name:'freemen',age:18},{name:'vinko',age:18}];
array.forEach((item,index)=>{
console.log(item); //{name:'freemen',age:18}{name:'vinko',age:18}
console.log(index);0 1
})

join将所有的数组元素连接成一个字符串
const array = [1,2,3,4,5];
const result = array.join(',');
console.log(result);//1,2,3,4,5

indexOf返回第一个与给定参数相等的数组元素的索引,没有找到则返回-1
lastIndexOf 返回在数组中搜索到的与给定参数相等的元素的索引里最大的值
map对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组
const array = [{name:'freemen',age:18},{name:'vinko',age:18}];
const result = array.map(item=>{
if(item.name==='freemen'){
return item;
}
return {};
})

reverse 颠倒数组中元素的顺序,原先第一个元素现在变成最后一个,同样原先的最后一个元素变成了现在的第一个
slice传入索引值,将数组里对应索引范围内的元素作为新数组返回
some对数组中的每一项运行给定函数,如果任一项返回true,则返回true
const array = [1,2,3,4,5];
const result = array.some(item=>{
return item===6;
})
console.log(result);

sort按照字母顺序对数组排序,支持传入指定排序方法的函数作为参数
const result = array.sort((a,b)=>{
return a-b;
})

toString 将数组作为字符串返回
2、栈 peek获取栈顶
isEmpty栈是否为空
3、react-redux有用到队列的数据结构(发布订阅机制) 所有的订阅者都会拿到消息执行,执行的原则是先进先出
4、冒泡排序

设计模式

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
装饰者模式:不改变对象自身代码的基础上新增功能
策略模式的实现:有效解决if else逻辑复杂不可维护的情况
let strategys = {
s(base){
log('phone')
return base*5
}
a(base){
log('书包')
return base*4
}
b(base){
log('鼠标')
return base*3
}
}
function catulaBonus(base,grade){
return strategys[grade](base)
}
代理模式:转发请求

发布-订阅模式又叫观察者模式、它定义对象间的一种一对多的依赖关系当一个对象的状状态发生改变时所有依赖于它的对象都将得到通知
最基础的例子是事件监听
迭代器模式:对一个目标数组进行遍历
倒叙迭代器
let reverseEach = function(obj,callback){
if(!array.isArray(obj)){
throw Error('params is must an array')
}
for(let i =obj.length-1;i>=0;i--){
callback.call(obj[i],obj[i],i)
}
}
reverseEach(array,(item,index)=>{
log(`item:${item}`)
log(`index:${index}`)
})

网络协议面试题及实操

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
1、http常见状态码及含义:
200请求成功
301永久重定向
302临时重定向
401未授权
403没权限,服务器拒绝了请求
404表示服务器上没有找到该资源
405表示请求的方式不对
500服务器错误
2get和post区别
1.get用来获取数据,post用来提交数据
2.get参数有长度限制(受限于url长度,具体的数值取决于浏览器和服务器的限制,最长2048字节),而post无限制。
3.get参数通过 url 传递,post放在请求体 (request body) 中。 get是明文传输,post的请求参数是放在请求体中
4.get请求只能进行url编码,而post支持多种编码方式。

3、http协议中的header及含义
accept:代表客户端希望接受的数据类型
accept-encoding:浏览器发给服务器,声明浏览器支持的编码类型
accept-language:表示浏览器所支持的语言类型
Cache-Control:缓存开关,no-cache表示禁用缓存
referer: referer的正确英语拼法是referrer。由于早期HTTP规范的拼写错误,主要用于防止盗链和恶意请求

4、网络分层模型 OSI七层模型
应用层
表示层
会话层
传输层
网络层
链路层
物理层

浏览器面试题

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
1、浏览器事件传输机制
事件传输机制冒泡和捕获分别由微软和网景公司提出
这两个概念都是为了解决页面中事件流(事件发生顺序)的问题

事件触发的三个阶段:捕获阶段、目标阶段、冒泡阶段
addEventListener(,true)是捕获阶段 从外到内
addEventListener(,false)是冒泡阶段 从内到外

2、localStorage和sessionStorage的区别 本地存储和会话存储的区别
数据生命周期:
localStorage:永久性存储 除非被用户主动清理,否则一直存在
sessionStorage:页面关闭就清理
数据存储大小
localStorage: 5M
sessionStorage:5M
3、浏览器的事件循环机制
你对浏览器的事件循环机制了解多少
宏任务
scripti(整个代码段)
setTimeout
setInterval
setlmmediate
I/O
UI render
微任务
process.next Tick
Promise
Async/Await
MutationObsarver(html5新特性)
4、浏览器的回流和重绘
浏览器的渲染过程是怎样的?
1.解析HTML,生成DOM树,解析css,生成CSSOM树
2.将DOM树和CSSOM树结合,生成渲染树(Render Tree)
3. Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点
的几何信息(位置,大小)
4. Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点
的绝对像素
5.调用GPU触发渲染,将结果展示在页面上

回流:
我们通过构造渲染树,我们将可见DOM节点以及它对应的样式结合起来,可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流
(重新绘制当前页面,包括整个结构和样式)
重绘:
我们通过构造渲染树,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(位置、大小),那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点
(重新绘制当前页面样式)
比方说我们软件设计中的设计模式源于建筑学
回流:相当于把房子拆了重建
重绘:相当于重新粉刷
结论:回流必然导致重绘,重绘不一定会伴随着回流

何时触发回流和重绘
1.添加或删除可见的DOM元素
2.元素的内容、位置或尺寸发生变化
3.页面一开始渲染的时候
4.浏览器的窗口尺寸变化

避免回流和重绘 图片
使用文档片段fragment避免不停的往页面插入元素

5、跨域
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
如果两个URL的protocol、port (en-US)(如果有指定的话)和 host都相同的话,则这两个URL是同源。

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
跨域的解决方法:
jsonp:通过动态创建 script 标签,通过 script 标签的 src 请求来通过jsonp跨域
原理:
利用<script>标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的jSON 数据。JSONP请求一定需要对方的服务器做支持才可以
优点: JSON优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题
缺点∶仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题
1.页面和其打开的新窗口的数据传递
2.多窗口之间消息传递
3.页面与嵌套的iframe消息传递
4.上面三个场景的跨域数据传递
6、浏览器主要组成部分
用户界面
浏览器引擎
渲染引擎
网络模块
用户界面后端
javascript解析器
数据存储模块

用户界面:包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
浏览器引擎:在用户界面和呈现引擎之间传送指令。
渲染引擎–负责显示请求的内容。如果请求的内容是 HTML,它就负责解析HTML和CSS内容,并将解析后的内容显示在屏幕上
网络模块:用于网络调用,比如HTTP请求。其接口与平台无关,并为所有平台提供底层实现。
用户界面后端:用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
JavaScript解释器:用于解析和执行JavaScript代码。
数据存储模块:这是持久层。浏览器需要在硬盘上保存各种数据,例如Cookie SessionStorage LocalStorage

image-20211229224218562

image-20211229224445796

软素质

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
一、说一下自己的优点和缺点?
1.避开岗位的核心技能
2.把缺点放在场景中描述
3.对缺点进行合理化解释
4.优点随便说,主要方向还是在岗位上
二、是否能接受加班?(建议分情况作答)
假设公司有重要的项目要赶。示范回答:贵公司现在正处于发展上升期,也在官网上有看到公司的重要项目成果,我觉得有时候因为赶项目进度、工作需要等忙起来是非常正常的,面对这种情况,我是非常愿意配合公司和团队的工作,让工作能够更顺利地完成,此外,我也相信自己一定能在公司安排的工作中获得到锻炼,获得更快地成长。
·假设自己作为新人,对业务不熟悉。示范回答:我作为公司刚进去的新人,可能刚开始进入公司接触业务时不太熟练,
会出现需要加班的情况,但我更愿意提高工作效率,并积极向公司的前辈请教学习,在一定的时间内完成工作而不是拖到下班之后。当然,如果有紧急的事情,忙起来需要加班也是可以接受的。
三、对薪酬的要求?
·薪资并不是我求职的唯一标准,我来贵司求职的主要动机是兴趣,这份工作是我喜欢做的,也相信自己可以胜任,更相
信公司会给出一个合理的薪酬。
·我希望薪资可以达到XX,据我了解,贵司这个岗位薪资范围是A-~B,而结合岗位职责及任职要求,我对自己也进行了
相应评估,也愿意接受贵司的下一步考核。
四、为什么你觉得这个岗位适合自己?(为什么要聘用你)
1.描述应聘岗位的胜任条件,强调自己的工作能力跟岗位的匹配度,岗位要求的工作技能是否自己掌握了,掌握的程度是怎样的,最好在面试中说出来。(建议:在面试前最好是要针对应聘岗位,把自己胜任的条件一一列出来,做到知己知彼。)
2.描述自己能为公司做出什么贡献,公司是一个讲究利益的地方,聘用你肯定要你为公司做出贡献。那么你在回答这个问
题时,就需要说出你的加入可以为公司带来什么,这非常重要。(一定要明确你的工作目标和职业规划,表明你的立场和专业程度,让HR信任你。)
3.描述出自身的优势。公司为何要聘用你,而不聘用别人,肯定是你有比别人优秀的地方。那么在回答这个问题时,就一
定要说出自己与众不同的地方,最好是要举一个例子,来支持你的观点。
(提示:这个问题,主要是想进一步了解你的信息,以及为这次面试做了多少功课。在面试前,最好是要尽可能获取有关公司可行业的资料信息。在回答的时候,结合自己所做的功课,建立个人和公司的联系,说明自己在哪一方面能够匹配公司的要求。)
五、对我们公司有多少了解?
·如果不了解,就按实际情况回答就好,知道多少就说多少,(很多时候去面试对这个公司的了解都是从网上查到的,不
会太深入);最好是提前做好一些调研和准备工作。
六、缺乏工作经验,如何胜任这份工作?
·承认工作经验的重要性。
·突显个人优势。用自己的其他优势特长来补足经验上的不足,比如说记忆力好、动手能力强、语言能力强、学习能力强等。
·强调自己会不断提高工作能力。切忌用假大空的话。

简历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1、简历投递
1.朋友内推:更好的了解公司以及团队内部的情况
2.招聘软件:boss直聘 打钩
3.公众号
2、如何准备项目经验
1.准备1-2个实际做过的或者可以讲清楚整个业务流程的项目
2.说清楚其中作用的技术栈
3.讲清楚在项目开发过程中遇到的难点
4.复盘整个过程发掘出可以优化的地方
1、样式布局可以进行优化:以采用css module的方式 以.css/.less文件文件为主,而不是styled-components的方式
虽然采用styled-components的方式可以有效避免组件之间样式的冲突 但是编写代码起来繁琐(包括没有代码提示)
同时jsx 中的原生标签不能一眼看出来,也额外增加了标签变量。

2、前端的页面,还不完整,可以加一个后台管理系统来管理数据。

3、学历是否能反应一个人的真实水平
当然不能 学历是判断潜力的一个相对客观的一个指标
4、简书项目
1.不同的浏览器内核里对html body标签默认的样式是不同的,有可能在某一个浏览器上的body的margin间距值是10 而在另外一个浏览器的间距值就是8 为了让这个代码在所有浏览器的表现形式是一致的,我们要先把这些浏览器默认对这些标签的实现进行一个统一
reset.css就是干这个活 ,当你引入这个之后,标签基本上在所有浏览器的margin,padding值都会一模一样,这样就防止在不同的浏览器打开呈现的效果不同。

image-20220101141245546

高频考点

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
React面试题
. React组件通讯的常见方式。JSX本质是什么?
1、父组件通过向子组件传递 props
2、利用回调函数,可以实现子组件向父组件通信
3、跨级组件间通信:使用context对象
4、非嵌套组件间通信:使用事件订阅

. context 是什么,有何用途?
Context 一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
创建Context容器对象:
渲染子组件时,外面包裹容器对象的Provider属性, 通过value属性给后代组件传递数据
后代组件读取数据:
第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
const theme = this.context // 读取context中的value数据 theme就是value的值
第二种方式: 函数组件与类组件都可以 函数式没有this所以第一种不适合函数式组件
外面包裹容器对象的Consumer属性

. shouldComponentUpdate 的深入理解。
控制组件更新的“阀门” 应不应该更新状态 **
在父组件更新时会触发此函数,在此函数中可以根据props是否变化来setState,并且在此函数中的setState不会再次触发子组件的render

. 描述redux单项数据流。
react要去改变store里的数据,首先要一个派发action,action会通过dispatch方法传递给store,store再把之前的数据和传递过来的action转发给reducers,reducers是一个函数,当reducers接收到state和action之后,做一些处理之后,会返回一个新的state给到store,store用这个新的state替换掉之前的state,store数据发生改变之后,react组件会感知到store里的state发生改变(用的是``store.subcribe()``来监测store里状态的改变)就会从store里面重新取出数据更新组件的内容,页面就发生变化了

. setState是同步还是异步?
**执行层面**
同步:原生事件、setTimeout
异步:react合成事件、生命周期钩子函数
**本质层面**
本质:本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致他们没法立马拿到更新后的值,形式了所谓的“异步”

. 用React设计一个todolist,请设计组件结构,设计state 数据结构。

webpack面试题
. 前端代码为何要进行构建和打包?
. module chunk bundle分别是什么意思,有何区别?

. loader和plugin 的区别。
. webpack如何实现懒加载?
. webpack '常见性能优化方式(开放型题目,自由发挥)。
. babel-runtime和babel-polyfill的区别。


JavaScript面试题
1、var 和 let const的区别
var是ES5语法,let const是ES6语法;var有变量提升
var和let是变量,可修改;const是常量,不可修改﹔
let const有块级作用域,var没有
2、typeof 能判断哪些类型
undefined string number boolean symbol
object(注意,typeof null === 'object')
function
3、列举强制类型转换和隐式类型转换
强制:parseInt parseFloat toString 等
隐式:if、逻辑运算、==、+拼接字符串
4、数组的pop push unshift shift分别是什么
功能是什么?
push()方法是向数组末尾添加一个或者多个元素,并返回新的长度。
pop()方法删除数组的最后一个元素,把数组的长度减1,返回的是删除的元素。
unshift()方法是向数组的开头添加一个或多个元素,并且返回新的长度。
shift()方法是数组的第一个元素从其中删除,并返回第一个元素的值。
返回值是什么?
pop()返回的是删除的元素
push()返回的是数组的长度****
unshift()返回的是数组的长度****
shift()返回的是删除的第一个元素元素
是否会对原数组造成影响?
都产生影响
以下对原数组不产生影响
toString:得到数组中每个值的字符串形式拼接而成的、一个以逗号分隔的字符串。
valueOf:返回数组的原始值
join:使用指定的分隔符,将数组的每个元素连接起来,返回构建的字符串。
concat:传递一个或多个数组,则会将数组的每一项都添加到结果数组中。
slice:返回起始位置到数组末尾的所有项

纯函数:不改变原数组(没有副作用),且返回一个数组 =>等同于对原数组不产生影响
map
filter
slice
concat
非纯函数:forEach reduce some every

5、slice(-2)//截取后面两个 纯函数
splice 非纯函数 splice(1,2,'a','b')//从第一个元素开始截取两个 然后添加ab
6、const res = [10,20,30].map(parseInt)
拆解开是
[10,20,30].map((num,index)=>{
return parseInt(num,index) //parseInt(20,1) parseInt(30,2)不符合parseInt的转换规则
})
结果为[10,NaN,NaN]//map返回的是一个数组
**parseInt如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。**
7、ajax请求get和post的区别
get 一般用于查询操作,post 一般用户提交操作
get参数拼接在url上(明文传输), post放在请求体内(数据体积可更大)
安全性:post易于防止 CSRF
8、闭包应用场景:作为参数被传入,作为返回值被返回
自由变量的查找,要在函数定义的地方(而非执行的地方
影响∶变量会常驻内存,得不到释放。闭包不要乱用
9、如何阻止事件的冒泡和默认行为
event.stopPropagation
event.preventDefault
这两个API
10、查找、添加、删除、替换DOM节点的方法?
查找:
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性
添加appendChild()
删除removeChild()
替换replaceChild()
11、如何减少DOM操作?(耗性能)
缓存DOM查询结果(获取dom的list长度的时候先把list拿出来 避免重复获取)
多次DOM操作,合并到一次插入
12、==会尝试类型转换,===严格相等
13、函数声明和函数表达式的区别
函数声明function fn() {...}
函数表达式const fn = function() {...}
函数声明会在代码执行前预加载,而函数表达式不会
14、new Object()和 Object.create()区别
{}等同于new Object,原型Object.prototype
Object.create(null)没有原型
Object.create({...})可指定原型 =>意思是创建了一个空对象,但是空对象的原型指向了{...}
15、下图关于this的题 第一个是1 第二个是undefined 如果将函数作为一个独立函数来执行的话 this就是undefined
16、\w 命中字母数字下划线 \d匹配数字
17、trim去除空格字符=>保证浏览器的兼容性
18、Math.max
19、如何用JS实现继承
class继承
prototype继承
20、如何捕获JS中的异常 =>手动捕获catch
21、 什么是JSON ?
json是一种数据格式,本质是一段字符串。
json格式和JS对象结构一致,对JS语言更友好
window.JSON是一个全局对象:JSON.stringify JSON.parse
22、获取当前页面的url参数
传统方式,查找location.search
新API,URLSearchParams
23、数组去重的几种方式
Set方式=>无序 不能重复
24、手写一个深拷贝
Object.assign()不是深拷贝,这是追加信息
const obj = {a: 10,b:20,c: 30}
Object.assign(obj, {d: 40}) //obj:{a:10,b: 20,c: 30,d: 40}
const obj1= Object.assign({},obj, {e: 50})
obj1={a:10,b: 20,c: 30,d: 40,e: 50}
第一层级的浅层拷贝
const obj = {a: 10,b:{x:100,y:100}}
const obj1= Object.assign({},obj, {c: 50})
obj.a=100 那么obj变成{a: 100,b:{x:100,y:100}} obj1没有改
obj.b.x=101 obj和obj1都改了 只拷贝了第一层级a:10 c:30 xy就不拷贝了

image-20220101203115228

拍平数组 如果是很多重就递归拍平

image-20220101205927937

React 基础

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
186
187
188
189
190
191
192
**使用jsx创建虚拟DOM
1.<script type="text/javascript" src="../js/babel.min.js"></script> -> 引入babel,用于将jsx转为js
const VDOM = (
<h1 id="title">
<span>Hello,React</span>
</h1>
)
/* 此处一定不要写引号,因为不是字符串 下面这些代码经过babel翻译为js原生代码的写法*/
babel.js的作用
1.浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
2.只要用了JSX,都要加上type="text/babel", 声明需要babel来处理

React面向组件编程:
1. 组件名必须首字母大写
2. 虚拟DOM元素只能有一个根元素
3. 虚拟DOM元素必须有结束标签

**使用js创建虚拟DOM
2.<script type="text/javascript" > //原生js
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))//标签名 标签属性 标签内容

3.关于虚拟DOM:
1.本质是Object类型的对象(一般js对象)
2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
一定注意区分:【js语句(代码)】与【js表达式】
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {}
2.语句(代码):
下面这些都是语句(代码):
(1).if(){}
(2).for(){}
(3).switch(){case:xxxx}
4.jsx语法规则:
1.定义虚拟DOM时,不要写引号。
2.标签中混入JS表达式时要用{}。
3.样式的类名指定不要用class,要用className。
4.内联样式,要用style={{key:value}}的形式去写。
5.只有一个根标签 就是说h2只能一个,如果要两个就得加一个div [div包裹很不情愿,后期有办法处理,可以通过Frangment标签.]
6.标签必须闭合
7.标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素(都是小写的),若html中无该标签对应的同名元素,则报错。good会报错
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。GOOD
5.创建函数式组件
**函数名大写 函数必须有返回值
function MyComponent(){
console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式 禁止这种自定义的函数this指向window
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
**渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test')) //标签必须闭合<MyComponent/>
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?)
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
6.创建类式组件
class MyComponent extends React.Component {
render(){
//不用写构造器 写render 返回值也必须写 继承React.Component也必须写
//render是放在哪里的? —— 组件的的原型对象上,供实例使用。
//render中的this是谁?—— 组件的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:',this);//结果MyComponent的实例对象
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test')) //const my = new MyComponent()
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
7.类式组件可以自定义方法————要用赋值语句的形式+箭头函数,否则this指向为undefined。
类中可以直接写赋值语句,就类似于在实例自身追加一个属性名
因为类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined。
函数式不知道方法只能支持赋值语句。

8.方法的定义在render外部,解构赋值是在render内部

9.严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!React不认这样的更改//this.state.isHot = !isHot //这是错误的写法

10.对标签属性进行类型、必要性的限制放到类里 如果Person有初始化状态,就这样写state = {}在这里面写就可以了。
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}

Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
11.用函数式创建组件,由于没有实例函数因此没有this。无法使用state和refs这两个属性 但是props属性例外。
function Person (props){
const {name,age,sex} = props **** C参数全部都在props
return (
<div>你好</div>
)
}

12. 对标签属性进行类型、必要性的限制 类的关键字 static ***可以当做简写形式
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串 把这两个东西放进一个打括号里
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
13.渲染类组件标签的基本流程:
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
14. 强烈注意
* 组件中render方法中的this为组件实例对象
** 组件自定义的方法中this为undefined,如何解决?
a) 通过函数对象的bind()强制绑定this
b) 箭头函数
*** 状态数据,不能直接修改或更新
15.ref的三种形式
* 字符串形式的ref <input ref="input1"/>
const {input1} = this.refs//调用的时候要加了s
这种字符串形式的ref马上要被废弃,因为string类型的refs存在一些效率上的问题。

** 回调形式的ref <input ref={(c)=>{this.input1 = c}}
这么写的结果就是把ref当前所处的节点挂在了实例自身上,并且取了个名字叫input1
const {input1} = this//取input1就直接从实例自身取就可以了,直接写this即可

*** createRef创建ref容器·myRef = React.createRef() <input ref={this.myRef}/>
使用直接this.myRef.current.... 即可
这是最推荐的 但是也是最繁琐的 因为有多少个ref就要创建多少个myRef
React.createRef调用后可以返回一个容器,该容器可以存储**被ref所标识的节点**,该容器是“专人专用”的
ref所在节点input被放到了myRef的容器里

16.回调函数的特点: 1、你定义的函数 2、你没调用 3、这函数最终执行了
17.事件的处理;
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件(onClick) —————— 为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了高效
(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
<button onClick={this.showData}>点我提示左侧的数据</button>
//展示左侧输入框的数据
showData = (event)=>{//发生事件的事件源
console.log(event.target);**输出的是<button>点我提示左侧的数据</button>
alert(this.myRef.current.value);
}
失去焦点那边可以省略ref 因为发生事件的元素正好是你操作的元素,就可以省略
点击按钮提示那个不能省略 因为给的是button加的Click事件,但是要拿input的数据

18.受控组件:输入类的DOM随着你的输入把东西维护到状态里去,等需要用的时候直接从状态里取出来
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this.state//拿到的真正的值 下面就不用.value
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
非受控组件:现用现取
const {username,password} = this //获取那两个节点
//对输入类的DOM 现用现取
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>{/*点了登陆就触发那个表单提交*/}
</form>
19.高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等

使用函数柯里化:不加小括号是因为把一个函数作为onChange回调,加了小括号我把这个函数返回值交给onChange回调,下面写的返回值就是 一个函数 返回的就是函数。
onChange={this.saveFormData('username')
saveFormData = (dataType)=>{ //dataTye就是传的东西
return (event)=>{
this.setState({[dataType]:event.target.value}) //函数作为onchange的回调是返回值作为回调
}
}
//onchange要的就是一个函数 现在给的就是一个函数 onchange回调执行的时候 react帮你调用然后传入event

不使用函数柯里化:onChange={event => this.saveFormData('username',event) } 直接传一个函数
//保存表单数据到状态中 同时接到dataType event
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value}) //因为上面传的是一个函数,因此这里就不用返回一个函数
}

image-20211207195755851

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
20.组件生命周期理解
1. 组件从创建到死亡它会经历一些特定的阶段。
2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

**挂载时**
constructor:组件构造函数,常用于初始化组件state,以及对组件内响应方法的绑定

componentWillMount:在第一次渲染前的钩子函数,也常用于初始化state,在新版生命周期中已弃用

render:组件渲染函数,返回一个jsx元素,用于显示真实DOM中的元素

componentDidMount:在组件挂载后执行,一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

**更新时**
compontentWillReceiveProps:
在父组件更新时会触发此函数,在此函数中可以根据props是否变化来setState,并且在此函数中的 setState不会再次触发子组件的 render

shouldComponentUpdate:/控制组件更新的“阀门” 应不应该更新状态
在setState和compontentWillReceiveProps后会触发此函数,可以在此函数中编写相关方法判断是否继续render,相当于一个自定义 的diff函数

componentWillUpdate:组件的props或state发生改变后,render之前触发,在第一次组件挂载时不会触发此函数 ***弃用

render

componentDidUpdate:在组件的props或state更新完并执行完render后触发

**卸载时**
componentWillUnmount:在组件卸载或者销毁之前触发 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

其中componentWillMount、compontentWillReceiveProps、componentWillUpdate在新版生命周期中均已被弃用

image-20211207212028192

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
新版生命周期:
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. getDerivedStateFromProps
3. render()
4. componentDidMount() =====> 常用*****
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps
2. shouldComponentUpdate()
3. render() ******必须要用
4. getSnapshotBeforeUpdate
5. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用***
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

新版生命周期:中将较于旧版,增加了两个新的钩子,分别是getDerivedStateFromProps、getSnapshotBeforeUpdate,
并弃用了componentWillMount、compontentWillReceiveProps、componentWillUpdate这三个钩子

static getDerivedStateFromProps(nextProps, prevState)
若state的值在任何时候都取决于props那么可以使用getDerivedStateFromProps 横跨挂载和更新

getDerivedStateFromProps为静态函数,传入参数为nextProps、prevState。其中,nextProps为将要更新的props,prevState 为上一个状态,可根据传入的props,以及过去的state来增加限制条件,防止无用的更新

getSnapshotBeforeUpdate(prevProps, prevState)
在更新之前获取快照 在组件更新DOM前调用,可以在此DOM更新前捕获一些页面信息(例如滚动位置),此生命周期返回的值会作为参数传入 componentDidUpdate

key的作用
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?

1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。

2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实 DOM

b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面

2. 用index作为key可能会引发的问题: (this.setState({persons:[p,...persons]})这个就是破坏了顺序性操作)
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。

3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。

3. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。

慢动作回放----使用index索引值作为key

初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
//这里的input框给小王用了 所以小张残留的信息给了小王了 因此会产生错误DOM更新,界面有问题
<li key=1>小李---19<input type="text"/></li>

更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>

-----------------------------------------------------------------

慢动作回放----使用id唯一标识作为key

初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>

更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM: 因为把小王放在第一个了 所以遍历的时候小王是第一个
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>

React应用(基于React脚手架)

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
1.使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
2.react脚手架项目结构:
(1).index.html -------- 主页面
(2).App.js --------- App组件
(3).index.js ------- 入口文件
3.功能界面的组件化编码流程:
1. 拆分组件: 拆分界面,抽取组件
2. 实现静态组件: 使用组件实现静态页面效果
3. 实现动态组件
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.2 保存在哪个组件?
3.2 交互(从绑定事件监听开始
4.document.getElementById('root')//为什么能找到public里的index.html ,这是因为React底层帮忙写好的。
5.状态在哪里,操作状态的方法就在哪里。

6.常用的ajax请求库:
1. jQuery: 比较重, 如果需要另外引入不建议使用
2. axios: 轻量级, 建议使用
1) 封装XmlHttpRequest对象的ajax
2) promise风格
3) 可以用在浏览器端和node服务器端

7.SPA的理解
1. 单页Web应用(single page web application,SPA)。
2. 整个应用只有一个完整的页面。
3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
4. 数据都需要通过ajax请求获取, 并在前端异步展现。

路由

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
8.路由的理解
1. 一个路由就是一个映射关系(key:value)
2. key为路径, value可能是functioncomponent

9.路由分类
1. 后端路由:
(1) 理解: valuefunction, 用来处理客户端提交的请求。
(2) 注册路由: router.get(path, function(req, res))
(3) 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
2. 前端路由:
(1) 浏览器端路由,valuecomponent,用于展示页面内容。
(2) 注册路由: <Route path="/test" component={Test}>
(3) 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
10.react-router-dom的理解
1. react的一个插件库。
2. 专门用来实现一个SPA应用。
3. 基于react的项目基本都会用到此库。

内置组件
1. <BrowserRouter>
2. <HashRouter>
3. <Route>
4. <Redirect>
5. <Link>
6. <NavLink>
7. <Switch>

Redux

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
1.redux是什么
(1).redux是一个专门用于做状态管理的JS库(不是react插件库)。
(2).它可以用在react, angular, vue等项目中, 但基本与react配合使用。
(3).作用: 集中式管理react应用中多个组件共享的状态。

2.什么情况下需要使用redux
(1).某个组件的状态,需要让其他组件可以随时拿到(共享)。
(2).一个组件需要改变另一个组件的状态(通信)。
(3).总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
3. redux的三个核心概念
(1).动作的对象
(2).包含2个属性
type:标识属性, 值为字符串, 唯一, 必要属性
data:数据属性, 值类型任意, 可选属性
(3).例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
4.reducer
1. 用于初始化状态、加工状态。
2. 加工时,根据旧的state和action, 产生新的state的纯函数。

5.store
1. 将state、action、reducer联系在一起的对象
2. 如何得到此对象?
1) import {createStore} from 'redux'
2) import reducer from './reducers'
3) const store = createStore(reducer)
3. 此对象的功能?
1) getState(): 得到state
2) dispatch(action): 分发action, 触发reducer调用, 产生新的state
3) subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

6.redux的核心API
createstore()
作用:创建包含指定reducer的store对象
7.store对象
1. 作用: redux库最核心的管理对象
2. 它内部维护着:
1) state
2) reducer
3. 核心方法:
1) getState()
2) dispatch(action)
3) subscribe(listener)
4. 具体编码:
1) store.getState()
2) store.dispatch({type:'INCREMENT', number})
3) store.subscribe(render)

8.applyMiddleware():作用:应用上基于redux的中间件(插件库)
9.combineReducers():作用:合并多个reducer函数

10.redux异步编程理解:
1. redux默认是不能进行异步处理的,
2. 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)
使用异步中间件:npm install --save redux-thunk
1. 一个react插件库
2. 专门用来简化react应用中使用redux

11. react-Redux将所有组件分成两大类
1. UI组件
1) 只负责 UI 的呈现,不带有任何业务逻辑
2) 通过props接收数据(一般数据和函数)
3) 不使用任何 Redux 的 API
4) 一般保存在components文件夹下
2. 容器组件
1) 负责管理数据和业务逻辑,不负责UI的呈现
2) 使用 Redux 的 API
3) 一般保存在containers文件夹下
12.相关API
1. Provider:让所有组件都可以得到state数据
<Provider store={store}>
<App />
</Provider>

2. connect:用于包装 UI 组件生成容器组件
import { connect } from 'react-redux'
connect(mapStateToprops,mapDispatchToProps)(Counter)

3. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToprops = function (state) {
return {
value: state
}
}
4. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性

13.纯函数
1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
2. 必须遵守以下一些约束
1) 不得改写参数数据
2) 不会产生任何副作用,例如网络请求,输入和输出设备
3) 不能调用Date.now()或者Math.random()等不纯的方法
3. redux的reducer函数必须是一个纯函数

14.高阶函数
1. 理解: 一类特别的函数
1) 情况1: 参数是函数
2) 情况2: 返回是函数
2. 常见的高阶函数:
1) 定时器设置函数
2) 数组的forEach()/map()/filter()/reduce()/find()/bind()
3) promise
4) react-redux中的connect函数
3. 作用: 能实现更加动态, 更加可扩展的功能

redux工作流程

image-20211208153416819

简书项目

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
1、在reducer.js文件里,不能对原始的state做修改,为了防止这一点得引入facebook团队研发出来的一个库:immutable.js

它会帮助我们生成一个immutable对象 (不可改变的)如果state是immutable对象就是不可以被改变,就不会出问题。
2、immutable这个库提供了一个fromJS方法,可以帮助我们把一个js对象转化为immutable对象,用这个方法把数据对象(js对象)

转化为immutable对象。此时header下的index文件的mapState方法里的数据已经转化为immutable类型的数据(state.header)

如果是immutable类型的数据,再进行state.header.focused这种语法是不合理的了。immutable.js里面调用state.header里的属性不能通过点focused的方式来调用,得通过点get的方法传入focused的属性来获取对应的属性。此时不会报错,但是点击的时候就会报错报错的原因在与当你派发一个action的时候,传入reducers返回的是一个普通对象,普通对象时没有get方法的 为了防止这种情况,可以使用immutable对象的set方法,immutable对象的set方法,会结合之前immutable对象的值和设置的值,返回一个全新的对象,此时没有改变原始的state
3、:借助immutable这个库避免编写改变数据里state的情况
4、获取ajax数据一般来说不会直接写在组件里,会把异步的操作放到action,或者放到redux-saga里面进行处理,这里统一使用redux-thunk,把异步操作都放至action里处理
5、redux-thunk其实就是action和store之间的中间件 其实就是对dispatch方法的升级 以前的action返回的是一个对象,现在可以返回函数
6、在public文件夹下创建一个api文件夹之后在创建headerList.json的文件输入一下数据 之后在网页访问这个链接页面就可以显示数据
原理:首先先到功能目录下看看有没有对应的路由,找不到就会去public目录下找api的headerList.json,找到后就会把文件的内容输出出来
通过这个特性,我们可以创建一些假数据,保存。模拟的数据要和后端对的数据要保持一致,就是定一下格式
7、这样写实际上还是有问题的:创建store,默认list是一个空数组,fromJS方法会将js对象转换为immuable对象,list是个数组,也会变成immuable的数组,但是调用set去改变list的时候,action.data实际上是一个普通的数组, list数组会由immuable数组变成一个普通的数组,这样的话数据类型就会变了,解决这个问题
8、至于优化觉得可以做一些性能上的优化。因为每点击一次焦点的时候就会发送一次ajax请求。实际上列表中的数据获取一次就可以了
解决问题:给``handleInputFocus(list)``传入list 然后输出list可以看出第一次请求的时候list.size是0,之后就是50,
所以控制当size为0的时候才发送请求,之后不等于0的时候就不发生了,这样就避免了每次获取焦点的时候都发一个ajax请求,从而做到性能上的调优。

9、前两周这个包刚更新过如果按之前的编码规则会使得路由组件无法显示
import Home from './Home '
老版本
<Route path=" / home" component={ Home } />
新版本
<Route path=" / home" element={ <Home/> } />
10、在public下的api定义一个接口home.json。之后在home组件下借助componentDidMount这个声明周期函数来发ajax请求去获取数据,获取了数据,(当组件挂载完毕)。就要将获取的数据取修改store里初试的数据
11、修改store里的数据也就是状态 就得使用connect的第二个参数,通过这个函数就可以定义一个方法将action派发给store,因为UI组件不能和store直接通信,只能通过容器组件。派发给store之后store就会派发给reducer。
12、因为首页的index.js都调用了connect的方法,和store做了连接,这就会产生一个问题,只要store发生了改变,那么每一个组件都会被重新渲染,也就是每个函数的render函数会被重新执行,可能有些数据发生改变了,但是那个数据和这个组件一点关系都没有,但是这个组件依然会被重新渲染,导致性能不好。提高组价性能 shouldComponentUpdata,可以在这里做性能优化的代码,判断只有与这个组件相关的数据发生改变才让render函数重新执行(重新渲染),否则return false不让render函数重新执行。
通过这种方式来避免虚拟DOM的比对,提高性能,react也考虑到了这点,如果你在每个特组件都去自己写should....太麻烦了,react内置了新的组件类型,引入PureComponent,区别就是PureComponent内在自己底层实现了should...,这样就不用我们手写收should......做性能优化。之后就是每个UI组件都替换为PureComponent
之所以项目用**PureComponent**,是因为项目的数据管理用了immuable.js的框架,它可以保证我们的数据是immuable的,这样PureComponent和immuablejs数据格式的管理相结合,使用PureComponent一点问题都没有,但是如果你在你的项目里面,没有使用immuable.js管理你的数据,那么使用PureComponen有的时候会遇到坑。(偏底层的坑)
13、当我们在react实现页面跳转的时候,我们要用到react-router-dom第三方模块,它的这种跳转是单页应用跳转,link标签要在Router里面否则会报错

面试题

HTML CSS 面试题

1、如何理解HTML 语义化?

  • 让人更容易读懂(增加代码可读性)

  • 让搜索引擎更容易读懂(SEO 搜索引擎优化)

2、默认情况下,哪些HTML 标签是块级元素、哪些是内联元素?

  • 块级元素:display: block/table; 有div h1 h2 table ul ol p 等

  • 内联元素:display: inline/inline-block; 有span img input button 等

3、盒子模型的宽度如何计算?

image-20211210121939411

  • offsetWidth = ( 内容宽度+ 内边距+ 边框),无外边距

  • 因此,答案是122px

  • 补充:如果让offsetWidth 等于100px ,该如何做? 在div1样式做设置盒子模型为:IE盒子模型(box-sizing: border-box;)

    设置了box-sizing: border-box之后;宽度其实变成了78 加上内边距20 边框2 加起来就100了。

4、argin 纵向重叠的问题

image-20211210123149043

  • 相邻元素的margin-top 和margin-bottom 会发生重叠

  • 空白内容的

    也会重叠 —>空白内容重叠可忽略

  • 答案:15px

5、margin 负值的问题

对margin 的top left right bottom 设置负值,有何效果?

  • margin-top 和margin-left 负值,元素向上、向左移动

  • margin-right 负值,右侧元素左移,自身不受影响

  • margin-bottom 负值,下方元素上移,自身不受影响

6.float 布局

如何实现圣杯布局和双飞翼布局? 手写clearfix

圣杯布局和双飞翼布局的目的:

  • 三栏布局,中间一栏最先加载和渲染(内容最重要)
  • 两侧内容固定,中间内容随着宽度自适应
  • 一般用于PC网页

圣杯布局例子:

image-20211210132014561

三者都设置浮动且center宽度设置为百分百加上容器本身设置内边距

使得right的宽度无法在left的右侧排列被挤下来了

image-20211210132236216

若要使得left移动至上图样式 设置margin-left:-100% ,设置这个实则就是父元素宽度的百分之百。也就是center(container)的宽度

(可以看成浮动元素它是别挤下来的 实际上是紧靠灰色部分的右侧。所以设置了这个就到了上图的位置)

之后在设置相对定位 (right:200px即可)因为设置了相对定位是相对于自身移动,对其他元素没有影响。(可以看到定位的坐标为灰色的左上顶点处)

image-20211210133101515

给margin-right设置负150即可成为下图

image-20211210133244236

注意:不好理解!!! 就是给自身元素设置的margin-right:自身宽度。就会使自身宽度不占位置从而上移

上述的布局就是圣杯布局 圣杯布局两边留白是通过padding设置的 而双飞翼布局是通过margin来进行两边留白的

双飞翼布局:

  1. 双飞翼布局是通过margin来进行两边留白。
  2. left直接设置margin-left:-100%即可。

image-20211210134341275

right:也设置margin-left:-190px。

image-20211210134356122

1
2
3
4
5
6
7
手写clearfix  BFC解决清除浮动的问题
.clearfix:after{
content:'';
display:table;
clear:both;
}
将clearfix属性添加到容器的class内。 因为是浮动元素,高度塌陷了。为了防止不塌陷所以在container内设置该样式

7、flex 布局

flex 实现一个三点的色子

  • flex-direction :主轴方向(横向或者纵向)
  • justify-content:主轴对齐方式(开始对齐,居中对齐,结束对齐)
  • align-items:交叉轴的对齐方式(开始对齐,居中对齐,结束对齐)
  • flex-wrap:换行
  • align-self:子元素在交叉轴的对齐方式(开始对齐,居中对齐,结束对齐)
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
<style type="text/css">
.box {
width: 200px;
height: 200px;
border: 2px solid #ccc;
border-radius: 10px;
padding: 20px;

display: flex;
justify-content: space-between;/*对齐方式:两边对齐*/
}
.item {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #666;
}
.item:nth-child(2) {
align-self: center; /*第二个子元素垂直居中*/
}
.item:nth-child(3) {
align-self: flex-end;/*第三个子元素垂直方向尾对齐*/
}

</style>
body>
<div class="box">
<span class="item"></span>
<span class="item"></span>
<span class="item"></span>
</div>
</body>

image-20211210145834952

8、absolute 和relative 分别依据什么定位?

relative 依据自身定位

absolute 依据最近一层的定位元素定位(定位元素有:absolute relative fixed body)

9、居中对齐有哪些实现方式?

水平居中

垂直居中

1
2
3
4
5
6
7
8
9
水平居中:
inline 元素:text-align: center
block 元素:margin: auto
absolute 元素:left: 50% + margin-left 负值
垂直居中:
inline 元素:line-height的值等于height 值
absolute 元素:top: 50% + margin-top 负值 **必须知道子元素的尺寸
absolute 元素:transform(-50%, -50%)
absolute 元素:top, left, bottom, right = 0 + margin: auto

10、CSS -图文样式

line-height 如何继承

image-20211210143133796

  • 写具体数值,如30px ,则继承该值(比较好理解)
    • 就直接继承30px
  • 写比例,如2 / 1.5 ,则继承该比例(比较好理解)
    • 直接比例乘以p标签的font-size 2*16=32px
  • 写百分比,如200% ,则继承计算出来的值(考点)
    • 直接body里的font-size的比例乘200%就是20*2=40px

11、CSS -响应式

rem 是什么?

响应式布局的常见方案?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rem 是什么?
rem 是一个长度单位
px ,绝对长度单位,最常用
em ,相对长度单位,相对于父元素,不常用
rem ,相对长度单位,相对于根元素,常用于响应式布局
html {
font-size: 100px; //等于1rem
}
**任何使用长度的地方都可以用rem 例如宽度高度都可以
响应式布局的常见方案?
media-query,根据不同的屏幕宽度设置根元素font-size
body {
font-size: 0.16rem; /*在iphone6/7 就是16px*/
}
rem,基于根元素的相对单位

JavaScript面试题

1、typeof 能判断的类型?

考点:JS变量类型

2、何时使用===何时使用==

考点:强制类型转换

3、window.onload和 DOMContentLoaded区别

考点:页面加载过程,(页面渲染过程)

4、JS创建10个标签,点击弹出对应的序号

考点:JS作用域

5、手写节流throttle和防抖debounce

考点:性能、体验优化

6、Promise解决什么问题?

考点:JS异步

7、值类型和引用类型的区别

值类型各用各的,不会相互干扰。而引用类型一旦赋值了 b改变了 a也改变了

1
2
3
4
5
6
7
8
9
10
**值类型
let a = 100
let b = a
a=200
log(b)//100
**引用数据类型
let a={age:20}
let b=a
b.age=21
log(a.age)//21

image-20211210160818584

image-20211210160828452

8、typeof 运算符

  • 识别所有值类型

  • 识别函数

  • 判断是否是引用类型(不可再细分)

    image-20211210184526582

9、深拷贝

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
const obj1 = {
age: 20,
name: 'xx×',
address: {
city: 'beijing'
},
arr:[ 'a','b', 'c']
}
//const obj2 = obj1
const obj2 = deepClone(obj1) //这边就是进行了深拷贝

obj2.address.city = 'shanghai'

console.log(obj1.address.city) //这是浅拷贝

//深拷贝就是obj2改了 obj1不会变
function deepClone(obj={}){
if (typeof obj !== 'object'||obj ==null) {
//obj是null,或者不是对象或者不是数组,直接返回
return obj *****************
}
//初始化返回结果 obj是对象和数组
let result
if (obj instanceof Array) {
result = []
}else {
result = {}
}

for ( let key in obj){ //是数组和对象就可以遍历
//保证key(age,name...) 是自己的属性,而不是原型的属性 因为这里没有考虑到原型上的东西可忽略,但是面试的时候不可忽略
if (obj.hasOwnProperty(key)) {
//递归调用!!!
result[key] = deepclone(obj[key])//obj[key]就是key对应的值 首先20进来在*****返回了
}
}

return result

}


***let result = {
age: 20,
name: 'xx×',
address: {
city: 'beijing'
},
arr:[ 'a','b', 'c']
} //深拷贝的结果。

10、变量计算-类型转换

image-20211210183556963

image-20211210190327229

let x1=obj1.x 是干扰人的

11、原型和原型链

1
2
3
4
5
6
7
8
**两种写的方式**
console. log(
`姓名${this.name} ,学号${this.number}
)

console.log(
'姓名'+ this.name + ',学号'+this.number
)

class

  • constructor

  • 属性

  • 方法

继承

  • extends
  • super
  • 扩展或重写方法
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
//父类
class People {
constructor( name) {
this.name = name
}
eat() {
console. log(`$ithis.name} eat something`)
}
}

//子类
class Student extends People {
constructor(name, number) {
super(name) //继承父类的name
this.number = number
}
sayHi(){
console.log(`姓名 ${this.name}学号${this.number}`)
}
}

const xialuo = new Student('夏洛',100)
log(xialuo.name)
log(xialuo.number)
xialuo.sayHi()
xialuo.eat()

image-20211210192840339

image-20211210193103817

image-20211210195041939

image-20211210195103225

image-20211210195508425

image-20211210201830160

1
2
1、如何准确判断一个变量是不是数组?
a instanceof Array

12、作用域和闭包

1
2
3
1this的不同应用场景,如何取值?
2、手写bind 函数
3、实际开发中闭包的应用场景,举例说明

image-20211210203042511

作用域

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6新增)

if for {} 就是块级作用域

image-20211210204400524

自由变量

  • 一个变量在当前作用域没有定义,但被使用了

  • 向上级作用域,一层一层依次寻找,直至找到为止

  • 如果到全局作用域都没找到,则报错xx is not defined

闭包

作用域应用的特殊情况,有两种表现:

  • 函数作为参数被传递
  • 函数作为返回值被返回

image-20211210204753650

答案为100

image-20211210204829747

答案为100

闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方!!!

13、this

应用场景

  • 作为普通函数被调用 ->window
  • 使用call apply bind- >传入什么就是绑定什么
  • 作为对象方法被调用 ->返回对象本身
  • 在class方法中调用 ->当前实例本身
  • 箭头函数 ->上级作用域的this的值来决定

注意:this取什么值,是在函数执行的时候确认的,不是在函数定义的时候确认的

image-20211210205632681

bind和call的区别是 bind会返回一个新的函数再去执行

image-20211210205745060

this===window是因为function这个函数执行本身是setTimeout触发的执行,它并不是zhangsan.方法触发的执行。

image-20211210210004770

特点:箭头函数本身没有this都是取它上级作用域的this

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
手写bind函数
由于fun1的隐式原型===Function的显示原型
bind方法只有在Function.prototype

//模板
function fn1(a,b,c){
log('this',this)
log(a,b,c)
return 'this is fn1'
}
const fn2 =fn1.bind({x:100},10,20,30)
const res = fn2()
log(res)

//模拟bind
Function.prototype.bind1=function(){
//把参数列表变成数组
const args = Array.prototypes.slice.call(arguments)//slice是Array.prototypes上的API
//获取this(数组的第一项)
const t =args.shift()
//fn1.bidn(...)中的fn1
const self = this
return function(){ //因为上面fn1.bind返回的是一个函数 函数是要执行的
return self.apply(t,args)//这里的args已经为20,30,40 因为上面做了shift的操作会改变原数组
}
}


let i a
for (i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)

}

如果是上述代码 无论点哪个标签都是显示10,因为此时i是全局作用域,只有当点击的时候才会去执行回调函数,而回调函数的alert(i)是一个自由变量所以会去全局找(循环都结束了),因为都是10 (js引擎在退出循环的时候,迭代变量i保存的是导致循环退出的值)
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {//不会立马执行
e.preventDefault()
alert(i)
})
document.body.appendChild(a)

}
如果是上述代码就可以避免这个问题,因为此时i为块级作用域,(js引擎在后台会为每个迭代循环声明一个新的迭代变量),自由变量i就会在块中去找,因此就可以实现点击什么显示什么。

14、异步和单线程

JS本质是单线程的。也就是说,它并不能像JAVA语言那样,两个线程并发执行。但我们平时看到的JS,分明是可以同时运作很多任务的,这又是怎么回事呢?

首先,JS的代码,大致分为两类,同步代码异步代码。JS引擎的主线程负责执行代码,由于只有这一个线程,执行当然是同步的,即按照顺序来。另外,还有一个叫做任务队列的东西,所有的异步代码都是从队列当中来。

所以实际上我们会发现,JS根本不可能同时执行两个任务,本质上还是单线程。

因为这些任务的发生都不是在当下,而是过段时间以后再执行。因此时间不可控,你不能因为5秒后要执行一个函数,就让主线程闲置5秒什么都不干吧?所以你只能继续执行后续的同步代码。而当你单击鼠标或滚动窗口时,主线程可能正在执行其它代码,忙着呢!没工夫处理,因此,事件触发线程就负责来接收这个事件,并把要执行的任务暂时保存在任务队列当中。等主线程把手里的同步代码执行完,就立刻会向任务队列提取最新的任务。

单线程和异步

  • 遇到等待(网络请求,定时任务)不能卡住
  • 需要异步
  • 回调callback函数形式

image-20211211093254048

异步打印顺序100 300 200 异步不会阻塞 后面代码的执行300会立马出来。

同步 弹出200会卡主 不点确定300不会出来

  • 基于JS是单线程语言
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行

应用场景

  • 网络请求,如ajax图片加载
  • 定时任务,如setTimeout
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
手写Promise加载一张图片 代码演示
const url = 'http...'

function loadImg(src){
const p = new Promise( //Promise传入一个函数 函数有两个参数
(resolve,reject)=>{
const img = document.createElement('img')
img.onload=()=>{
resolve(img)
}
img.onerror=()=>{
reject(new Error(`图片加载失败${src}`))//或者 const err = new Error()/ reject(err)
}
img.src =src
}
)
return p
}
loadImg(url).then(接收一个函数函数就是上面resolve里面的对象) //Promise对象就有thenloadImg(url).then(img=>{
console.log(img.width)
return img
}).then(img=>{
console.log(img.height)
}).catch(ex=>console.error(ex))

情况2 如果有两张图
thenloadImg(url).then(img=>{
console.log(img.width)
return img //普通对象 **如果是普通对象下一个then函数里参数就会接收到返回的对象
}).then(img=>{
console.log(img.height)
return loadImg(url2)//promise实例**如果是promise实例的话下一个then函数里的参数是loadImg(u2)加载完成之后的图片对象
}).then(img2=>{
console.log(img2.width)
return img2
})
这就是利用promise解决回调地狱的问题

小结

  • 单线程和异步,异步和同步区别
    • 基于JS是单线程语言
    • 异步不会阻塞代码执行
    • 同步会阻塞代码执行
  • 前端异步的应用场景:
    • 网络请求
    • 定时任务
  • Promise解决callback hell

15、JS Web API(DOM)

  • DOM
  • BOM
  • 事件绑定
  • ajax
  • 存储

vue 和React框架应用广泛,封装了DOM操作
但DOM操作一直都会前端工程师的基础、必备知识
只会vue而不懂 DOM操作的前端程序员,不会长久

不要被工具和框架限制了自己的能力

1
2
3
4
5
6
7
8
9
10
11
12
1、DOM的本质是从HTML文件解译出的一棵树(一层一层)

2、DOM是哪种数据结构
是树(DOM树)结构
3、DOM操作的常用API
* DOM节点的操作
* DOM结构操作
* attribute和property的操作
4、attr和property 的区别
* property:修改对象属性,不会体现到 html结构中(尽量用这个)
* attribute:修改html 属性,会改变html结构
* 两者都有可能引起 DOM重新渲染

image-20211211102904678

image-20211211102949224

property本身不是API的一部分,是一种JS属性操作的形式

image-20211211103301872

设置attribute会将设置的属性嵌到dom结构里的(修改标签的属性)

而property修改的是JS变量的一个属性(不会对标签产生任何的影响)

image-20211211103431882

property和attribute

  • property:修改对象属性,不会体现到 html结构中(尽量用这个)
  • attribute:修改html 属性,会改变html结构
  • 两者都有可能引起 DOM重新渲染

DOM结构操作

  • 新增/插入节点
  • 获取子元素列表,获取父元素
  • 删除子元素

image-20211211104105539

移动节点就是原本div1没有p2的 设置了此操作div1就有了p2

image-20211211104241164

获取子元素列表有时候不是我们想要的 因为它会将文本元素一块打印出来

1
2
3
4
5
6
7
8
9
10
11
12
//首先先将divchild变成一个数组再进行遍历
//Array.prototype.slice.call(div1.childNodes)就是将返回的结果变成一个数组
const divchild = Array.prototype.slice.call(div1.childNodes).filter(child=>{
if(child.nodeType===1){
return true
}
return false
})
console.log('divchild',divchild)//结果就是dom元素 3个p标签/就不是text文本

**filter():方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素;不会改变原始数组;
return后面判断结果,取布尔值,true的话就添入新的filter数组中,false的话,不会添进filter的数组中。**

image-20211211105346903

DOM性能

  • DOM操作非常“昂贵”,避免频繁的DOM操作(占用CPU多 )
  • 对DOM查询做缓存
  • 将频繁操作改为一次性操作

image-20211211110819646

1
2
3
4
5
6
7
8
const list = document.getElementById('list')

for(let i=0;i<10;i++){
const li = document.createElement('li')
li.innerHTML = `LIST Item ${i}`
list.appendChild(li)
}
这样就是频繁操作DOM,耗费性能

image-20211211112538924

16、BOM操作(Browser Object Model)

image-20211211143716541

image-20211211143728545

17、事件

1
2
3
1、编写一个通用的事件监听函数
2、描述事件冒泡的流程
3、无限下拉的图片列表,如何监听每个图片的点击?

事件绑定

image-20211211143946225

image-20211211144103348

elem:绑定的按钮id

事件冒泡

image-20211211145246199

stopPropagation组织冒泡 没有这个点击激活的时候由于会冒泡到body就会触发body从而打印取消

事件代理(是基于事件冒泡来做的)

image-20211211145459660

绑定在父元素上 代码简介 (比每一个a标签都绑定一个元素)

减少浏览器的内存占用 (每一个a标签都去挂载事件监听,耗内存)

禁止滥用

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
function bindEvent(elem,type,selector,fn){
if(fn == null) { //传递三个参数和四个参数的对比
fn = selector
selector = null
}

elem.addEventListener(type,event => { //整体对照下面的例子进行观察
const target = event.target//触发的元素
if (selector) { //这里的selector就是a
//代理绑定
if(target.matches(selector)){ //target.matches一个DOM元素是否符合CSS选择器
fn.call(target,event)
}
}else {
//普通绑定
fn.call(target,event)//这里的target就是this的意思
}
})
}

//普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click' , event =>{
//console.log (event.target)//获取触发的元素
event.preventDefault()//阻止默认行为
alert(this.innerHTML)
})
//代理绑定
const div3 = document.getElementById ('div3')
bindEvent(div3, 'click ','a', event =>{
event.preventDefault()
alert(this.innerHTML)
})

const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click' , function(event ){
//console.log (event.target)//获取触发的元素
event.preventDefault()//阻止默认行为
alert(this.innerHTML)
})
//代理绑定
const div3 = document.getElementById ('div3')
bindEvent(div3, 'click ','a', function(event){
event.preventDefault()
alert(this.innerHTML)
})

image-20211211153208756

下图就可以验证target.matches(selector) 其中代理绑定的是a标签不是button标签,因此就可以判断是否符合css选择器

上述代码有错误,因为箭头函数没有this,会向上级作用域查找,为window,而window.innerHTML就不符合要求了

因此得将箭头函数转换为普通函数

描述事件冒泡的流程

  • 基于 DOM 树形结构
  • 事件会顺着触发元素向上冒泡
  • 应用场景:代理(是基于事件冒泡机制才能使用的)

无限下拉图片列表,如何监听每个图片的点击

  • 事件代理
  • 用e.target获取触发元素
  • 用matches来判断是否是触发元素

18、跨域

1
2
3
4
 同源策略
**ajax请求时,浏览器要求当前网页和server 必须同源(安全)
**同源︰协议、域名、端口,三者必须
前端:http://a.com:8080/; server:https://b.com/api/xxx 三者都不相同

image-20211211154807380

加载图片css js可无视同源策略

1
2
3
4
5
6
1、<img src=跨域的图片地址/>
2、<link href=跨域的css地址/>
3、<script src=跨域的js地址></script>
4、<img/>可用于统计打点,可使用第三方统计服务
5、<link /> <script>可使用CDN ,CDN一般都是外域
6、<script>可实现JSONP

跨域

  • 所有的跨域,都必须经过server端允许和配合
  • 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号

JSONP
访问https://imooc.com/,服务端定返回一个html文件吗?

不一定,服务器可以任意动态拼接数据返回,只要符合html格式要求

1
2
3
4
1、<script>可绕过跨域限制
2、服务器可以任意动态拼接数据返回
3、所以,<script>就可以获得跨域的数据,只要服务端愿意返回
(跨域必须经过服务端的允许和配合,服务端如果愿意给你返回一个拼接好的数据的script的JSONP文件的话,这样就可以通过<script>可绕过跨域限制,从而获得跨域的数据)

img

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
function ajax(url){

const p = new Promise((resolve,reject) =>{
const xhr = new XMLHttpRequest()
xhr.open ( 'GET', '/data/test.json' , true)
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if (xhr.status === 200){
resolve(
JSON. parse(xhr.responseText)
)
}else if (xhr.status===404){
reject(new Error(' 404 not found '))
}
}
}
xhr.send(null)
})
return p
}

const url = '/data/test.json'
ajax(url)
.then(res =>console.log(res))
.catch(err =>console.error(err))

ajax知识点

  • XMLHttpRequest
  • 状态码:readyState status
  • 跨域︰同源策略(如何绕过),JSON,CORS

19、存储

1、描述cookie localStorage sessionStorage区别

容量 cookie 4kb 另外一个5M
API 易用性
是否跟随http请求发送出去

cookie

  • 本身用于浏览器和server通讯(本身是http请求的一部分)

  • 被“借用”到本地存储来( localStorage sessionStorage这是html5之后才提出来的,所以之前都是拿cookie做本地存储)

  • 可用document.cookie= ‘..’来修改(前端修改cookie的方式)=>赋值同名覆盖 不同名叠加

  • image-20211211163645518

    注意:cookie的设计并不是用来本地存储,它是为了浏览器和客服端通讯,只是被借用到本地存储来

cookie的缺点

  • 存储大小,最大4KB(限制)
  • http请求时需要发送到服务端,增加请求数据量
  • 只能用document.cookie = ‘.…’来修改,太过简陋

localStorage和sessionStorage

  • HTML5专门为存储而设计,最大可存5M(已经很大了)
  • API简单易用 =>setItem getItem
  • 不会随着http请求被发送出去

image-20211211163925702

localStorage和 sessionStorage

  • localStorage数据会永久存储,除非代码或手动删除
  • sessionStorage 数据只存在于当前会话,浏览器关闭则清空
  • 一般用localStorage会更多一些

20、防抖

防抖 debounce

  • 监听一个输入框的,文字变化后触发chaange事件
  • 直接用keyup 事件,则会频发触发change事件
  • 防抖∶用户输入结束或暂停时,才会触发change事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function debounce(fn, delay = 500){

let timer = null

return function(){.
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(()=>{
fn.apply(this, arguments)
timer = null
}, delay)
}
}
input1.addEventListener('keyup',debounce(function(){
console.log (input1.value)
}),600)

对照下面这个

image-20211211172257947

节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
节流throttle
1、拖拽一个元素时,要随时拿到该元素被拖拽的位置
2、直接用drag事件,则会频发触发,很容易导致卡顿
3、节流:无论拖拽速度多快,都会每隔100ms触发一次

function throttle(fn, delay = 100){

let timer = null

return function(){.
if (timer) {
return
}
timer = setTimeout(()=>{
fn.apply(this, arguments)
timer = null
}, delay)
}
}
input1.addEventListener('drag',throttle(function(e){//这里的e是传给throttle返回的函数 而function包裹的函数没有e
// 由于有fn.apply(this, arguments) 有这个fn是function这个函数 其中arguments有event
console.log (e.offsetX,e.offsetY)
}),200)

image-20211211173501159

关于简历

1
2
3
4
5
简洁明了,突出个人技能和项目经验(技术栈)
可以把个人博客,开源作品放在简历里
不要造假,保证能力上的真实性(斟酌用词,不要使用精通, 了解熟练即可)

谈谈自己的缺点

React框架

1、事件

  1. bind this:修改方法的this指向。使用箭头函数this就指向当前实例,就不用bind(this)

  2. 关于event 参数

    • event. preventDefault()//阻止默认行为

    • event. stopPropagation() //阻止冒泡

    • event.target 指向当前元素,即当前元素触发

    • event.nativeEvent.target 指向当前元素 即当前元素触发

    • event.nativeEvent.currentTarget 绑定是绑定在document上**

  • event是SyntheticEvent事件,模拟出来DOM事件所有能力

  • event.nativeEvent是原生事件对象

  • 所有的事件,都被挂载到document上**

  • 和DOM事件不一样,和Vue事件也不一样

  1. 传递自定义参数

2、setState

  1. 不可变值(不能直接修改state)

    • concat,slice,filter 这些东西不会改变原来的list值。而push pop splice会改变不可用
    • 或者可以通过this.state.list.slice() 不传入值就相当于给list做了一个副本,这样就可以使用push,pop这种API,原本的list不变
    • 扩展对象可以用解构赋值 {…this.state.list,a:100}
  2. 可能是异步更新

    image-20211212205326882

    先打印再累加 想要获取最新的值就在setState传入第二个参数,第二个参数的值是一个函数

    但是在setTimeout中setState是同步的 自己定义的DOM事件,setState是同步的

    image-20211212205931478

    标准写法应该这样

    image-20211212211952584

    要销毁这个自定义的DOM事件

  3. 可能会被合并(仅限对象才会被合并,函数不会被合并)

image-20211212215230884

3、组件生命周期

image-20211212215915750

4、高级特性

  1. 函数组件

    image-20211212220821520

  2. 非受控组件

    image-20211212221502666

    建议写受控组件 因为受控没有ref 因为官网尽量不要写ref

  3. Portals

    image-20211212221609897

    Portals使用场景
    overflow: hidden 父元素设置了BFC影响了子元素的展示,可以让子元素逃离父元素之外展示
    父组件z-index值太小:同理
    fixed需要放在body第一层级:同理

    image-20211212222543204

  4. context

    image-20211212222646168

    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
    1) 创建Context容器对象:
    const XxxContext = React.createContext()

    2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
    <xxxContext.Provider value={数据}>
    子组件
    </xxxContext.Provider>

    <Provider value={{username,age}}>
    <B/>
    </Provider> 这样包裹一下 B组件以及B的子组件都能用只不过需要声明接收

    3) 后代组件读取数据:

    //第一种方式:仅适用于类组件
    static contextType = xxxContext // 声明接收context
    this.context // 读取context中的value数据

    //第二种方式: 函数组件与类组件都可以 函数式没有this所以第一种不适合函数式组件
    <xxxContext.Consumer>
    {
    value => ( // value就是context中的value数据
    要显示的内容
    )
    }
    </xxxContext.Consumer>
    ```
    在应用开发中一般不用context, 一般都它封装react插件
  5. 异步组件

    性能优化:异步组件—>组件比较大,路由需要懒加载

    lazyLoad 懒加载 路由组件最常用 就是避免100个组件去请求100次 最好的就是点哪个就请求哪个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
    const Login = lazy(()=>import('@/pages/Login')) //现点现加载

    //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
    <Suspense fallback={<h1>loading.....</h1>}>
    <Switch>
    <Route path="/xxx" component={Xxxx}/>
    <Redirect to="/login"/>
    </Switch>
    </Suspense> //这就是如果网络慢慢 迟迟不回来 要fallback指定一个组件component={Xxxx}显示
    //Network选项卡 点谁加载谁 把网络故意调慢 点了About加载 会显示Loading
    //<h1>loading.....</h1>如果Loading要写成一个组件就另外写一个文件就可以 但是引入的时候不要懒加载
  6. 性能组件(永远是面试的重点)

    性能优化对于React更加重要

    image-20211213121121649

    image-20211213121203921

    判断下一个状态的数据和前一个数据是否相同,不相同可以渲染,相同就不可以渲染。优化性能

    背后的逻辑是什么? SCU默认会返回true,但是有返回false的权利

    SCU一定要每次都用嘛?不一定,性能优化需要的时候用。需要的时候才优化

    SCU使用总结**

    • SCU 默认返回true ,即React默认重新渲染所有子组件

    • 必须配合“不可变值”一起使用

    • 可先不用SCU,有性能问题时再考虑使用

      PureComponent 和memo

    • PureComponent , SCU默认实现了浅比较

    • memo ,函数组件中的PureComponent

    • 浅比较已使用大部分情况(尽量不要做深度比较)

    immutable.js

    • 彻底拥抱“不可变质”
    • 基于共享数据(不是深拷贝),速度好
    • 有一定学习和迁移成本,按需使用
  7. 高阶组件HOC

    关于组件公共逻辑的抽离

    • mixin , 已被React 弃用
    • 高阶组件HOC
    • Render Props
    • connect也是高阶组件

    image-20211213143101772

    接收一个组件,返回的是一个由函数拼接而成的组件

    image-20211213144047471

    image-20211213144120493

    image-20211213144230500

  8. Render Props(比HOC更容易理解)

image-20211213145106982

注意:暴露的是什么,组件就传什么,比如下图,暴露的是APP 在入口文件APP相当于子组件所以直接在APP输入props即可

image-20211213145353719

image-20211213145442204

image-20211213145533126

image-20211213145754832

5、Redux

image-20211213150838083

redux怎么去获取异步action

单向数据流的图

image-20211213152256044

6、React原理

函数式编程

1
纯函数,不可变值

vdom和diff

1
diff比较同级,不跨级比较

jsx本质

1
2
3
4
5
6
7
8
9
1、是一个 React.createElement 函数,他接收多个参数,执行返回vnode,vnode通过vdom的patch或者其他方法渲染页面
//调用 React 的 createElement API
2、不是模板引擎,而是语法糖
3、第一个参数,可能是组件(大写),也可能是html 标签名(小写)
4、第二个参数是属性信息,如果没有属性则为 null
第三个参数是子元素;
如果拥有多个子元素,可以依次放在第三个、第四个...
也可以用在数组中存放多个子元素
4、组件名,首字母必须大写( React规定)

合成事件

1
2
3
4
5
6
7
1、所有事件挂载到document
2、event 不是原生的,是 SyntheticEvent合成事件对象
3、和Vue 事件不同,和DOM事件也不同
为何要合成事件机制?
1、更好的兼容性和跨平台
2、载到document ,减少内存消耗,避免频繁解绑
3、方便事件的统一管理(如事务机制)

setState 和 batchUpdata

1
2
3
4
5
6
7
8
9
10
11
12
13
1、setState无所谓异步还是同步
2、看是否能命中batchUpdate机制
3、判断isBatchingUpdates

哪些能命中batchUpdate机制
1、生命周期(和它调用的函数)
2、React 中注册的事件(和它调用的函数)
3、React可以“管理”的入口

哪些不能命中batchUpdate机制
1、setTimeout setInterval 等(和它调用的函数)
2、自定义的DOM事件(和它调用的函数)
3、React“管不到”的入口

image-20211214123112436

Y就是异步的State N 就是同步的State

image-20211214123342209

image-20211214122922788

执行函数前处于batchUpdate 且isBatchingUpdates=ture。执行函数完之后isBatchingUpdates=false。

因为settimeout()这个函数式异步的就直接执行isBatchingUpdates=false。了 之后进行this.set的时候isBatchingUpdates已经是false了。所以判断setState是异步还是同步的时候,就看isBatchUpdates是true还是false 且设置这个是在入口中设置的。

组件之间如何通讯

1
2
3
1、父子组件props
2、自定义事件
3、Redux和Context

Context是什么,如何应用

1
2
3
父组件,向其下所有子孙组件传递信息
如一些简单的公共信息,主题色,语言等。
复杂的公共信息,用redux

shouldComponentUpdate用途

1
2
性能优化
配合不可变值一起使用,否则会出错

image-20211214163434174

两次异步操作合并成一次所以只加1,异步操作玩之后执行同步

纯函数

1
2
3
返回一个新值,没有副作用(不会偷偷修改其他值)
重点:不可变值
如arr1=arr.slice()

函数组件和class组件的区别

1
2
3
纯函数,输入props,输出JSX
没有实例,没有生命周期,没有state
不能扩展其他方法

什么是受控组件

1
2
表单的值,收state 控制
需要自行监听onChange ,更新state

何时使用异步组件

1
2
加载大组件
路由懒加载

多个组件有公共逻辑,如何抽离

1
2
高阶组件
Render Props

Redux如何进行异步请求

1
2
使用异步action
如redux-thunk

PureComponent 有何区别

1
2
3
实现了浅比较的shouldComponentUpdate
优化性能
但要结合不可变值使用

React性能优化

1
2
3
4
5
6
7
8
9
渲染列表时加key
自定义事件、DOM事件及时销毁
合理使用异步组件
减少函数bind this的次数
合理使用SCU PureComponent和memo
合理使用Immutable.js
webpack层面的优化(后面会讲)
前端通用的性能优化,如图片懒加载
使用SSR

React和Vue的区别

1
2
3
4
5
6
7
都支持组件化
都是数据驱动视图
都使用vdom 操作 DOM

React使用JSX拥抱JS,Vue使用模板拥抱html
React函数式编程,Vue声明式编程
React 更多需要自力更生,Vue把想要的都给你

7、webpack

webpack介绍

1
2
3
4
5
6
7
8
9
10
webpack已经是前端打包构建的不二选择
每日必用,面试必考
成熟的工具,重点在于配置和使用,原理并不高优

前端代码为何要进行构建和打包
module chunk bundle 分别什么意思,有何区别?
loader和plugin的区别?
webpack如何实现懒加载?
webpack常见性能优化
babel-runtime和 babel-polyfill的区别

webpack基本配置

1
2
3
4
5
6
7
8
9
10
11
12
13
vue-cli create-react-app  **cli工程师
常用上述脚手架,而不会自己配置webpaclk ?
则面试不会通过

拆分配置和merge (公共的配置common 生产环境下的配置,开发环境下的配置)

跨域问题:开发环境下还会定义一个devserver 里可以通过设置代理proxy将本地/api/XXX代理到 localhost:3000/api/xxx

loader的执行顺序是从后往前
postcss-loader 浏览器的兼容性的
对css文件:先postcss-loader 浏览器的兼容性的一些东西做了再变成css插入到style中
对less文件,先通过less解析语法成css然后插入style中
图片大小小于5kb用base64格式产出否则用file-loader的形式,产出url格式

webpack高级配置

1
2
3
4
5
基本配置只能做demo,不能做线上项目
多入口:entry要写两个。
output要通过[name]的变量去操作
plugins要:每一个入口都要创建一个新的HtmlWebpackPlugin的实例(new
chunks:会帮你只引入什么名字的js文件 不写chunk会把html文件一块引入。

image-20211214193227860

抽离css文件

1
2
3
开发环境下用的是通过css-loader转换成出css文件插入到style-loader里面去的
生成环境下需要用MiniCssExtractPlugin.loader,通过这种方式就不再是塞到style-loader里面去了
单独拧出来,(在plugins里面加了一个抽离css文件的plugins的配置)还是用new MiniCssExtractPlugin标志一下抽离出的css文件是什么。抽离完还要压缩(optimization配置里面)抽离出来就可以在index.html引入抽离的css文件了

image-20211214195006905

抽离公共代码

1
2
3
4
5
6
7
抽离公共部分相互引用,减少加载和执行的次数
开发环境下没必要做。

第三方模块复用一次的原因是是避免细微的改动重新加载这个模块,影响加载速度,耗费性能。
test 是模块来自
大小限制最好写3kb 5kB这样 name就是代码分割产出的chunk
多入口plugins里面引用new HtmlWebpackPlugin下也要考虑代码分割的chunk

image-20211214200446809

懒加载

1
2
引入动态数据->懒加载
异步代码也会产出一个chunk 文件名不是自己规定的

image-20211214205110387

moudle chunk bundle的区别

1
2
3
4
5
module -各个源码文件,webpack 中一切皆模块 只要是引入的就是模块
chunk -多模块合并成的,如entry(一个入口就可以生成一个至多个chunk,因为入口定义的文件也包括其他文件一块引进来)
import() splitChunk 拆分代码也可以定义chunk 懒加载引入动态数据也是一个chunk
可以理解成内存中的一个概念,还没整理的输出
bundle -最终的输出文件(一个chunk对应一个bundle)bundle可能是很多个文件不止一个文件

webpack性能优化

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
1、优化打包构建速度-开发体验和效率
优化 babel-loader - 缓存 加一个cacheDirectory参数,加上之后只要ES6代码没有改,就不会重新编译,缓存一下。
- 有include和exclude两者选一个去明确打包范围

happyPack -多进程打包工具 -JS单线程,开启多进程打包
-提高构建速度(特别是多核CPU )
-把对.js文件的处理转交给id为babel的HappyPack实例 图3

IgnorePlugin -避免引入无用模块 -例如moment.js这个日期处理的库 加载时间的库,200多kb 就是因为把语言的库引进来了,可以通 -过引入这个插件来避免引入这个模块,至于语言的模块就自己手动引入 -还能优化产出的体积 图1

ParallelUglifyPlugin -开启多进程进行代码压缩JS JS单线程,开启多进程压缩更快和HappyPack原理类似 图四压缩就是直接输结果

noParse -避免重复打包 像一些min.js文件基本都是采用模块化处理过了,不用再重新打包 在moudule文件下加一下就好了如下图2

自动刷新 -一保存代码编译完之后浏览器自动刷新 开发环境用(devServer)默认开启会用

热更新 -自动刷新的升级版,改完代码之后浏览器不要刷新,代码就生效,体验更好
自动刷新:整个网页全部刷新,速度较慢
自动刷新:整个网页全部刷新,状态会丢失 包括你点击的路由 自动刷新玩就会回到首页和你输入的状态下次就不见
热更新:新代码生效,网页不刷新,状态不丢失
引入插件,new实例,devServer设置hot为true
如果是改js文件的话就会导致自动刷新,原因是因为某些模块没有开启热更新的监听 图4
自动刷新会影响你开发的体验再去开启热更新

DllPlugin-针对大的库和大的第三方插件没必要每一次都把所有打包一遍,我们可以实现把第三方库打包好之后引用 *动态链接库插件
- 前端框架如vue React,体积大,构建慢
- 较稳定,不常升级版本
- 同一个版本只构建一次即可,不用每次都重新构建
webpack 已内置 DllPlugin支持
DllPlugin插件 -先把react进行预打包出dll文件(webpack.dull.js) 图5 产出两个文件 一个是加载了打包所有关于入口 - react-react-dom的所有文件,另一个是索引
DlIReferencePlugin插件 -使用dll文件。使用就不要重新打包
使用首先现在入口文件的index.html引入这个src = ‘./react.dull.js’这个文件 然后new实例 图6

webpack优化构建速度(可用于生产环境) 不可用生产环境
优化 babel-loader 自动刷新
happyPack 热更新
IgnorePlugin DllPlugin
ParallelUglifyPlugin *必须用
noParse


**总结**
IgnorePlugin直接不引入,代码中没有
noParse 引入,但不打包
项目较大,打包较慢,开启多进程能提高速度
项目较小,打包很快,开启多进程会降低速度(进程开销)
按需使用

**ES6 Module和Commonjs区别**
ES6 Module静态引入,编译时引入 必须放在最上面 否则报错 图7
Commonjs动态引入,执行时引入,有可能需要有可能不需要 (图7 里面的if就不知道是否会被执行)
只有ES6 Module 才能静态分析,实现Tree-Shaking(webpack打包的时候执行,打包的时候代码还没执行)
webpack只是一个静态分析,静态构建,编译,代码还没正式在线上被用户去运行


2、优化产出代码-产品性能 **更重要
体积更小
合理分包,不重复加载
速度更快,内存使用更少
****每一个都是优化
小图片base64编码
bundle加hash(打包出的bunlde代码时加上hash)
懒加载
提前公共代码->做一个公共的包,不需要重复打包公共的模块
IgnorePlugin 还能优化产出的体积
使用CDN加速: 打包出的html 引入的文件前缀都加了cdn 图7 这仅仅是第一步还需要将打包的文件上传到cdn的网址
使用mode:production:去打包生成环境下的代码 自动开启代码压缩 Vue React 等会自动删掉调试代码(如开发环境的warning )
自动开启Tree-shaking=>对未使用的代码删除 只要mode为production即可
ES6 Module才能让tree-shaking生效commonjs就不行


Scope Hosting 将两个文件合并,多个函数放在一个函数里,使得作用域少 使得内存占用少一些,代码体积更小 图8

image-20211214213424143

image-20211214213751310

image-20211214214448414

image-20211214214856230

image-20211214215938574

image-20211214221035644

image-20211214221515068

image-20211214222435551

image-20211214223716971

image-20211214224034222

babel

1
2
3
4
5
6
7
8
9
10
11
12
13
前端开发环境必备工具 同webpack,需要了解基本的配置和使用
环境搭建&基本配置
babel-demo npx babel src/index.js 这样就可以通过babel编译index,js文件
babel其实就是通过plugin将ES6语法转换为ES5

babel-polyfill(po里),core-js标准库集成了所有ES6 ES7新语法的polyfill的补丁(兼容性)但是这个库对ES6的generator函数(处理异步),被async/await代替不支持, 但是regenerator这个库就支持了。 babel-polyfill就是这两个库的集合
Babel 7.4之后弃用babel
推荐直接使用core-js和regenerator
问题:会污染全局环境 如果是自己独立开发可以用



babel-runtime:不会污染全局环境。产出第三方lib就要用这个,否则会污染全局环境

8、 面试真题演练

1.前端为何要进行打包和构建

1
2
3
4
5
6
7
8
代码层面
体积更小(Tree-Shaking、压缩、合并),加载更快
开发而言:编译高级语言或语法(TS,ES6+,模块化,scss )
兼容性和错误检查(Polyfill,postcss,eslint)
研发层面(前端工程化)
统一、高效的开发环境
统一的构建流程和产出标准
集成公司构建规范(提测、上线等)

loader和plugin区别

1
2
3
loader模块转换器,如less >css
plugin扩展插件,如HtmlWebpackPlugin
常用的loader和plugin有什么? 之前讲过的能掌握即可

babel和 webpack的区别

1
2
babel - JS新语法编译工具,不关心模块化
webpack-打包构建工具,是多个loader、plugin 的集合

如何产出一个lib

1
参考webpack.dll.js里面有一个output.library

image-20211215192019355

webpack如何实现懒加载

1
2
3
import语法
结合Vue React异步组件
结合React-router异步加载路由

为何Proxy不能被Polyfill

1
Proxy 的功能用 Object.defineProperty 无法模拟

组件和状态设计

1
2
3
4
5
6
todoList:
1、用数据描述内容
2、结构化
3、可扩展性
4、功能上拆分层次
5、容器组件(只管理数据)、UI(只显示视图)

9、项目流程

PM(项目管理)想在项目开发过程中增加需求,该怎么办

1
2
3
不能拒绝,走需求变更流程即可
公司如果有规定,按规定走
否则,发起项目组和leader的评审,重新评估排期

项目即将延期了,该怎么办

1
2
3
4
项目沟通
多人协作,沟通是最重要的事情
每日一沟通(如站会),有事说事,无事报平安
及时识别风险,及时汇报

你将如何保证项目质量

1
2
3
4
5
符合开发规范
写出开发文档
及时单元测试
Mock API
Code Review (让大佬帮你浏览一遍)

JavaScript核心原理

image-20211216213550505

数据类型的判断方法

image-20211216214052951

第三种方法最好,Object.prototype.toString 它返回的结果是[object Xxx 就比如object Number]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'123'==123  true
""==null false
null==0 false
"==0 true
[]==0 true
[]==" true
[]==![] true
null == undefined // true
Number(null) 0
Number('') 0
parselnt('') NaN
parselnt(
{}+10 ='10[object Object]'

'1'+undefined = '1undefined'

image-20211216215624904

image-20211216215633721

image-20211216215927226

Object.valueOf()方法

image-20211216220410734

image-20211216220424019

image-20211216220432142

浅拷贝

自己创建一个新的对象,来接受你要重新复制或引用的对象值。如果对象属性是基本的数据类型,复制的就是基本类型的值给新对象;但如果属性是引用数据类型,复制的就是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响到另一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
第一种方法通过Object.assign方法
let target= o;
let source = {a: { b: 1};
Object.assign(target, source); //target为浅拷贝的对象,source是拷贝的来源
console.log(target); //{a: { b:1}};

let target={};
let source ={a: {b: 2};
Object.assign(target, source);
console.log(target);//{a:{ b: 10};
source.a.b = 10;
console.log(source); //{a:{ b: 10}
console.log(target); //{a: { b: 10};

第二种方法通过扩展运算符方式

image-20211216222245203

因为a是基本数据类型,只拷贝了值1,但是b是一个对象,拷贝了c:1的地址

因此会有上述结果 也证明了是浅拷贝

如果是深拷贝即便是对象也都不会变 c还是1

1
第三种方法:concat方法也是浅拷贝 (只能用于数据的浅拷贝)使用场景比较局限

image-20211216222513533

slice拷贝数组

image-20211216222657736

深拷贝:将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

方法一:JSON.stringfy

image-20211216223420804

用这个方法进行深拷贝的弊端:

  1. 拷贝的对象的值中如果有函数、undefined、symbol这几种类型,经过JSON.stringify序列化之后的字符串中这个键值对会消失
    2.拷贝 Date引用类型会变成字符串
    3.无法拷贝不可枚举的属性
    4.无法拷贝对象的原型链
    5.拷贝 RegExp 引用类型会变成空对象
    6.对象中含有NaN、Infinity 以及-Infinity,JSON序列化的结果会变成null

7.无法拷贝对象的循环应用,即对象成环(obj[key]= obj)

继承方式

1
2
3
4
5
6
7
8
9
10
11
JS的继承到底有多少种实现方式呢?
原型链继承
两个实例使用同一个原型对象的话 内存空间是共享的,当一个发生改变的时候,另外一个也随之进行了变化 图1
构造函数继承(借助call)
它使父类引用的属性不会被共享,优化了第一种继承方式的弊端)但是缺点是只能继承父类的实例属性和方法,不能继承原型属性或者方法
组合继承(前两种的组合)
原型式继承 Object.create()
寄生式继承
寄生组合继承
ES6的extends关键字是用哪种继承方式实现的呢?
寄生组合继承

image-20211216224316214

image-20211217110552271

new关键词的主要作用就是执行一个构造函数、返回一个实例对象,根据构造函数的情况,来确定是否可以接受参数的传递

image-20211217110749763

new关键字执行之后总是会返回一个对象,要么是实例对象,要么是return语句指定的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(){
this.name = 'Jack';
return {age: 18} //返回与this无关的对象
}
}
var p= new Person();
console.log(p)//{age: 18}
console.log(p.name)// undefined
console.log(p.age)// 18

function Person(){
this.name = 'Jack';
return 'tom'; //不是一个对象
}
var p= new Person();
console.log(p)//{name: 'Jack'}
console.log(p.name)// Jack

apply call bind都可以改变函数func this的指向,call和apply传参的写法不同,apply的第二个参数为数组,call第2个至第n个都是func的传参,bind和这两个都不同,bind改变this的指向但是不是立马执行,call和apply是立马执行

A对象有个getName的方法,B对象也需要临时使用同样的方法那么这时候可以借用A对象的getName方法

![

](https://gitee.com/letter-from-berlin/PictureBed/raw/master/202112171245503.png)

image-20211217124702882

image-20211217124803739

image-20211217124853074

这两个方法是直接返回执行结果,而bind方法是返回一个函数,因此上面直接用eval执行得到结果

image-20211217125007222

实现bind的核心在于返回的时候需要返回一个函数,因此这里的fbound需要返回,但是在返回的过程中,原型链对象上的属性不能丢失,所以这里需要使用Object.create方法将this.prototype上的属性挂到fbound的原型上面,最后再返回fbound

image-20211217125102925

JavaScript高级

基础总结深入

数据类型的分类和判断

  • 基本(值)类型
    • Number ——- 任意数值 ———— typeof
    • String ——- 任意字符串 ——— typeof
    • Boolean —— true/false ——- typeof
    • undefined —- undefined ——- typeof/===
    • null ———— null ————— ===
  • 对象(引用)类型
    • Object ——- typeof/instanceof
    • Array ——— instanceof
    • Function —— typeof

数据,变量, 内存的理解

  • 什么是数据?
    • 在内存中可读的, 可传递的保存了特定信息的’东东’
    • 一切皆数据, 函数也是数据
    • 在内存中的所有操作的目标: 数据
  • 什么是变量?
    • 在程序运行过程中它的值是允许改变的量
    • 一个变量对应一块小内存, 它的值保存在此内存中
  • 什么是内存?
    • 内存条通电后产生的存储空间(临时的)
    • 一块内存包含2个方面的数据
      • 内部存储的数据
      • 地址值数据
    • 内存空间的分类
      • 栈空间: 全局变量和局部变量
      • 堆空间: 对象
  • 内存,数据, 变量三者之间的关系
    • 内存是容器, 用来存储不同数据
    • 变量是内存的标识, 通过变量我们可以操作(读/写)内存中的数据

对象的理解和使用

  • 什么是对象?
    • 多个数据(属性)的集合
    • 用来保存多个数据(属性)的容器
  • 属性组成:
    • 属性名 : 字符串(标识)
    • 属性值 : 任意类型
  • 属性的分类:
    • 一般 : 属性值不是function 描述对象的状态
    • 方法 : 属性值为function的属性 描述对象的行为
  • 特别的对象
    • 数组: 属性名是0,1,2,3之类的索引
    • 函数: 可以执行的
  • 如何操作内部属性(方法)
    • .属性名
    • [‘属性名’]: 属性名有特殊字符/属性名是一个变量

函数的理解和使用

  • 什么是函数?
    • 用来实现特定功能的, n条语句的封装体
    • 只有函数类型的数据是可以执行的, 其它的都不可以
  • 为什么要用函数?
    • 提高复用性
    • 便于阅读交流
  • 函数也是对象
    • instanceof Object===true
    • 函数有属性: prototype
    • 函数有方法: call()/apply()
    • 可以添加新的属性/方法
  • 函数的3种不同角色
    • 一般函数 : 直接调用
    • 构造函数 : 通过new调用
    • 对象 : 通过.调用内部的属性/方法
  • 函数中的this
    • 显式指定谁: obj.xxx()
    • 通过call/apply指定谁调用: xxx.call(obj)
    • 不指定谁调用: xxx() : window
    • 回调函数: 看背后是通过谁来调用的: window/其它
  • 匿名函数自调用:
    1
    2
    3
    (function(w, obj){
    //实现代码
    })(window, obj)
    • 专业术语为: IIFE (Immediately Invoked Function Expression) 立即调用函数表达式
  • 回调函数的理解
    • 什么函数才是回调函数?
      • 你定义的
      • 你没有调用
      • 但它最终执行了(在一定条件下或某个时刻)
    • 常用的回调函数
      • dom事件回调函数
      • 定时器回调函数
      • ajax请求回调函数(后面讲解)
      • 生命周期回调函数(后面讲解)

函数高级

原型与原型链

  • 所有函数都有一个特别的属性:
    • prototype : 显式原型属性
  • 所有实例对象都有一个特别的属性:
    • __proto__ : 隐式原型属性
  • 显式原型与隐式原型的关系
    • 函数的prototype: 定义函数时被自动赋值, 值默认为{}, 即用为原型对象
    • 实例对象的proto: 在创建实例对象时被自动添加, 并赋值为构造函数的prototype值
    • 原型对象即为当前实例对象的父对象
  • 原型链
    • 所有的实例对象都有proto属性, 它指向的就是原型对象
    • 这样通过proto属性就形成了一个链的结构——>原型链
    • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
    • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

执行上下文与执行上下文栈

  • 变量提升与函数提升
    • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)
    • 函数提升: 在函数定义语句之前, 就执行该函数
    • 先有变量提升, 再有函数提升
  • 理解
    • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
    • 执行上下文栈: 用来管理产生的多个执行上下文
  • 分类:
    • 全局: window
    • 函数: 对程序员来说是透明的
  • 生命周期
    • 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
    • 函数 : 调用函数时产生, 函数执行完时死亡
  • 包含哪些属性:
    • 全局 :
      • 用var定义的全局变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===>window
    • 函数
      • 用var定义的局部变量 ==>undefined
      • 使用function声明的函数 ===>function
      • this ===> 调用函数的对象, 如果没有指定就是window
      • 形参变量 ===>对应实参值
      • arguments ===>实参列表的伪数组
  • 执行上下文创建和初始化的过程
    • 全局:
      • 在全局代码执行前最先创建一个全局执行上下文(window)
      • 收集一些全局变量, 并初始化
      • 将这些变量设置为window的属性
    • 函数:
      • 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
      • 收集一些局部变量, 并初始化
      • 将这些变量设置为执行上下文的属性

作用域与作用域链

  • 理解:
    • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化
    • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量
  • 分类:
    • 全局
    • 函数
    • js没有块作用域(在ES6之前)
  • 作用
    • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
    • 作用域链: 查找变量
  • 区别作用域与执行上下文
    • 作用域: 静态的, 编码时就确定了(不是在运行时), 一旦确定就不会变化了
    • 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
    • 联系: 执行上下文环境是在对应的作用域中的

闭包

  • 理解:

    • 当嵌套的内部函数引用了外部函数的变量时就产生了闭包
    • 通过chrome工具得知: 闭包本质是内部函数中的一个对象, 这个对象中包含引用的变量属性
  • 作用:

    • 延长局部变量的生命周期
    • 让函数外部能操作内部的局部变量
  • 写一个闭包程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function fn1() {
    var a = 2;
    function fn2() {
    a++;
    console.log(a);
    }
    return fn2;
    }
    var f = fn1();
    f();
    f();
  • 闭包应用:

    • 模块化: 封装一些数据以及操作数据的函数, 向外暴露一些行为
    • 循环遍历加监听
    • JS框架(jQuery)大量使用了闭包
  • 缺点:

    • 变量占用内存的时间可能会过长
    • 可能导致内存泄露
    • 解决:
      • 及时释放 : f = null; //让内部函数对象成为垃圾对象

内存溢出与内存泄露

  1. 内存溢出

    • 一种程序运行出现的错误
    • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
  2. 内存泄露

    • 占用的内存没有及时释放
    • 内存泄露积累多了就容易导致内存溢出
    • 常见的内存泄露:
      • 意外的全局变量
      • 没有及时清理的计时器或回调函数
      • 闭包

对象高级

对象的创建模式

  • Object构造函数模式

    1
    2
    3
    var obj = {};
    obj.name = 'Tom'
    obj.setName = function(name){this.name=name}
  • 对象字面量模式

    1
    2
    3
    4
    var obj = {
    name : 'Tom',
    setName : function(name){this.name = name}
    }
  • 构造函数模式

    1
    2
    3
    4
    5
    6
    function Person(name, age) {
    this.name = name;
    this.age = age;
    this.setName = function(name){this.name=name;};
    }
    new Person('tom', 12);
  • 构造函数+原型的组合模式

    1
    2
    3
    4
    5
    6
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    Person.prototype.setName = function(name){this.name=name;};
    new Person('tom', 12);

继承模式

  • 原型链继承 : 得到方法

    1
    2
    3
    4
    5
    6
    function Parent(){}
    Parent.prototype.test = function(){};
    function Child(){}
    Child.prototype = new Parent(); // 子类型的原型指向父类型实例
    Child.prototype.constructor = Child
    var child = new Child(); //有test()
  • 借用构造函数 : 得到属性

    1
    2
    3
    4
    5
    6
    function Parent(xxx){this.xxx = xxx}
    Parent.prototype.test = function(){};
    function Child(xxx,yyy){
    Parent.call(this, xxx);//借用构造函数 this.Parent(xxx)
    }
    var child = new Child('a', 'b'); //child.xxx为'a', 但child没有test()
  • 组合

    1
    2
    3
    4
    5
    6
    7
    function Parent(xxx){this.xxx = xxx}
    Parent.prototype.test = function(){};
    function Child(xxx,yyy){
    Parent.call(this, xxx);//借用构造函数 this.Parent(xxx)
    }
    Child.prototype = new Parent(); //得到test()
    var child = new Child(); //child.xxx为'a', 也有test()
  • new一个对象背后做了些什么?

    • 创建一个空对象
    • 给对象设置proto, 值为构造函数对象的prototype属性值 this.proto = Fn.prototype
    • 执行构造函数体(给对象添加属性/方法)

线性机制和事件机制

线程与进程

  • 进程:
    • 程序的一次执行, 它占有一片独有的内存空间
    • 可以通过windows任务管理器查看进程
  • 线程:
    • 是进程内的一个独立执行单元
    • 是程序执行的一个完整流程
    • 是CPU的最小的调度单元
  • 关系
    • 一个进程至少有一个线程(主)
    • 程序是在某个进程中的某个线程执行的

浏览器内核模块组成

  • 主线程
    • js引擎模块 : 负责js程序的编译与运行
    • html,css文档解析模块 : 负责页面文本的解析
    • DOM/CSS模块 : 负责dom/css在内存中的相关处理
    • 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
  • 分线程
    • 定时器模块 : 负责定时器的管理
    • DOM事件模块 : 负责事件的管理
    • 网络请求模块 : 负责Ajax请求

js线程

  • js是单线程执行的(回调函数也是在主线程)
  • H5提出了实现多线程的方案: Web Workers
  • 只能是主线程更新界面

定时器问题:

  • 定时器并不真正完全定时
  • 如果在主线程执行了一个长时间的操作, 可能导致延时才处理

事件处理机制(图)

  • 代码分类
    • 初始化执行代码: 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
    • 回调执行代码: 处理回调逻辑
  • js引擎执行代码的基本流程:
    • 初始化代码===>回调代码
  • 模型的2个重要组成部分:
    • 事件管理模块
    • 回调队列
  • 模型的运转流程
    • 执行初始化代码, 将事件回调函数交给对应模块管理
    • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
    • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行

H5 Web Workers

  • 可以让js在分线程执行

  • Worker

    1
    2
    3
    var worker = new Worker('worker.js');
    worker.onMessage = function(event){event.data} : 用来接收另一个线程发送过来的数据的回调
    worker.postMessage(data1) : 向另一个线程发送数据
  • 问题:

    • worker内代码不能操作DOM更新UI
    • 不是每个浏览器都支持这个新特性
    • 不能跨域加载JS
  • svn版本控制

  • svn server

数据结构

image-20211228223836643

1
2
3
4
5
栈:后进先出
前端与栈=>JS中的函数调用堆栈:最后调用的函数,最先执行完.
栈:对于10进制转2进制可以倒叙输出
JavaScript 中没有栈,但可以用Array 实现栈的所有功能。
栈常用操作: push、pop、获取栈顶元素=>stack[stack.length-1]

队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
先进先出  JavaScript 中没有队列,但可以用Array 实现队列的所有功能。
push和shift方法实现队列的先进先出 队头queue[0]
const queqe = []
queqe.pop(1)
queqe.pop(2)
const item = queqe.shift()
const item = queqe.shift()
什么场景用队列?
1、排队食堂打饭
2、JS异步中的任务队列:JS是单线程,无法同时处理异步中的并发任务。使用任务队列先后处理异步任务。
3、计算最近3000ms内请求次数
输入:inputs = [[],[1],[100],[3001],[3002]]
输出:[null,1,2,3,3]
JS异步中的事件循环和任务队列:一段JS代码刚执行的时候会有一个主事件,会放到任务队列里,JS引擎会在任队列里取一个事件执行,因为JS引擎是单线程的,所以每一次只会执行一个事件,在执行事件的过程中,如果里面有异步任务,比如DOM操作,ajax,setTimeout等等,它就会丢到外部API执行,而且丢完就不管了,就会执行后面的代码,而外部API执行异步任务结束的时候,会把回调函数的JS代码放到任务队列里,然后任务队列里面前面的事件都执行完了,那么回调函数的JS代码就会方法哦JS引擎里执行,如果回调函数里面还有异步任务就继续做这个循环,这就是事件循环。

链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
多个元素组成的列表。
元素存储不连续,用next 指针连在一起。
数组:增删非首尾元素时往往需要移动元素。
链表:增删非首尾元素,不需要移动元素,只需要更改next的指向即可。
JavaScript中没有链表。可以用Object模拟链表。

原型链的本质就是链表
原型链上的节点是各种原型对象,比如
Function.prototype、Object.prototype....
原型链通过__proto__属性连接各种原型对象。
obj -> Object.prototype -> null (都是自身的__proto__)
func ->Function.prototype -> Object.prototype ->null
arr ->Array.prototype -> Object.prototype ->null arr的原型链
如果A沿着原型链能找到 B.prototype,那么A instanceof B 为true
如果在A对象上没有找到×属性,那么会沿着原型链找x属性。

集合

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
集合:无序且唯一的数据结构  Set
常用的操作:去重,判断某元素是否在集合中,求交集

去重:
const arr=[1,1,2,2]
const arr2=[...new Set(arr)]//集合转数组,答案为1,2
判断元素是否在集合中:
const set = new Set(arr)
cosnt has = set.has(3) //false
求交集:
const set2 = new Set([2,3])//{2,3}
const set3 = new Set([...set].filter(item=>set2.has(item)))//先将集合set转化为数组之后在利用数组的过滤方法
前端与集合:使用ES6的Set
使用Set 对象:new、add、delete、has、size
迭代Set:多种迭代方法、SetArray互转、求交集/差集
for(let item of mySet) log(item)//就会把mySet里的值全部打印出来
for(let item of mySet.values()) log(item)//就会把mySet里的值全部打印出来
for(let item of mySet.keys()) log(item)//就会把mySet里的值全部打印出来
for(let [key,value] of mySet.entries) log(key,value)//就会把mySet里的值打印两遍,分别是key和value的值
const myArr = [...mySet];//集合转数组
const myArr = Array.from(mySet);//集合转数组

const mySet2 = new Set([1,2,3,4]);//数组转集合
cosnt intersection = new Set([...mySet].filter(item=>mySet2.has(item))) //交集
const difference = new Set([...mySet].filter(item=> !mySet2.has(item)))
数组转集合 : set = new Set(arr)
集合转数组: arr2 = [...new Set(arr)]
集合总结:
集合是一种无序且唯一的数据结构。
ES6中有集合,名为Set。
集合的常用操作:去重、判断某元素是否在集合中、求交集

字典

1
2
3
4
5
6
7
8
9
也是存储唯一值的数据结构,但是是以键值对的形式来存储的
ES6中有字典,名为Map
常用的常用操作,就是键值对的增删改查
const m = new Map()
m.set('a':'aa');key为a value为aa
查的话可以通过键来查值 m.get('a')就会显示aa
m.delete('b')//删除键
m.clear()//删除所有键
m.set('a','aaa')//键的值会被覆盖 set get等方法的时间复杂度都是O(1)

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
分层数据的抽象模型 在前端广泛应用
前端DOM树
JS没有树,但可以用ObjectArray构建树
树的深度与广度优先遍历
深度优先遍历:尽可能深的搜索树的分支。 ***** 重点
访问根节点。
对根节点的children 挨个进行深度优先遍历。
const tree = {
val:'a',
children:[
{
val:'b',
children:[
{
val:'d',
children:[]
}{
val:'e',
children:[]
}
]
}
{
val:'c',
children:[]
}
]
}
const dfs = (root)=>{
log(root.val)
root.children.forEach(dfs); // 输出abdec
}
dfs(tree)
广度优先遍历:先访问离根节点最近的节点。先进先出
新建一个队列,把根节点入队。
把队头出队并访问。
把队头的children挨个入队。
重复第二、三步,直到队列为空。
const bfs = (root)=>{
const q = [root]
while(q.length>0){
const n = q.shift()//队头出队并访问
log(n.val) //abcde
n.children.forEach(child=>{
q.push(child)
})
}

}
bfs(tree)

递归版的二叉树先中后序遍历
先序遍历:根左右
const preorder = (root) =>{
if(!root){return;}
log(root,val)
preorder(root.left)
preorder(root.right)
}
preorder(bt)
中序遍历: 左根右
const inorder = (root) =>{
if(!root){return;}
inorder(root.left)
log(root,val)
inorder(root.right)
}
preorder(bt)
后序遍历: 左右根
const postorder = (root) =>{
if(!root){return;}
postorder(root.left)
postorder(root.right)
log(root,val)
}
preorder(bt)

非递归版的二叉树先中后序遍历 **用栈来实现
先序遍历:根左右
const preorder = (root) =>{
if(!root){return;}
const stack = [root]
while(stack.length){
const n = stack.pop()
log(n.val)=> res.push(n.val)
if(n.right) stack.push(n.right)
if(n.left) stack,push(n.left)
}
}
preorder(bt)


中序遍历:左根右
const inorder = (root) =>{
if(!root){return;}
const stack = [root]
let p = root
while(p||stack.length){ //stack.length 会为空的。 遍历完根的全部左节点就空了
while(p){
stack.push(root)
p=p.left
}
const n = stack.pop()
log(n.val)
p = n.right

}
}
inorder(bt)

后序遍历:左右根 将其看成先序遍历的倒叙(根左右)根右左和根左右区别
const postorder = (root) =>{
if(!root){return;}
const stack = [root] // 先序的
const output= [] // 倒叙的
while(stack.length){
const n = stack.pop()
output.push(n)
if(n.left) stack,push(n.left)
if(n.right) stack.push(n.right)
}
while(output.length){
const n = output.pop()
log(n.val)

}
}
postorder(bt)
左右根
const postinder=(root)=>{
if(!root)return
const stack = [root]
const output = []
while(stack.length){
n=stack.pop()
output.push(n)
if(n.left) stack.push(n.left)
if(n.right) stack.push(n.right)
}
while(output.length){
const n = output.pop()
log(n)
}
}

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
图是网络结构的抽象模型,是一组由边连接的节点
JS没有图,但可以用ObjectArray构建图
操作:深度优先遍历和广度优先遍历 和树差不多
**深度优先遍历**
const graph = {
0:[1,2],
1:[2],
2:[0,3],
3:[3]
}
const path = require('graph') //graph就是下面的箭头图
const visited = new Set()
const dfs = (n)=>{
log(n)
visited.add(n)//集合add
graph[n].forEach(c=>{
if(!visited.has(c)){
dfs(c)
}
})
}
dfs(2)

**广度优先遍历**
新建一个队列,把根节点入队。
把队头出队并访问。
把队头的没访问过的相邻节点入队。
重复二三步,直至队列为空
const q = [2]
const visited = new Set()
visited.add(2)
while(q.length){
n = q.shift()
graph[n].forEach(c=>{
if(!visited.has(c)){
q.push(c)
visited.add(n)
}
})
}

LeetCode 133 克隆图
图的深度优先遍历算法
if(!root) return;
const visited = new Map()
const dfs=(n)=>{
const nodeCopy = new Node(n.val)
visited.set(n,nodeCopy)
(n.neighbors||[]).forEach(ne=>{
if(!visited.has(ne)){
dfs(ne)
}
nodeCopy.neighbors.push(visited.get(ne))
})
}
dfs(node)
return visited.get(node)

图的广度优先遍历算法
if(!root) return;
const q = [root]
const visited = new Map()
visited.set(root,new Node(root.val))
while(q.length){
const n = q.shift()
(n.neighbors||[]).forEach(c=>{
if(!visited.has(c)){
q.push(c)
visited.set(c,new Node(c.val))
}
visited.get(n).neighbors.push(visited.get(c))
})
}
return visited.get(node)

图总结: 图是网络结构的抽象模型,是一组由边连接的节点
图可以表示任何二元关系

image-20211224095045641

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
堆是一种特殊的完全二叉树(子节点全部填满,没填满也是缺少右边的节点)
JS中通常用数组表示堆
堆能高效、快速地找出最大值和最小值,时间复杂度:O(1).
找出第K个最大(小)元素。
堆的时间复杂度是O(1)
JS实现最小堆内:
class MinHead{
constructor(){
this.heap = [] //声明数组绑定this
}

swap(l1,l2){
const temp = this.heap[l1]
this.heap[l1]=this.heap[l2]
this.heap[l2]=temp
}
getPare(i){
return Math.floor((i-1)/2)// 二进制数向右移动一位 等于Math.floor((i-1)/2)
}
shiftup(index){
if(index==0){return;}
const Pxia = this.getPare(index)
if(this.heap[Pxia]>this.heap[index]){
this.swap(Pxia,index)
this.shiftup(Pxia)
}

}
getleft(i){
return 2*i+1
}
getRight(i){
return 2*i+2
}
shiftDown(index){
const Lnode = this.getleft(index)
const RLoad = this.getRight(index)
if(this.heap[index]>this.heap[Lnode]){
this.swap(index,Lnode)
this.shiftDown(Lnode)
}
if(this.heap[index]>this.heap[RLoad]){
this.swap(index,RLoad)
this.shiftDown(RLoad)

}
}
pop(){ //删除堆顶
this.heap[0] = this.heap.pop()
this.shiftDown(0)

}
insert(value){
this.heap.push(value)
this.shiftup(this.heap.length-1)

}
peek(){
return this.heap[0]
}
size(){
return this.heap.length
}
}
const h = new MinHead()
h.insert(3)
h.insert(2)
h.insert(1)
h.pop()

image-20211225095227038

image-20211225100257620

排序

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
1
排序:把某个乱序的数组变成升序或者降序的数组。 冒泡.选择.插入.归并.快速
搜索:找出数组中某个元素的下标。顺序.二分.
2
JS中的排序:数组的sort方法。
JS中的搜索:数组的indexOf方法。
(1)冒泡排序:
第一个比第二个大就交换 这样最大的元素在最后一个
执行n-1轮,就可以完成排序

for (let i =0 ; i < this.length - 1; i += 1){ 这是轮数
for ( let j = 0;j < this.length - 1-i; j +=1){ 这是将5放到最后一位 this.length - 1是为了j+1有值
if (this[j] > this[j +1]){
const temp = this[j];
this[j] = this[j +1];
this[j + 1] = temp;
}
};
const arr = [5,4,3,2,1]
就完成排序了 两个嵌套循环 时间复杂度为O(n^2)

(2)选择排序
找到数组中的最小值,选中它并将其放置在第一位。
接着找到第二小的值,选中它并将其放置在第二位。
以此类推,执行n -1轮。
const arr = [5,4,3,2,1]
const selectionSort = (array)=>{
let length = array.length;
let minIndex;//要定义
for (let i = 0; i < length; i++) {
// 更新 minIndex
minIndex = i;
for (let j = i; j < length; j++) { //这是第一轮获取下标
if(array[minIndex]>array[j]){
minIndex = j;
}
}
// 如果该最小值和原最小值不同 则交换其值 相等就没必要交换了
if(i!==minIndex){
var aux = array[i];
array[i] = array[minIndex];
array[minIndex] = aux; //这是交换值 交换玩从第二个值开始 所以j=i
}
}
return array;
}
两个嵌套循环 时间复杂度为O(n^2)

(3)插入排序:
从第二个数开始往前比。
比它大就往后排。
以此类推进行到最后一个数。
第一轮的插入如下
array = [5,4,3,2,4]
const temp = this[1]//4
let j = 1
while(j>0){
if(this.[j-1]>temp){ 5>4
this[j] = this[j-1] //55321
}else{
break;
}
j-=1 //j=0
}
this[j]=temp //45321
-----------
45321
for(let i=1;i<this.length;i+=1){
const temp = this[i]//
let j = i
while(j>0){
if(this.[j-1]>temp){ 4>3
this[j] = this[j-1] //45521
}else{
break;
}
j-=1 //j=1
}
this[j]=temp //43521
}
所以就完成了// 时间复杂度为O(n^2)

(4)归并排序:
分:把数组劈成两半,再递归地对子数组进行“分”操作,直到分成一个个单独的数。
合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组。

新建一个空数组res,用于存放最终排序后的数组。
比较两个有序数组的头部,较小者出队并推入res中。
如果两个数组还有值,就重复第二步。
[5,4,3,2,1]
rec=(arr)=>{
if(arr.length===1){return res;}
const mid = Math.floor(arr.length/2)
const left = arr.slice(0,mid)[5,4]
const right = arr.slic(mid,arr.length)[3,2,1]
const leftcount = rec(left)
const rightcount = rec(right)
const res= []
while(leftcount.length||rightcount.length){
if(rightcount.length&&leftcount.length){
res.push(leftcount[0]<rightcount[0]?leftcount.shift():rightcount.shift())
}else if(rightcount.length){
res.push(rightcount.shift())
}else if(leftcount.length){
res.push(leftcount.shift())
}
}
return res
}
logN 是求2的多少次方 因为是对半劈所以分的时间复杂度为O(logN),合的复杂度是O(n) 分合是嵌套关系所以复杂度就是O(nlogN)

(5)快速排序
分区:从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素放在基准的后面。
递归:递归地对基准前后的子数组进行分区。
rec = (arr)=>{
const left = []
const right = []
const mid = arr[0]
for(let i = 1;i<arr.length;i+=1){
if(arr[i]<mid){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return [...res[left],mid,...res[right]]
}
const res = rec(this)
时间复杂度:递归的时间复杂度是O(logN)=>劈两半 分区操作的时间复杂度是O(n)=>写一个for循环比较与基准值大还是小的元素
[3,4,5,2,1]
rec=(arr)=>{
const left=[]//45
const right=[]//21
const mid = arr[0]//3
for(let i =1;i<arr.length;i++){
if(arr[i]>mid){
right.push(arr[i])//45
}else{
left.push(arr[i])//21
}
}
return [...rec(left),mid,...rec(right)]
}

(6)顺序搜索
for遍历 如果找到值相同就返回下标 否则返回-1 时间复杂度为O(n)
(7)二分搜索-折半搜索(前提是有序)
从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束。
如果目标值大于或者小于中间元素,则在大于或小于中间元素的那一半数组中搜索。
binarySearch=function(item){
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
}
时间复杂度为O(logN) 每一次比较都使搜索范围缩小一半

LeetCode21.合并两个有序链表
输入1->2->4,1->3->4
输出1->1->2->3->4->4
新建一个新链表,作为返回结果。
用指针遍历两个有序链表,并比较两个链表的当前节点,较小者先接入新链表,并将指针后移一步。
var mergeTwoLists = function(l1,l2){
const res = new ListNode(0)
let p1 = res
let p2 = l1
let p3 = l2
while(p2&&p3){
if(p2.val<p3.val){
p1.next=p2
p2=p2.next //p1用过了 所以要后移一位
}else{
p1.next = p3
p3=p3.next
}
p1 = p1.next
}
if(p2){
p1.next = p2
}
if(p2){
p1.next = p3
}
return res.next
}
时间复杂度因为有一个while循环体 所以为O(n) n为两个链表的长度只和
空间复杂度为O(1),是个常量级别的。新建的临时变量是指针,不是数组,不是矩阵,更不是链表,用的是常量级别的变量来完成操作的
374:猜数字大小=>二分搜索
唯一的不同是通过调用guess函数,来判断中间元素是否是目标值 而之前的是目标值已给定
var guessNumber = function(n){
let left = 1
let right = 10
while(left<right){
const mid = Math.floor((right+left)/2)
const result = guess(mid)
if(result===0){
return mid
}else if(result === 1){
left = mid+1
}else{
right = mid -1
}
}
}
时间复杂度:每次搜索区别都缩半 所以是O(logN)
空间复杂度为:O(1),没有新建临时变量时数组,矩阵线性增长的需要
总结:
排序:把某个乱序的数组变成升序或者降序的数组。
搜索:找出数组中某个元素的下标。
JS中的排序:数组的sort方法。
JS中的搜索:数组的indexOf方法。

分而治之

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
算法设计的一种方法
把一个问题分成多个和原问题相似的小问题,递归解决小问题,再将结果合并以解决原来的问题
分:把数组从中间一分为二。
解:递归地对两个子数组进行归并排序。
快排也是分而治之的思想 **归并排序,快速排序,二分搜索,翻转二叉树都可以用分而治之的思想进行求解
226翻转二叉树
先翻转左右子树,再将子树换个位置
分:获取左右子树
解:递归地翻转左右子树
合:将翻转后的左右子树换个位置放到根节点上
var invertTree = function(root){
if(!root){return null}
return{
val:root.val
left:invertTree(right)
right:invertTree(left)
}
}
100.两棵相同的树
var isSameTree = function(p1,p2){
if(!p1&&!p2){return true}
if(p&&q&&p.val===q.val&&isSameTree(p.left,q.left)&&isSameTree(p.right,q.right)){
return true
}
return false
}
时间复杂度:递归遍历树的节点 O(n)n就是树的节点树
空间复杂度:由于是递归,在内部形成了堆栈,所以说空间复杂度为O(n)
101.对称二叉树
转化为:左右子树是否镜像。
分解为:树1的左子树和树2的右子树是否镜像,树1的右子树和树2的左子树是否镜像。
符合“分、解、合”特性,考虑选择分而治之。
var isSymmetric = function(root){
if(!root){return ture}
const isMirror = (l,r)=>{
if(!l&&!r)return true
if(l&&r&&l.val===r.val&&isMirror(l.left,r.right)&&isMirror(l.right,r.left)){
return true
}
return false
}
return isMirror(root.left,root.right)
}
空间复杂度:O(n) 访问所有节点
时间复杂度:堆栈的高度 树的高度O(n) 如果二叉树分布均匀的话就是O(logN)

动态规划

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
动态规划是算法设计中的一种方法 是将一个问题分解为相互重叠的子问题,通过反复求解子问题,来解决原来的问题
分而治之是子问题相互独立
例如斐波那契数列
定义子问题 F(n)=F(n-1)+F(n-2)
反复执行,从2循环到n,执行上述公式
LeetCode70、爬楼梯
var climbStairs = function(n){

if(n<2)return 1
const dp = [1,1]
for(i=2;i<n;i++){
dp[i]=dp[i-1]+dp[i-2]//
}
return dp[n]
}
for循环 时间复杂度为O(n)
创建了一个数组,数组会线性增加 O(n) 想要降低空间复杂度
可以
var climbStairs = function(n){

if(n<2)return 1
let dp0 = 1
let dp1 = 1
for(let i=2;i<n;i++){
const temp = dp0
dp0=dp1
dp1 = dp1+temp
}
return dp1
}
LeetCode198 打家劫舍
[1,2,5,1] // 最大金额为6 1+5=6
f(k) = 从前k个房屋中能偷窃到的最大数额。 输入:[1,2,3,1] 输出:4
Ak = 第k个房屋的钱数。
f(k) = max(f(k - 2)+Ak, f(k - 1))。
考虑使用动态规划。
var rob = function(nums){
if(nums.length ===0)return 0
const dp = [0,nums[0]] //只有一个房屋和两个房屋的金额
for(let i = 2 ;i<nums.length;i++){
dp[i]=Math.max(dp[i-2]+nums[i-1],dp(i-1))
}
return dp[nums.length]
}
时间复杂度、空间复杂度为O(n)
var rob = function(nums){
if(nums.length ===0)return 0
let dp0=0
let dp1=nums[0]
for(let i = 2 ;i<nums.length;i++){
const dp[2]=Math.max(dp[0]+nums[i-1],dp(1))
dp[0] = dp[1]
dp[1] = dp[2]

}
return dp[1]
}
空间复杂度可以优化 没有存任何的数组和矩阵 空间复杂度为O(1)

贪心算法

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
贪心算法是算法设计中的一种方法 
期盼通过每个阶段的局部最优选择,从而达到全局的最优。
结果并不一定是最优。
455 分饼干
对饼干数组和胃口数组升序排序。
遍历饼干数组,找到能满足第一个孩子的饼干。
然后继续遍历饼干数组,找到满足第二、三、.....、n个孩子的饼干。
var findContentChildren = function(g, s) {
const sortFunc = function(a,b){
return a-b
}
g.sort(sortFunc)//78910 这一部分的时间复杂度是O(nlogn) + for循环O(n) 所以时间复杂度是O(nlogn)
s.sort(sortFunc)//5678 虽然说有gs两个数组但是都是已有的数组,中间没有临时存储任何线性增长的变量包括链表矩阵O(1)
let i = 0
s.forEach(n=>{
if(n>=g[i]){
i+=1
}
})
return i
}
122 买卖股票的最佳时机
前提:上帝视角,知道未来的价格。
局部最优:见好就收,见差就不动,不做任何长远打算。
输入:[7,1,5,3,6,4] 输出:7
var maxProfit = function (prices){
let profit = 0
for(let i=1;i<prices.length;i++){
if(prices[i]>prices[i-1]){
profit += prices[i]-prices[i-1]
}
}
return profit
}
时间复杂度是O(n) 空间复杂度为O(1)

动态规划

1
2


Leetcode

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
20.有效的括号
用栈来做:
1.新建一个栈
2.扫描字符串,遇左括号入栈,遇到和栈顶的左括号匹配的右括号就出栈,类型不匹配就直接判定不合法 ([})
3.最后判断栈是否为空,不为空也判定不合法
if(s.length%2!==0){return false}
const stack = []
for(let i= 0 ;i<s.length;i++}{
const c = s[i]
if(c==='('||c==='['||c==='{')){
stack.push(c)
}else{
const top = stack[stack.length-1]
if(top==='('&&c===')')||
(top==='{'&&c==='}')||
(top==='['&&c===']'){
stack.pop()
}else{
return false
}
}

}
return stack.length===0 //判断最后栈是否为空 为空就为true 因为是栈

用字典来做:
if(s.length%2!==0){return false}
const stack = []
const map = new Map
map.set('(',')')
map.set('[',']')
map.set('{','}')
for(let i= 0 ;i<s.length;i++}{
const c = s[i]
if(map.has(c)){
stack.push(c)
}else{
const top = stack[stack.length-1]
if(map.get(top)===c){
stack.pop()
}else{
return false
}
}

}


933.最近的请求次数(队列先进先出)
有新请求就入队,3000ms前发出的请求出队。
队列的长度就是最近请求次数。
在构造函数内将队列挂载this
输入:inputs = [[],[1],[100],[3001],[3002]]
输出:[null,1,2,3,3]
var RecentCounter = function(){
this.q = [] //将队列挂载在构造函数自身
}
RecentCount.prototype.ping = function(t){ //每一次都会调用ping方法
this.q.push(t);
while(this.q[0]<t-3000){ 时间复杂度,空间复杂度都是O(n) 队头是1
this.q.shift()//踢掉队头
}
return this.q.length;
}

237.删除链表中的节点
1、将被删节点的值改为下个节点的值。node.val = node.next.val 5->1->4->9->8 => 5->1->9->9->8
2、删除下个节点。 node.next = node.next.next

206.反转链表
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才是那个头节点
2.两数相加 (遍历链表)
var addCounts = function(l1,l2){
const l3 = new ListNode(0) //创建一个新的链表节点
let p1 = l1
let p2 = l2
let p3 = l3
let carry=0
while(p1||p2){
const v1 = p1?p1.val:0
const v2 = p2?p2.val:0
const v3 = v1+v2+carry
carry = Math.floor(v3/10)
p3.next = new List(v3%10)
if(p1) p1=p1.next
if(p2) p2=p2.next
p3=p3.next
}
if(carry){
p3.next = new ListNode(carry)
}
return l3.next

}
83.删除排序链表中的重复元素
var deleteDuplicates = function(head){
let p = head
while(p&&p.next){
if(p.val===p.next.val){
p.next = p.next.next
}else{
p=p.next
}
}
return head
}


141.环形链表
var hasCycle = function(head){
let slow = head
let fast = head
while(slow&&fast&&fast.next){
slow =slow.next
fast = fast.next.next
if(slow === fast){
return true
}

}
return false
}

349.两个数组的交集
用集合来做:
解题思路:求交集且无序唯一。nums1=[1,1,2,1],nums2=[2,2] 输出[2]
const set1 = new Set(nums1)
const set2 = new Set(nums2)
//return [...set1].filter(item=>set2.has(item))
return [...set1].filter(item=>nums2.includes(item)) //时间复杂度filter循环为O(n),n为set1的长度,has和includes方法是O(m),m为num2的长度因此嵌套时间复杂度为为O(n*m) 空间复杂度就是一个去重后的数组,因为位O(m)

用字典来做:
const map = new Map()
nums1.forEach(n=>{
map.set(n,true)
})
const res = []
nums2.forEach(n=>{
if(map.get(n)){
res.push(n)
map.delete(n)
}
})
return res //时间复杂度为O(m+n) =>遍历没有嵌套一个为nums1的长度,一个为nums2的长度
//算法的输入输出都是数组,但是不能计算在内,空间复杂度指的是临时变量的内存消耗,中间的临时变量时一个字典,字典虽然不是数组也不是矩阵,但是存储的值也可能是线性增长的,所以说这个算法的空间复杂度是O(m)

1.两数之和
const map = new Map()
for(let i=0;i<nums.length;i++){
const n = nums[i]
const n2 = target-n
if(map.has(n2)){
return [map.get(n2),i]
}else{
map.set(n,i)
}
} //时间和空间复杂度都为O(n)

3.无重复字符的最长子串
1、先找出所有的不包含重复字符的子串。
2、找出长度最大那个子串,返回其长度即可。
用双指针维护一个滑动窗口,用来剪切子串。
不断移动右指针,遇到重复字符,就把左指针移动到重复字符的下一位。
过程中,记录所有窗口的长度,并返回最大值0
const l = 0
res = []
const map = new Map
for(let r= 0,r<s.length,r++){
if(map.has(s[r])&&map.get(s[r]>=l)){ //妙呀,因为字典里已经有a了
l=map.get(s[r])+1
}
res=Math.max(res,r-l+1)
map.set(s[r],r)
}
return res
//abbcdea
时间复杂度为O(n),空间复杂度为O(m),m为字符串中不重复的字符

76、最小覆盖子串
先找出所有的包含T的子串。
找出长度最小那个子串,返回即可。
用双指针维护一个滑动窗口。
移动右指针,找到包含T的子串,移动左指针,尽量减少包含T的子串的长度。
循环找到最小子串
let l = 0
let r = 0
const need = new Map()
for(let c of t){
need.set(c,need.has(c)?need.get(c)+1:1)
}
let res= ''
let needType=need.size
while(r<s.length){
c1 = s[r]
if(need.has(c1)){
need.set(c1,need.get(c1)-1)
if(need.get(c1)===0) needType-=1
}
while(needType===0){
const newStr = s.substring(l,r+1)
if(!res||newStr.length<res.length) res = newStr
c2=s[l]
if(need.has(c2)){
need.set(c2,need.get(c2)+1)
if(need.get(c2)===1) needType+=1
}
l+=1

}
r+=1
}
return res

104.二叉树的最大深度
解题思路
1、最大深度,考虑使用深度优先遍历。
2、深度优先遍历过程中,记录每个节点所在的层级,找出最大的层级即可。
1.一个变量,记录最大深度。
2.优先遍历整棵树,并记录每个节点的层级,同时不断刷新最大深度这个变量。
3.遍历结束返回最大深度这个变量
先验证深度优先遍历的结果有没有问题
var maxDepth = function(root){
let res = 0
const dfs = (n)=>{
if(!n){return;}
log(n.val)
dfs(n.left)
dfs(n.right)
}
dfs(root)
}
-------
var maxDepth = function(root){
let res = 0
const dfs = (n,l)=>{
if(!n){return;}
log(n.val,l)=>替换res = Math.max(res,l);
dfs(n.left,l+1)
dfs(n.right,l+1)
}
dfs(root,1)
return res
}
这样执行用时很高这是因为它每次都去遍历每个节点的时候都去刷新res
没必要刷新,只要是叶子节点再去刷新就可以了
if(!n.left&&!n.right){
res = Math.max(res,l)
}

111.二叉树的最小深度
1、求最小深度,考虑使用广度优先遍历。
2、在广度优先遍历过程中,遇到叶子节点,停止遍历,返回节点层级。
1.广度优先遍历整棵树,并记录每个节点的层级。
2.遇到叶子节点,返回节点层级,停止遍历。
var minDepth = function(root){
if(!root){return;}
const q = [root]
while(q.length){
const n = q.shift()
log(n.val)
if(n.left) q.push(n.left)
if(n.right) q.push(n.right)
}
}
--------------
var minDepth = function(root){
if(!root){return;}
const q = [[root,1]]//添加层级
while(q.length){
const [n,l] = q.shift()
log(n.val,l)=>
if(!n.left&&!n.right){
return l
}
if(n.left) q.push([n.left,l+1]
if(n.right) q.push([n.right,l+1])
}
}

102.二叉树的层序遍历
1、层序遍历顺序就是广度优先遍历。
2、不过在遍历时候需要记录当前节点所处的层级,方便将其添加到不同的数组中。
if(!root)return []
const q = [[root,0]]
const res = []
while(q.length){
const [n,l] = q.shift()
log(n,val)=>
if(!res[l]){
res.push([n.val]) //先推第二层的左节点
}else{
res[l].push(n.val) //再推第二层的右节点
}
if(n.left) q.push([n,left,l+1])
if(n.right) q.push([n.right,l+1])
}
return res


--------------------------
if(!root)return []
const q = [root]
const res = []
while(q.length){
let len =q.length
res.push([])
while(len--){
const n = q.shift()
res[res.length-1].push(n.val)
if(n.left) q.push(n.left)
if(n.right) q.push(n.right)
}
}
return res

112 路径之和
215.数组中第K个最大元素(最小堆)
var findKthLargest = function(nums, k) {
const h = new MinHead()
nums.forEach(n=>{
h.insert(n)
if(h.size()>k){
h.pop()
}
})
return h.peek()
};
347.前K个高频元素

2.两数相加

image-20211221143956591

83.删除排序链表中的重复元素

image-20211221151123774

141.环形链表

image-20211221150620235

算法里没有额外的线性增长的数据结构,没有数组,没有矩阵,没有链表 空间复杂度为O(1)

面试题

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
instanceof的原理,并用代码实现
知识点:如果A沿着原型链能找到 B.prototype,那么A instanceof B 为true
解法:遍历A的原型链,如果找到B.prototype,返回true,否则返回false
const instanceof(A,B){
const p = A
while(p){
if(p===B.prototype){
return true
}
p=p.__prop__
}
return false
}




使用链表指针获取JSON的节点值
const json={
a:{b:{c:1}},
d:{e:2},
}
const path = ['a','b','c']

let p = json;
path.forEach(k=>{ //遍历abc
p=p[k] //p=1
})

前端与树的内容:
遍历JSON的所有节点值
const json={
a:{b:{c:1}},
d:[1,2]
}
const dfs=(root)=>{
log(n,path)
//通过Object.keys(n)就可以拿到root的孩子节点
Object.keys(n).forEach(k=>{
dfs(n[k],path,path.concat(k))
})
}
dfs(json,[])

渲染Antd的树组件
const {Tree} = antd
const {TreeNode} = Tree
const json=[
{
title:'一'
key:'1'
children:[{title:'三',key='3',children:[]}]
}
{
title:'二'
key:'2'
children:[{title:'四',key='4',children:[]}]
}
]
class Demo extends React.Component{
dfs=(n)=>{
return (
<TreeNode title ={n.title} key={n.key}>
{n.children.map(this.dfs)}
</TreeNode>
)
}
render(){
return <Tree> {json.map(this.dfs)}</Tree>
}
}
RaeactDOM.render(<Demo/>,document.getElementById('root'))

深度优先遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
**树**
const dfs = (root)=>{
log(root.val)
root.children.forEach(dfs); // 输出abdec
}
dfs(tree)

**图**
const path = require('graph') //graph就是下面的箭头图
const visited = new Set()
const dfs = (n)=>{
log(n)
visited.add(n)//集合add
graph[n].forEach(c=>{
if(!visited.has(c)){
dfs(c)
}
})
}
dfs(2)

广度优先遍历

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
**树**
const bfs = (root)=>{
const q = [root]
while(q.length>0){
const n = q.shift()//队头出队并访问
log(n.val) //abcde
n.children.forEach(child=>{
q.push(child)
})
}
}
bfs(tree)
**图**
const q = [2]
const visited = new Set()
visited.add(2)
while(q.length){
n = q.shift()
graph[n].forEach(c=>{
if(!visited.has(c)){
q.push(c)
visited.add(n)
}
})
}

版本控制

什么是版本控制

版本控制(Revision control)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。

Github 和 Git区别。

Git是版本控制系统,Github是在线的基于Git的代码托管服务。GitHub是2008年由Ruby on Rails编写而成。 GitHub同时提供付费账户和免费账户。 这两种账户都可以创建公开的代码仓库,但是付费账户也可以创建私有的代码仓库。 为什么现在Github这么火,以至于世界顶级公司和项目的源码很多都托管在Github上——颜值高! 现在这世代还是得看看颜值的。 你说Sourceforge等代码托管网站也年岁很久了,为什么没有Github那么火呢? 关键是Github长得好看(你长这么好看,说什么都是对的)。 Linus Torvalds的Github页面,打开看看吧,Linux的源码就在上面,随时在更新!

常见的版本控制工具

  • Git
  • SVN
  • CVS
  • VSS
  • TFS
    版本控制的产品非常多,现在影响力最大且使用最广泛的是Git与SVN

    阅读全文 »

react简书实战

day1

实战环节

环境的搭建->Header组件的编写->首页的编写->详情页的编写->登陆相关业务逻辑的处理->项目开发结束上线

核心技术点

  1. creat-react-app脚手架工具进行项目的搭建

  2. 组件化思维进行项目开发

  3. 深入学习jsx模板语法

  4. 项目的开发调试

  5. 虚拟DOM和diff算法,生命周期

  6. 项目react-transition-group第三方的模块实现复杂的动画效果

  7. Redux Antd UI组件 容器组件 无状态组件等等内容

  8. redux使用之中的redux中间键 比如redux-thunk、redux-saga这些新的技术

  9. 项目样式布局采用最新的Styled-components技术来实现CSS样式的编码,避免组件之间样式的互相影响

  10. 在进行redux之中数据管理的时候,使用react推出的数据框架 Immutable.js,这个库有效的避免我们对数据的误操作

    如果使用这个库还会涉及redux-immutable这个redux的中间键

  11. 使用axios这个工具发送ajax请求

react基础知识的巩固

React开发环境的准备:引入.js文件来使用React->通过脚手架工具来编码->Create-react-app官网提供的脚手架

  • 创建脚手架:

    1. 检查node -v的版本 和 npm -v的版本

    2. 输入命令npm install -g create-react-app

    3. 输入命令create-react-app 文件夹名

    4. 创建之后切换到创建的文件夹 cd 文件名夹

    5. 运行yarn start即可显示 React 主界面

  • 三个文件:

    (1)public下的index.html文件 :页面上显示的html的内容

    ​ (2)src下的index.js: 整个react项目的入口文件 会引入App文件

    ​ (3)src下的App.js : 负责页面上显示Hello Word的内容

day2

React中的组件

​ 定义组件:定义的那个类继承React.Component

  • 只要是jsx语法想在react之中正常的运行,就要引入react 例如 组件(组件标签都需要大写开头) render()下的

    (小写开头的就是h5标签)这些都属于jsx语法

  • index.js ``文件中通过ReactDOM将APP这个组件显示在``id=root这个标签下面

  • render返回的必须是在一个大的元素之中,但是有时候又不想让这个div显示就可以用的占位符来替代最外层的div,不会渲染为任何标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<input 
value={this.state.inputvalue} --inputvalue就是输入框的值
onChange={this.handleInputChange} --通过调用该函数来改变改值
/>

handleInputChange=(e)=>{
this.state.inputValue = e.target.vaue
console.log(e.target.value) --e.target是Input框返回的DOM节点,e.target.value返回的就是value值
console.log(this)
this.setState({
inputValue:e.target.value
})
}
/*如果需要实现改变inputvalue的值就在handleInputChange函数里写this.state.inputValue = e.target.vaue
如果handleInputChange这个函数没有使用箭头函数,此时的this为undefined,可以使用ES6的语法将this的指向指向为Todolist组件
onChange={this.handleInputChange.bind(this)}即可。
  • 改变state里的数据不能像上述引用的方式去改变,必须调用setState的方法去改变state里的数据。

  • 由于react中state是不允许做任何的改变的immutable的特性,因此我们在做list操作的时候需要先做一个拷贝,对拷贝的数据进行处理 如果改变之后做性能优化的时候就会出现问题。

JSX细节语法补充

1
2
3
4
5
6
7
8
9
10
11
1.两种注释方法:
{
//注释内容
}
或者
{/*注释内容*/}
2.为了防止某些标签的内容不被转义 也就是在输入框输入<h1>啦啦啦<h1/>点击提交的时候是显示啦啦啦
return <li key={index} onClick={this.handledelete} dangerouslySetInnerHTML={{__html:item}}></li>
3.在输入框前添加label标签的时候 点击babel标签的内容光标会自动聚焦到输入框上
<label htmlFor='insertArea'>输入内容</label>
<input id='insertArea' className='input' value={inputvalue}

注意

1
2
3
4
5
6
7
8
9
10
新版的react  setState返回的不是一个对象了 要把对象替换成函数
之前: handlechange=(e)=>{
this.setState(
{inputvalue:e.target.value}
)}

现在: const value = e.target.value //对value值做保存
this.setState(()=>({
inputvalue:value})) //写成函数就会变成异步的setState 如果直接写e.target.value就会有问题
}

React优点

  1. 声明式开发:使得我们能减少大量DOM操作代码量
  2. 可以与其他框架并存
  3. 组件化
  4. 单向数据流
  5. 视图层框架
  6. 函数式编程 好处:用react写出来的项目更容易实现前端自动化测试

思考

单向数据流:父组件可以向子组件传值,但是子组件不能改变这个值,只能单向进行传递,不能反过来修改。这就说明为什么在修改item项目的时候,为什么不向子组件传递一个list,而要传递index与item了。

再或者说如果一个父组件有很多个子组件且子组件都共用list,如果将list传给某一个子组件且在该子组件进行修改,会导致其余四个组件中list也发生变化,这样页面出现bug的时候,就不知如何去定位这个bug,对代码的调试是很不方便的,因此要有单向数据流的概念。

如果真的想修改:首先父组件传递给子组件一个方法,在子组件去调用父组件传的这个方法,然后去做删除。

React是一个视图层的框架:react并不是什么问题都解决,只帮助你解决数据和页面渲染上的一些东西,至于组件之间怎么传值(就如1给2传递的话就不能按一层一层传了),并不负责,交给其他组件来做,做到大型项目时,react不够用,只能借助它去搭建视图,做组件传值的时候还要借助redux等的数据层的框架来做额外的支撑。

image-20211117154738062

巩固知识

  • 父组件给子组件传值的时候,子组件要对传来的值做类型的限制。如果对传来的值做了isRequired的限制,如果不传就会报错。
1
2
3
4
5
6
7
8
9
10
11
限制类型有两种放置方法:
1.TodoItem.propTypes={
item:PropTypes.string,
dele:PropTypes.func,
index:PropTypes.number
} --放置在定义完组件的下方(最下方)
2.TodoItem.propTypes={
item:PropTypes.string,
dele:PropTypes.func,
index:PropTypes.number
} --放置在render的上方
  • 当组件的state和props发生改变的时候,render函数就会重新执行。

  • 当父组件的render函数被运行时,它的子组件的render都将重新运行一次。

虚拟DOM(本质上就是一个js对象,之所以能提高性能,因为js去比较js对象不怎么耗性能,去比较真实的DOM很耗性能)

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
真实DOM和虚拟DOM性能的差异:
使用真实DOM
1. state数据 **初始数据state
2. JSX 模版 **对应render里的jsx代码
3.数据+模版结合,生成真实的DOM,来显示
4. state 发生改变
5.数据+模版结合,生成真实的DOM,并不直接替换原始的DOM
6.新的DOM 和原始的DOM做比对,找差异
7.找出input框发生了变化
8.只用新的DOM中的input元素,替换掉老的DOM中的input元素
**相比全部替换原始DOM性能提升
**但是还是消耗了一部分性能:新的DOM和原始DOM做比对的性能 性能提升也不是很明显

使用虚拟DOM:
1. state数据
2. JSX 模版
3. 数据+模版结合,生成真实的DOM,来显示
假设真实DOM为:<div id='abc'><span>hello world</span></div>
4.生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)(相比上面第四步没有生成虚拟DOM 实看是损耗了性能)
[ 'div', {id: 'abc'}, [ 'span',{},'hello world ']] //标签名 标签属性 标签内容 **JS对象 也就是虚拟DOM
5. state 发生变化
6. 数据+模版生成新的虚拟DOM(极大的提升了性能相比创建真实DOM,js对象创建的过程性能是极低的)
[ 'div', {id: 'abc'},[ 'span', {},'bye bye' ]]
7.比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大的提升性能)
**比对的是原始的js对象和新的js对象两个js对象的比较是极不损耗性能的。
8.直接操作DOM,改变span中的内容

深入了解虚拟DOM

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
实际的流程是:
1. state数据

2. JSX 模版(类似于div)

/*JSX -> createElement方法 -> JS对象 -> 真实的DOM
return <div>item<div> =>等价为
return React.createElement('div',{},'item')
*/

3.数据 + 模板 生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)(损耗了性能)
[ 'div', {id: 'abc'}, [ 'span',{},'hello world ']]

4. 用虚拟DOM的结构生成真实的DOM,来显示
<div id='abc'><span>hello world</span></div>

5. state 发生变化

6. 数据+模版生成新的虚拟DOM(极大的提升了性能)
[ 'div', {id: 'abc'},[ 'span', {},'bye bye' ]]

7.比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大的提升性能)

8.直接操作DOM,改变span中的内容

虚拟DOM的好处: 1.性能提升:因为生成真实DOM要耗费很大的性能
2.它使得跨端应用得以实现

第2点好处的底层原理:没有虚拟DOM React是无法写原生应用的,根本无法在原生(React Native)的应用里被使用。
例如:没有虚拟DOM,流程就是准备好state数据和模板之后就会渲染DOM,渲染DOM在浏览器是没有问题的,可是在移动端的原生应用里,比如安卓或者ios机器上的代码是不存在DOM这个概念的,所以没有虚拟DOM,在原生的应用里根本无法被使用,所以代码只能运行在浏览器里面,但是有虚拟DOM就不一样了,代码首先会被转化为虚拟DOM(是个js对象)js对象在浏览器可以被识别,在原生的应用里也可以被识别。
换句话说:虚拟DOM生成好了在浏览器下面,可以将虚拟DOM变成真实DOM,浏览器下面会显示出真实的页面。假设在原生应用里,虚拟DOM生成之后可以不生成真实DOM,而让虚拟DOM生成些原生的组件,这样的话同样的代码包括state,jsx的代码都可以被复用,原生的应用里面可以把对应的页面展示出来。因为生成的虚拟DOM,在哪里都可以运行,只需要在网页上将虚拟DOM转化为真实DOM,这样的话网页就可以跑起来。这使得React既能开发网页应用,还可以开发原生应用

虚拟DOM中的Diff算法

1
2
3
4
5
6
比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大的提升性能)
当数据发生改变的时候(state和props的改变,其实props的改变实际上也是父组件的state发生改变),虚拟DOM和真实DOM才会发生比对。
归根到底,当你调用setState的时候,数据才发生改变,然后虚拟DOM才发生比对。(setState是异步的,实际上是为了提升react底层的性能,原因:假设连续调用三次setState,变更三组数据,React会把三次setState合并成一次,只是做了一次虚拟DOM的比对,然后去更新一次DOM,可以额外减去那两次DOM比对带来的性能上的耗费)--性能优化
Diff算法就是两个虚拟DOM比对的算法,React采用的是同层的虚拟DOM比对的算法。左边的为虚拟DOM,当数据改变的时候会生成新的虚拟DOM。同层比对,如果一层不满足要求,之后的也不比对了,后面的就直接废弃掉,整个就替换掉,这样会提升性能。(直观上如果第一层不一样后面都一样,导致后面一样的节点没办法复用,性能很低,怎么会提升性能。实际上是因为同层比对的算法很简单,算法简单带来的是比对的速度会非常快。虽然可能会导致DOM重新渲染上的浪费,但是它大大的减少了两个虚拟DOM之间比对的算法上性能的消耗。)
key值要保持相对稳定,在项目中能不用index做key值就不用。
同层比对和key值比对这些比对的方式都是Diff算法的一部分。--性能优化

image-20211130220031980

ref

如果ref和setState一起使用的时候,要注意setState是异步函数,不会立马执行。有时候会使得无法正确输出页面DOM的真实情况

为了避免这种情况,setState还有第二个参数,它是一个回调函数,执行的时机是在等异步的完全结束了再执行。

生命周期函数

生命周期函数指在某一个时刻组件会自动调用的函数(当state和pros变化的时候render就会被调用,render函数就是生命周期函数) 组件一创建就会被调用 constructor函数和生命周期函数

每一个组件都可以有生命周期函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
shouldComponentUpdate(nextProps,nextState) {
if(nextProps.content !==this.props.content) {
return true;
}else {
return false;
}
}
设置shouldComponentUpdate这个声明周期函数提升了组件的性能,性能提升在避免组件做无谓的render操作。render重新执行就意味着react底层需要对组件重新生成一份虚拟DOM,这样就要和之前的虚拟DOM做比对,虽然虚拟DOM的比对比真实DOM的比对性能要快的多,但是能省略这个比对过程,当然可以节约更多的性能。--性能优化

一般把ajax请求放到ComponentDidMount()来做

如何发送ajax请求,首先我们要借助一个库 在todolist输入命令行:yarn add axios

componentDidMount() {
axios.get('/api/todolist')

.then(()=>{alert( 'success' )})

.catch(()=>{alert( 'error' )})
}

day3

CSS过渡动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可以使用关键帧
@keyframes identifier {
0% {
opacity: 1;
color: red;
}
50% {
opacity: 0.5;
color: salmon;
}
100%{
opacity: 0;
color: blue;
}
}
.hide{
animation: identifier 2s ease-in forwards; //如果没有forwards 动画执行结束
}
注意:关键帧当动画执行结束后,最后一帧动画的CSS效果不会被保存下来。加了forwards属性就可以保存下来了
实际上用CSS3开发出的动画具有局限性,涉及js动画的时候就没法处理了。因此得使用下方的模块

使用react-transition-group实现动画

使用这个可能实现更加复杂的动画效果

[官网文档API网址](React Transition Group (reactcommunity.org)

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
App.js文件里
对单个元素实现效果:
<CSSTransition>组件

<CSSTransition
in={this.state.show} //显示的属性
timeout={1000} //显示的时间
classNames='fade' //入场和出场动画属性名匹配
unmountOnExit
onEntered={(el)=>{el.style.color='blue'}}//入场动画结束时显示的
appear={true}//在入场动画开始前就显示样式 同时在style.css要加上appear属性
>
<div>hello</div>
</CSSTransition>
style.css文件里
.fade-enter, .fade-appear {
opacity: 0;
}

.fade-enter-active , .fade-appear-active{
opacity: 1;
transition: opaciy 1s ease-in ;
}

.fade-enter-done {
opacity: 1;

}
补充:很多相关的API都可以展示效果(例如出场动画第一帧,中间帧等等)

对多个元素实现效果:
<TransitionGroup>
{this.state.list.map((item,index)=>{
return( <CSSTransition
timeout={1000}
classNames='fade'
unmountOnExit
onEntered={(el)=>{el.style.color='blue'}}
appear={true}
key={index}
>
<div>{item}</div>
</CSSTransition>)

})}
</TransitionGroup>

注意:如果CSSTransition实现不了的效果就可以去浏览TransitionAPI。(API位置在官网文档最下方)

redux(数据层框架)

用一张图很好的表示有无Redux之间的区别(数据的传递更加简化,便捷)

有Redux不管组件的层次结构有多深,走的流程都是一样的。组件改变->数据修改->其余组件取用

img

整体redux流程:react要去改变store里的数据,首先要一个派发action,action会通过dispatch方法传递给store,store再把之前的数据state和action转发给reducers,reducers是一个函数,当reducers接收到state和action之后,做一些处理之后,会返回一个新的state给到store,store用这个新的state替换掉之前store里的数据,store数据发生改变之后,react组件会感知到store发生改变(用的是store.subcribe()来监测store里状态的改变)这个时候就会从store里面重新取出数据更新组件的内容,页面就发生变化了(this.setState(store.getState()))。

对于todolist的案例要使点击某项实现删除某项的功能 首先先绑定一个删除的回调函数,派发一个action(某项的索引index会传进去)之后在reducers值删除(由于reduces可以接收state,但是决不能修改state)这里就要对之前的状态做一个深拷贝

1
2
3
4
5
6
7
if(action.type === 'delete_todo_item'){
const newstate = JSON,parse(JSON.stringfy(state));
newState.list.splice(action.index,1)//做删除
return newstate
}
**注意** 讲解简书这个项目的老师和尚硅谷的老师(好像不需要深拷贝,用的另外一个方法)对redux基础知识的讲解有点出处。
之后会做整合的博客

对ActionTypes的拆分:目的是为了拼写错误难以找出错误,有了拆分的文件就拼写错误控制台就会直接定位报错的位置

1
export const CHANGE_INPUT_VALUE='change_input_value'

利用actionCreators对action进行管理:目的提高代码的可维护性,加上前端有自动化测试的工具,如果你把action写在一个文 件里,自动化测试也会非常方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
管理前:handleInputChange(e){
const action = {
type:CHANGE_INPUT_VALUE,
value:e.target.value
};
store.dispatch(action);
}

管理后:在actionCreators文件内:
export const getInputChangeAction=(value)=>({
type:CHANGE_INPUT_VALUE,
valuee.target.value
})
handleInputChange(e){
const action = getInputChangeAction(e.target.value)
store.dispatch(action);
}
**高代码的可维护性
*自动化测试也会非常方便

补充

1
2
3
4
5
6
7
8
1. store是唯一的。
2. 只有store能改变自己的内容。
3. Reducer必须是纯函数:给定固定的输入,就一定会有固定的输出,而且不会有副作用。
4。rudux核心API
-creatStore 帮助创建一个store
-store.dispatch 帮助派发action,action会传递给store
-store.getState 帮助获取store里的数据内容
-store.subscribe 帮助订阅store里的改变,只要store改变,store.subscribe接收的这个函数里的回调函数就会执行

UI组件和容器组件

UI组件负责页面的渲染,容器组件负责页面的逻辑

无状态组件

当一个组件只有render函数的时候,就可以使用无状态组件去替换这个普通组件

无状态组件相对于普通组件的优势在于:性能比较高,因为无状态组件仅仅是一个函数,普通组件是js的一个类,类生成的对象还有生命周期函数render,执行的东西要远比无状态组件要多得多。

1
2
3
4
5
6
7
8
9
10
11
无状态组件:
const 组件名 = (props)=>{
return
}
普通组件
class 组件名 extends Component{
render(){
return
}
}
前提:一个组件只有一个render的时候就可以用无状态组件去替换普通组件

Redux中发送异步请求获取数据

通过axios发送请求,将后台返回的数据传给store,但是传之前要做几个流程。首先,创建一个actionCreartors的一个函数initListAction,然后在actionTypes(防止拼写错误的文件)定义一个常量

1
2
3
4
5
6
7
8
9
10
11
axios.get('./list.json').then((res)=>{
const data = res.data;
const action = initListAction(data)
store.dispatch(action)
})
然后在reducers文件里写
if(action.type === INIT_LIST_ACTION){
const newstate = JSON,parse(JSON.stringfy(state));
newState.list = action.data;//这里的data就是后台传过来的数据
return newstate
}

使用Redux-thunk中间件进行ajax请求发送

这个中间件可以使我们把异步请求或者复杂的逻辑放到action里处理

使用redux-thunk可使得创建actionCreartors定义的函数返回的结果可以是一个函数(之前返回的是一个对象),这样就可以发异步请求。

1
2
3
4
5
6
7
8
9
10
11
12
componentDidMount(){
const action = getTodoList();
store.dispatch(action)
}
export const getTodoList = () => {
return (dispatch) =>{
axios.get( '/list.json').then((res)=>{
const data = res.data;
const action = initListAction(data);
dispatch(action); ***最终执行的
})
解释:componentDidMount当组件挂载时执行,store派发函数的action实则是派发给store,但是它先会默认自动执行getTodoList()这个函数,随之就会去执行那个异步action,最终派发的也就是dispatch(action)

day4

redux的标准流程

view在redux之中会派发一个action,action通过store的dispatch方法,分发给store,store接收到action连同之前的state一起传给reducer,reducer返回一个新的数据给store,store拿到新的状态去更新旧的状态。

redux的中间件(redux-saga中间件的使用)

中间件的概念是指action和store之间的中间,指的是redux的中间键(dispatch的升级)因为只有redux有action和store的概念。

redux-sage远比redux-thunk复杂的多,里面内置了很多API

有一点区别的是如果上方store.dispatch(action)store传递的action是一个函数,store接收到action之后它不仅可以派发到reducer还可以派发到一个sagas.js的文件里,可以在sagas.js文件里做一些异步逻辑的操作。

React-Redux的使用

  1. 给组件包裹一个即可让组件使用store里的数据,是react-redux的核心组件。

  2. 通过redux的connect方法,将组件和store做一个连接 export default connect(mapStateToProps,null)(组件)

  3. 做连接就有一个规则,规则就在第一个参数mapStateToProps(翻译过来就是把store里的数据映射给组件,变成组件的props)

1
2
3
4
5
6
const mapStateToProps = (state) => { //state就是store里的数据
return {
inputvalue:state.inputvalue// 这一步映射到组件的props <input value={this.props.inputValue}/>
list:state.list
}
}
  1. connect的第二个参数是mapDispatchToProps(把store的diapatch方法挂载到props)
1
2
3
4
5
6
7
8
9
10
11
12
13
const mapDispatchToProps = (dispatch) => { //state就是store里的数据
return {
changeInputvalue(e){
const action={
type:'change_input_value' //组件里就是onclick={this.props.changeInputvalue}
value:e.target.value
}
dispatch(action); //这一派发就要去写reducer了(匹配类型返回一个新的newState->newState有inputvalue和list)
}
}
}
上述的代码还需像之前一样创建actiontypes和actionCreaters文件
connect生成返回的结果就是一个容器组件(可以理解成业务逻辑,数据,方法对UI组件进行包装)

day5

开始实战

1、引入reset.css文件

使用reset.css的好处:不同浏览器的内核里面对html,body这样的标签,默认样式得设置是不同的,有可能在某一个浏览器上margin间距值是10,而在另外一个浏览器上这个间距值可能就是8。为了能让我们代码在所有浏览器表现形式都是一致的,首先就得对这些浏览器默认对这些标签的实现进行统一,reset.css就是干这个活,使得这些标签在任何浏览器上的margin padding默认都是一样的。一般在开发项目的时候,首先就要先引入reset.css文件。


2、简书网站头部区块的编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
与之前不一样的是,对样式的处理需要创建一个style.js文件(样式写在这个文件里,实际上是一个个的组件,不会影响其他页面的组件样式)
也就是我们这个页面用这个组件时候。这些样式只作用这个组件上面,对其他任何地方都没有影响,有效避免写项目潜在的可能css样式冲突的问题
import styled from 'styled-components'
export const HeaderWrapper = styled.div`
height:56px;
background:red;
`
HeaderWrapper是带样式的div标签
用法在header组件里引入后使用 <HeaderWrapper>header</HeaderWrapper>

注意点:
1.在style.js写的样式都要加分号``;``不然样式显示不出来
2.a标签不能设置宽高,必须转换为块元素
3.头部导航条设置padding 不然会导致设置的图标重合

image-20211120103759703

就像这样 因此需要去设置头部导航条

1
2
3
4
5
6
7
8
9
10
  width:960px;
height:100%;
margin: 0 auto;
+padding-right:70px;
+box-sizing:border-box
+号为添加的代码,设置一个内聚变以便使其隔开,但是为了保证盒子宽度不变,还得将盒子设置成border-box

由于搜索框和图标是独立的,因此需要<SearchWrapper></SearchWrapper>标签将其包裹
由于该标签被Nav标签包裹且在Nav标签里的子标签都设置了浮动,宽度失效。如果SearchWrapper不设置浮动,宽度就会继承Nav的宽度
就与搜索框的宽度不一致,因此也得将SearchWrapper设置浮动。

day6

整体交换的流程

创建store文件夹->创建index.js ,reducer.js->编写相应的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
index.js:
import {createStore} from 'redux'
import reducer from './reducer'

const store = createStore(reducer)
export default store;

reducer.js:
const defaultState = {};

export default (state = defaultState,action)=>{
return state;
}

之后回到App.js引入

1
2
3
4
5
6
7
8
9
import store from './store/index'
import {Provider} from 'react-redux'
export default class App extends Component {
render () {
return (
<Provider store={store}>
<Header/>
</Provider>
}

回到header组件引入connect,,将组件和store做一个连接

1
2
3
4
5
6
7
8
9
10
11
12
import {connect} from 'react-redux'
const mapStateToProps=()=>{
return {

}
}
const mapDispatchToProps=()=>{
return{

}
}
export default connect(mapStateToProps,mapDispatchToProps)(Header)

由于初始化的数据需要放在reducer.js上 所以将组件里的数据进行转移 —>形成版本1

1
2
3
4
5
6
7
8
9
10
11
12
Header/index.js 将状态转移后删除
constructor(props){
super(props)
this.state={
focused:false
}
}

reduer.js
const defaultState = {
focused:false
}

由于header组件里有很多是用到state里的focused,因此得将从state拿改成从props拿, 此时就需要mapState…方法

将store里状态的focused映射到组件的Props里去,因此就可以将下方图中的state替换成props

image-20211122095058838

1
2
3
4
5
6
7
8
9
10
11
handleInputFocus=()=>{     
this.setState({
focused:true
})
}
handleInputBlur=()=>{
this.setState({
focused:false
})
}
这两个方法之前是为了去改变state里的数据 现在数据都不在state里存放了因此就可以连同方法删掉

之后我们要实现当聚焦的时候要改变focused的值 ->一整套流程

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
当你聚焦的时候,改变store里的数据,就要去调用store的dispatch方法,就要把方法定义在mapDispatch...里
Focus={this.handleInputFocus}
onBlur={this.handleInputBlur}
改成
onFocus={this.props.handleInputFocus}
onBlur={this.props.handleInputBlur}
有这个方法之后,首先要改变store里的数据首先要创建一个action
const mapDispatchToProps=(dispatch)=>{
return{
handleInputFocus(){
const action = {
type:'search_focus'
};
dispatch(action)
},
handleInputBlur(){
const action = {
type:'search_blur'
};
dispatch(action)
}}}

reducers.js
export default (state = defaultState,action)=>{
if(action.type==='search_focus'){
return{
focused:true
}
}
if(action.type==='search_blur'){
return{
focused:false
}
}
return state;

}

总结

我们把之前组件里的数据移除了,转移到redux公共存储的框架进行存储。Header组价就变成了一个无状态组件

1
2
3
4
5
无状态组件的写法 const Header = (props)=>{
1.render以下的内容(不包括render)
2.所有的this去掉
}
到这形成第2

day7

安装redux-devtools-extension工具使得网页可以显示redux组件操作状态的变化

1
2
3
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(reducer,composeEnhancers())

image-20211122212415727

由于reducers存放数据和数据的处理,随着功能的强大,数据量会越来越大,因此不能都放在reducers里存储。

于是在head文件夹下创建一个store的文件夹,创建一个header下的reducer,将store里的reuders移至header的reducers

全局的reducer作为一个仓库,专门来放置各个组件部分的reducers。 此时功能尚未实现,因为header下的index.js文件mapState…方法用的是全局的reducer,因此得修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const mapStateToProps=(state)=>{
return {
focused: state.header.focused
}
}

仓库reducers
import {combineReducers} from 'redux'
import {reducer as headreducer} from '../common/header/store' //间接引入reducers 目录结构会少两层

const reducer = combineReducers({

header:headreducer

})
export default reducer
间接引入reducers 目录结构会少两层 因为header下的reduces间接由header下的index.js文件管理了 。引入的时候就方便了

day8

由于现在在header组件下创建了一个头部的reducers,由于在头部组件里改变数据的时候派发action,然后reducer接收action然后去修改数据,之前课程说过action不要写成对象,类型也不要写成字符串。用actionCreate去创建action,同时类型定义成常量。

按这个做下去逻辑太绕了,不易理解。大改文件夹存放的位置

image-20211123111850307

在reducer.js文件里,不能对原始的state做修改,为了防止这一点得引入facebook团队研发出来的一个库:immutable.js

它会帮助我们生成一个immutable对象 (不可改变的)如果state是immutable对象就是不可以被改变,就不会出问题。

immutable这个库提供了一个fromJS方法,可以帮助我们把一个js对象转化为immutable对象,用这个方法把数据对象(js对象)

转化为immutable对象。此时header下的index文件的mapState方法里的数据已经转化为immutable类型的数据(state.header)

如果是immutable类型的数据,再进行state.header.focused这种语法是不合理的了。immutable.js里面调用state.header里的属性不能通过点focused的方式来调用,得通过点get的方法传入focused的属性来获取对应的属性。此时不会报错,但是点击的时候就会报错报错的原因在与当你派发一个action的时候,传入reducers返回的是一个普通对象,普通对象时没有get方法的 为了防止这种情况应该这样写(没修改前与修改前的代码)

总结:借助immutable这个库避免编写改变数据里state的情况

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
未修改前的reducers:
import {SEARCH_FOCUS,SEARCH_BLUR} from '../constant'
const defaultState = {
focused:false
}
const reducer = (state = defaultState,action)=>{
if(action.type===SEARCH_FOCUS){
return{
focused:true}
}
if(action.type===SEARCH_BLUR){
return{
focused:false}
}
return state;
}
export default reducer

修改前的header下的index.js
const mapStateToProps=(state)=>{
return {
focused:state.header.focused
}
}

修改后的reducers:
import {SEARCH_FOCUS,SEARCH_BLUR} from '../constant'
import {fromJS} from 'immuatble'
const defaultState = fromJS({
focused:false
})
const reducer = (state = defaultState,action)=>{
if(action.type===SEARCH_FOCUS){
// immutable对象的set方法,会结合之前immutable对象的值和设置的值,返回一个全新的对象,并没有改变原始的state
return state.set('focused',true) //这里的变更不是直接变更
}
if(action.type===SEARCH_BLUR){
return state.set('focused',false)
}
return state;
}
export default reducer

修改后的header下的index.js
const mapStateToProps=(state)=>{
return {
focused:state.header.get('focused')
}
}

即便是修改后的index.js 由于state是js对象,state.header是immutable对象 所以去调用focused属性的时候,数据获取的行为是不统一的。(不太靠谱)因此我们想让state也成为immutable对象,而不是js对象

state对应的数据是在redux下reducers里的index.js里创建的,因此我们要依赖第三方的模块 redu-immutable

1
2
3
4
import {combineReducers} from 'redux-immutable'//只要combineReducers是由这个库引入的reducer就是immuable对象了  
之后回到header下改如下代码即可
focused:state.get('header').get('focused')
可以简写为 focused:state.getIn(['header','focused'])

下一阶段(搜索区域的布局) F11退出全屏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  <SearchList>
<SearchInfoItem>教育</SearchInfoItem>
<SearchInfoItem>简书</SearchInfoItem>
<SearchInfoItem>生活</SearchInfoItem>
<SearchInfoItem>投稿</SearchInfoItem>
<SearchInfoItem>历史</SearchInfoItem>
</SearchList>
由于子元素设置浮动了 父元素的高度无法被撑起所以给父元素设置一个BFC
export const SearchList = styled.div`
overflow:hidden;//此时只是开启BFC使其高度和宽度不丢失
`;
之后就是该列表聚焦的时候显示,不聚焦的时候不显示,设置一个函数,给函数接收一个变量show。
如果show为真就返回整个列表,假就返回null
最后再调用一下这个方法 {getListArea(props.focused)} 传入focused的值即可

换一批的功能

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
此时的header是一个无状态组件,之后还要添加很多功能,就会出现函数嵌套函数,就会使Header变得越来越庞大,不好维护
因此还是使用Component组件 将原来props前加一个this 且将显示列表的方法定义在组件里接口

由于列表里的内容实际上是通过ajax数据获取的,因此header里不进要有focused的数据还得有列表的内容
因此在header的reducer里加一个list:[];
当导航的input框做了聚焦之后,就要获取ajax数据,也就是onFocus的时候,onFocus执行的是handleInputFocus这个方法
找到这个方法在方法里获取ajax数据,之前的课程有说过,获取ajax数据一般来说不会直接写在组件里,会把异步的操作放到action,或者放到redux-saga里面进行处理,这里统一使用redux-thunk,把异步操作都放至action里处理
redux-thunk其实就是action和store之间的中间件 其实就是对dispatch方法的升级
因此得写在创建store之前被使用
import {createStore,compose,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducers'

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,composeEnhancers(
applyMiddleware(thunk)
))
export default store;
有了redux-thunk之后,就可以去action里面做异步操作了
dispatch(getList());去派发这样一个action
以前的action返回的是一个对象,现在可以返回函数
函数里写个输出123 只要聚焦控制台就会显示123
发请求前要先安装包axios
axios.get('/api/headerList.json').then((res)=>{
}).catch(()=>{
console.log('ERROR');
})
}
在public文件夹下创建一个api文件夹之后在创建headerList.json的文件输入一下数据 之后在网页访问这个链接页面就可以显示数据
原理:首先先到功能目录下看看有没有对应的路由,找不到就会去public目录下找api的headerList.json,找到后就会把文件的内容输出出来
通过这个特性,我们可以创建一些假数据,保存。
模拟的数据要和后端对的数据要保持一直,就是定一下格式
public下的api下的header.json添加模拟的数据 此时:
axios.get('/api/headerList.json').then((res)=>{
console.log(res // 当你点击搜索的时候,控制台已经有输出了 见下图
}).catch(()=>{
console.log('ERROR');
})
}

image-20211123162311226

之前header下的reducer里定义了一个list:[],现在发送了一个ajax请求,获取到了结果,现在就是希望能用获取的结果替换那个空数组

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
action.js文件里
export const getList = () =>{
return (dispatch)=>{ //返回函数,就要就是去派发一个异步的请求
axios.get('/api/headerList.json').then((res)=>{
const data = res.data;
const action = changeList(data.data);
dispatch(action)
}).catch(()=>{
console.log('ERROR');

})}}

reducer.js文件里
if(action.type===CHANGELIST){
return state.set('list',action.data)
//console.log(action);//返回的是{type: "change_list", data: Array(50)}
return state
这样写实际上还是有问题的:创建store,默认list是一个空数组,fromJS方法会将js对象转换为immuable对象,list是个数组,也会变成immuable的数组,但是调用set去改变list的时候,action.data实际上是一个普通的数组, list数组会由immuable数组变成一个普通的数组,这样的话数据类型就会变了,解决这个问题
export const changeList = (data) =>({
type:CHANGELIST,
data:fromJS(data)
})
这样转换一下 const action = changeList(data.data);//输出data后的结果
这里派发的时候数据就会转换成immuable的数据类型。这样reducer里接收到action的数据类型也就是immuable类型了。
做到这里就可以使用redux的插件去查看显示header里面就list的数组了

image-20211123170830305

之后就是将list里的数组逐一放至getListArea的方法里展示

总结

实际上讲的就是项目中用react-thunk发送ajax数据。获取到数据之后,存到store里面,然后在页面上进行显示,有几个关键点

第一个我们把异步获取的逻辑都拆分到actions文件夹下,如果你想拆分到这里来,就要求action返回的对象不能是一个js对象,而是一个函数,如果要返回的是一个函数就必要要用redux-thunk这个中间件,这是第一个重点

第二个当我们获取到数据,想要改变store里的数据的时候,我们要注意redux里的list因为外层包裹了一个fromJS,它会在创建的时候,会把内部的list定义的数组由一个普通的数组转换为immuable类型的数组,但是想直接改变这个状态的数组,就会把之前immuable类型变成普通数组的类型,因为我们就要再action之前传递action之前就要把数据转换为immuable类型的数据,这样数据统一就不会有问题。

页面循环展示数据,就要调用map方法通过对每一项进行循环展示,对对应的内容都展示在页面上 —功能实现

避免无意义的ajax请求发送(上方衔接最后补充的内容)

每点击一次焦点的时候就会发送一次ajax请求。实际上列表中的数据获取一次就可以了

解决问题:给handleInputFocus(list)传入list 然后输出list可以看出第一次请求的时候list.size是0,之后就是50,

所以控制当size为0的时候才发送请求,之后不等于0的时候就不发生了,这样就避免了每次获取焦点的时候都发一个ajax请求,从而做到性能上的调优。

1
2
3
4
5
6
7
8
 handleInputFocus(list){
if(list.size===0){
dispatch(getList());
} 或者 (list.size>0)&&dispatch(getList());意思都一样
dispatch(searchFocus());
},

样式加一个cursor:pointer 可使鼠标放上去有个小手的标志。

路由

1
2
3
4
5
import {BrowserRouter,Route} from 'react-router-dom'
BrowserRouter代表路由 Route代表路由规则
首先先引入路由组件
下载包:npm add react-router-dom
**注意** 前两周这个包刚更新过如果按之前的编码规则会使得路由组件无法显示(太坑了官网也不发布,害我整了半天)

image-20211124201227148

把首页组件拆分成一个个小的组件

外层元素:margin-left: -18px;

内层元素:margin-left: 18px;、

这样就可以充分利用左侧栏的宽度,使其保持一直

1
2
3
4
5
6
7
8
9
10
   <TopicItem>
<img
className='topic-pic'
src='//upload.jianshu.io/collections/images/261938/man-hands-reading-boy-large.jpg? imageMogr2/auto-orient/strip' alt='niao'
/>
社会热点
</TopicItem>
这是一个标题数据,之后我们拿来用的时候需要从redux里取 数据都存在store里,而Topic是home的自组件 而home是APP的子组件
因此Topic是APP的自组件 想使用store里的数据需要connect做连接
定义一个获取状态的方法 mapState,从reducer获取到list给Topic组件遍历即可
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
import React, { Component } from 'react'
import {TopicWrapper,TopicItem} from '../style'
import {connect} from 'react-redux'
class Topic extends Component {
render() {
return (
<TopicWrapper>
{this.props.list.map((item)=>{
return (
<TopicItem key={item.get('id')}>
<img
className='topic-pic'
src={item.get('imgUrl') } //reducer.js定义好的
alt="nihao"/>
{item.get('title')}
</TopicItem>
)
})}

</TopicWrapper>
)
}
}
const mapState =(state) =>({
list: state.get('home').get('topicList')
})
export default connect(mapState,null)(Topic)//因为不需要修改状态 所以暂且先传入一个null

day9

页面的组件,首先闲在reducer的初始状态定义好相应的信息

1
2
3
4
5
6
7
8
9
10
11
articleList:[{
id: 1,
title:'大数据分析可视化平台',
desc:'支持直连MySQL、SQL Server、PostgreSQL、Oracle、SAP HANA、Hive等数据源,也可上传本地Excel/Csv文件或通过API连接数据。可完成多源交互分析',
imgUrl:'https://lupic.cdn.bcebos.com/20200412/3048304592_14_800_600.jpg'
},{
id: 2,
title:'大数据分析可视化平台',
desc:'支持直连MySQL、SQL Server、PostgreSQL、Oracle、SAP HANA、Hive等数据源,也可上传本地Excel/Csv文件或通过API连接数据。可完成多源交互分析',
imgUrl:'https://lupic.cdn.bcebos.com/20200412/3048304592_14_800_600.jpg'
}],

之后在组件里通过调用connect 里调用因为这个状态是immuable数据类型 因此需要用get方法来引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react'
import {RecommendWrapper,RecommendItem} from '../style'
import {connect} from 'react-redux'
// import imgone from '../1.jpg'
class Recommend extends Component {
render() {
return (
<RecommendWrapper>
{
this.props.list.map((item)=>{
return <RecommendItem imgUrl={item.get('imgUrl')} key={item.get('id')}/>
})
}
</RecommendWrapper>
)
}
}
const mapState=(state)=>({
list: state.getIn(['home','recommendList'])
})
export default connect(mapState)(Recommend)

首先所有的数据内容都存在前端的代码里,都是写死的,实际做项目的时候,通过接口来获取的,不能写死。

在public下的api定义一个接口home.json。之后在home组件下借助componentDidMount这个声明周期函数来发ajax请求去获取数据,获取了数据,(当组件挂载完毕)。就要将获取的数据取修改store里初试的数据

所以要创建一个action,派发给store,需要store和组件建立连接

修改store里的数据也就是状态 就得使用connect的第二个参数,通过这个函数就可以定义一个方法将action派发给store

因为UI组件不能和store直接通信,只能通过容器组件。派发给store之后store就会派发给reducer。

1
2
3
4
5
case 'change_home_data':
console.log(action);//返回的就是ajax请求获取出来的内容
state.set('topicList',action.topicList)
//action.topicList是JS对象而上面的topicList是immuable对象不能替换因此要在前面加一个fromJS(action.topicList)
由于是多个对象可以用state的一个方法做一个合并 这样就实现了通过ajax异步的形式获取到了首页的内容

上述的操作都是不合适的因为UI组件不应该有太多的业务逻辑,发ajax请求不应该在UI组件里发,放在容器组件里

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
修改前
componentDidMount(){
axios.get('./api/home.json').then((res)=>{
const result = res.data.data;
console.log(res);//如果成功会打印 因为这个路径下有这个文件 因此是可以成功的
const action={
type:'change_home_data',
topicList:result.topicList,
articleList:result.articleList,
recommendList:result.recommendList

}
// console.log(res.data.data);
this.props.changeHomeData(action)

})
}
}
const mapDispatch = (dispatch)=>({
changeHomeData(action){
dispatch(action);
}
})
export default connect(null,mapDispatch)(Home)
第一次修改后:
componentDidMount(){

// console.log(res.data.data);
this.props.changeHomeData()


}}
const mapDispatch = (dispatch)=>({
changeHomeData(){
axios.get('./api/home.json').then((res)=>{
const result = res.data.data;
const action={
type:'change_home_data',
topicList:result.topicList,
articleList:result.articleList,
recommendList:result.recommendList
}
dispatch(action);
})
}
})

export default connect(null,mapDispatch)(Home)

再次升级 ajax请求不放在容器组件里 放在action里 通过调用那个函数

1
2
3
4
5
6
7
8
componentDidMount(){      
// console.log(res.data.data);
this.props.changeHomeData()
}}
const mapDispatch = (dispatch)=>({
changeHomeData(){
dispatch(getHomeInfo())//因为派发的action是一个函数 且返回的是一个函数。如果这个action是个函数,就会顺序执行这个函数,执行这个函数action就会去派发 页面就重新执行了
}});

而后action里代码可以做升级

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
action下home.js

import axios from 'axios'
export const getHomeInfo = () =>{
return (dispatch)=>{ //返回函数,就要就是去派发一个异步的请求
axios.get('./api/home.json').then((res)=>{
const result = res.data.data;
const action={
type:'change_home_data',
topicList:result.topicList,
articleList:result.articleList,
recommendList:result.recommendList
}
dispatch(action);
})

}
}
优化后
import axios from 'axios'
const changeHomeData=(result)=>({
type:'change_home_data',
topicList:result.topicList,
articleList:result.articleList,
recommendList:result.recommendList
})

export const getHomeInfo = () =>{
return (dispatch)=>{ //返回函数,就要就是去派发一个异步的请求
axios.get('./api/home.json').then((res)=>{
const result = res.data.data;
const action = changeHomeData(result)
dispatch(action); 或者写成 dispatch(changeHomeData(result))
})

}
}
之后让type成常量



<LoadMore onClick={this.props.getMore}>更多文字</LoadMore>*** 不用写()

对于页数的编码,首先先在reducers定义一个初始状态,定义完之后就可以回到List组件里定义mapState方法中的page:state…

定义完之后将UI组件里的LoadMore里的onClick的回调函数设置为箭头函数 传page进去, 下面调用方法的函数也传page

之后发异步action

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
 <LoadMore onClick={()=>this.props.getMore(this.props.page)}>更多文字</LoadMore>
</div>

)
}
}
const mapState =(state)=>({
list:state.get('home').get('articleList'),
page:state.get('home').get('articlePage')
})

const mapDispatch=(dispatch)=>({
getMore(page){
dispatch(getMoreList(page))
}})

异步action
export const getMoreList=(page)=>{
return (dispatch)=>{

axios.get('./api/homeList.json?page=' + page).then((res)=>{
const result = res.data.data;
const action = AddHomeData(result,page+1) //这里每次点击更多的时候页面加1
dispatch(action);
});

}}
每一个异步action都有一个同步action
const AddHomeData = (list,nextpage)=>({
type:ADD_HOME_LIST,
list:fromJS(list),
nextpage

})
之后就回到reducer 去更新原始页面的状态

回到顶部

回到顶部
1
2
3
4
5
handleScrollTop() {
window.scrollTo(0, 0);//回到顶部
}

之后实现页面再顶部的时候不显示回到顶部这个图标 在底部才实现

然后在home.js下的传入connect的第一个参数

1
2
3
4
5
const mapState =(state)=>({
showScroll:state.getIn(['home','showScroll']) //由于reducer的初始值有定义
})
控制这个值的truefalse 来实现是否显示回到顶部的功能
{this.props.showScroll ? <BackTop onClick={this.handleScrollTop}>回到顶部</BackTop>:null}

day10

在组件刚挂载的时候绑定一个监听事件来监测滚轮是是否滑动

1
2
3
4
5
6
7
8
9
10
11
12
13
componentDidMount(){
this.props.changeHomeData()
this.bindEvents();
}
bindEvents(){
window.addEventListener('scroll',this.props.changeScrollTopShow)
}
}

调用的方法:
changeScrollTopShow(e){
console.log(e); //打印的就是下图一个个事件
}

image-20211126095729529

如果不打印事件 换做

1
2
3
4
5
document.documentElement.scrollTop(获取滚动条位置)要获取当前页面的滚动条纵坐标位置
changeScrollTopShow(){
console.log(document.documentElement.scrollTop);
}
我也不明白为什么我打印出的是小数

image-20211126100207963

之后将这个值设置大于400的时候显示回到顶部,否则隐藏,因此我们要改reducer里的数据,通过发action进行改

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
changeScrollTopShow(){
if(document.documentElement.scrollTop>400){
dispatch(toggleTopShow(true))
}else{
dispatch(toggleTopShow(false))
}

action.js文件
export const toggleTopShow=(show)=>({
type:TOGGLE_SCROLL_TOP,
show
})

reducer.js文件
case TOGGLE_SCROLL_TOP:
return state.set('showScroll',action.show)
功能实现 但是需要注意的是创建组件组件挂载的时候,往window上绑定的scroll事件的监听的时候,一定要把scroll事件监听在window上移除
这样组件的事件就不会影响其他的组件
reducer.js代码优化:

case CHANGE_HOME_DATA:
return state.merge({
topicList:fromJS(action.topicList),
articleList:fromJS(action.articleList),
recommendList:fromJS(action.recommendList)
})

定义一个方法
const changeHomeData=(state,action)=>{
return state.merge({
topicList:fromJS(action.topicList),
articleList:fromJS(action.articleList),
recommendList:fromJS(action.recommendList)
})
}
然后调用:
case CHANGE_HOME_DATA:
return changeHomeData(state,action) ***修改部分
case ADD_HOME_LIST:
return addhomedata(state,action)

首页性能调优

因为首页的index.js都调用了connect的方法,和store做了连接,这就会产生一个问题,只要store发生了改变,那么每一个组件都会被重新渲染,也就是每个函数的render函数会被重新执行,可能有些数据发生改变了,但是那个数据和这个组件一点关系都没有,但是这个组件依然会被重新渲染,导致性能不好。提高组价性能 shouldComponentUpdata,可以在这里做性能优化的代码,判断只有与这个组件相关的数据发生改变才让render函数重新执行(重新渲染),否则return false不让render函数重新执行。

通过这种方式来避免虚拟DOM的比对,提高性能,react也考虑到了这点,如果你在每个特组件都去自己写should….太麻烦了,react内置了新的组件类型,引入PureComponent,区别就是PureComponent内在自己底层实现了should…,这样就不用我们手写收should……做性能优化。之后就是每个UI组件都替换为PureComponent

之所以项目用PureComponent,是因为项目的数据管理用了immuable.js的框架,它可以保证我们的数据是immuable的,这样PureComponent和immuablejs数据格式的管理相结合,使用PureComponent一点问题都没有,但是如果你在你的项目里面,没有使用immuable.js管理你的数据,那么使用PureComponen有的时候会遇到坑。(偏底层的坑)

实现点击title跳转至详情页面

当我们在react实现页面跳转的时候,我们要用到react-router-dom第三方模块,它的这种跳转是单页应用跳转,所谓单页应用跳转就是做一个单页应用的时候,单页应用指的是,不管怎么做页面的跳转,整个网站只会加载一个html文件,只会加载一次html文件,那么来看使用a标签来做页面的跳转,符不符合单页应用的定义。network里Doc可以看加载的html文件,使用a标签就是会重新加载html文件,会由原来的localhost变为detail的html,增加了一次html请求,比较耗性能,因为借助react-router-dom的时候,完全没必要重新加载页面,做页面的跳转,正确的做法是用Link代替a标签。

注意:link标签要在Router里面否则会报错,在做头部点击简书logo的时候,因为link在head里,而APP组件的Header是在BrowseRouter外部因此违背了这个规则 导致报错

image-20211126111958584

或者是报这个错

img

day11

详情页面的布局

从reducer里导入初试状态的content时候会导致标签转义,所以页面显示的内容不是该有的内容。

1
2
3
dangerouslySetInnerHTML={{__html: this.props.content} 这样写不会被转义

之后这些数据想在后端被获取而不是通过store里的原始数据取获取。就需要发ajax请求,步骤都是和之前一样

实现点击不同的title显示不同的详情页(匹配id)

在List.js文件夹下 路径下加上一个id 打开浏览器不显示了

原因是在APP.js组件里路由组件匹配的路径是/detail 而不是/detail/1 因此得在路由组件匹配的时候出换一个‘/detail/:id’

获取参数的第一种方法:动态获取参数

首先打开详情页面的代码 在render和return之间先 打印输出log(this.props)//在控制台里可以看出props里match里的params里有id 然后就是在组件挂载时传入id

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
action.js
export const getDetail=(id)=>{
return (dispatch)=>{
axios.get('/api/detail.json?id='+ id).then((res)=>{//把id传给后端
const result=res.data.data;
// console.log(res.data.data);
dispatch(changeDetail(result.title,result.content))

})
}
}
detail下的index.js
class Detail extends Component {
render() {
return (
<DetailWrapper>
<Header>{this.props.title}</Header>
<Content dangerouslySetInnerHTML={{__html: this.props.content}}/>
</DetailWrapper>
)
}
componentDidMount(){
const {id} = this.props.param
this.props.getDetail(id);//通过后端去返回
}
}
const mapState=(state)=>({
title: state.get('detail').get('title'),
content: state.get('detail').get('content')
})
const mapDispatch=(dispatch)=>({
getDetail(id){
dispatch(getDetail(id));
}
})

然后传递路由参数出现了问题 是由于使用了react-router-dom v6的版本 下面有解释
export default connect(mapState, mapDispatch)((props)=>(
<Detail
{...props}
param={useParams()}
/>
))

巨坑由于使用了react-router-dom v6的版本 使得组件props参数里没有location history等属性。如果使用v5的话就完全避免了这个问题(使用v5下载包的时候附上版本号yarn add react-router-dom@5.3.0)

image-20211126192533225

image-20211126192612881

登陆组件

创建reducers 定义初始状态,login:false 之后在去头部的map方法里那login组件里的login

1
2
3
4
5
login:state.getIn(['login','login'])
{
login ? <NavItem className='right'>退出</NavItem>:<NavItem className='right'>登陆</NavItem>
}
就可以在页面的头部看到是否处于登陆状态还是退出状态

接下来如果用户没有登陆不是显示这个字样 而是跳转到登陆页面

1
2
3
4
5
6
7
8
9
10
11
12
13
 <Link to='/login'><NavItem className='right'>登陆</NavItem></Link>
回到登陆组件
<Button onClick={() => this.props.login(this.account, this.password)}>登录</Button>
因为登陆需要调用ajax接口,把账号密码传递过去的。这时候需要把它写进mapDispatch里 因为要发异步的请求,异步action
只有它才能派发异步action

<Input placeholder="账号" ref={(input) => {this.account = input}} />
<Input placeholder="密码" type="password" ref={(input) => {this.password = input}} />
这两句话的意思就是:通过ref把这两个DOM分别存在this.account和this.password里
这样在登陆的时候就可以调用箭头函数把account和password传进去
之后就是发ajax请求 action reducers 常见的套路
实现的结果就是输入账号密码之后头部的登陆变成了退出 现在就是要实现点击登陆跳转到首页
重定向到首页 又是版本的问题,之前的重定向Redirect也不能用了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const {loginState} = this.props;//false
if(!loginState){
return (
<LoginWrapper>
<LoginBox>
<Input placeholder="账号" ref={(input) => {this.account = input}} />
<Input placeholder="密码" type="password" ref={(input) => {this.password = input}} />
<Button onClick={() => this.props.login(this.account, this.password)}>登录</Button>
</LoginBox>
</LoginWrapper>
)
}else{
return < Navigate to='/' />
}
之后做退出的功能回到header组件的index.js下
1
2
3
4
5
6
7
8
 {
login ?
<NavItem className='right'>退出</NavItem>:
<Link to='/login'><NavItem className='right'>登陆</NavItem></Link>
}
在这里添加一个点击事件onClick={logout}通过调用该回调函数去修改login的值,所以要去派发action
因为这是在header.js下的 要引用login的action
修改数据得到的结果也就是头部的显示结果 就是点击退出的时候上面的退出会改成登陆

创建写文章(只有在登陆状态下才能写)

对于一个组件的实现流程:首先建立writer文件夹下的index.js 把login复制过来,沿用它的一些逻辑

如果登陆了就写文章,如果没有登陆就跳转到登陆见面 然后把这个组件放到APP.js里面 。

同时在头部组件里的一个写文章外部加一个Link 跳转到Writer组件里之后就不做了

代码优化

  1. 在每个方法后面加分号;
  2. 在action里发异步请求的时候都默认发成功的请求,在企业级项目都有错误的情况.catch()异常逻辑进行补充

异步组件

首先点开network组件 JS刷新页面, 跳转页面,登陆页面,都是加载bundle.js,存在一个问题:所有页面对应的组件代码,都是bundle.js,现在访问的是首页,实际上登陆和详情页面的代码一起都加载了,这样首页加载的速度就有点慢。解决问题:当你访问首页的时候,你只加载首页的代码,访问详情页的时候再去加载详情页的代码 使用异步组件来实现,底层比较复杂,但是使用封装起来的第三方模块会非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
npm add react-loadable
异步组件的时候会有一个问题如果是v5版本的话
detail下的index.js是有用到params参数的,以前可以直接获取是因为
因为APP.js里以前引入的Detail组件,路由对应的组件就是这个组件import Detail from './pages/detail';
<Route path='/detail/:id' exact component={Detail}></Route>
如果是这样detail可以直接拿到路由信息
但是现在不一样了引入的是import Detail from './pages/detail/loadable.js';
所以loadable.js可以获得所有的路由信息,但是它下面的组件获取不了路由内容,因为它不是Router直接对应的组件
箭头所示的就是下面的组件
解决方法就是在index.js组件里加一个withRouter **这是v5的 v6不需要以上操作
调用该方法让Detail组件有能力获取到Router里所以的参数和内容
export default connect(mapState, mapDispatch)(withRouter(Detail));

image-20211126220845485

注意:使用jsx语法要引入React

day12

补充(之前漏看的视频)

首先在header组件的初始状态定义一个page和totalpage。page的作用是通过页码对内容进行控制,totalpage指的是整体的数据一共有多少页。 通过点击输入框就可以将获取列表,在列表里的action获取到数据后在同步action里面定义总共的页码。

之后在把页面传给reducers,打开reudx点击搜索就可以看到如下获取的列表和总共的页码

1
之后在map方法里获取页码  ``  page:state.getIn(['header','page']),``

image-20211127192232109

之后在map方法里获取页码 page:state.getIn(['header','page']),

让它一页显示10个

1
2
3
4
5
6
7
8
9

const {focused,list,page}=this.props
const pageList=[];
const Listpage = list.toJS(); //因为从props里获取的list是immuable数据类型 该数据类型不能使用Listpage[i]方法,要先转换
for(let i=(page-1)*10;i<page*10;i++){
pageList.push(
<SearchInfoItem key={Listpage[i]}>{Listpage[i]}</SearchInfoItem>
)
}

之后点击搜索显示的就是10个内容 但是才存在一个问题,点击换一换的时候input框会失焦,一失焦下面的区块就隐藏掉了,什么时候隐藏,其实并不是靠这个input的focus来控制的。实际点击区块的白色部分的时候,已经失焦但是区块不会隐藏,把鼠标移出去的时候才会隐藏。因此下面区块的展示和隐藏并不是完全由focus的状态来决定的还由一个mouseleft和mousemove决定的。

因此再加一个属性 mouseIn:false,,因为一开始鼠标就是没进入这个区块,鼠标移入进去,变成true。

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
<SearchInfo onMouseEnter={this.props.handleMouseEnter}//定义一个方法 派发action
改变mouseIn:false,这个的状态
同理再加一个 onMouseLeave={this.props.handleMouseLeave}这个方法
一直我们是通过focus来控制显示还是隐藏,其实现在focus不管用,还得借助mouseIn这个变量一起来控制,因此要从mapstate拿到mouseIn
if(focused||mouseIn)如果mouseIn为true也显示
下面就是点击换一换 下面的内容也要跟着变
定义方法onClick={this.props.handleChangePage}
handleChangePage(){
dispatch(changePage())//派发action
},
之后就是修改page的状态由于状态修改page不能直接加1
因此还要获取totalPage的状态
handleChangePage(page,totalPage){
if(page<totalPage){
dispatch(changePage(page+1))
}else{
dispatch(changePage(1))
}
把这个函数设为箭头函数之后把page和totalpage传进去之后进行判断 之后去修改状态即可
但是还是会报错原因就是输出for循环的时候输出的是undefined。原因是因为当你刚进入页面的时候,header就会被渲染,初始顺序就是list是个空,page是个1, 然后for循环一定会执行,所以会循环19 但是list是一个空数组,那么空数组list[0]都是undefined
那么key就是undefined。 因此外层要包裹一个if判断
if(pageList.length){
for(let i=(page-1)*10;i<page*10;i++){
pageList.push(
<SearchInfoItem key={Listpage[i]}>{Listpage[i]}</SearchInfoItem>
)
}
}

之后点换一换的时候让i标签的动画动一下

1
2
3
4
5
6
7
<SearchInfoSwitch
onClick={()=>handleChangePage(page,totalPage,this.spinIcon)}
>
//将this.spinIcon传进onClick方法
<i ref={(icon)=>{this.spinIcon = icon}} className="iconfont spin">&#xe675;</i>
换一批
</SearchInfoSwitch>

之前换一批图标功能未实现,且iconfont图标在下侧,原因是因为之前设置的iconfont样式覆盖了它

只要把之前的iconfont样式改成zoom即可

当你点击换一批的时候,我们希望i标签的值发生变化, 有一个ref的内容可以回去i标签真实的DOM节点

之后把相应的值传进,之后在方法下方输出spin 控制台就会输出

image-20211128094949731

就获取到spin对应的DOM了

  
1
2
3
4
5
6
7
8
9
10
11
spin.style.transform='rotate(360deg)';//当你点击的时候就会旋转360 但是只会执行一次因为是转360
可以获取上一次旋转的角度之后每一次加360度 下一次就是720
使用单纯CSS transition动画效果来实现
let originAngle = spin.style.transform.replace(/[^0-9]/ig,'')//不是0-9的数字都替换为空
if(originAngle){
originAngle=parseInt(originAngle,10)
} else{
originAngle=0;
}
spin.style.transform='rotate('+(originAngle+ 360)+'deg)'//0-360 360-720都会有过渡效果
react是面向数据编程,最难的是在redux里如何对数据进设计

1、react是面向数据编程,最难的是在redux里的数如何的被设计


2、改变它里面的数据,遵循redux里单向数据流的流程,首先先派发action给store,store再给reducers,reducers返回一个新的数组再给store,store去变更自己的内容,当数据发生变更,页面就自动发生变化了。

day13

简书项目上线

首先在后端的htdocs文件夹下创建一个api文件夹,后端的人在api文件夹下的json所各种复杂的运算处理,然后前端就可以将简书项目public文件夹下的api文件夹删除了,因为开发要做接口的模拟,然后在控制台运行npm run build,就会生成一个build文件夹,在build目录下有很多项目需要上线的文件,把build下的所有文件复制粘贴至htdocs文件夹下(和api一个同级)这样就完成了项目的上线。

整体的流程就是将前端的代码拷贝到后端的项目,访问的也就是后端的项目(访问路径就是之前的一个软件XAMPP)。

image-20211128153306716

image-20211128153010553

点start就可以在本地启动一个服务器(启动一个Apache服务器),会启动在localhost:80端口上(可以简写localhost)

完美撒花

  1. react的基础语法
  2. redux数据层框架
  3. react-redux如何方便我们在react之中使用redux
  4. react-router(本课程用的是4.0的版本 而我采用的是6.0天呐)
  5. immuable.js 相关的第三方模块