0%

JS执行流程

AeMjI.png

AenZ2.png

AeHxj.png

详细过程

全局代码的执行

1.解析代码:

栈中:先会创建一个执行上下文Excution context(EC). 在EC中,有一个Variable Object,与堆中:Global Object(GO)相绑定.

Global Object中就是window对象,里面放着一些Math,Date,Number.和var变量,代码中所写的var变量,函数等等都是GO的属性,在解析阶段这些值都是undefinied.如果有函数,会立刻创建一个函数的对象Function Object,并且Function Object中包含基本的属性length name.GO中这个函数属性就指向这个Function Object. 如果这个函数是多层嵌套,只会有最外层的函数不会继续向内解析内层函数.

2.执行时

var变量会被依次赋值. 执行到函数时,栈中会创建一个新的EC,堆中会创建一个(Activation object)AO对象, 并且这个EC的VO会与这个AO对象相绑定,会使用arguments作为初始化.

作用域链只跟函数声明的位置有关,和执行的位置无关.

内存管理

垃圾回收Garbage Collection (GC)

Reference Counting 引用计数

  • 当一个对象有一个引用指向它时,那么这个对象的引用retainCount就+1

  • 对象的引用为0时,这个对象就可以被销毁掉

弊端可能会产生循环引用

mark-Sweep 标记清除

  • 核心思路是可达性Reachability

  • 设置一个根对象root object,垃圾回收期会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些没有引用到的对象,就认为是不可用的对象

  • 这个算法可以很好的解决循环引用的问题

Mark-Compact

回收期间会将保留的存储对象搬运到连续的内存空间,从而整合空闲空间,避免内存碎片化

Generational collection

对象会分为两组,新的和旧的

  • 许多对象的出现,完成它们的工作并很快死去,它们可以很快被清理

  • 那些长期存活的对象会变得老旧,而且被检查的频次也会减少

Incremental collection

  • 如果有许多对象,并且我们试图依次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延长.

  • 所以引擎试图将辣椒手机工作分层几部分来做然后将这几部分会逐一进行处理,这样会有许多微小的延迟而不是一个大的延迟

Idle-time collection闲时收集

垃圾收集器只会在CPU空闲时尝试运行,以减少可能对代码执行的影响

闭包Closure

闭包实现上是一个结构体,它存储了一个函数和一个关联的环境.

广义上,JS中的函数都是闭包

狭义上,JS中一个函数,如果访问了外层作用域的变量,那么它是一个闭包

函数对象的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
function foo() {}
var bar = function() {}
// 自定义属性
foo.message = "hello "
console.log(foo.message)
//1.name属性
console.log(foo.name,bar.name)

//2.length本来接受参数的个数
function foo(a,b,c) {}
console.log(foo.length)
</script>

Arguments

arguments是一个类数组对象,可以用索引找元素,但无法用filter等方法

因此可以把arguments转Array

1
2
3
4
function foo() {
var newArgs1 = Array.from(arguments)
var newArgs2 = [...arguments]
}

剩余参数

注意rest参数是一个真正的数组,可以进行数组的所有操作

1
2
3
4
function foo(num1, num2, ...otherNums) {
clg(otherNums)
}
foo(222,12,123,123)

函数式编程

纯函数

  • 相同的输入值,需产生相同的输出
  • 函数输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关
  • 不能有语义上可观察的函数副作用,诸如”触发事件”,使输出设备输出,或更改输出值以外物件的内容

柯里化(Curring)

  • 把接受多个参数的函数,变成接受一个单一参数的函数,并且返回接受余下的参数,而且返回结果的新函数的技术
  • 柯里化声称,如果你固定某些参数,你将得到接受余下参数的一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   function foo(x, y, z) {
console.log(x + y + z)
}
foo(10, 20, 30)

function foo(x) {
return function(y){
return function(z){
console.log(x, y, z)
}
}
}

foo(10)(20)(30)
//优化写法
var foo3 = x => y => z => console.log(x + y + z)

组合函数

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
    function double(num){
return num*2
}

function pow(num){
return num ** 2
}

// 实现一个组合函数的封装
function composeFn(...fns){
//1.边界判断(edge case)
var length = fns.length
for (var i =0; i < length; i++){
var fn = fns[i]
if (typeof fn !== "function"){
throw new Error("position ${i} must be function")
}
}
//2.返回的新函数
return function(...args){
var result = fns[0].apply(this, args)
for(var i = 1; i <length; i++){
var fn =fns[i]
result= fn.apply(this,[result])
}
}
}

var newFn = composeFn(double, pow)
console.log(newFn(100))
newFn(100)

with语句的使用(了解)

1
2
3
4
5
6
7
var obj = {
ms: "hello"
}

with (obj) {
console.log(ms)
}

严格模式

  • 严格模式通过抛出错误来消除一些原有的静默错误

严格模式可以在js文件开启,在函数开头开启

1
2
3
4
5
6
js:
"use strict"

function alugg(){
"use strict"
}

严格模式的限制

  • 不会意外创建全局变量
  • 会引起静默失败的复制操作抛出异常
  • 试图删除不可删除的属性
  • 不允许函数参数有相同的名称
  • 不允许使用with
  • this绑定不会转成对象类型

对象属性控制

属性描述符

可以修改属性,可以添加属性

Object.defineProperty()

1
2
3
4
5
6
7
var obj = {
name: "why",
age:18
}
Object.defineProperty(obj, "name", {
configurable: false,
})

Object.definePropertiest一次定义多个属性描述符

  • 数据属性Data Properties描述符(Descriptor)
    • configurable
    • enumerable
    • value
    • writable
  • 存取属性(Accessor访问器 Properties)描述符
    • configurable
    • enumerable
    • get
    • set

通过对象定义属性,configurable,enumerable,writable默认都是true,但通过对象属性描述符默认则是false

**Configurable:**表示属性是否可以通过delete删除属性,是否可以修改它的特性,或者是否可以将它修改为存取属性描述符

**Enumerable:**表示属性是否可以通过for-in或者Objects.keys()返回该属性

**Writable:**表示是否可以修改属性的值

原型

查找原型

1
2
obj.__proto__ (隐式原型)
obj.getPrototypeOf(obj)

对象的原型

–proto–

当通过[[get]]方式获取一个属性对应的value时

  • 它会优先在自己的对象中查找,如果找到直接返回
  • 如果没有找到,那么会在原型对象中查找

函数原型

将函数看成是一个普通对象时,它是具备__proto__

将函数看成是一个函数时,它是具备prototype

new操作符

  • 在内存中创建一个新的对象(空对象);
  • 在这个对象内部的__proto_属性会被赋值为该构造函数的prototype属性

**意义:**当多个对象拥有共同的值时,我们可以将它放到构造函数对象的显式原型;由构造函数创建出来的所有对象,都会共享这些属性

Constructor

原型对象上是有一个属性:constructor

默认情况下原型上都会添加一个属性constructor,这个constructor指向当前的函数对象

重写函数原型对象

当函数原型需要添加太多东西时,可以重写原型对象

1
2
3
4
5
6
7
8
Person.prototype = {
message: "hello person",
info: {name:"haha", age:30},
running: function() {},
eating: function() {},
constructor:Person
}

对象原型链

一个对象上获取属性,如果当前对象中没有获取到就回去它的原型上面获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = {
name = "why",
age:18
}

obj.__proto__ = {
}

obj.__proto__.__proto__ = {
}


obj.__proto__.__proto__.__proto__ = {
address:"xixihah"
}

实现继承

原型链实现方法继承

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
function Person(name, age){
this.name = name
this.age = age
}

Person.prototype.running = function() {
console.log("run")
}
Person.prototype.eating = function() {
console.log("eating")
}

function Student(name, age,sno,score){
this.name = name
this.age = age
this.sno = sno
this.score = score
}

Student.prototype.studying = function() {
console.log("eating")
}

var p = new Person("why", 18)
Student.prototype = p

借用构造函数实现属性继承

1
2
3
4
5
6
7
8
9
10
function Person(name, age){
this.name = name
this.age = age
}

function Student(name, age,sno,score){
Person.call(this,name,age)
this.sno = sno
this.score = score
}

以上配合使用叫做组合继承

实现了继承但存在很多缺点

  1. 无论什么情况都会调用两次构造函数
    • 一次创建子类原型时
    • 另一次在子类构造函数内部
  2. 所有子类实例事实上有两份父类属性
    • 一份在实例自己里面(person本身的),另一份在子类的原型对象中(person.-proto-里面)

寄生组合继承

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
function Person() {}
function Student() {}

// Method1
var obj = {}
Object.setPrototypeOf(obj, Person.prototype)
Student.prototype = obj

//Method2
function F() {}
F.prototype = Person.prototype
Student.prototype = new F()

//Method3
var obj = Object.create(Person.prototype)
Student.prototype = obj

//真实开发需要封装继承过程 --- 寄生组合式继承
function inherite(Subtype, Supertype){
Subtype.prototype = Object.create(Supertype.prototype)
Object.defineProperty(Subtype.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: Subtype
})
}

对象的部分方法

**hasOwnProperty:**对象是否有某一个属于自己的属性(而不是原型属性上)

**in/for in:**判断某个属性是否在某个对象或者对象的原型上(变量的不仅仅是自己对象上的内容,也包括原型上的内容)

**instanceof:**用于检测构造函数的prototype(Person,Student类)是否出现在某个实例对象的原型链上

isPrototypeOf用于检测某个对象是否出现在某个实例对象的原型链上

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
name: "why",
age:18
}

var info = createObject(obj)
info.address = "xx"


clg(info.hasOwnProperty("name"))//false因为是继承过来的
clg(info.hasOwnProperty("address"))

clg("name" in info) true

构造函数的类方法

通过prototype添加方法可以看作实例方法

1
2
3
4
5
6
7
8
9
10
//实例方法
Person.prototype.running = function() {
console.log("run")
}

//类方法:添加Person对象本身的方法
Person.randomPerson = function() {
return new Person("abc", 225)
}

Class定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
//1构造函数
constructor(name, age) {
this.name = name
this.age = age
}
//2.实例方法
//本质是放在Person.prototype上
running() {
console.log(this.name + "running")
}
}

var p1 = new Person("hha",12)

访问器

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
class Person {
constructor(name, age) {
this._name = name
this.age = age
}

set name(value) {
return this._name
}

get name() {
return this._name
}
}
var p1 = new Person("hha",12)
console.log(p1.name)

class Rectangle {
constructor(x, y, width, height) {
this.x = x
this.y = y
this.width = width
this.height = height
}

get position() {
return { x: this.x, y:this.y }
}

get size() {
return { width: this.width, height: this.height}
}
}

var rect1 = new Rectangle(10,20,30,40)
console.log(rect1.position)
console.log(rect1.size)

类方法

1
2
3
4
5
6
7
class Person {
constructor(name, age) {
this._name = name
this.age = age
}
static randomPerson(){}
}

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class Student extends Person {
constructor(name, age, sno, score){
super(name, age)
this.sno = sno
this.score = score
}
//重写
eating() {
console.log("override")
}

studying() {
console.log("study")
}
}

var s1 = new Student("alugg", 15, "xixi",150)
s1.studying()
s1.running()

类的混入minxin

Js只支持单继承.如何继承多个类

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
function mixinAnimal(BaseClass) {
return class extends BaseClass{
running() {
console.log("running")
}
}
}

function mixinFlyer(BaseClass) {
return class extends BaseClass {
flying() {
console.log("flyingg")
}
}
}

class Bird {
eating(){
console.log("eating")
}
}

var NewBird = mixinFlyer(mixinAnimal(Bird))
var bird = new NewBird()
bird.flying()
bird.running()
bird.eating()

多态

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
class Shape {
getArea() {}
}

class Rectange extends Shape {
constructor(width, height) {
super()
this.width = width
this.height = height
}

getArea() {
return this.width * this.height
}
}

class Circle extends Shape {
constructor(radius) {
super()
this.radius = radius
}

getArea() {
return this.radius * this.radius * 3.14
}
}

var rect1 = new Rectange(10,20)
var c1 = new Circle(10)

function getShapeArea(shape){
console.log(shape.getArea())
}

getShapeArea(c1)
getShapeArea(rect1)

对象字面量增强

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
var name = "why"
var age = 18
var key = "address"

var obj = {
//属性增强
name, //name:name
age, //age:age

//方法增强
swimming: function() {
console.log(this)
},
running() {
console.log(this)
},
eating: () => {
console.log(this)
}

//3.计算属性名
[key]:"xixihaha"
}

function foo() {
var message = "hello world"
var info = "my name is why"
return { message, info}
}

解构

解构复制可以将数组或对象拆包至一系列变量中

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
var names = ["abc", "cba", "nba", "xixiha"]
var obj = { name: "why", age:18, height:1.88 }
//1.1基本使用
var [name1, name2, name3] = names
console.log(name1, name2, name3)

//1.2顺序问题
var [name1, , name3] = names

//1.3 解构出数组
var [name1, name2, ...newNames] = names

//1.4 解构的默认值
var [name1, name2, name3 = "default"] = names

//2 对象的结构
var {name, age, height } = obj
console.log(name, age, height)

//2.2顺序问题 对象的结构不存在顺序
var {height, name, age} = obj
console.log(name, age, height)

//2.3重命名
var {height:wHeight, name:nName, age:wAge} = obj
console.log(wHeight, nName, wAge)

//2.4 默认值
var { height:wHeight, name:nName, age:wAge, adress:wAddress="CN"} = obj

let/const基本使用

let与var差不多

const

  • 保存的数据一旦被赋值就不能被修改
  • 但如果赋值的是引用类型,那么就可以通过引用找到对应的对象,修改对象的内容

二者不可以重复

let/const是否有作用域提升

作用域提升:在声明变了的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升

let,const虽然被创建除了了,但是不能被访问,我认为不能称之为作用域提升

暂时性死区

块作用域顶部一直到变量声明完成之前,这个变量处在暂时性死区.

暂时性死区和定义的位置没有关系,和代码执行顺序有关系

let/const不会添加window

函数声明,内置函数,var都添加到了window所在的对象式环境记录中

而let,const这些被添加到了声明式环境记录中

块级作用域

let,const,class, function都具有块级作用域的限制

但注意function在作用域外面依然可以访问

字符串模版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
//1.基本用法
const name = "alugg"
const age = 22
const info = `my name is ${name}, age is ${age}`
console.log(info)

//2.标签模板字符串的用法
function foo(...args) {
console.log("参数", args)
}

foo`my name is ${name}, age is ${age}`
</script>

模版字符串调用时如果有其他变量:

  • 模板字符串将会被拆分
  • 第一个元素是数组,是被插入模块拆分的字符串的组合
  • 后面的元素是一个个模块字符串传入的内容

函数默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo(args1="默认值", arg2) {
//默认值写法1:
arg1 = arg1 ? arg1:"我是默认值"
//默认值写法2:
arg1 = arg1||"我是默认值"
//严谨写法
agr1 = (arg1=== undefined || arg1 === null) ? "我是默认值":arg1
//ES6写法
arg1 = arg1 ?? "我是默认值"
//最简便:直接写入参数

//解构配合默认值
function alo({ name = "why", age = 18} = {}){
console.log(name, age)
}
  • 注意有默认参数的形参尽量写在后面
  • 有默认参数的形参是不会计算在length之内

展开语法

  • 可以在函数调用/数组构造时,将数组表达式或string在语法层面展开
  • 可以在构造字面量对象时,将对象表达式按key-value的方式展开
1
2
3
4
5
6
7
8
9
const obj = {
name: "why",
age: 18,
height:1.88
}

const info2 = {
...obj
}

Symbol

用来生成一个独一无二的值生成后可以用来做属性名

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
const s1 = Symbol()
const s2 = Symbol()
const obj3 = {
[s1]:"aa",
[s2]:"bb"
}

const obj2 = {}
obj1[s1] = "aaa"
obj2[s2] = "bbb"
Object.defineProperty(obj, s1, {
value:"aaa"
})

//2.获取symbol.key
const symbolKeys = Object.getOwnPropertySymbols(obj)
for (const key of symbolKeys) {
console.log(obj[key])
}

//3.description
//3.1Symbol函数之间生成的值都是独一无二的
const s3 = Symbol("ccc")
console.log(s3.description)
//3.2相同的key,通过Symbol.for可以生产相同的Symbol值
const s4 = Symbol.for(s3.description)
const s5 = Symbol.for(s3.description)
console.log(s5===s6)

//获取传入的key
console.log(Symbol.keyFor(s5))

Set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建Set
const set = new Set()

//添加元素
set.add(10)
set.add(12)

//应用场景 数组去重
const names = ["av","av","bas","sab","aa"]

const newNamesSet = new Set(names)
const newNames = Array.from(newNamesSet)
console.log(newNames)

//常见属性
console.log(set.size)

常见方法

  • add()
  • delete
  • has
  • clear
  • forEach(callback,[,args])

WeakSet

  • 只能放对象类型,不能存放基本数据类型
  • 对元素的引用是弱引用没如果没有其他引用对某个对象进行引用,GC可以对该对象进行回收
  • 不能遍历

常见方法

add

delete

has

Map

可以用对象作为key,弥补了对象的不足

1
2
3
4
5
6
7
8
9
<script>
const obj1 = {name:"why"}
const obj2 = {age:18}

const map = new Map()
map.set(obj1, "abc")
map.set(obj2, "cba")
console.log(map.get(obj1))
</script>

常见属性

size

常见方法:

set(key, value)

get(key)

has(key)

delete(key)

clear()

forEach(callback,[args])

WeakMap

  • key只能使用对象
  • key对对象的引用是弱引用

ES8

  • Object.values(obj) 获取所有值,补充了Object.keys

  • Object.entries()以数组形式获取一组组key values

字符串填充

  • padstart 应用场景:对时间进行填充,对敏感数据进行处理

ES10

  • flat(int 展开深度)的使用:将一个数组,按照指定的深度遍历,将遍历到的元素和子数组中的元素组成一个新数组

nums.flat(1)

flatMap().先map后flat

  • Object.fromEntries把一个entries转化成Object

ES11

  • Bigint
  • ??空值合并运算符, info = info?? “默认值”

ES12

  • FinalizationRegisry:可以让对象在被垃圾回收时请求一个回调函数
  • WeakRefs: obj = new WeakRef(info) 对info的一个弱引用
  • 逻辑赋值运算符: message || = “默认值” message ??=”默认值”
  • xxx.replaceAll(string1, string2):把字符串xxx中所有的sting1全部替换为string2

Proxy

监听对象的操作,有多个捕获器

set四个参数:

  • target:目标对象
  • property:将设置为key
  • value:新属性值
  • receiver:调用的代理对象

get函数有三个参数

  • target:目标对象
  • property:被获取的属性的key
  • receiver:调用的代理对象

监听对象

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
<script>
const obj = {
name: "why",
age: 18,
height:1.88
}

const objProxy = new Proxy(obj, {
set:function(target, key, newValue) {
console.log(`监听设置${key}的设置值`,newValue)
target[key] = newValue
},
get:function(target, key) {
console.log(`监听获得${key}`)
return target[key]
},
deleteProperty:function(target,key) {
console.log(`监听到删除${key}属性`)
},

has:function(target,key) {
console.log(`监听in判断${key}属性`)
return key in target
}


})

console.log(objProxy.age)
objProxy.name = "kk"
console.log(objProxy.name)
objProxy.address = "Pekin"
console.log(objProxy.address)

delete objProxy.name
console.log("age" in objProxy)
</script>

监听函数

apply, construct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo(num1,num2) {
console.log(this, num1, num2)
}

const fooProxy = new Proxy(foo, {
apply: function(target, thisArg, otherArgs){
console.log("监听执行了apply")
target.apply(thisArg, otherArgs)
},

construct: function(target, otherArray){
console.log("监听了New 操作")
return new target(...otherArray)
}
})
fooProxy.apply("abc", [111,222])

Reflect

减轻Object类的方法来操作对象,以前经常使用Object.xxx方法来操作对象,现在直接用Relfect.xx来操作方法.

Reflect常常和Proxy搭配使用,reflect常见方法和Proxy中的方法对应也是13个.

receiver指的就是objProxy,特定场景receiver可以排上用场.

场景:此时在Obj中有有对象访问器,给obj设置属性时.本质上是通过对象访问器set来进行赋值,但此时set中的this指向的是obj,并不是objProxy,只有对objProxy操作时才监听得到,虽然此时可以监听得到objProxy.name = “kobe”这一步,但内部的真正赋值this._name = newValue是无法监听到的,因此receiver可以把对象访问器中的this的指向改掉,改成objProxy

三大好处:

  • ​ .避免直接操作对象
  • ​ 有布尔返回值,可以判断本次操作是否成功
  • ​ receiver就是外层Proxy对象,可以决定对象访问器中setter/getter的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
<script>

const obj = {
_name: "why",
set name(newValue) {
console.log("this:", this)//默认是obj
this._name = newValue
},
get name() {
return this._name
}
}

const objProxy = new Proxy(obj, {
set: function(target, key, newValue, receiver) {
//1.避免直接操作对象
//2.有布尔返回值,可以判断本次操作是否成功
//好处3:receiver就是外层Proxy对象,可以决定对象访问器中setter/getter的this的指向
const isSuccess = Reflect.set(target, key, newValue, receiver)
if(!isSuccess) {
throw new Error(`set ${key} failure`)
}
console.log("Proxy设置被调用")
},
get: function(target, key, receiver){
console.log("Proxy获取被调用")
return Reflect.get(target, key,receiver)
}
})

objProxy.name = "kobe"
console.log(obj.name)
</script>

construct借用其他类的构造函数

1
2
3
4
5
6
7
8
9
10
11
function Person(name, age) {
this.name = name
this.age = age
}

function Student(name, age){
const _this = Reflect.construct(Person,["why", 18], Student)
//等同于Person.call(this, name, age)
}
const stu = new Student("whu", 18)
console.log(first)

Promise

解决异步函数调用的规范问题

三个状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
//括号里回调函数叫executor
const promise = new Promise((resolve, reject) => {
//状态一旦被确定就不会再更改,也不能再执行某一回调函数来改变状态
//1.待定状态pending
console.log(1112)
console.log(22233)
//2.兑现fulfilled
resolve("haha")
//or 拒绝状态rejected
reject("xixi")
})
promise.then(value=>{
console.log(value)
}).catch(err => {
console.log(err)
})
</script>

resolve传入值

  • 普通值或对象,那么这个值会作为then回调函数
  • 如果resolve传入的是另一个Promise,那么这个新Promise会决定原Promise的状态
  • 如果传入的一个对象中实现then方法,那么会执行该then方法,并根据then方法的结果来决定Promise的状态
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
const p = new Promise(() => {
setTimeout(() => {
resolve("p的resolve")
}, 2000)
})

//括号里回调函数叫executor
const promise = new Promise((resolve, reject) => {
//1.普通值
resolve([
{name:"macbook", price:998},
{name:"ipadpro", price:250},
])

//2.resolve(promise)
//如果resolve的值本身Promise对象,那么当前的Promise的状态会有传入的Promise来决定
resolve(p)

//3.resolve(thenable)
resolve({
then: function(resolve, reject) {
resolve("thenable value")
}
})
})
promise.then(value=>{
console.log(value)
}).catch(err => {
console.log(err)
})

then的返回值

  • 如果返回一个普通值,那么它处于fulfilled状态,并且会将结果作为resolve的参数

  • then方法是返回一个新的Promise,这个新Promise的决议是等到then方法传入的回调函数有返回值时,进行决议.

  • 返回一个thenable值,会调用then方法,决定状态

1
2
3
4
5
6
7
8
9
promise.then(res => {
console.log("第一个then",res)
return "xixi"
}).then(res => {
console.log("第二个then",res)
return "ccc"
}).then(res => {
console.log("第三个",res)
})

finally

无论是then还是reject,Finally必定会执行

1
2
3
4
5
6
7
8
9
10
11
const promise22 = new Promise((resolve, reject) => {
reject("bbb")
})

promise.then(res => {
console.log("then", res)
}).catch(err => {
console.log("catch", err)
}).finally(()=> {
console.log("abon")
})

类方法

resolve/reject

有时候已经有一个现成的内容希望将其转位Promise来使用,这个时候可以使用Promise.resolve方法来完成,因为不需要进行额外处理了,所以没必要再new一个对象来处理.(Promise.resolve的用法相当于new Promise并且执行resolve操作)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Promise.resolve("hello world").then(res => {
console.log("then结果",res)
})
//相当于
new Promise(resolve =>{
resolve("hello world")
})

Promise.reject("xixihaha").catch(err => {
console.log("rej结果",err)
})

真实开发场景:
const studentList = []
const promise = Promise.resolve(studentList)

promise.then(res => {
console.log("then结果",res)
})

all

  • 将多个Promise包裹在一起形成一个新的Promise
  • 新的Promise状态由包裹的所有Promise共同决定
    • 当所有Promise状态编程fulfilled状态时,新Promise状态为fulfilled,并且将所有Promise的返回值组成一个数组
    • 当有一个Promise状态为reject时,新的Promise状态为reject,并且将第一个rejct的返回值作为参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1 resolve")
},3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2 resolve")
},3000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 resolve")
},3000)
})
Promise.all([p1, p2, p3]).then(res => {
console.log("all promise res:",res)
})

allSettled

  • 会在所有Promise都有结果,无论是Fullfilled,还是Rejected时,才会有最终的状态

  • 并且这个Promise的结果一定是fulfilled

race

表示多个Promise相互竞争,谁想有结果,那么就使用谁的结果

可迭代对象

将Infos编程一个可迭代对象

  1. 必须实现一个特定的函数:[Symbol.iterator]
  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
26
27
28
29
30
31
32
33
34
35
36
37
38
<script>
const infos = {
friends: ["kobe", "james", "curry"],
[Symbol.iterator]: function() {
let index = 0
const infosIterator = {
next: () => {
// done: Boolean
// value:具体值/undefined
if (index < this.friends.length) {
return { done:false, value:this.friends[index++] }
} else {
return { done:true}
}
}
}
return infosIterator
}
}
//可迭代对象具有以下特点
const iterator = infos[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())

//可以使用for of操作
for(const item of infos){
console.log(item)
}

//可迭代对象必然包含一个[Symbol.iterator]函数
//数组是一个可迭代对象
const students = ["xixi", "haha","kk"]
console.log(students[Symbol.iterator])
const studentIterator = students[Symbol.iterator]()
console.log(studentIterator.next())


</script>

原生可迭代对象

String,Array, MapmSet, arguments对象,NodeList集合

应用场景

  • Js中的语法:for…of,展开语法,yield,解构赋值

  • 创建一些对象,new Map([Iterable]), new WeakMap([iterable]), new Set([iterable]), new WeakSet([iterable])

  • 一些方法调用:Promise.all(iterable), Promise.race(iterable), Array.from(iterable)

自定义类的对象迭代

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
<script>
class Person {
constructor(name, age, height, friends) {
this.name = name
this.age = age
this.height = height
this.friends = friends
}

// 实例方法
running() {}
[Symbol.iterator]() {
let index = 0
const iterator = {
next: () => {
if (index < this.friends.length) {
return { done: false, value: this.friends[index++] }
} else {
return { done: true}
}
}
}
return iterator
}
}
</script>

生成器

更加灵活的控制函数什么时候继续执行,暂停执行

生成器也是一个函数,但是和普通函数有一些区别:

  • 生成器函数需要阿紫function后面加一个*
  • 生成器函数可以通过yield来控制函数执行流程
  • 返回值是一个Genrator
    • 生成器事实上是一个特殊的迭代器

参数和返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
function* foo(name1) {
console.log("1111",name1);
console.log("222", name1);
const name2 = yield "aaa"
console.log("333",name2);
console.log("444",name2);
yield "bbb"
console.log("5555")
console.log("666")
return undefined
}
const generator = foo("第一次接受参数")
console.log(generator.next()) // {done: false, value:"aaa"}
console.log(generator.next("第二次接受参数")) // {done: false, value:"bbb"}
console.log(generator.next()) // {done: true, value:"undefined"}
</script>

提前结束

return传值后这个生成器函数就会结束,之后调用next不会继续生产值

1
console.log(generator.return("终止"))

生成器替换迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
const names = ["abc", "cba", "nba"]
const nums = [100, 22, 42,54,123]

function* createArrayIterator(arr) {
for (let i =0; i<arr.length; i++){
yield arr[i]
}
}
//使用语法糖
function* createArrayIterator(arr) {
yield* arr
}
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
</script>

生成器在类中替换迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
constructor(name, age, height, friends) {
this.name = name
this.age = age
this.friends = friends
}

*[Symbol.iterator]() {
yield* this.friends
}
}

const p = new Person("why",15,2.8,["curry","kobe","James"])
for (const item of p) {
console.log(item)
}

const pIterator = p[Symbol.iterator]()
console.log(pIterator.next())
console.log(pIterator.next())
console.log(pIterator.next())

异步处理

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
<script>
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 2000)
})
}

//需求:
/*
1.发送一次网络请求,等到这次请求的结果
2.结合第一次结果,发送第二次网络请求
3.发送第三次
*/

// 方式一:
function getData(){
//第一次
requestData("why").then(res1 => {
console.log("第一次结果",res1)
//第二次
requestData(res1+"kobe").then(res2 => {
console.log("第二次结果",res2)
//第三次
requestData(res2+"LA").then(res3 => {
console.log("第一次结果",res3)
})

})
})
}

//方式二 使用Promise进行重构
//链式调用
function getData() {
requestData("why").then(res1 => {
console.log("第一次结果",res1)
return requestData(res1 + "kobe")
//return结果会传给下一个then
}).then(res2 => {
console.log("第二次结果", res2)
return requestData(res1 + "james")
}).then(res3 => {
console.log("第三次结果" ,res3)
})
}

//方式三: 生成器
function* getData() {
//yield后的参数会作为返回对象的value

const res1 = yield requestData("why")
console.log("res1", res1)

const res2 = yield requestData(res1 + "kobe")
console.log("res2", res2)

const res3 = yield requestData(res2 + "james")
console.log("res3", res3)
}
const generator = getData()

generator.next().value.then(res1 => {
generator.next(res1).value.then(res2 =>{
generator.next(res2).value.then(res3 => {
generator.next(res3)
})
})
})


//方式四:
async function getData() {
const res1 = await requestData("why")
console.log("res1", res1)

const res2 = await requestData(res1 + "kobe")
console.log("res2", res2)

const res3 = await requestData(res2 + "james")
console.log("res3", res3)
}

const generato2 = getData()

async 的返回值

异步函数有返回值时

  • case1: 异步函数可以有返回值,但是异步函数返回值相当于被包裹在Promise.resolve中
  • case2:异步函数返回值是Promise,状态由Promise决定
  • case3:如果返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定
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
<script>
async function foo2() {

//1.返回一个普通值
// return ["abc", "cba","nba"]
// return 31

//2.返回一个promise
// return new Promise((resolve, reject) => {
// setTimeout(()=>{
// resolve("aaa")
// },3000)
// })

//3.返回一个thenable对象
return {
then: function(resolve, reject) {
setTimeout(() => {
resolve("bbb")
}, 2000)
}
}
}

foo2().then(res => {
console.log("res", res)
})
</script>

async的异常

如果在async中抛出异常,那么程序并不会像普通函数一样报错,而是会作为Promise的reject来传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//如果异步函数中抛出异常,这个异常不会立即被浏览器处理
//进行处理:Promise.reject(err)
async function foo3() {
console.log("---1")
console.log("---2")
"abc".filter()
console.log("---3")
}

foo3().then(res => {
console.log("res", res)
}).catch(err => {
console.log("err", err)
})

await关键字使用

  • 只能在异步函数中使用
  • await后面会跟上一个表达式,这个表达式会返回一个Promise
  • awit会等到Promise变成fulfilled状态,之后再继续执行异步函数
  • await也可以直接跟上一个异步函数调用(因为异步函数本来就直接返回一个Promise)

如果await后面返回的是reject,那么就得捕获

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
function bar_resolve() {
return new Promise(resolve => {
setTimeout(() =>{
resolve(123)
},3000)
})
}

function bar_reject() {
return new Promise( reject => {
setTimeout(() =>{
reject("error message ")
},3000)
})
}

async function foo() {
console.log("----")
const res1 = await bar_resolve()
console.log(res1)
console.log("++++")
const res2 = await bar_reject()
console.log(res2)
console.log("++++")
}
foo().catch(err =>{
console.log("err",err)
})

await和async结合使用

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
function why() {
return new Promise((resolve) => {
setTimeout(()=>{
resolve(123)
},2000)
})
}

async function test() {
console.log("test function");
return test
}

async function bar() {
console.log("bar fun");
return new Promise((resolve) => {
setTimeout( () => {
resolve("bar")
}, 2000)
})
}

async function demo() {
console.log("demo fun");
return new Promise((resolve) => {
setTimeout( () => {
resolve("demo")
}, 2000)
})
}

async function foo() {
const res1 = await why()
console.log(res1);
const res2 = await test()
console.log(res2);
const res3 = await bar()
console.log(res3);
}
foo()

微任务和宏任务(中级面试会考察)

macrotask: settimeout, ajax,setInterval,DOM监听, UIO rendering

microtask:Promise的then中的回调函数,Mutation Observer API, queueMicrotask()

浏览器执行顺序:在执行微任务优先.

错误处理

抛出异常

Error对象包含三个属性

  • message:创建Error对象时传入的message
  • name:Error的名称,通常和类的名称一致
  • stack:整个Error的错误信息,包括函数调用栈,当我们直接打印Error对象时,打印的就是stack

Error的子类

  • RangeError:下标值越界时使用的错误类型
  • SyntaxError:解析语法错误时使用的错误类型
  • TypeError出现类型错误时,使用的错误类型
1
2
3
4
5
6
7
8
9
<script>
function foo() {
"abc".filter()
// throw {errorMessage:"错误信息"}
throw new Error("我是错误信息")
}
foo()
console.log("+++++");
</script>

捕获异常

1
2
3
4
5
6
7
8
try{
foo()
console.log("+++++")
}catch(error){
console.log(error);
} finally{

}

Storage

  • lcoalStorage:本地存储,提供的是一种永久性存储方法,在关闭掉网页重新打开时,存储的内容依然保留
  • sessionStorage:会话存储,提供的是本次会话的存储,在关闭掉会话时,存储的内容会被清除掉

二者区别

  • 关闭网页后重新打开,localStorage会保留,sessionStorage会被删除
  • 页面内实现跳转,localStorage会保留,sessionStorage也会保留
  • 页面外实现跳转(打开新网页),localStorage会保留,sessionStorage不会被保留
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let token = localStorage.getItem("token")
if(!token){
clg("服务器获取token")
localStorage.setItem("token", token)
}
let username = localStorage.getItem("username")
let password = localStorage.getItem("password")
if(!username || !password){
console.log("用户输入密码")
username = "coderwhy"
password = "12345"
loaclStorage.setItem("username", username)
loaclStorage.setItem("password", password)
}


console.log(token)

常见方法

  • setItem()
  • key()
  • removeItem()\
  • clear()

通常来说不是直接使用这些方法,而是封装成一个类来使用.

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 Cache {
constructor(isLocal = true) {
this.storage = isLocal ? localStorage: sessionStorage
}
setCache(key,value) {
this.storage.setItem(key, JSON.stringify(value))
}

getCache(key) {
const result = this.storage.getItem(key)
if (result) {
return JSON.parse(result)
}
}

removeCache(key) {
this.storage.removeItem(key)
}

clear() {
this.storage.clear()
}
}

const localCache = new Cache()
const sessionCache = new Cache(false)

正则表达式

1
2
3
4
5
//创建正则
// 1>匹配的规则 pattern
//2> 匹配的修饰符flags
const re1 = new RegExp("hello","i")
const re2 = /hello/i

运用的方法

  • exec,test来自于RegExp的方法
  • String中match,matchAll,search,replace,split

防抖和节流函数库(underscore)

防抖debounce

  • 事件触发时,相应的响应函数不会立即出发,而是会等待一定时间;

  • 当时间密集触发时,函数的触发会被频繁的推迟

  • 只有等待了一段时间也没有事件触发,才会真正的执行响应函数

应用场景:

  • 输入框频繁的输入内容,搜索或提交信息
  • 频繁的点击按钮,触发某个事件
  • 监听浏览器滚动事件,完成某些特定操作
  • 用户缩放浏览器resize事件

节流(throttle)

  • 事件触发时,会执行这个时间的响应函数
  • 如果这个事件会被频繁出发,那么节流函数会按照一定频率来执行函数
  • 不管在这个中间有多少次触发这个时间,执行函数的频率总是固定的

应用场景:

  • 监听页面的滚动事件
  • 鼠标移动事件
  • 用户频繁点击按钮操作
  • 游戏中的设计
    • 飞机大战,空格发射一个子弹,即使按下的频率非常快,子弹也会保持一定的频率来发射.

深浅拷贝

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
<script>
const info = {
name: "why",
age: 18,
friend: {
name: "kobe"
}
}

//1.引用赋值:
const obj1 = info

//2.浅拷贝
const obj2 = { ...info }
// obj2.friend.name = "james"
console.log(info.friend.name);

const obj3 = Object.assign({}, info)
// obj3.friend.name = "curry"
console.log(info.friend.name);

//3.深拷贝
//3.1 JSON方法
//缺陷:无法处理函数,和Symbol属性,对象的循环引用,也会报错
const obj4 = JSON.parse(JSON.stringify(info))
info.friend.name = "curry"
console.log(obj4.friend.name);
</script>

深拷贝:基于遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function isObject(value) {
//null -> object
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}

function deepCopy(originValue) {
//1.是否为原始类型
if(!isObject(originValue)){
return originValue
}

const newObj = {}
for (const key in originValue){
newObj[key] = deepCopy(originValue[key]);
}
return newObj
}

const newObj = deepCopy(pers1)
console.log(newObj);

事件总线

跨组件之间进行相应

XHR

请求发送

四步完成

  • 创建网络请求的AJAX对象
  • 监听XMLHttpRequest对象状态的变化,或监听onload事件
  • 配置网络请求(通过open方法)
  • 发生send网络请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
//1.创建XMLHttpRequest对象
const xhr = new XMLHttpRequest()

//2.监听状态的改变(宏任务)
xhr.onreadystatechange = function(){
console.log(xhr.response);
const resJSON = JSON.parse(xhr.response)
}
//3.配置请求open
//参数 method,url, async:true默认,异步请求
xhr.open("get","http:xxxx")

//4.发送请求
xhr.send()
</script>

XHR的state

0:UNSENT:代理被创建,但尚未调用open()方法

1:OPENED:open()方法已经被调用

2:HEADERS_RECEIVED:send()方法已经被调用,并且头部和状态已经可获得

3:LOADING:下载中,responseTEXT已经包含部分数据

4:DONE:下载操作已完成

XHR其他时间监听

除了onreadystateechange还有其他时间可以监听

  • loadstart:请求开始
  • progress:一个响应数据包到达,此时整个response body都在response中
  • abort:调用xhr.abort()取消了请求
  • error:发生连接错误,例如,域错误.不会发生诸如404这类的HTTP错误
  • load:请求成功完成
  • timeout由于请求超时而取消了请求
  • loadend:在load,error,timeout或abort之后触发

响应数据和响应类型

如果告知类型和实际类型不符,则取到的数据为null

1
xhr.responseType="json" //告诉xhr获取到的数据的类型是json,不然默认是text

获取Http statuts

1
2
3
4
5
6
7
8
xhr.onload = function() {
if(xhr.status >=200 && xhr.status<300){
console.log(xhr.response)
} else {
console.log(xhr.status, xhr.statusText)
}

}

客户端传递参数的四种方式

  • GET请求的query参数
  • POST请求x-www-form-urlencoded格式
  • POST请求FormData格式
  • POST请求JSON格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
//方式1:get -> query
xhr.open("get","http://123.2021.32.32/get?name=alugg&age=18")

//方式2:post -> urlencoded
xhr.open("post","23.2021.32.32/posturl")
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")

//方式3:post -> formdata
xhr.open("post","23.2021.32.32/postformxx")
const formData = new FormData(formEl)//formEL:表单Dom对象
xhr.send(formData)

//方式4:post -> json
xhr.open("post","23.2021.32.32/postjson")
xhr.setRequestHeader("Content-type", "application/json")
xhr.send(JSON.stringify({name:"why", age:18, height:1.88}))
</script>

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function hyajax({
url,
method = "get",
data = {},
timeout = 10000,
headers = {},
success
} = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()

xhr.onload = function() {
if (xhr.status >= 200 && xhr.status<300){
resolve(xhr.response)
} else {
reject ({status:xhr.status, message: xhr.statusText})
}
}

xhr.responseType = "json"

if (method.toUpperCase() === "GET") {
const queryStrings = []
for (const key in data) {
queryStrings.push(`{key}=${data[key]}`)
}
url = url + "?" + queryStrings.join("&")
xhr.open(method, url)
xhr.send()
} else {
xhr.open(method, url)
xhr.setRequestHeader("Content-type", "application/json")
xhr.send(JSON.stringify(data))
}
})
}

hyajax({
url:"http://123123/hjoms/",
method: "GET",
data:{
name:"xixi",
age:18
}
}).then(res => {
console.log("res:", res);
}).catch(err => {
console.log("err", err);
})

超时设置

1
xhr.timeout =3000

文件上传

XHR可以监控上传进度

Fetch不能

Fetch

可以看作是XHR的一种替代方案

  • 返回值是一个Promise,请求成功时调用resolve回调then,请求失败,调用reject回调catch
  • 不像XMLHttpRequest一样,所有操作都在一个对象上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//1.fetch发送get请求
async function getData() {
const response = await fetch("xxurl")
const res = await response.json()
console.log("res", res);
}
//2.fetch发送Post
async function getData2(){
const response = await fetch("urlxx", {
method: "post",
headers: {
"Content-type":"application/json"
},
body: {
name:"why",
age: 18
}
})

const res = await response.json()
console.log(res);
}

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
// 使用组件重构  
//类组件和函数组件
<script type="text/babel">
class App extends React.Component {
//组件数据
constructor() {
super();
this.state = {
message: "hello wolrd",
name: "why"
};
}

//组件方法
btnClick() {
//setState内部完成两件事:1.将message修改 2.自动执行render
this.setState({
message: "hello react"
})
}

//渲染内容render
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.btnClick.bind(this)}>修改文本</button>
//注意必须绑定好this
</div>
)
}
}

const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<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
26
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"))

class App extends React.Component {
constructor() {
super()

this.state = {
movies: ["xixi","haha", "zeze"]
}
}

render() {
return (
<div>
<h2>Movie List</h2>
<ul>
{this.state.movies.map(movie => <li>{movie}</li>)}
</ul>
</div>
)
}
}


root.render(<App/>)

JSX书写规范

  • jsx结构中只能有一个根元素

  • jxs结果通常会包裹一个(),将jsx当做一个整体实现换行

  • 标签可以是单标签,但必须以/>结尾

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    render() {
    const { message } = this.state

    return (
    <div>
    <h2>{message}</h2>
    <br/>
    </div>
    )
    }

jsx的使用

jsx嵌入变量作为子元素

  • case1:Number, String, Array类型时,可以直接显示

  • case2:null,undefined,Boolean类型时,内容为空

    如果希望可以显示null,undefined,boolean那么需要转成字符串(调toString,或+””)

  • case3:Object对象类型不能作为子元素

  • case4:可以插入表达式(运算表达式,三元运算符,执行一个函数)

基本属性绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class App extends React.Component {
constructor() {
super()
this.state = {
title:"hahaa",
imgURL: "xxxx"
}
}

render() {
const { title,imgURL } = this.state
return (
<div>
<h2 title={title}>I'm h2</h2>
<img src={imgURL} />
</div>
)
}
}

类绑定

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
<script type="text/babel">
class App extends React.Component {
constructor() {
super()
this.state = {
isActive: true,
objStyle:{color:"red", fontsize:"30px"}
}
}

render() {
//需求isActive:true -> active
const { isActive, objStyle } = this.state
//方式1
const className = `abc cba ${isActive ? 'active':''}`
//方式2:
const classList = ["abc", "cba"]
if(isActive) classList.push("active")
//方式3 使用classnames

return (
<div>
<h2 className={className}>I'm h2</h2>
<h2 className={classList.join(" ")}>I'm h2</h2>

<h2 style={objStyle}></h2>
</div>
)
}

React事件绑定

三种方式解决this问题

  • bind给btnClick显示绑定this
  • 使用ES6 class fields语法
  • 事件监听时传入箭头函数(最优)
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
<script type="text/babel">
class App extends React.Component {
constructor() {
super()
this.state = {
message:"hello world",
counter:1000
}
this.btn1Click = this.btn1Click.bind(this)
}

btn1Click() {
console.log("btn1Clik", this);
this.setState({counter: this.state.counter+1})
}

btn2Click = () => {
console.log("btn2Clik", this);
this.setState({counter: 1000})
}

btn3Click() {
console.log("btn3", this);
this.setState({counter: 3})
}
render() {
const { message } = this.state
return (
<div>
{/* 方式一:bind绑定*/}
<button onClick={this.btn1Click}>按钮1</button>
{/* 方式二:ES6 class fields*/}
<button onClick={this.btn2Click}>按钮2</button>
{/* 方式三:直接传入一个箭头函数*/}
<button onClick={() => this.btn3Click()}>按钮3</button>
<h2>当前计数:{this.state.counter}</h2>
</div>
)
}
}

基本参数传递

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 App extends React.Component {
constructor() {
super()
this.state = {
message:"hello world",
}
}

btnClick(event, name, age) {
console.log("btnClick", event, this);
console.log(name, age);
}

render() {
return (
<div>
{/* event参数的传递*/}
<button onClick={this.btnClick.bind(this)}>按钮1</button>
<button onClick={(event) => this.btnClick(event)}>按钮2</button>

{/*额外的参数传递*/}
<button onClick={(event) => this.btnClick(event, "why", 18)}>按钮</button>
</div>
)
}
}

条件渲染

  • 条件判断
  • 三元运算
  • 与运算&&
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
class App extends React.Component {
constructor() {
super()
this.state = {
message:"hello world",
isReady: false,
friend:{
name:"kobe",
desc:"xixihaha"
}
}
}

render() {
const { isReady,friend } = this.state
let showElement = null
if (isReady) {
showElement = <h2>开始战斗</h2>
} else {
showElement = <h2>做好准备</h2>
}
return (
<div>
{/*方式一:根据条件给变量赋不同的内容*/}
<div>{showElement}</div>
{/*方式二 :三元运算*/}
<div>{isReady ? <button>开始战斗</button>:<h2>做好准备</h2>}</div>
{/*方式三 :&&逻辑与运算*/}
{/*friend没取到值时为null,就不显示,取到了再显示*/}
<div>{ friend && <div>{friend.name+" "+friend.desc}</div>}</div>
</div>
)
}
}

列表渲染

展示:map

过滤:filter

截取数组:slice

注意每个列表的子元素需要添加一个key

  • 主要是提高diff算法的效率
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
class App extends React.Component {
constructor() {
super()
this.state = {
students:[
{id:111,name:"haha", score:12},
{id:222, name:"yy", score:13},
{id:333,name:"xxx", score:9},
{id:444,name:"a12", score:14},
]
}
}

render() {
const { students } = this.state
// const filterStudents = students.filter(item => {
// return item.score>10
// })
// const sliceStudents = filterStudents.slice(0,2)
return (
<div>
<h2>学生列表数据</h2>
<ul>{
Students.filer(item => item.score >10).slice(0,2).map(item => {
return (
<div class="item" key={item.id}>
<h2>学号:{item.id}</h2>
<h2>姓名:{item.name}</h2>
<h2>分数:{item.score}</h2>
</div>
)
})}
</ul>
</div>
)
}

JSX的本质

可以通过Babel官网查看

jsx其实就React.createElement(component, props, …children)函数的语法糖

  • 所有的jsx最终都会被转换成React.createElement的函数调用

createElement需要传递三个参数

  • type:
    • 当前ReactElement的类型
    • 如果是标签元素,那么就使用字符串表示’div’
    • 如果是组件元素,那么就直接使用组件的名称
  • config:
    • 所有jsx中的属性都在config以对象的属性和值的形式存储
    • 比如传入className作为元素的class
  • children
    • 存放在标签中的内容,以children数组的方式进行存储

虚拟DOM

通过React.createElement最终创建出来一个ReactElement对象

这个对象组成了一个JS的对象树,也就是虚拟DOM

目的:传统Web会把数据的变化时时刚更新到用户界面,每次微小的变动都会引起DOM树重新渲染

虚拟DOM是将所有操作累加起来,基于diff算法,统计计算出所有变化后,统一更新一次DOM.虚拟DOM还可以做到跨平台,利用REACT渲染到Web端,利用其他的可以渲染到ios端

购物车案例

值得注意点:

  • 数量的增删可以抽取到一个事件,通过加+1或-1来实现增删
  • 条件渲染时,可以尝试代码抽取到两个函数,一个购物车不为空,购物车为空,最后在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
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
  class App extends React.Component {
constructor() {
super()
this.state = {
message:"hello world",
books:books
}
}

changeCount(index, count) {
const newBooks = [...this.state.books]
newBooks[index].count += count
this.setState({ books: newBooks })
}

removeItem(index) {
const newBooks = [...this.state.books]
newBooks.splice(index, 1)
this.setState({books: newBooks})
}

renderBookList() {
const { books } = this.state
const totalPrice = books.reduce((preValue, item) => {
return preValue + item.count * item.price
},0)
return <div>
<table>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>日期</th>
<th>价格</th>
<th>数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{
books.map((item,index) => {
return (
<tr key={item.id}>
<td>{index+1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{item.price}</td>
<td>
<button onClick={() => this.changeCount(index,-1)} disabled={item.count <= 1}>-</button>
{item.count}
<button onClick={() => this.changeCount(index, 1)}>+</button>
</td>
<td><button onClick={() => this.removeItem(index)}>删除</button></td>
</tr>
)
})
}
</tbody>
</table>
<h2>总价格:{totalPrice}</h2>
</div>
}

renderBookEmpty() {
return <div><h2>购物车空</h2> </div>
}

render() {
const { books } = this.state
return books.length ? this.renderBookList() : this.renderBookEmpty()
}

}
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App/>)
</script>