0%

事件总线(EventBus)

事件总线是一种观察者模式,包括三个角色

  • 发布者:发布事件
  • 订阅者:订阅事件,并进行响应
  • 时间总线:无论发布者还是订阅者都通过事件总线作为中台

目的:开发过程中不同组件之间通信 组件通信

事件总线的实现

  • 事件的监听方法on
  • 事件的发射方法emit
  • 事件的取消监听off

由于一个EventBus对象会在多个地方使用,所以将其中的属性和函数定义为静态的

也可采用实例化的方法,那样需要把这个实例放在全局变量中,如在React在根节点定义Context

EventBus的实现:

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
class EventBus{
static eventMap = {}

static on(eventName, eventFn){
(this.eventMap[eventName] || (this.eventMap[eventName] = []))&&this.eventMap[eventName].push(eventFn)
}
static emit(eventName, ...args){
if(!this.eventMap[eventName]){
console.log(eventName,"没有监听")
return
}
for(let fun of this.eventMap[eventName]){
fun.apply(this,args)
}
}
static off(eventName, eventFn){
let eventFns = this.eventMap[eventName]
if(!eventFns){
console.log(eventName,"没有监听")
return
}
for(let i=0;i<eventFns.length;i++){
if(eventFn===eventFns[i]){
eventFns.splice(i,1)
console.log(this.eventMap)
break
}
}
}
}

EventBus的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let function1 = function(...args){
console.log("function1", this.eventMap, args)
}
let function2 = function(...args){
console.log("function2", this.eventMap, args)
}

EventBus.on("click",function1)
EventBus.on("click",function2)

EventBus.on("click2",function1)

EventBus.emit("click", "oww", "18")
EventBus.emit("click2", "oww1", "22")

EventBus.off("click",function2)

image-20230325134633475

堆:

  • 堆是一种完全二叉树,复习一下完全二叉树的定义,完全二叉树的形式是指除了最后一层之外,其他所有层的结点都是满的,而最后一层的所有结点都靠左边。
  • 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

image-20230324192427183

完全二叉树:对于任意一个父节点的序号n来说(这里n从0算),它的子节点的序号一定是2n+1,2n+2,因此我们可以直接用数组来表示一个堆。

堆的创建:

1
2
3
4
5
6
class Heap{
constructor(){
this.array = []
this.length = 0
}
}

image-20230324192636065

大顶堆/最大堆:

对于任意一个父节点来说,其子节点都小于这个父节点

最大堆的插入:

  • 当一个元素要插入时,先放到堆尾,根据堆的特性,对位置进行调整。
  • 父节点要大于直接点,找到插入节点的父节点,比父节点值大则交换位置,否则插入成功
  • 交换后,继续检测该节点与现在父节点的大小,如果大则交换位置,一直上浮到根节点

image-20230324193433568

JavaScript实现:length可以用this.array.length替代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
insert(x){
this.array.push(x)
if(this.array.length===0){
return
}
let x_index = this.array.length-1
let parent_index = x_index
while(parent_index!==0){
let parent_index = x_index%2===0?(x_index-2):(x_index-1)
parent_index = parent_index/2
if(this.array[parent_index]<x){
this.array[x_index] = this.array[parent_index]
this.array[parent_index] = x
}else{
break
}
x_index = parent_index
}
return
}
// 循环最大次数k即为树的深度:2^0+2^1+...+2^k = n 即k=logn 时间复杂度为O(logN)

最大堆的删除

  • 堆的删除一般是值删除堆顶元素,先删除堆顶元素
  • 要保证堆一直为完全二叉树,不能直接拿子节点的较大值,而是将末尾元素拿到堆顶,再下沉
  • 下沉即先比较该节点与左子节点,如果子节点更大,则交换,如果子节点更小,则与右节点比较
  • 直至到最深层,或者比左右子节点都大

JavaScript实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pop(){
this.array[0] = this.array[this.array.length-1]
this.array.pop()
let x_index = 0
while(true){
let left_index = 2 * x_index + 1
let right_index = 2 * x_index + 2
left_index = left_index>=this.array.length?x_index:left_index
right_index = right_index>=this.array.length?x_index:right_index
let large_child_index = this.array[x_index]<this.array[left_index]?left_index:x_index
large_child_index = large_child_index!==x_index?large_child_index:(this.array[x_index]<this.array[right_index]?right_index:x_index)
if(large_child_index===x_index) break
let temp = this.array[x_index]
this.array[x_index] = this.array[large_child_index]
this.array[large_child_index] = temp
x_index = large_child_index
}

}
// 最大循环次数k即为树的深度 k = logn 时间复杂度为O(logN)

小顶堆/最小堆

插入与删除与最大堆类似

插入JavaScript实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
insert(x){
debugger
this.array.push(x)
if(this.array.length===0){
return
}
let x_index = this.array.length-1
let parent_index = x_index
while(parent_index!==0){
let parent_index = x_index%2===0?(x_index-2):(x_index-1)
parent_index = parent_index/2
if(this.array[parent_index]>x){
this.array[x_index] = this.array[parent_index]
this.array[parent_index] = x
}else{
break
}
x_index = parent_index
}
return
}

image-20230324210219667

删除JavaScript实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pop(){
debugger
this.array[0] = this.array[this.array.length-1]
this.array.pop()
let x_index = 0
while(true){
let left_index = 2 * x_index + 1
let right_index = 2 * x_index + 2
left_index = left_index>=this.array.length?x_index:left_index
right_index = right_index>=this.array.length?x_index:right_index
let small_child_index = this.array[x_index]>this.array[left_index]?left_index:x_index
small_child_index = small_child_index!==x_index?small_child_index:(this.array[x_index]>this.array[right_index]?right_index:x_index)
if(small_child_index===x_index) break
let temp = this.array[x_index]
this.array[x_index] = this.array[small_child_index]
this.array[small_child_index] = temp
x_index = small_child_index
}

}

image-20230324210321704

解决前K个高频元素/低频元素题

LeetCode链接

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

  • 输入: nums = [1,1,1,2,2,3], k = 2
  • 输出: [1,2]

示例 2:

  • 输入: nums = [1], k = 1
  • 输出: [1]

提示:

  • 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
  • 你的算法的时间复杂度必须优于 $O(n \log n)$ , n 是数组的大小。
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
  • 你可以按任意顺序返回答案。

步骤:

  • 获取到每个数据出现的频率,使用哈希表,这里可以用map,key是数字,value是次数
  • 对频率排序
  • 找出前K个高频元素

注:

  • 如果使用快速排序的时间复杂度为O(n)
  • 仅维护大小为K的小顶堆,小顶堆的堆顶始终是最小值,如果堆长度小于K则继续往队礼添加,如果长度大于K则弹出堆顶的最小值

image-20230324211848724

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
// js 没有堆 需要自己构造
class Heap {
constructor(compareFn) {
this.compareFn = compareFn;
this.queue = [];
}

// 添加
push(item) {
// 推入元素
this.queue.push(item);

// 上浮
let index = this.size() - 1; // 记录推入元素下标
let parent = Math.floor((index - 1) / 2); // 记录父节点下标

while (parent >= 0 && this.compare(parent, index) > 0) { // 注意compare参数顺序
[this.queue[index], this.queue[parent]] = [this.queue[parent], this.queue[index]];

// 更新下标
index = parent;
parent = Math.floor((index - 1) / 2);
}
}

// 获取堆顶元素并移除
pop() {
// 堆顶元素
const out = this.queue[0];

// 移除堆顶元素 填入最后一个元素
this.queue[0] = this.queue.pop();

// 下沉
let index = 0; // 记录下沉元素下标
let left = 1; // left 是左子节点下标 left + 1 则是右子节点下标
let searchChild = this.compare(left, left + 1) > 0 ? left + 1 : left;

while (searchChild !== undefined && this.compare(index, searchChild) > 0) { // 注意compare参数顺序
[this.queue[index], this.queue[searchChild]] = [this.queue[searchChild], this.queue[index]];

// 更新下标
index = searchChild;
left = 2 * index + 1;
searchChild = this.compare(left, left + 1) > 0 ? left + 1 : left;
}

return out;
}

size() {
return this.queue.length;
}

// 使用传入的 compareFn 比较两个位置的元素
compare(index1, index2) {
// 处理下标越界问题
if (this.queue[index1] === undefined) return 1;
if (this.queue[index2] === undefined) return -1;

return this.compareFn(this.queue[index1], this.queue[index2]);
}

}

const topKFrequent = function (nums, k) {
const map = new Map();

for (const num of nums) {
map.set(num, (map.get(num) || 0) + 1);
}

// 创建小顶堆
const heap= new Heap((a, b) => a[1] - b[1]);

// entry 是一个长度为2的数组,0位置存储key,1位置存储value
for (const entry of map.entries()) {
heap.push(entry);

if (heap.size() > k) {
heap.pop();
}
}

// return heap.queue.map(e => e[0]);

const res = [];

for (let i = heap.size() - 1; i >= 0; i--) {
res[i] = heap.pop()[0];
}

return res;
};

由于key和value是要绑定的,所以元素不能是简单的数组,而应该是包含key和value的二维数组,同时为了使得代码重用,将比较函数抽取出来,当做参数传递

时间复杂度

时间复杂度:时间增长趋势 T(n) = O(f(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
for(let i=0;i<=n;i++){
x++
}
// let i=0 1
// i<=n; i++; x++ 各n次
// 结果为 O(1+3n) = O(n) O(n)算n趋近无限大,所以常数忽略

for(let i=0;i<=n;i++){
for(let j=0;j<=n;j++){
x++
}
}
// 时间复杂度:O(n)*O(n)=O(n^2)

for(let i=0;i<=n;i++){
x++
}
for(let i=0;i<=n;i++){
for(let j=0;j<=n;j++){
x++
}
}
// 时间复杂度 O(n+n^2) = O(n^2)

let x = 0
let y = 1
let temp = x
x = y
y = temp
// 时间复杂度不随着任何一个变量增大而增大,复杂度为O(1)


let i = 1
while(i<n){
i = i*2
}
// 循环次数k:2^k = n k=logn O(logn)


for(let j=0;j<=n;j++){
let i = 1
while(i<n){
i = i*2
}
}
// 时间复杂度O(n)*O(logn) = O(nlogn)


for(let i=0;i<=n;i++){
for(let j=0;j<=m;j++){
x++
}
}
// 时间复杂度O(n)*O(m) = O(nm)

空间复杂度

空间复杂度:内存空间增长的趋势

1
2
3
4
5
6
7
8
9
10
11
let x = 0
let y = 1
let temp = x
x = y
y = temp
// x和y无论多大多不会影响空间占用 O(1)
let arr = []
for(let i=0;i<n;i++){
arr.push(i)
}
// 占用空间与n成正比,O(n)

组件化开发(React二)

组件化开发:如果把一个页面的所有处理逻辑放在一起,处理起来会很复杂,因此将一个页面拆分为不同的功能块

  • 一个完整的页面可以划分为很多个组件
  • 每个组件都用于实现页面的一个功能
  • 每一个组件又可以细分
  • 组件本身又可以在很多地方复用

image-20230324172119865

React组件:

  • 类组件与函数组件
  • 根据内部是否有状态需要维护:无状态组件、有状态组件(this.state,不考虑hooks的情况下函数组件是无状态组件,而类组件不定义this.state也可以看做无状态组件)
  • 根据组件的不同职责:展示型组件、容器型组件

组件之间有重叠,但都是为了数据逻辑和UI展示的分离

  • 函数组件、无状态组件、展示型组件主要关注UI的展示
  • 类组件、有状态组件、容器型组件主要关注数据逻辑

类组件与函数组件

类组件

定义要求:首字母大写、继承自React.Component、实现render函数

  • constructor可选,在其中初始化数据
  • this.state中维护组件内部的数据
  • render是类组件唯一必须实现的方法

函数组件(没有hooks的函数组件)

  • 没有生命周期、会被挂载、没有生命周期函数
  • this关键字不能指向组件实例,因为没有组件实例
  • 没有内部状态state
1
2
3
4
5
6
7
function App(){
return (
<div>Hello World</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)

生命周期

生命周期:从创建到销毁的整个过程,了解生命周期在最合适的地方完成想要的功能

React的生命周期、生命周期函数

  • 装载阶段:组件第一次在DOM树种被渲染的过程——装载完成:componentDidMount
  • 更新阶段:组件状态发生变化,重新更新渲染的过程——更新完成:componentDidUpdate
  • 卸载阶段:组件从DOM树种被移除的过程——即将移除:componentWillUnmount

image-20230324173626696

各函数任务及时刻

constructor:

  • 不初始化state或不进行方法绑定,则可以不实现构造函数
  • 作用一:给this.state赋值对象来初始化state
  • 作用二:为事件绑定this——调用super()

componentDidMount:

  • 组件挂载到DOM Tree后立马调用
  • 作用一:进行依赖于DOM的操作
  • 作用二:发送网络请求的最好位置
  • 作用三:添加订阅

componentDidUpdate:

  • 组件更新后立即调用,首次渲染不会执行这个方法
  • 作用一:组件更新后,此处进行DOM相关操作
  • 作用二:当更新前后props发送了变化,此处发送网络请求

componentWIllUnmount:

  • 组件卸载及销毁之前直接调用
  • 作用一:进行必要的清理操作
  • 作用二:清除timer,取消网络请求或取消上面创建的订阅

不常用生命周期函数

  • getDerivedStateFromProps:state 的值在任何 时候都依赖于 props时使用;该方法返回一个对象 来更新state
  • getSnapshotBeforeUpdate:在React更新DOM 之前回调的一个函数,可以获取DOM更新前的一 些信息(比如说滚动位置)
  • shouldComponentUpdate:该生命周期函数很 常用,但是我们等待讲性能优化时再来详细讲解

image-20230324174415395

组件之间的通信

父组件传值到子组件

  • 父组件通过 属性=值 的形式来传递给子组件数据
  • 子组件通过props参数获取父组件传过来的值

默认值设置:ChildComponent.defaultProps

参数类型设置:ChildComponent.propTypes isRequired代表是否必须

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from "react";
import ChildComponent from "./component/ChildComponent";
//编写组件
class App extends React.Component{
constructor(){
super()
this.state = {
message: "I am your father!",
info:"You are my child!"
}
}
render(){
let {message,info} = this.state
return (
<div>
<h2>{message}</h2>
<ChildComponent info = {info}/>
</div>
)
}
}
export default App

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component } from 'react'
import { PropTypes } from 'prop-types'
export class ChildComponent extends Component {

render() {
return ([
<div key="1">{this.props.info}</div>,
<div key="2">{this.props.info2}</div>
])
}
}
ChildComponent.propTypes = {
info: PropTypes.string.isRequired
}
ChildComponent.defaultProps = {
info:"Tell me who are you!",
info2:"You are my Dad!"
}
export default ChildComponent

image-20230324180859034

子组件传值到父组件

  • 通过props给子组件传递一个回调函数,在子组件中调用这个函数

父组件代码:

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
import React from "react";
import ChildComponent from "./component/ChildComponent";
//编写组件
class App extends React.Component{
constructor(){
super()
this.state = {
message: "I am your father!",
info:"You are my child!"
}
}
dataFromChild = function(info){
this.setState({
message:info
})
}
render(){
let {message,info} = this.state
return (
<div>
<h2>{message}</h2>
<ChildComponent info = {info} dataFromChild = {(message)=> this.dataFromChild(message)}/>
</div>
)
}
}
export default App

子组件代码:

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 { PropTypes } from 'prop-types'
export class ChildComponent extends Component {
sendDataToFather = function(){
this.props.dataFromChild(555)
}
render() {
return ([
<div key="1" onClick = {()=>{this.sendDataToFather()}}>{this.props.info}</div>,
<div key="2">{this.props.info2}</div>
])
}
}
ChildComponent.propTypes = {
info: PropTypes.string.isRequired
}
ChildComponent.defaultProps = {
info:"Tell me who are you!",
info2:"You are my Dad!"
}
export default ChildComponent

注:各个地方函数的使用要注意,是否需要this,一般传递过去的函数都是箭头函数,调用的函数一般为function

1
2
<ChildComponent info = {info} dataFromChild = {(message)=> this.dataFromChild(message)}/>
<ChildComponent info = {info} dataFromChild = {this.dataFromChild}/>

如果直接传递,那么函数的调用由React确定就不知道this是啥

传递过去箭头函数,那么我们不关心什么时候调用这个箭头函数,而箭头函数包含的是隐式绑定的this,所以调用的函数需要function,要不然找不到this

React中实现插槽

为了使组件具有更好的通用性,组件的内容不能限制为固定的div、span等,因此父组件传元素到子组件中,使得元素类型可以多变

image-20230324214907031

通过组件的children子元素实现(父像子传元素)

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Component } from 'react'
export class ChildComponent extends Component {
render() {
const {children} = this.props
return (
<div>
<div>
{children[0]}
</div>
<div>
{children[1]}
</div>
<div>
{children[2]}
</div>
</div>
)
}
}
export default ChildComponent

父组件代码1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";
import ChildComponent from "./component/ChildComponent";
//编写组件
class App extends React.Component{
constructor(){
super()
}
render(){
return (
<ChildComponent>
<button>按钮</button>
<h2>标题</h2>
<span>span</span>
</ChildComponent>
)
}
}
export default App

结果:

image-20230324215146004

更改父组件的子元素

1
2
3
4
5
<ChildComponent>
<span>按钮</span>
<i>标题</i>
<button>按钮</button>
</ChildComponent>

结果:

image-20230324215351358

通过props实现插槽(父向子传元素)

  • 上面描述过通过props传值到子元素,props可以传的值不仅仅有JavaScript对象,React对象也可以作为参数传过去
  • 可以避免通过children获取时索引不能精准的获取传入的元素

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from "react";
import ChildComponent from "./component/ChildComponent";
//编写组件
class App extends React.Component{
constructor(){
super()
}
render(){
const spanJSX = <span>按钮</span>
const iJSX = <i>标题</i>
const buttonJSX = <button>按钮</button>
return (
<ChildComponent spanJSX={spanJSX} iJSX = {iJSX} buttonJSX={buttonJSX}/>
)
}
}
export default App

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react'
export class ChildComponent extends Component {
render() {
const {spanJSX,iJSX,buttonJSX} = this.props
return (
<div>
<div>{spanJSX}</div>
<div>{iJSX}</div>
<div>{buttonJSX}</div>
</div>
)
}
}
export default ChildComponent

结果:

image-20230324220143962

作用域插槽

  • 上面两种插槽方法子组件接受到的都是有父组件写死的元素,但某些时候在子组件中需要根据状态更改组件的内容
  • 所以需要实现元素类型父组件决定,数据或状态子组件决定
  • 类似于这种由父组件子组件共同协作完成的,通常使用传递函数实现

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import ChildComponent from "./component/ChildComponent";
//编写组件
class App extends React.Component{
constructor(){
super()
}
render(){
return (
<ChildComponent itemtyPE = {item=><button>{item}</button>}/>
)
}
}
export default App

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { Component } from 'react'
export class ChildComponent extends Component {
render() {
const {itemtyPE} = this.props
const message = "哈哈哈"
return (
<div>
{itemtyPE(message)}
</div>
)
}
}
export default ChildComponent

image-20230325112140472

非直接父子组件的通信

常见方法:

  • 通过props属性自上而下传递
  • 但是对于某些数据需要在多个组件中共享(如地区、用户信息等等)
  • 如果在顶层App定义这些信息,一层层传递下去,对于一些中间层不需要这些数据的组件来说,是一种冗余的操作

Context方法:

  • context提供了一种在组件之间共享值的方法,而不必显示的通过树的逐层传递props
  • context的目的是共享对于组件树来说是“全局的数据”,如当前用户、主题

注册Context

由于后续会在多个组件中用到这个Context1,所以将Context1的注册放到一个js文件中

1
2
3
4
5
import React from "react";

const Context1 = React.createContext()

export default Context1

Context值的设置

父组件设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";
import ChildComponent from "./component/ChildComponent";
import Context1 from "./context/Context1";
//编写组件
class App extends React.Component{
constructor(){
super()
}
render(){
// 通过COntext1.Provider 包围要传递值的子组件,那么这个子组件及其后代组件都可以获取到传递的值
return (
<Context1.Provider value={{color:"red",size:18}}>
<ChildComponent/>
</Context1.Provider>
)
}
}
export default App

Context值的获取

后代类组件Context值的获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { Component } from 'react'
import Context1 from '../context/Context1'
import GrandsonComponent from './GrandsonComponent'
export class ChildComponent extends Component {
constructor(){
super()
}
render() {
return (
<div>
<h2 style={{color:this.context.color}}>子组件</h2>
<GrandsonComponent/>
</div>
)
}
}
ChildComponent.contextType = Context1 // 通过设置contextType为this.context指定Context,然后通过this.context获取值
export default ChildComponent

后代函数组件Context值获取:

由于函数组件中没有this,所以不能够使用上面类组件获取值的方法,使用Context.Consumer获取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Context1 from "../context/Context1"

export function GrandsonFunComponent(){
return(
<Context1.Consumer>
{
value=>{
return <h2 style={{color:value.color}}>函数孙子组件</h2>
}
}
</Context1.Consumer>
)
}
export default GrandsonFunComponent

多个Context的注册与获取

注册第二个Context:

1
2
3
4
5
import React from "react";

const Context2 = React.createContext()

export default Context2

单组件设置多Context:

父组件采用Context嵌套的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";
import ChildComponent from "./component/ChildComponent";
import Context1 from "./context/Context1";
import Context2 from "./context/Context2";
//编写组件
class App extends React.Component{
constructor(){
super()
}
render(){
return (
<Context2.Provider value={{color:"blue"}}>
<Context1.Provider value={{color:"red",size:18}}>
<ChildComponent/>
</Context1.Provider>
</Context2.Provider>
)
}
}
export default App

后代组件获取多Context:

通过设置contextType与Context.Consumer组合获取多个Context

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
import React, { Component } from 'react'
import Context1 from '../context/Context1'
import Context2 from '../context/Context2'
import GrandsonComponent from './GrandsonComponent'
import GrandsonFunComponent from './GrandsonFunComponent'
export class ChildComponent extends Component {
constructor(){
super()
}
render() {
return (
<div>
<h2 style={{color:this.context.color}}>子组件</h2>
<Context2.Consumer>
{
value=>{
return <h3 style={{color:value.color}}>子组件2</h3>
}
}
</Context2.Consumer>
<GrandsonComponent/>
<GrandsonFunComponent/>
</div>
)
}
}
ChildComponent.contextType = Context1
export default ChildComponent

Context默认值的使用

使用了Context但不是Context.Provider的后代元素

Context的注册(带默认值):

1
2
3
4
5
import React from "react";

const Context1 = React.createContext({color:"green"})

export default Context1

Context的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";
import ChildComponent from "./component/ChildComponent";
import Context1 from "./context/Context1";
import Context2 from "./context/Context2";
//编写组件
class App extends React.Component{
constructor(){
super()
}
render(){
return (
<div>
<Context2.Provider value={{color:"blue"}}>
<Context1.Provider value={{color:"red",size:18}}>
<ChildComponent/>
</Context1.Provider>
</Context2.Provider>
<ChildComponent/>
</div>
)
}
}
export default App

Context的获取:

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 Context1 from '../context/Context1'
import Context2 from '../context/Context2'
import GrandsonComponent from './GrandsonComponent'
import GrandsonFunComponent from './GrandsonFunComponent'
export class ChildComponent extends Component {
constructor(){
super()
}
render() {
return (
<div>
<h2 style={{color:this.context.color}}>子组件</h2>
<GrandsonComponent/>
<GrandsonFunComponent/>
</div>
)
}
}
ChildComponent.contextType = Context1
export default ChildComponent

image-20230325122726808

任意组件之间的通信——事件总线(EventBus)

事件总线实现

setState的细节原理

为什么要setState:React没有数据劫持,直接更改this.state时,React不知道数据已经发生了变化,需要通过this.setState()来告知React数据发生了变化,需要刷新页面

setState的异步/同步

一般情况下,setSTate是异步的

设置为异步的原因:

  • 可以显著提升性能,如果每次调用setState都进行一次更新,那么render会频繁的调用,界面重新渲染,这样效率很低(可以联系到回流-重绘),因此设置为异步获取到数据的多次更新,之后批量更新
  • 如果同步更新了state,但还没执行render函数,那么state与子组件的props不能保持同步,可能出现若干问题

setState批量更新方法

  • 获取到一系列setState的更新值,按先后顺序把旧的state与新的state合并(添加新的属性、更新改变了的属性),在最后将合并好的state赋值给this.state并刷新到页面上(SCU优化)
  • 注意:先合并再赋值给this.state

异步结果的获取:

既然是异步,那么我们便不知道更新啥时候结束,但有些时候又想在更新结束后进行一定的操作,这个操作应该在哪里进行?

方法一:setState的回调函数

  • setState接受两个参数:新的state+回调函数
  • 回调执行顺序:render->componentDidUpdate(因此也可以在这个生命周期函数中对更新结束后的值操作)->setState回调
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
import React from "react";
//编写组件
class App extends React.Component{
constructor(){
super()
this.state ={
counter:3
}
}
buttonClick(){
this.setState({
counter:this.state.counter + 1
},()=>{
console.log("setState回调",this.state)
})
this.setState({
counter:this.state.counter + 1
},()=>{
console.log("setState回调",this.state)
})
this.setState({
counter:this.state.counter + 1
},()=>{
console.log("setState回调",this.state)
})
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
render(){
console.log("render")
const { counter } = this.state
return (
<div>
<h2>{counter}</h2>
<button onClick={()=>{this.buttonClick()}}>更新state</button>
</div>
)
}
}
export default App

image-20230327111305434

注:

  • 多次setState合并,一次render,一次componentDidUpdate,多次回调
  • 这里多次一个提交了3次+1,但是最后结果只加了1,是因为this.state会在多次提交的state与原始state合并结束后再赋值给this.state,也就是这里获取到的this.state每次都是初始值3,相当于提交了3次 counter=4

this.setState的第一个参数也可以是一个函数:

  • 返回值是要更改的值
  • 参数是已经合并后的state而不是this.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
50
import React from "react";
//编写组件
class App extends React.Component{
constructor(){
super()
this.state ={
counter:3
}
}
buttonClick(){
setTimeout(()=>{
console.log("setTimeOut宏任务")
},0)
new Promise((resolve)=>{
resolve("")
}).then((res)=>{
console.log("promise微任务")
})
this.setState((state)=>{
console.log("setState",state)
return {counter:state.counter+1}
},()=>{
console.log("setState回调",this.state)
})
this.setState((state)=>{
return {counter:state.counter+1}
},()=>{
console.log("setState回调",this.state)
})
this.setState((state)=>{
return {counter:state.counter+1}
},()=>{
console.log("setState回调",this.state)
})
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
render(){
console.log("render")
const { counter } = this.state
return (
<div>
<h2>{counter}</h2>
<button onClick={()=>{this.buttonClick()}}>更新state</button>
</div>
)
}
}
export default App

执行顺序为:this.setState第一个参数函数->render->componentDidUpdate->this回调

image-20230327112819884

注:

  • 多次setState合并,一次render,一次componentDidUpdate,多次回调
  • setState函数执行、render、componentDidUpdate执行都是在微任务后,宏任务前,因此前面的函数应该都是放在微任务队列中
  • 提交3次+1,最后得到的结果也和预期相同,因为将3次setState的第一个参数函数放入微任务队列中,在原来的state基础上进行更改,然后合并,得到新的state,在调用下一个微任务setState时,用前面合并好的state作为参数,再进行更改与合并操作
  • 虽然setState中是在微任务中分3次调用,但setState的回调是在完成componentDidUpdate再放入微队列中,此时this.state已经更改为最后值,所以获取到的都是最后结果

回调的好处:

  • 在数据更改后处理一些state的逻辑
  • 回调函数会把之前的state和props传入进来
  • 由于setState是一个异步函数,所以对更新后值的处理要放入回调中

方法二:组件的生命周期函数componentDidUpdate

上面已讲

setState的同步

  • React18之前,在组件生命周期和React合成事件中,setState是异步
  • React18之前,在setTimeout或者原生dom事件中,setState是同步

注:

  • 由React去决定运行时机的大多是异步
  • 由setTimeout/或者原生Dom的响应函数中调用这种用户编写决定运行时机的一般是同步

React18之后,setState默认都是异步的

React18之后获取同步的setState

使用特殊的flushSync操作

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
import React from "react";
import {flushSync} from "react-dom"
//编写组件
class App extends React.Component{
constructor(){
super()
this.state ={
counter:3
}
}
buttonClick(){
setTimeout(()=>{
console.log("setTimeOut宏任务")
},0)
new Promise((resolve)=>{
resolve("")
}).then((res)=>{
console.log("promise微任务")
})

flushSync(()=>{
this.setState((state)=>{
console.log("setState",state)
return {counter:state.counter+1}
},()=>{
console.log("setState回调",this.state)
})
})
this.setState((state)=>{
return {counter:state.counter+1}
},()=>{
console.log("setState回调",this.state)
})
this.setState((state)=>{
return {counter:state.counter+1}
},()=>{
console.log("setState回调",this.state)
})
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
render(){
console.log("render")
const { counter } = this.state
return (
<div>
<h2>{counter}</h2>
<button onClick={()=>{this.buttonClick()}}>更新state</button>
</div>
)
}
}
export default App

image-20230327114712050

可以看到使用flushSync中的setState在微任务执行之前就已经运行,说明flushSync确实将setState更改为同步方式运行

React的SCU优化

React更新机制

React渲染流程:

image-20230327115317127

React更新流程:

image-20230327120054249

React在props或state发生改变时,会调用React的render方法,会创建一颗新树,React会基于新树与旧树的不同去判断如何有效的更新UI

  • 如果两棵树进行完全比较更新,算法的时间复杂度为O(n^3),n为树中元素的数量,开销太大,React的更新性能会变得低效

React对diff算法的优化:

  • 同层节点之间相互比较,不会跨节点比较
  • 不同类型的节点,产生不同的树结构
  • 开发中,通过key来指定哪些节点在不同的渲染下保持稳定

keys的优化:

  • 在最后位置插入数据,有无key意义不大
  • 在前面插入数据,没有key,所有的li都需要更改,有key,后续的li只需要移动

key的注意事项:

  • key应该是唯一的
  • key不能使用随机数,每次render的随机数不同
  • 不能使用index作为key,index在操作后也可能会改变,要使用一个不会随render改变的值

shouldComponentUpdate优化(即SCU优化)

image-20230327121337396

  • 在上图中,如果App中值发生了改变,那么App的render函数会重新执行,而在App的执行过程中,会执行其所有子组件的render函数,都进行diff算法,性能很低
  • 子组件调用render函数应该有一个前提——依赖的数据state/props发生改变,此时才调用render函数

shouldComponentUpdate函数

  • 参数:nextProps(修改后的pros)、nextState(修改之后的state)
  • 返回值:true(调用render方法)、false(不调用render方法)、默认返回true(只要state改变就会调用render方法)

在值发生变化时,就可以在shouldComponentUpdate监听自己的state和props是否发生变化从而觉得是否进行render

类组件PureComponent

继承自PureComponent自动实现了SCU优化,不需要自己去编写shouldComponentUpdate函数

如果使用Component,只要调用setState就会执行render函数,而PureComponent会帮我们实现比较setState后state是否改变,如果改变才执行render

不可变数据的力量

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
import React from "react";
import {flushSync} from "react-dom"
//编写组件
class App extends React.Component{
constructor(){
super()
this.state ={
books:[
{name:"aaa",count:1},
{name:"bbb",count:1},
{name:"ccc",count:1},
{name:"ddd",count:1}
]
}
}
addNewBook(){
const newBook = {name:"eee",count:1}
this.setState({

})
}

render(){
console.log("render")
const { books } = this.state
return (
<div>
<ul>
{books.map((item,index)=>{
return(
<li key={index}>
<span>name:{item.name}-count:{item.count}</span>
<button>+1</button>
</li>
)
})}
</ul>
<button onClick={e=>this.addNewBook()}>添加新书</button>
</div>
)
}
}
export default App

上述在setState中没进行任何操作,但render依然会执行,而改成PureComponent时不会执行

PureComponent中render执行要求:

  • PureComponent在比较时底层会使用shallowEqual方法,shallowEqual是一种浅层比较,如果比较的两个对象是同一个,则不会调用render,即使this.state发生了改变,也不会调用render
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
constructor(){
super()
this.state ={
books:[
{name:"aaa",count:1},
{name:"bbb",count:1},
{name:"ccc",count:1},
{name:"ddd",count:1}
]
}
}
addNewBook(){
const newBook = {name:"eee",count:1}
this.state.books.push(newBook)
const books = this.state.books
this.setState({
books:books
})
}
  • 如果要想在这种情况下调用render,可以利用books创建一个新的对象,用这个对象去更改state,在这种情况下,即使未更改this.state的,也会调用render
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
constructor(){
super()
this.state ={
books:[
{name:"aaa",count:1},
{name:"bbb",count:1},
{name:"ccc",count:1},
{name:"ddd",count:1}
]
}
}
addNewBook(){
const books = [...this.state.books]
this.setState({
books:books
})
}

函数组件memo

函数由于不能继承于PureComponent,可以使用memo替代,即用memo包裹原来的函数组件

image-20230327131630892

获取原生DOM或获取组件

在React的开发模式中,通常情况下不需要、也不建议直接操作DOM原生,但是某些特殊的情况,确实需要获取到DOM进行某些操作:

  • 管理焦点,文本选择或媒体播放;
  • 触发强制动画;
  • 集成第三方 DOM 库;
  • 我们可以通过refs获取DOM;

通过refs获取DOM的方式:

  • 传入字符串:使用时通过 this.refs.传入的字符串 格式获取对应的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
addNewBook(){
console.log(this.refs.button)
}

render(){
return (
<div>
<button onClick={e=>this.addNewBook()} ref="button">查看DOM</button>
</div>
)
}
}
export default App
  • 传入一个对象:通过React.createRef()方式创建一个对象,使用使用对象的current属性来访问DOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.titleRef = React.createRef()
}
addNewBook(){
console.log(this.titleRef.current)

}

render(){
return (
<div>
<button onClick={e=>this.addNewBook()} ref={this.titleRef}>查看DOM</button>
</div>
)
}
}
export default App
  • 传入一个函数:该函数会在DOM被挂载时回调,回调函数会传入一个元素对象,我们可以访问并保存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from "react";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.titleRef = React.createRef()
}
render(){
return (
<div>
<button ref={(e)=>{console.log(e)}}>查看DOM</button>
</div>
)
}
}
export default App

通过refs获取组件的方式:

针对类组件:

  • 上面的ref同样可以加在子组件上,获取得到子组件的实例,就可以在父组件中获取子组件对子组件的函数、变量进行操作

针对函数组件:

  • 函数组件由于没有实例,是不可能获取到它的
  • 但是在某些情况,想获取得到函数实例中的某个DOM元素,可以使用foreardRef
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
import React from "react";
import { forwardRef } from "react";
//编写组件

const HelloWorld = forwardRef(function(props, ref){
return (
<div>
<h1 ref={ref}>Hello World</h1>
</div>
)
})
class App extends React.PureComponent{
constructor(){
super()
this.HelloWorldRef = React.createRef()
}
render(){
return (
<div>
<HelloWorld ref = {this.HelloWorldRef}/>
<button onClick={()=>{console.log(this.HelloWorldRef.current)}}>查看DOM</button>
</div>
)
}
}
export default App

受控组件与非受控组件

  • HTML中,表单元素(input、textarea、select)通常会自己维护state并根据用户的输入进行更新
  • React中,可变状态通常保存在组件的state属性中,并且只能通过setState来更新
  • 在JSX中,如果给表单元素设置了value属性,显示this.state.value的值,那么表单元素就不会自己维护state,其state来源于React的state,因此当设置了value属性后,input元素输入后输入框里的东西不会变化,相当于未输入
  • 此时只能给input元素添加onChange(e)监听,从而在监听函数中使用输入的值在setState中更改value的值,使得页面刷新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";
import { forwardRef } from "react";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.state ={
username:"oww"
}
}
inputChange(e){
this.setState({username:e.target.value})
}
render(){
const {username} = this.state
return (
<div>
<input type="text" name="username" value={username} onChange={(e)=>{this.inputChange(e)}}/>
</div>
)
}
}
export default App

计算属性名

当对多个表单元素变成受控元素时,其更新需要多个监听,此时可用计算属性名将这些监听抽取到一个函数中

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
import React from "react";
import { forwardRef } from "react";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.state ={
username:"oww",
password:""
}
}
inputChange(e){
this.setState({[e.target.name]:e.target.value})
}
render(){
const {username,password} = this.state
return (
<div>
<input type="text" value={username} name="username" onChange={(e)=>{this.inputChange(e)}}/>
<input type="text" value={password} name="password" onChange={(e)=>{this.inputChange(e)}}/>
</div>
)
}
}
export default App

checkbox表单多选监听

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
import React from "react";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.state ={
hobbies:[
{id:"sing",text:"唱",checked:false},
{id:"jump",text:"跳",checked:false},
{id:"rap",text:"rap",checked:false}
]
}
}
inputChange(e,index){
const hobbies = [...this.state.hobbies]
hobbies[index].checked = e.target.checked
this.setState({
hobbies:hobbies
})
}
render(){
const {hobbies} = this.state
return (
<div>
<h2>爱好列表</h2>
{hobbies.map((item, index)=>{
return (
<label htmlFor={item.id} key={item.id}>
<input type="checkbox" id= {item.id} checked={item.checked} onChange={(e)=>this.inputChange(e,index)}/>{item.text}
</label>
)
})}
</div>
)
}
}
export default App

select多选

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
import React from "react";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
this.state ={
fruits:[
{id:"apple",text:"苹果"},
{id:"banana",text:"香蕉"},
{id:"orange",text:"橘子"}
],
checked:["apple"]
}
}
selectChange(e){
let checked = Array.from(e.target.selectedOptions).map(item=>item.value)
this.setState({
checked:checked
})
}
render(){
const {fruits,checked} = this.state
return (
<div>
<h2>爱好列表</h2>
<select multiple value={checked} onChange={e=>this.selectChange(e)}>
{fruits.map(item=>{
return <option value={item.id} key={item.id}>{item.text}</option>
})}
</select>
</div>
)
}
}
export default App

高阶组件

高阶函数:满足下列一个条件

  • 接受一个函数或多个函数作为输入
  • 输出一个函数

高阶组件(Higher-Order Components、HOC):

  • 高阶组件的参数为组件,返回值为新组件的函数
  • 高阶组件本身不是一个组件,而是一个函数

高阶组件不是React的API,而是基于React组合特性而形成的一种设计模式

高阶组件应用——props的增强

HOC代码:高阶函数包裹代码

例如本代码可以将用户信息封装传给子组件,不需要像上面的父向子传值,在子组件中写其他代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { PureComponent } from "react"

function enhanceProps(WrapperCpn, otherProps){
class UserComponent extends PureComponent{
constructor(){
super()
this.state = {
userinfo:{
username:"ouwenwu",
age:22
}
}
}
render(){
return <WrapperCpn {...this.state.userinfo} {...otherProps}/>
}
}
return UserComponent
}
export default enhanceProps

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";
import ChildComponent from "./component/ChildComponent";
import enhanceProps from "./HOC/HOC";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
render(){
const EnhancePropsCpn = enhanceProps(ChildComponent,{color:"blue"})
return (
<div>
<EnhancePropsCpn/>
</div>
)
}
}
export default App

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react'
export class ChildComponent extends Component {
constructor(){
super()
}
render() {
return (
<div>
<h2 style={{color:this.props.color}}>username:{this.props.username}</h2>
</div>
)
}
}
export default ChildComponent

结果:

image-20230327211035646

高阶组件应用——Context的共享

HOC代码:HOC可以返回类组件也可以返回函数组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { PureComponent } from "react"
import Context1 from "../context/Context1"

function enhanceProps(WrapperCpn){
return props=>{
return (
<Context1.Consumer>
{
value=>{
return <WrapperCpn {...value} {...props}/>
}
}
</Context1.Consumer>
)
}
}
export default enhanceProps

父组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from "react";
import ChildComponent from "./component/ChildComponent";
import Context1 from "./context/Context1";
import enhanceProps from "./HOC/HOC";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
render(){
const EnhancePropsCpn = enhanceProps(ChildComponent)
return (
<div>
<Context1.Provider value={{color:"red"}}>
<EnhancePropsCpn/>
</Context1.Provider>

</div>
)
}
}
export default App

子组件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from 'react'
export class ChildComponent extends Component {
constructor(){
super()
}
render() {
return (
<div>
<h2 style={{color:this.props.color}}>username:</h2>
</div>
)
}
}
export default ChildComponent

结果:

image-20230327212419080

注:可以看出对Context不再需要在每一个子组件中写之前提到的获取Context的方法,只需要将所有需要该Context的子组件包裹在高阶组件之中

高阶组件应用——渲染判断定权

与上面的主要差距为,会对传入的参数进行判断来决定返回哪个子组件

image-20230327212646566

高阶组件应用——生命周期劫持

在高阶组件中劫持子组件的生命周期,在自己的生命周期中完成逻辑

image-20230327212743063

总结

高阶组件的主要作用是提高代码的复用性。提高组件之间的复用

缺陷:

  • HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将产生非常多的嵌套
  • HOC可以劫持props,在不遵从约定的情况下可能造成冲突

官网:https://zh-hans.reactjs.org/

React简单项目创建(React一)

React特点

  • 声明书编程:只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染UI界面
  • 组件化开发:将复杂的界面拆分为一个个小的组件
  • 多平台适配:React(Web)、ReactNative(移动端平台)、ReactVR(虚拟现实Web应用程序)——虚拟DOM也对跨平台有很大用处

React开发依赖

  • react:react所必须的核心代码
  • react-dom:react渲染在不同平台上所需要的核心代码——虚拟DOM→真实DOM(浏览器)/原生控件(移动端)
  • babel:将jsx转换为React代码的工具,如果直接用React.createElement来写React对象,可以不用babel,同时可以将ES6转换为ES5语法

react CDN引入:

1
2
3
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> 
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

React第一个Hello World

1
2
3
4
5
6
7
<!-- root作为根节点-->
<div id="root"></div>
<script type="text/babel">
const message = "hello World"
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<h2>{message}</h2>)
</script>

注:

  • script一定要设置 type=”text/babel” 否则不会被babel解析,jsx语法就不能识别
  • ReactDom.createRoot()是创建一个React根,不一定是整个html的根
  • render函数,参数是要渲染的组件
  • {}语法可以引入JavaScript语法

React组件化开发

组件化开发:root.render参数是一个HTML元素或一个组件,那么可以将复杂的业务逻辑封装到一个组件中,然后传入到ReactDom.render中

React组件:

  • 类组件与函数组件
  • 根据内部是否有状态需要维护:无状态组件、有状态组件(this.state,不考虑hooks的情况下函数组件是无状态组件,而类组件不定义this.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
class App extends React.Component{
constructor(){
super()
this.state = {
movies:["盗梦空间","星际穿越","大话西游","流浪地球"],
currentIndex: 0
}
}
clickLi(event,index){
this.setState({currentIndex:index})
}
render(){
return (
<div>
{this.state.movies.map((item,index)=>{
return(
<li
key={item}
onClick={(event)=>{
this.clickLi(event,index)
}}
className = {index===this.state.currentIndex?"active":""}>{item}
</li>
)
})}
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)

数据依赖

  • 参与界面更新的数据:当数据改变时,需要更新组件渲染的数据(调用this.setState更新数据会自动检测改变的变量,并重新执行render函数更新页面)——参与数据流,放入当前对象的state对象中
  • 不参与页面更新的数据:数据改变时,不需要更新页面的数据

事件绑定

this指向问题:

  • babel默认严格模式,那么默认调用的函数的this为undefined
  • 一般的button点击,this指向button本身,但是这里调用的button是一个React的Element对象,并不是页面中的button,是React内部调用响应函数,因此不知道如何绑定正确的this

绑定this方法:

  • bind方法:调用bind方法生成一个带特定this的函数赋值给onClick

    1
    <button onClick={this.changeText.bind(this)}> 
  • 箭头函数方法:如上面代码,给onClick赋值一个箭头函数,在箭头函数中调用隐式绑定想要运行的函数,箭头函数由于没有this,会向上寻找到this即class对象

事件参数传递:点击函数会传入一个event,可以将event与其他的参数放在箭头函数里面调用函数时传给最后的响应函数

1
<li onClick={event=>this.clickLi(event,"oww","22")}>按钮</li>

map映射

往往会从数据数组中得到要展示的Element列表,就可以使用map映射

1
2
3
4
5
6
7
8
9
10
11
12
13
<div>
{this.state.movies.map((item,index)=>{
return(
<li
key={item}
onClick={(event)=>{
this.clickLi(event,index)
}}
className = {index===this.state.currentIndex?"active":""}>{item}
</li>
)
})}
</div>

JSX语法

1
const element = <div>Hello World</div>

类似于上诉代码的形式就是一个JSX代码

React选择JSX的理由:

  • React认为渲染逻辑本质上与其他UI逻辑存在耦合(UI需要绑定事件、UI需要展示数据状态、状态改变时,又会改变UI)

  • 因为渲染逻辑与UI逻辑的耦合,React没有将标记分离到不同的文件中,而是组合在一起形成组件

注:JSX只能有一个根元素,也就是上面不能存在两个兄弟DIV,实在需要可以用数组[]包围,JSX中的单标签必须以/>结尾

JSX中的变量

  • Number、String、Array可以直接显示
  • null、undefined、Boolean内容为空,如确实要显示,转换为字符串后使用
  • 嵌入表达式使用(运算表达式、三元运算符、执行函数——是一个函数的执行得到返回值)
  • Object对象不能作为子元素

JSX绑定属性

  • title属性/img的src属性/a元素href属性:直接使用title/src/href=””/{}
1
const element = <h2 title="h2">哈哈哈</h2>
  • class属性:小驼峰法className设置
1
const element = <h2 className={'abc cba ${isActive?'active':''}'}>哈哈哈</h2>
  • 内联style属性:style=””/{}设置——外层{}是JSX语法,里层{}是包裹对象,font-size要用小驼峰法fontSize
1
const element= <h2 style={{color:"red", fontSize:"30px"}}>哈哈哈</h2>

JSX本质

JSX实际上是React.createElement的语法糖,下面两种方法等效,label会将JSX转换为React对象

1
2
const element = React.createElement('div',{class:"active",style={color:"red"}},children)
const element = <div className = "active" style={{color:"red"}}>children1,children2</div>

虚拟DOM

  • 通过React.createElement最终可以创建出一个ReactElement对象,其与其子元素构成一个对象树,这个树就是虚拟DOM

  • React从JSX到真实DOM的流程:

    image-20230324165543425

虚拟DOM帮助实现声明式编程

虚拟DOM:虚拟DOM是一种编程理念

  • UI以一种虚拟化的方式保存在内存中,是一个较为简单的JavaScript对象
  • 通过ReactDom.render函数让虚拟DOM与真实DOM同步起来,这一个步骤叫做协调
  • 更新状态后整体刷新,而不是原生的局部刷新

Diff算法:如果简单的整体刷新会导致项目运行效率较低

  • Diff算法对于没有改变的DOM节点,保持原样不动,仅仅创建并替换变更过的DOM节点,实现DOM节点复用
  • 因此需要实现DOM节点的版本控制,如果对原生的DOM节点进行版本控制,会有大量的DOM查询操作,所以React将DOM的diff操作转移到轻量js对象上,可以避免大量的DOM操作,这个轻量的js对象就是虚拟DOM

实现过程:

  • 维护一个使用JS对象表示的虚拟DOM,与真实DOM一一对应
  • 对前后的虚拟DOM做diff操作,找到变化的虚拟DOM
  • 将变化的DOM应用于真实DOM(不是不操作DOM,而是对DOM的操作次数降到最低

React的声明式编程

  • 通过虚拟DOM表示希望UI是什么状态
  • React确保DOM和这些状态匹配
  • 不需要直接进行DOM操作,而去改变虚拟DOM,从手动更改DOM、属性操作、事件处理中解放出来(传统的DOM API太多,操作复杂,容易出现Bug,代码不易维护),用户只需要关心状态和最终的UI样式

CSS基础

CSS样式

  • 内联样式(元素的style属性中)
  • 内部样式表(HTML的style元素中)
  • 外部样式表(.css文件中,通过link元素导入)

文本

text-decoration:none(无装饰线)、underline(下划线)、overline(上划线)、through(中划线/删除线)

text-transform:capitaliza(每个单词首字母大写)、uppercase(每个单词字符变为大写)、lowercase(每个单词字符变小写)、none(没有影响)

text-indent:(em/px)第一行内容的缩进——em是当前文字的大小,2em就是两个文字

text-align:left(左对齐)、right(右对齐)、center(居中对齐)、justify(两端对齐)

  • 行内级元素的对齐方式
  • display:inline与width、height不能同时设置,行内级元素没有宽高(高度为line-height)

letter-spacing/word-spacing:分别设置字母、单词之间的间距(默认是0,可以为负数)

字体

font-size:字体大小

  • 具体数值加单位:100px、1em(em是父元素计算后的font-size)
  • 百分比:基于父元素font-size计算

font-family:字体名称(一般设置一个,继承下去)

font-wight:加粗 100|200|300|400|500|600|700|800|900 normal是400 bold是700

font-style:斜体 normal(常规显示)、italic(用字体的斜体显示)、oblique(文本倾斜显示)

font-variant:normal(常规显示)、small-caps(小写字母替换为缩小后的大写字母)

line-height☆:

  • 一行文字所占的高度
  • 两行文字基线之间的间距
  • 基线:与小写字母x最底部对齐的线

  • line-height实现文字垂直居中:line-height=height

display:block、inline(不可以随意设置宽高)、inline-block、flex

元素隐藏方法:

  • display:none; 元素不显示出来,不占据空间(和不存在一样)
  • visivility:hidden; 元素不可见,但占据空间
  • rgba设置a为0 设置alpha值,透明度,不会影响子元素
  • opacity设置为0 会影响所有的子元素

注:raba和opacity设置时,文字根据opacity显示

常见选择器

  • !important:10000
  • 内联样式:1000
  • id选择器:100
  • 类选择器、属性选择器、伪类:10
  • 元素选择器、伪元素:1
  • 通配符:0

可以简单认为:选择器查询到的结果越多,权重越低

css设置不生效:

  • 选择器优先级太低
  • 选择器没选中对应的元素
  • css使用形式不对(例如行内级元素的宽高、被同类型css属性覆盖)

盒子模型

宽高:width/height、min-height/max-height、max-width/min-height(移动端适配)

padding:padding-top、padding-right、padding-bottom、padding-left

  • 10px 20px 30px 40px 分别对应:top、right、bottom、left
  • 10px 20px 30px 缺少left,left使用right的值
  • 10px 20px 缺少bottom、left分别使用top、right的值
  • 10px 其余值都用这个值

border:

  • border-top-width、border-right-width、border-bottom-width、border-left-width
  • border-top-color、border-right-color、border-bottom-color、border-left-color
  • border-top-style、border-right-style、border-bottom-style、border-left-style
  • border-top、border-right、border-bottom、border-left
  • border-radius

注:同样可以用border属性来进行缩写

out-line:

外轮廓不占用空间、border占用空间,外轮廓默认显示在border的外面

  • outline-width: 外轮廓的宽度
  • outline-style:取值跟border的样式一样,比如solid、dotted等
  • outline-color: 外轮廓的颜色
  • outline:outline-width、outline-style、outline-color的简写属性,跟border用法类似

作用:去除a元素,input元素的focus轮廓效果

margin:与padding值的设置类似

margin的上下传递(左右不传递)——父子元素

margin-top传递

html代码:

1
2
3
4
<div class="container">
<div class="content"></div>
</div>
<h2>哈哈哈哈</h2>

样式设置:

1
2
3
4
5
6
7
8
9
10
11
.container{
width: 300px;
height: 300px;
background-color: red;
}
.content{
width: 100px;
height: 100px;
background-color: orange;
margin-top: 100px;
}
  • 如果块级元素的顶部线和父元素的顶部线重叠,那么这个块级元素的margin-top值会传递给父元素

image-20230325163555417

如果没有折叠,应该是块级元素与父元素组件有间隔,但实际上margin-top值传递给了父元素

margin-bottom传递

1
2
3
4
5
6
7
8
9
10
11
.container{
width: 300px;
height: auto;
background-color: red;
}
.content{
width: 100px;
height: 300px;
background-color: orange;
margin-bottom: 100px;
}
  • 如果块级元素的底部线和父元素的底部线重写,并且父元素的高度是auto,那么这个块级元素的margin-bottom值会传递给父元素

image-20230325163706773

如果没有折叠,应该是块级元素与父元素组件有间隔,但实际上margin-bottom值传递给了父元素

防止margin传递

  • 给父元素设置padding-top\padding-bottom (必须额外添加padding)
  • 给父元素设置border (有时候boder影响样式,设置boder透明依然会占据空间)
  • 触发BFC: 设置overflow为auto

margin的上下折叠——兄弟父子都有

  • 垂直方向上相邻的2个margin(margin-top、margin-bottom)有可能会合并为1个margin,这种现象叫做collapse(折叠)
  • 水平方向上的margin(margin-left、margin-right)永远不会collapse
  • 折叠后最终值的计算规则: 两个值进行比较,取较大的值
  • 防止上下折叠:只设置其中一个元素的margin

image-20230325164329528

元素水平居中方案

父元素一般为块级元素、inline-block:

  • 行内级元素:text-align:center
  • 块级元素:margin:0 auto

元素的宽度公式:

元素实际占用宽度 = border + padding + width

元素实际占用高度 = border + padding + height

普通块级元素

  • 父元素宽度 = 子元素宽度+margin-left+margin-right

  • 父元素高度 = 子元素高度+margin-top+margin-bottom

使用案例

  • 父元素宽高知道、margin-left,margin-right设置为0,则子元素宽度auto为赋值为父元素宽度——高度不适用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<style>
.container{
width: 800px;
height: 400px;
background-color: red;
}
.context{
margin-left: 0;
margin-right: 0;
height: 100px;
background-color: orange;
}
</style>
<body>
<div class="container">
<div class="context"></div>
</div>
</body>

image-20230420200031708

  • 父元素宽高知道,子元素宽度知道,margin-left,margin-right设置为auto会自动对半分,则可以水平居中——只针对block生效

  • 少在高度上使用这个公式,因为浏览器对margin-top,margin-bottom的处理有些特殊

绝对定位元素(absolute/fixed)

  • 定位参照对象的宽度 = left + right + margin-left + margin-right + 绝对定位元素的实际占用宽度

  • 定位参照对象的高度 = top + bottom + margin-top + margin-bottom + 绝对定位元素的实际占用高度

使用案例

  • 父元素宽高知道,子元素宽度知道,left 、right 、 margin-left 、 margin-right均设置为0,则子元素高度auto为赋值为父元素宽度——宽度同样使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
.container{
position: relative;
width: 800px;
height: 400px;
background-color: red;
}
.context{
position: absolute;
width: 100px;
top: 0;
bottom: 0;
margin-top: 0;
margin-bottom: 0;
background-color: orange;
}
</style>
<body>
<div class="container">
<div class="context"></div>
</div>
</body>

image-20230420201531432

  • 父元素宽高知道,子元素宽高知道,left、right设置为0,margin-left,margin-right设置为auto会自动对半分——高度同样适用

CSS中元素的定位

标准流:默认情况下,元素都是按照normal flow(标准流、常规流、正常流、文档流【document flow】)进行排布

  • 从左到右、从上到下按顺序摆放好
  • 默认情况下,互相之间不存在层叠现象
  • 标准流可以用margin-padding进行位置的调整
    • 设置一个元素的margin或者padding,通常会影响到标准流中其他元素的定位效果
    • 不便于实现元素层叠的效果

position属性

默认值(static):

  • 元素按照normal flow布局
  • left 、right、top、bottom没有任何作用

相对定位(relative)

  • 元素按照normal flow布局
  • 可以通过left、right、top、bottom进行定位
    • 定位参照对象是元素自己原来的位置
  • left、right、top、bottom用来设置元素的具体位置,对元素的作用如下图所示
  • 应用场景:在不影响其他元素位置的前提下,对当前元素位置进行微调

固定定位(fixed)

  • 元素脱离normal flow(脱离标准流、脱标)
  • 可以通过left、right、top、bottom进行定位
  • 定位参照对象是视口(viewport)
  • 当画布滚动时,固定不动

绝对定位(absolute):

  • 元素脱离normal flow(脱离标准流、脱标)——脱标前的元素不变,脱标元素会放在之前的元素后面,但是后面的元素会当脱标元素不存在(不更改top等的值)
  • 可以通过left、right、top、bottom进行定位
    • 定位参照对象是最邻近的定位祖先元素
    • 如果找不到这样的祖先元素,参照对象是视口
  • 定位元素(positioned element)
    • position值不为static的元素
    • 也就是position值为relative、absolute、fixed的元素

position为absolute/fixed元素的特点(绝对定位元素)

  • 可以随意设置宽高——position为relative的不能

  • 宽高默认由内容决定

  • 不再受标准流的约束

    • 不再严格按照从上到下、从左到右排布

    • 不再严格区分块级(block)、行内级(inline),行内块级(inline-block)的很多特性都会消失——同样position为relative的不能

  • 不再给父元素汇报宽高数据——不会撑起父元素,如下面代码的div高度会为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<style>
span{
position:absolute;
width: 100px;
}
div{
background-color: red;
}
</style>
<body>
<div>
<span>哈哈哈</span>
</div>

</body>
  • 脱标元素内部默认还是按照标准流布局

粘性定位(sticky)

  • 可以看做是相对定位和固定(绝对)定位的结合体
  • 它允许被定位的元素表现得像相对定位一样,直到它滚动到某个阈值点
  • 它允许被定位的元素表现得像相对定位一样,直到它滚动到某个阈值点
  • sticky是相对于最近的滚动祖先包含滚动视口的(

浮动float

以前多列布局的常用方案

  • 脱离标准流

浏览器内核(以webkit为例):WebCore负责解析HTML、布局、渲染等;JavaScriptCore:解析、执行JavaScript代码

ECMAScript3:用这个来学习JavaScript执行、作用域、作用域链、闭包等概念

ECMAScript5:用这个来学习块级作用域、let、const等概念

两者概念描述不一样,整体思路一致

V8引擎的执行原理

V8是用C++编写的开源高性能JavaScript和WebAssembly引擎,可以用于Chrome和Node.js

image-20230323184747752

Parse:将JavaScript代码转换为AST(抽象语法树)——函数在没调用的时候,不会被转换为抽象语法树

Ignition:将AST转换为字节码

TurboFan:将字节码编译为可以直接运行的机器码

  • 如果一个函数被多次调用,就会被标记为热点函数,就会被TurboFan转换为机器码,提高代码的执行性能
  • 机器码也会被还原为ByteCode,如果后续执行函数的过程中,类型发生了变化,之前的机器码不能进行准确的运算,就会逆向转换为字节码

JavaScript执行过程

执行上下文栈(ECS):js引擎内部有一个执行上下文栈,是执行代码的调用栈

全局执行上下文:全局代码块为了执行会生成一个全局执行上下文(GEC),并放入ECS中

函数执行上下文:执行到一个函数值,会根据函数体创建一个函数执行上下文(FEC),并放入到ECS中

AO对象:当进入一个函数执行上下文时,会创建一个AO对象,AO对象使用arguments作为初始化,初始值是传入的参数

VO对象:每一个执行上下文都会关联一个VO(Variable Object)对象,变量和函数的声明会被添加到这个VO对象中

注:全局上下文的VO对象就是GO、函数执行上下文的VO对象是AO

初始化全局对象

js在执行代码前,会在堆内存中创建一个全局对象:Global Object(GO)

  • 该对象所有的作用域(scope)都可以访问;
  • 里面会包含Date、Array、String、Number、setTimeout、setInterval等等;
  • 其中还有一个window属性指向自己;

在parser转成AST的过程中,会将全局定义的变量、函数等放入GlobalObject中,但不会赋值(变量的作用域提升)

  • JavaScript可以在变量声明前访问,但是值是undefined,浏览器会对函数进行特殊处理,使得其可以在定义前调用

在代码执行过程中,对变量赋值或者执行其他的函数

全局代码执行前:

image-20230323191418551

全局代码执行后:

image-20230323191040277

函数执行前:

image-20230323191504578

函数执行后:

image-20230323191448418

作用域和作用域链

作用域链:当进入到一个执行上下文时,执行上下文会关联一个作用域链,并根据代码类型,添加一系列的对象

1
2
3
4
5
6
7
8
function foo(age){
function bar(){
console.log(age)
}
return bar
}
var baz = foo(18)
baz()

image-20230323192141405

  • 其作用域链中有两个,分别是foo的作用域、全局作用域

注:作用域链是在函数声明是产生的,与调用时刻无关

回流reflow和重绘repaint

回流

第一次确定节点的大小和位置,称为布局

后续对节点的大小、位置修改并重新计算称为回流

引起回流的情况

  • DOM结构发生变化(添加或删除节点)
  • 改变布局(修改width、height、padding、font-size等)
  • 修改窗口大小
  • 调用getComputedStyle方法获取尺寸、位置星系、

避免回流的方法

修改样式时尽量一次性修改,比如通过cssText修改,或者通过添加class修改,而不是对css的各种属性分次修改

避免频繁的操作DOM,可以使用虚拟DOM,或在父元素中要操作的DOM完成,再一次性更新到DOM中

composite合成

绘制的过程中,浏览器会将布局后的元素绘制到多个图层中

默认情况下,标准流中的内容被绘制在同一个图层,对于某些特殊的属性,会创建一个新的图层,这些图层可以利用GPU加速绘制

可以形成新的合成层的属性:3D transforms、video、canvas、iframe、opacity、position:fixed、will-change、animation或transition设置了opacity、transform

分层确实可以提高性能,但以内存管理为代价,因此不能作为Web性能优化的一部分过渡使用

重绘

第一次渲染内容称为绘制

后续重新渲染称为重绘

引起重绘的情况

修改背景颜色、文字颜色、边框颜色、样式等

注:回流一定引起重绘、因此回流是一件很消耗性能的事情,所以开发中要避免回流

网页解析过程:

image-20230322214252101

image-20230322215014488

HTML解析过程

默认情况下服务器给浏览器返回index.html文件,因此解析HTML是所有步骤的开始,解析HTML过程会构建Dom Tree

image-20230322215643759

生成CSS规则

在解析过程中,如果遇到CSS的link元素,会有浏览器下载对应的CSS文件(不会影响DOM)的解析

对CSS下载完成后,会对CSS文件进行解析,解析出规则树,即CSSOM

image-20230322220941332

构建Render Tree

当DOM Tree和CSSOM Tree有了过后就可以两者结合构建Render Tree了

image-20230322221500301

注:link元素不会阻塞DOM Tree的构建,但会阻塞Render Tree的构建

​ Render Tree和Dom Tree并不是一一对应的关系,对于display为none的元素,不会出现在Render Tree中

布局和绘制

渲染树会显示每个节点的样式,但不会给出每个节点的尺寸和位置,布局就是确定渲染树中每个节点的宽度、高度和位置信息

绘制就是将布局阶段计算的每个frame转为屏幕上实际的像素点,包括将元素的可见部分进行绘制,比如文本、颜色、边框、阴影、替换元素

script元素和页面解析的关系

在HTML解析过程中,遇到script元素是不能继续构建DOM Tree的;会首先下载JavaScript代码,并执行脚本,等脚本执行结束后,继续解析HTML,构建DOM Tree

原因: JavaScript的一个作用是操作DOM,并修改DOM,如果等DOM Tree构建完成后执行JavaScript,会引起严重的回流和重绘,影像性能

但是: 目前的Vue/React开发模式,脚本比HTML页面更重,等到JavaScript解析会造成页面阻塞,在脚本执行完成钱,用户在界面上看不到任何东西

script解析优化:defer、async

defer: 浏览器不需要等到脚本下载,继续解析HTML,构建DOM Tree

​ 不会阻塞DOm Tree的构建,等到DOM Tree构建完成后,在DOMContentLoaded事件之前执行代码

​ 带多个defer的脚本按顺序执行

​ 从某种角度来说,defer可以提高性能,并且推荐放到head元素中

​ 注:defer仅适用于外部脚本,对于script默认内容会忽略

async: 脚本完全独立

​ 浏览器不会因async阻塞

​ async不能保证不同脚本之间的顺序,独立下载、运行

​ async不能保证在DOMcontentLoaded之前或之后运行

defer通常用于文档解析后会操作DOM的JavaScript代码,并且对多个script文件有顺序要求的

async通常用于独立的脚本,对其他脚本,DOM没有依赖的

回流和重绘(见浏览器优化)

jQuery是一个快速、小型且功能丰富的JavaScript

优点:丰富的功能(DOM操作、过滤器、时间、动画、Ajax等)、编写更少可读的代码提高开发人员的工作效率、跨浏览器支持(IE9+)

缺点:jQuery代码库一直在增长(jQuery1.5超过200KB)、不支持组件化开发、jQuery更适合组件化开发,当涉及到复杂的项目时,能力有限

jQuery使用

jQuery监听文档加载

1
2
3
4
$( document ).ready( handler )  :  //deprecated 
$( "document" ).ready( handler ) : //deprecated
$().ready( handler ) ://deprecated
$( handler ) ://推荐用这种写法,其它可以使用但是不推荐

jQuery与其他库变量名冲突(jQuery别名$的冲突)

1
jQuery.noConflict()

jQuery对象(类数组对象——可通过索引访问)

  • 如果传入假值:返回一个空的集合。
  • 如果传入选择器:返回在在documnet中所匹配到元素的集合。
  • 如果传入元素:返回包含该元素的集合。
  • 如果传入HTML字符串,返回包含新创建元素的集合。
  • 如果传入回调函数:返回的是包含document元素集合, 并且当文档加载完成会回调该函数。
  • 因为函数也是对象,所以该函数还包含了很多已封装好的方法。如:jQuery.noConflict、jQuery.ready等
1
2
3
4
5
6
const obj = {
"name":"oww"
}
const obj1 = $() // 创建空对象
const obj2 = $(obj) // 通过j对象创建jQuery对象
const obj3 = $('li') // 通过选择器创建jQuery对象

jQuery对象与Element对象的区别:jQuery会包含所选择到的对象,可以通过$(element)和obj3[index]之间相互转换,转换后具有不同的函数

jQuery选择器

常规选择器:通用选择器(*)、属性/后代/基本(id、class、元素)/兄弟/交集/伪元素(伪类不行)/可见选择器(:visible,:hidden)、jQuery扩展选择器(:eq(),:odd,:even:first:last)—eq是用索引、odd奇数、even偶数

jQuery过滤器

  • eq(index): 从匹配元素的集合中,取索引处的元素, eq全称(equal 等于),返回jQuery对象。
  • first() : 从匹配元素的集合中,取第一个元素,返回jQuery对象。
  • last(): 从匹配元素的集合中,取最后一个元素,返回jQuery对象。
  • not(selector): 从匹配元素的集合中,删除匹配的元素,返回jQuery对象。
  • filter(selector): 从匹配元素的集合中,过滤出匹配的元素,返回jQuery对象。
  • .find(selector): 从匹配元素集合中,找到匹配的后代元素,返回jQuery对象。
  • is(selector|element| . ): 根据选择器、元素等检查当前匹配到元素的集合。集合中至少有一个与给定参数匹配则返回true。  8.odd() :将匹配到元素的集合减少为集合中的奇数,从零开始编号,返回jQuery对象。
  • even():将匹配到元素的集合减少到集合中的偶数,从零开始编号,返回jQuery对象。
  • 支持链式调用
  • on添加监听
1
2
3
4
5
6
7
const $li = $('li')
$li.click((event)=>{
console.log("1", event)
})
$li.on('click',(event)=>{
console.log("2", event)
})

jQuery函数

jQuery对文本的操作

.text()、.text(text)

1
2
3
4
5
6
7
8
9
10
11
<div class="active list div">
<li><p>1</pp></li>
<li value="2">2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li><span>9</span></li>
</div>
1
2
3
4
const $li = $('li')
console.log(typeof $li.text()) // string 会把其本身和子元素的text拼接为1个string
console.log($li.text()) // 123456789
$li.text("aaa") // 更改所有选择元素的text

.html、.html(htmlString)

1
2
3
const $li = $('li')
console.log($li.html()) // <p>1</p> 获取选中第一个元素的html
$li.html("<span>10<span>") //给所有选中元素设置innerHtml

.val()、.val(value)

1
2
3
const $li = $('li')
console.log($li.odd().val()) // 2 获取选中第一个元素的value
$li.val(12)

jQuery对css的操作

.width()、.height()、.css(prppertyName) 获取选中第一个元素的指定样式属性值

.css(propertyName,value)、.css({propertyName1:value1,propertyName2,value2}) 为每一个匹配到的元素设置属性

jQuery对class的操作

.addClass(className)、.addClass(classNames)、.addClass(funcntion)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const $li = $('li')
$li.addClass(function(index){
if(index%2===0){
this.classList.add("active1")
}else{
this.classList.add("active2")
}

}) // 通过this手动给选中的元素添加类
$li.addClass((index)=>{
if(index%2===0){
return "active1"
}else{
return "active2"
}

}) // 通过返回类名,jQuery给元素添加类

.hasClass(className) 判断匹配到的元素是否分配了该类

1
2
3
4
const $div = $('div')
console.log($div.hasClass("active")) // true
console.log($div.hasClass("active list")) // true
console.log($div.hasClass("active div")) // false 该方法是判断所选中元素是否包含要查询的类的字符串

.removeClass()、.removeClass(className)、.removeClass(classNames)、.removeClass(function) 删除指定的类

.toggleClass()、.toggleClass(className[,state])、.toggleClass(classNames[,state]) 删除或添加指定的类