# 1.作用域

1.全局作用域
<script>标签和.js文件的最外层就是全局作用域,在全局作用域中声明的变量,任何其他作用域都可以被访问。
注意:window对象的属性也为全局变量 函数中未使用任何关键字声明的变量也为全局变量

2.局部作用域
局部作用域分为函数作用域和块作用域
(1)函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
   //函数的参数也是函数内部的局部变量,不同函数内部声明的变量无法互相访问,函数执行完毕后,函数内部的变量实际被清空。2)块作用域
在javascript中使用"{}"包裹的代码被称为代码块,使用let/const声明的变量,在{}中会产生块作用域  对象不形成作用域?
块级作用域举例:
// if 和 for 是语句,var不会产生作用域,但是内部使用let声明变量,会产生块级作用域
1for (let i = 0; i <= 3; i++) {
        // 块级作用域
        console.log(i); // 0 1 2 3
      }
2. if (true){
          let i = 10
      }
      console.log(i) //undefined


// Scope 作用域  
// Global 全局 
// Local 局部作用域
// Block 块级作用域
// Closure 闭包 
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

# 2.let const var 的区别 (易考面试题) ⭐⭐⭐⭐⭐

1.let / const 能形成块级作用域, var不可以
2.let / const 不能变量提升,var可以
3.let / const 不能声明前使用,var可以
  let / const 存在暂时性死区(temporal dead zone)
4.let / const 不能重复声明, var可以
5.在浏览器中, vars声明的全局变量会挂载在window对象上, let / const不会
6.const声明后必需马上赋值
7.const声明的变量不能改变值(简单数据类型不能改变值,引用数据类型不能改变地址)
1
2
3
4
5
6
7
8

# 3.作用域链

作用域链本质上是底层的变量查找机制

即函数在执行的时候,优先在当前函数作用域查找变量,如果当前函数作用域找不到,则会依次逐级到父级作用域乃至全局作用域中去查找
1
2
3

# 4.垃圾回收机制和内存泄漏

垃圾回收机制:Garbage Collection (简称GC)
JS的内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
全局变量一般不会回收(关闭页面回收),一般情况下,局部变量的值不用了,就会被自动回收

内存泄漏:不再用到的内存,没有及时释放。
1
2
3
4
5

# 5.浏览器垃圾回收算法:引用计数法和标记清除法

1)引用计数法
算法:
      // 1. 跟踪记录每个值被引用的次数。
      // 2. 如果这个值的被引用了一次,那么就记录次数1
      // 3. 多次引用会累加。
      // 4. 如果减少一个引用就减1。
      // 5. 如果引用次数是0 ,则释放内存。
缺陷: 循环引用
      即堆内存空间的对象互相引用,计数永远也不能为0 无法被回收,内存泄漏。
      
 (2)标记清除法
   // 标记清除算法
      // 主要将GC的过程分为两个阶段
      // 1. 标记阶段:标记空间中的活动对象和非活动对象
      // 2. 清除阶段:回收非活动对象,也就是销毁非活动对象

      // 标记阶段从一组根元素开始,
      // 递归遍历(一层一层的)这组根元素,
      // 在这个遍历过程中,能访问到的元素称为活动对象,不能访问到的可以判断为垃圾数据。

      缺陷: 内存碎片化  解决方式:标记整理法 Mark-compact
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 6.闭包 (易考面试题)⭐⭐⭐⭐⭐

闭包:内层函数引用外层函数的变量的集合。
闭包 = 内层函数 + 外层函数的变量
闭包的基本格式:
 function out() {
        let a = 10
        function innner() {
          console.log(a)
        }
        return innner
      }
      out()
闭包的作用:
1.可以让外部作用域访问到函数内部的变量
2.实现了数据的私有化

闭包的缺陷:会造内存泄漏

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

# 7.arguments动态参数

// arguments他是函数独有的,内置的(默认就有的)一个对象
      // 伪数组:属性名为索引,有length,没有pop push等数组的方法
      //   它接收了函数调用的时候 传过来的所有实参
      function sum(a, b, c, d, e) {
        console.log(arguments) //伪数组:[]
        console.log(arguments.length)
        let count = 0
        for (let i = 0; i < arguments.length; i++) {
          count += arguments[i]
        }
        console.log(count)
        // 错误演示:
        // arguments.push(999)
      }
      sum(1, 2, 3, 4, 5)
      sum(1, 2)
      sum(1, 2, 3, 4, 5, 6, 7, 8, 9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 8.rest剩余参数

 // 剩余参数
      // ...变量名
      // arr是一个真数组,存的是剩下的实参
      // ...arr只能放在形参位置的最后面
      function getSum(...arr) {
        console.log(arr)
        let c = 0
        arr.forEach((item) => (c += item))
      }
      //   getSum(3, 4, 5, 6) // [3,4,5,6]
      //   getSum(6, 8, 5, 6, 4, 43, 2) // [6, 8, 5, 6, 4, 43, 2]

      function getSum1(a, b, ...arr1) {
        console.log(arr1)
      }
      getSum1(3, 4, 5, 6)  // [5,6]
      getSum1(3, 4, 5, 6, 6, 7, 3) //[5, 6, 6, 7, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 9.扩展运算符 : ...

用途1:求最值
let arr1 = [3,4,5,6,7,8,9]
console.log(Math.max(...arr1)) 


用途2:赋值数组 合并数组
(1)赋值数组
      // const a1 = [2,3]
      // const a2 = a1
      // a2[0] = 999
      // console.log(a1); [999,3]  //a2改变 a1也会改变

      // const a3 =[5,6,7]
      // const a4 = [...a3]
      // console.log(a4);
      // a4[0]= 888
      // console.log(a3); //[5,6,7]
      // console.log(a4); //[888,6,7] //a4改变 a3不会改变2)合并数组
      //   const a5 =[5,6,7]
      //   console.log([...a5]);  // [5,6,7]
      //   const a6 =[8,9,10]
      //   const newArr = a5.concat(a6)   //concat 合并数组
      // //   console.log(newArr); // [5, 6, 7, 8, 9, 10]

      //   const newArr2 =[...a5,...a6]
      //   console.log(newArr2);

用途3:字符串展开与反转
const str = "hello world";
        //   console.log(str); //hello world
        // console.log(...str); //h e l l o   w o r l d
        // console.log([...str]);
        // console.log(str.split(''));  //将 hello world 用空格隔开 并转为一个数组
        // console.log(str.split('').reverse());  //数组顺序翻转
        // console.log(str.split('').reverse().join('')); //将字符连接起来

        // console.log([...str].reverse().join(''));

        // const str1 = "h?e?l?l?o89dw?d"
        // console.log(str1.split('?'));  //表示str1字符串用?隔开

用途4:伪数组转换为真数组
      // const divs = document.querySelectorAll("div"); //伪数组
      // console.log(divs);
      // divs.push(666)  //报错  divs为伪数组
      // const divs1 = [...divs]; //将divs转变为真数组
      // console.log(divs1);
      // divs1.push(666);
      // console.log(divs1); //有效

      // const divs2 = Array.from(divs)  //Array.form(伪数组) :将伪数组转换为真数组
      // console.log(divs2);
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

# 10. 剩余参数 与 扩展运算符的区别

image-20221223192726348


# 11.数组解构

含义: 数组解构就是将数组的单元值赋值给一系列变量的简洁语法

基础语法:
const [a.b,c] = [1,2,3]
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3

经典用法:交互2个变量
let a = 1
let b = 3; // 此处必有分号
[b,a] = [a,b]
console.log(a) // 3
console.log(b) // 1

不同情况下的数组结构用法
(1) 变量多 单元值少的情况
const [a,b,c,d] =['小米''苹果','华为']
console.log(a) // 小米
console.log(b) // 苹果
console.log(c) // 华为
console.log(d) // undefined

(2)变量多 单元值少的情况
const [a,b,c] = ['小米''苹果','华为','格力']
console.log(a) // 小米
console.log(b) // 苹果
console.log(c) // 华为

(3)利用剩余参数解决变量少 单元值多的情况:
const [a,b,...tel] = ['小米''苹果','华为','格力','vivo']
console.log(a) // 小米
console.log(b) // 苹果
console.log(tel) // ['华为','格力','vivo']

(4) 按需导入,忽略某些返回值:
const [a,,c,d] = ['小米''苹果','华为','格力']
console.log(a) // 小米
console.log(c) // 华为
console.log(d) // 格力

(5)多维数组解构
const [a,[b,c]] = ['苹果'['小米','华为']]
console.log(a) // 苹果
console.log(b) // 小米
console.log(c) // 华为
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

# 12.对象解构

对象解构:将对象的属性和方法快速批量赋值给一系列变量的简洁语法。

const obj = {
        name: '仙女',
        age: 18,
      }
      //   let age = 19
      //   obj.name
      // 对象的解构赋值,是按照属性名来解构的,可以交换位置
      // 解构属性的时候重命名,语法=>旧属性名:新属性名
      const { age: newAge, name } = obj
      console.log(name, newAge) // 仙女 18
      // console.log(name,age); //age is not defined

多级对象解构:
const pig = {
        name: 'xxx',
        sss: {
          xx: 23,
          uu: '灌灌灌灌',
          demo: {
            test: 345,
          },
        },
        age: 90,
      }
      // 分步解构
      //   const { name, age, sss } = pig
      //   const { xx, uu, demo } = sss
      //   const { test } = demo

      // 分析步骤
      //   const { name, age, sss:newSSS } = pig
      //   const { xx, uu, demo:demo1 } = newSSS
      //   const { test } = demo1

      // 合并:
      const {
        name,
        age,
        sss: {
          xx,
          uu,
          demo: { test },
        },
      } = pig

      console.log(name,age,xx);
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

# 13.箭头函数的this ⭐⭐⭐⭐⭐

箭头函数没有arguments对象
箭头函数不会创建自己的this,它只会从自己作用域的上一层沿用this1const sayHi = ()=>{
    console.log(this)  // window
}

(2)
btn.addEventListener('click',()=>{
    console.log(this)  // window
})
//此处回调函数若是普通函数 this指向的则是btn3const user ={
    name:"小明"walk:()=>{
        console.log(this)   // window   
    }
}
user.walk()
//此处若对象的方法使用的是普通函数 ,则this指向user
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 14.创建对象的三种方式

1.按照字面量的方式
const obj ={
    name:'abc'
}

2.new Object()
const obj1 = new Object() // 创建了一个空对象
obj1.name = '444'

 const obj2 = new Object({ name: '0000', age: 20 })

3.通过构造函数创建对象
function Person(age) {
        this.name = 'ddd'
        this.age = age
        this.sayHi = function () {
          console.log('哈哈哈')
        }
      }
      const p = new Person(18)
      const p1 = new Person(17)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 15.构造函数执行new的过程 (易考面试题) ⭐⭐⭐⭐⭐

1.创建一个空对象
2.this指向这个空对象 const this ={}
3.给这个空对象添加方法和属性
4.return this 返回这个对象(this)
1
2
3
4

# 16.构造函数原理

构造函数:即把多个类型对象的公共属性、公共方法抽取放到一个函数里,这个函数即为构造函数,它用来初始化对象,可以快捷创建多个类似的对象。
构造函数约定:
①构造函数的命名必须以大写字母开头
②构造函数只能由"new"操作符来执行

实例化:使用new关键字调用函数的行为
1
2
3
4
5
6

# 17.实例成员 -- 静态成员

实例成员:实例对象的属性和方法
静态成员:构造函数的属性和方法  console.dir()才可看到

function Person(){
    //实例对象动态添加属性
    this.name ='小明'
    this.age = 18
    //实例对象动态添加方法
    this.sayHi = function(){
        console.log('大家好')
    }
}
//静态属性
Person.eyes = 2
//静态方法
Person.walk = function(){
    console.log('it's the beginning)
}

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

# 18.Object的三个常用静态方法

1. Object.keys(数组名)  //获得对象的所有键
2. Object.values(数组名) //获得对象的所有值
3. Object.assign(拷贝数组名,被拷贝数组名)  //对象拷贝
1
2
3

# 19.Array的常用实例方法

image-20221222170110705

arr.reduce(function(累计值,当前元素[,索引号][,源数组]){},起始值)
arr.every(cbfn) :如果所有元素都满足条件,返回true
arr.some(cbfn):只要有一个元素满足条件,返回true
arr.find(cbfn)
arr.findIndex(cbfn)
1
2
3
4
5

# 20.Array的常见静态方法

Array.from()  :将伪数组转换为真数组
Array.isArray() :判断是否为数组  返回truefalse
1
2

# 21.String的常见实例方法

str.split('分隔符') // 将字符串拆分为数组   与 arr.join('连接符') 相反
str.substring(需要截取的第一个字符的索引号[,结束的索引号]) //结束的索引号不包括被截取的字符 ,如果没有结束的索引号,则默认截取到最后一个字符
str.startsWith(检测字符串[,检测位置索引号]) // 检测是否以某字符开头
str.includes()//判断字符串中是否包含某个子字串
1
2
3
4

# 22.Number的常见实例方法

num.toFixed(保留的小数位的长度,四舍五入)
1

# 23.JS原型五条规则(必背)⭐⭐⭐⭐⭐

1.所有的引用类型(数组,函数,对象),都具有对象的特性,可以自由扩展属性。
const obj = {} // obj.a =100
const arr = [] // arr.a =100
function fn(){} // fn.a = 100

2.所有的对象,都有一个__proto__属性,属性值是一个普通对象。(__proto__ ==>[[prototype]] 隐式原型)
obj.__proto__
arr.__proto__
fn.__proto__

3.所有的函数,都有一个prototype属性,属性值是一个普通对象 (prototype属性 ==>显示原型)
fn.prototype  // 箭头函数没有prototype属性

4.所有对象的隐式原型(__proto__)指向它的构造函数的显式原型(prototype)
obj.__proto__ === Object.prototype
arr.__proto__ === Array.prototype
fn.__proto__ === Function.prototype

5.当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么去它的__proto__中寻找。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 24.构造函数存在的问题 以及引入原型解决

// 构造函数存在浪费内存的问题(方法)
        function Star(name, age){
            this.name = name 
            this.age = age 
            this.sing = function(){
                console.log('唱歌')
            }
        }
        const kk = new Star('坤坤', 18)
        const jl = new Star('杰伦', 20)
        console.log(kk)

        kk.sing()
        jl.sing()
        console.log(kk.sing === jl.sing) // false
        // 每创建一个对象,都会在堆内存中新开辟一个空间,存储方法
        // 这个时候,就存在浪费空间的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

image-20221223155632231

function Star(name, age){
            this.name = name 
            this.age = age 
            // this.sing = function(){
            //     console.log('唱歌')
            // }
        }
        // 公共的方法写到原型上
        Star.prototype.sing = function(){
            console.log('唱歌')
        }

        const ldh = new Star('刘德华', 20)
        const zxy = new Star('张学友', 18)
        ldh.sing()
        zxy.sing()

        // sing是一个方法,引用类型,比较的是地址是否相等,是否是同一个对象
        console.log(ldh.sing === zxy.sing) // true 表示内存上是同一个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

image-20221223155716544


# 25. 原型prototype

原型:原始的模型
在JS里 原型就是一个对象,叫做原型对象  : 构造函数。prototype

1.所有函数都有一个prototype属性(显式原型),这个属性是一个指针,指向原型对象
 例如:
 function Person(){}
 Person.prototype ={} // 等号右边的{}就是prototype指向的对象,就为原型对象

2.原型对象默认有一个叫做 constructor的属性,这个属性指向构造函数本身
 person.prototype = {
     constructor:Person
 }

3.如果往原型对象上添加方法和属性,所有通过构造函数创建的实例,都共享原型对象上包含的属性和方法
 function Star(name,age){
     this.name =name
     this.age = age
 }
 Star.prototype.sing = function(){
     console.log('我会唱歌')
 }
 const ldh = new Star('刘德华'18)
 const zxy = new Satr('张学友'22)
 ldh.sing()  //可以调用原型上的sing
 zxy.sing()  //可以调用原型上的sing
 console.log(ldh.sing === zxy.sing)  //true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 26.proto

function Star(name, age) {
            this.name = name
            this.age = age
        }
        // 公共方法写在原型上
        Star.prototype.sing = function () {
            console.log('我会唱歌')
        }
        const ldh = new Star('刘德华', 18)
        ldh.sing()

1.方法属性的查找规则
首先看ldh这个实例对象身上是否有sing方法,如果有,就执行对象上的方法
如果ldh实例对象本身没有sing这个方法,就会通过__proto__去实例的原型上查找

2.
实例通过__proto__访问(链接)到了它的原型对象
==>ldh.__proto__ ==>访问到了原型
==>Star.prototype ==>访问到了原型
ldh.__proto__ === Star.prototype

3.
__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

原型原理:

image-20221223155737952


# 27.构造函数、实例、原型 三者之间的关系

1.构造函数有prototype属性,指向原型对象
2.原型对象默认有一个constructor属性,指向构造函数
3.构造函数 new 创建了一个实例
4.实例可以通过 __proto__访问的原型(对象)
1
2
3
4

image-20221223165218363


# 28.constructor的应用

function Star(name, age) {
        this.name = name;
        this.age = age;
      }
//  如果我们直接给原型对象赋值一个对象,相当于整个替换了原型对象里面的所有内容
// 原型对象里面的constructor就没有了,我们也不知道这个原型和哪个构造函数相关了
// 所以,可以手动的利用constructor指回原来的构造函数
Star.prototype = {
        constructor: Star,   //手动添加constructor
        sing: function () {
          console.log("sing");
        },
        dance: function () {
          console.log("dance");
        },
        rap: function () {
          console.log("rap");
        },
      };
      console.dir(Star);  // prototype里的 constructor就没有了


如果是单个给原型对象添加属性 就不会原型原型对象里constructor的存在

    //   Star.prototype.sing = function(){
    //       console.log('sing');
    //   }
    //   Star.prototype.dance = function(){
    //       console.log('dance');
    //   }
    //   Star.prototype.rap = function(){
    //       console.log('rap');
    //   }
    //   console.dir(Star);  //prototype里的 constructor还有
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

# 29.原型对象的方法中的this指向

function Star(name){
            this.name = name}
1.构造函数里面的this指向的是实例对象

 // 2.原型对象的方法中 this指向  也是指向实例对象
        Star.prototype.sing =function(){
            console.log('唱歌');
            console.log(this);  //指向实例对象
        }
        ldh.sing()
        zjl.sing()

// 构造函数自己的方法调用时 this指向构造函数本身
 Star.sayHi(){
     console.log(this) // Star
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 30.什么是原型?

// 原型就是一个对象,也叫做原型对象
        // 1. 每个函数都有prototype属性,它的值是一个指针,指向的就是原型对象。
        // 2. 通过构造函数生成的实例,都有一个__proto__属性,也指向原型对象。
        // 3. 原型上默认有一个constructor属性,指回构造函数

        // 原型的作用:
        // 我们可以把一些公共的属性和方法放到原型上,
        // 通过构造函数创建的实例,都共享原型上的属性和方法。
1
2
3
4
5
6
7
8

# 31.什么是原型链?

image-20221225205725157

JS中一切皆对象
Person.prototype原型对象,也是一个对象,它也有__proto__属性
Person.prototype这个对象的构造函数为:Object
所有Person.prototype这个对象的隐式原型 指向 它的构造函数(Object)的显式原型
consolelog(Person.prototype.__proto__ === Object.prototype) //true

Object.prototype原型 默认也有一个constructor属性 ,这个属性指向构造函数Object
Object.prototype.constructor === Object

Object.prototype本身也是一个对象,也有__proto__属性
Object.prototype.__proto__ === null
1
2
3
4
5
6
7
8
9
10
11

# 32.原型链重要知识点

正常的原型链都会终止于Object的原型对象(或者说终止于null)


当访问一个对象的属性和方法时,先在自身寻找,
如果自身没有,就会沿着__proto__这条链,向上寻找,一直找到最顶层Object.prototype为止。

例如:
数组的原型链
const arr = [1,2,3]  //arr = new Array(1,2,3)
arr --> Array.prototype --> Object.prototype --> null

函数的原型链
const fn = function(){} // fn == new Function()
fn --> Function.prototype --> Object.prototype --> null
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 33.原型链继承

image-20221226190918687

继承: 子类拥有父类的属性和方法
// 父类
        function Parent(name){
            this.name = name || '父类' //相当于给默认值
            this.arr = [1,2,3]
        }

        Parent.prototype.sayHi = function(){
            console.log('hi~~');
        }

        // 子类
        function Child(hobby){
            this.hobby = hobby
        }
// 核心 ===> 让子类的原型 等于 父类的构造函数的实例
        Child.prototype = new Parent() //相当于Child 和 Parent两个构造函数之间有了关联  // 因为没有传参 所以获得构造函数Parent的默认值: name:'父类' arr =[1,2,3]
        Child.prototype.constructor = Child // 手动的修正Child的constructor // 子类原型与父类实例相等后, 子类原型中的constructor属性会丢失,所以手动补上
      const boy1 = new Child('sing')
      const boy2 = new Child('dance')
      console.log(boy1,boy2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
优点:方法复用,共享
因为方法定义在父类的原型上,通过原型链继承,子类实例共享了父类原型上的方法
boy1.sayHi()
boy2.sayHi()
1
2
3
4
缺点:
1.子类在实例化的时候,不能给父类的构造函数传参
2.子类的实例共享了父类构造函数的属性和方法
  如果父类里的属性值是引用类型(数组,对象等),实例修改这个值后会互相影响
  boy1.arr.push(666)
  console.log(boy1.arr);// [1,2,3,66]
  console.log(boy2.arr);// [1,2,3,66]

因为有上面两个缺陷,原型链继承一般不会单独使用!
1
2
3
4
5
6
7
8
9

# 33.数据类型

数据类型有哪些? // 易考面试题  背
ES变量有两种不同类型的数据:
1.原始值(基本数据类型):
Number String Boolean / undefined null / Symbol BigInt
2.引用值(引用数据类型)
Object ==> Function Array Date Math RegExp

基础数据类型 和 引用数据类型 在内存里是怎么存储的? //面试题 背
栈(Stack): 基本数据类型的值 存在栈里
堆(Heap): 引用类型的数据,栈里面存放的是地址,这个地址指向堆里的数据。
1
2
3
4
5
6
7
8
9
10

# 34.检测数据类型的方式

# 34.1 typeof

typeof 可以用来判断一个变量是否是除了null以外的基本数据类型
// 基本数据类型
        console.log(typeof 1); // number
        console.log(typeof ""); // string
        console.log(typeof true); //boolean
        console.log(typeof undefined); // undefined
        console.log(typeof null); // object---有点儿特殊 Bug
        console.log(typeof Symbol('id')) //symbol
        console.log(typeof 9007199254740999n) // bigint
        console.log(typeof BigInt(9007199254740999))  //bigint

        // 2. typeof 总是返回一个字符串 
        console.log(typeof (typeof 1)) // string 

        console.log('-----------------------')
        // 3. typeof 不能区分数组
        console.log(typeof [1, 2, 3])  // 'object'
        console.log(typeof function(){}) // 'function'
        console.log(typeof {}) // 'object'

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

# 34.2 instanceof

//instanceof 可以用于引用类型的检测。基本数据类型无效

// 基本类型 无效
console.log('1' instanceof String) // false
console.log(1 instanceof Number) //false
console.log(true instanceof Boolean) // false 
console.log(Symbol('id') instanceof Symbol) //false
console.log(9007199254740999n instanceof BigInt) //false
console.log(BigInt(9007199254740999) instanceof BigInt) // false

// instanceof 只能用于引用类型的检测
console.log({} instanceof Object) // ture
console.log([] instanceof Object) // true
console.log(function (){} instanceof Function) // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 34.3 Object.rptotype.toString.call

let num = 123
let str = '123'
let bool = true
let und = undefined
let nul = null
let symb = Symbol('id')
let big = 9007199254740999n

let arr = [1,2,3]
let obj = {}
let fn = function(){}
let date = new Date()   // [object Date]
let reg = /a/g          // [object RegExp]
let error = new Error() // [object Error]
// 前面的object小写, 后面的类型, 大写开头; 字符串
console.log(Object.prototype.toString.call(num)) // [object Number]
console.log(Object.prototype.toString.call(str))  // [object String]
console.log(Object.prototype.toString.call(bool)) // [object Boolean]
console.log(Object.prototype.toString.call(und)) // [object Undefined]
console.log(Object.prototype.toString.call(nul)) // [object Null]
console.log(Object.prototype.toString.call(symb)) // [object Symbol]
console.log(Object.prototype.toString.call(big)) // [object BigInt]

console.log(Object.prototype.toString.call(arr))  // [object Array]
console.log(Object.prototype.toString.call(obj))  // [object Object]
console.log(Object.prototype.toString.call(fn))   // [object Function]
console.log(Object.prototype.toString.call(date))   // [object Date]
console.log(Object.prototype.toString.call(reg))   // [object RegExp]
console.log(Object.prototype.toString.call(error))   // [object Error]

// 最完美的方式~~~
console.log('-----')
// 封装一个CheckType 
const tempArr = [num, str, bool, und, nul, symb, big, arr, obj, fn, date, reg, error]

const checkType = (arr) => {
    arr.forEach(el => console.log(Object.prototype.toString.call(el)))
}

checkType(tempArr)
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

# 35.判断数组 将伪数组转换为真数组、

// 判断 检测数组 
        /* ====================  ===================== */
        // 1. Array.isArray()
        // 2. A instanceof B   
        // 3. Object.prototype.toString.call()
        /* ====================  ===================== */

// 将伪数组(类数组)转为真数组 
        /* ====================  ===================== */
        // 1. Array.from() 
        // 2. 扩展运算符 [...arrayLike]
        /* ====================  ===================== */
        
1
2
3
4
5
6
7
8
9
10
11
12
13

# 36.什么是浅拷贝 (面试题)

image-20221225223131545

let obj1 = {
            name:'JS',
            book:{
                title:"You Don't Know JS",
                price:"169"
            }
        }

在堆内存空间中新开辟空间,创建一个新的对象
拷贝原对象的第一层基本数据类型的值 和 引用数据类型的地址
1
2
3
4
5
6
7
8
9
10

# 37.实现浅拷贝的方式有哪些?(面试题)

// 1. Object.assign()
// 2. 扩展运算符
// 3. 数组的concat()
// 4. 数组的slice()

例如:
let obj1 = {
            name:'JS',
            book:{
                title:"You Don't Know JS",
                price:"169"
            }
        }


// 1. Object.assign()
        let obj2 = Object.assign({}, obj1)
        // 第一层 name属性值 是基本数据类型,拷贝前后两个对象相互不影响
        obj2.name = 'Hello World'
        console.log(obj2)
        console.log(obj1) // 如果obj1不受影响
        // 第一层 如果属性值是引用类型,引用类型的值改变,会相互影响
        obj2.book.price = '66'
        console.log(obj2)
        console.log(obj1)

// 2. 扩展运算符 spread ...
      let c = {
        name: "JS",
        book: {
          title: "You Don't Know JS",
          price: "169",
        },
      };

      let d = { ...c };
      d.name = "贵桑";
      d.book.price = "99";

// 3. Array.prototype.concat()
      const arr1 = [1, 2, { name: "淞桑" }];
      const arr2 = arr1.concat();
      // console.log(arr2)
      arr2[0] = 666;
      arr2[2].name = "贵儿桑";
      console.log(arr1);
      console.log(arr2);


// 4. Array.prototype.slice()
      const arr3 = [1, 2, { name: "杰伦" }];
      const arr4 = arr3.slice();
      arr4[0] = 888;
      arr4[2].name = "萧萧";
      console.log(arr3, arr4);
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

# 37.什么是深拷贝(面试题)

image-20221225225547653

在堆内存中新开辟一个空间来存放新的对象,递归的拷贝原对象的所有属性和方法,拷贝前后两个对象,互不影响。

递归的拷贝: 即一层一层的拷贝,每一层都新建一个内存空间,存放新的对象。
1
2
3

# 38.实现深拷贝的方式

// 1. JSON.parse(JSON.stringify(obj))
// 2. 手写递归实现
// 3. 使用一些JS库,比如lodash等   

1
2
3
4

# 39.深拷贝方式: JSON.parse(JSON.stringify(obj))存在的问题

 const obj = {
            name: 'zjl',
            age: 18,
            hobby:['dance','music'],
            book:{
                book_name:'Golang',
                price:66
            },
            test1: function(){},
            test2: undefined,
            test3: Symbol('id'),
            test4: new RegExp(/a/, 'g'),
            test5: NaN,
            test6: Infinity,
            test7: new Date()
        }
        // JSON.parse()   将JSON格式的字符串转为对象 
        // JSON.stringify() 将对象转为JSON格式的字符串

        const res = JSON.parse(JSON.stringify(obj))
        console.log(res)



        // 缺陷:
        // 1. 拷贝对象的属性值如果是function / undefined / Symbol , 这个键值对丢失!
        // 2. 如果拷贝对象的属性值是RegExp, 会变成{}
        // 3. 如果NaN, Infinity 会变成null
        // 4. 拷贝Date日期对象,会变成日期字符串
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

# 40.深拷贝 --- 手写递归(不完整版)

# 40.1 手写递归 (原对象属性中无数组和对象的情况)

const obj ={
    name:'somi',
    age:18
}
const o = {}
const deepClone =(newObj,oldObj) =>{
    for(let k in oldObj){
        newObj[k] = oldObj[k]
    }
}
deepClone(o,obj)
1
2
3
4
5
6
7
8
9
10
11

# 40.2 手写递归(原对象属性中有数组的情况)

const obj ={
    name:'some',
    age:18,
    hobby;['dance','music']
}
const o = {}
const deepClone =(newObj,oldObj) => {
    for(let k in oldObj){
        if(oldObj[k] instanceof Array){
            newObj[k] = []
            deepClone(newObj[k],oldObj[k])
        } else{
            newObj[k] = oldObj[k]
        }
    }
}
deepClone(o, obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 40.3 手写递归(原对象属性中有数组和对象的情况)

const obj ={
    name:'somi',
    age:18,
    hobby:['dance','music'],
    book:{
        title:'Javascript',
        price:66
    }
}
const o = {}
const deepClone = (newObj, oldObj) =>{
    for(let k in oldObj){
        if(oldObj[k] instanceof Array){
            newObj[k] = []
            deepClone(newObj[k],oldObj[k])
        } else if (oldObj[k] instanceof Object){
            newObj[k] = {}
            deepClone(newObj[k], oldObj[k])
        } else {
            newObj[k] = oldObj[k]
        }
    }
}
deepClone(o, obj)

//注意点: 当原对象属性中既有数组又有对象时, 需要先判断是否是数组,再判断是否是对象,因为数组也是属于Object。
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

# 41. 深拷贝-- 引入lodash库 (最简便的方式)

<script src='./lodash.min.js'></script>
<script>
    const obj ={
        name:'somi',
        age:18,
        hobby:['dance','music'],
        book:{
            title:'Javascript',
            price:66
        }
    }

// lodash库已经封装好了递归函数,直接引用就行
//_.cloneDeep() 名字不能修改 _ 表示lodash

const o = _.cloneDeeP(obj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 42.throw抛出异常

function fn(x,y){
    if(!x || !y){
        throw new Error('没有参数传递过来')
        //throw 可以抛出错误,并且它会中断代码的指向
        console.log(123)
    }
    return x + y
}
console.log(fn())
1
2
3
4
5
6
7
8
9

# 43. try-catch-finally 捕获异常

<p>123</P>
<script>
function fn(){
    try{
        // 将可能发生错误的代码写在try模块里,如果代码发生错误,会终止执行try里错误代码之后的代码,进而执行catch板块里的错误消息提示代码
        const p = document.querySelector('.p')
        console.log(p) // null
        p.style.color = 'orange' //这里报错了,这里的报错信息会体现在catch代码中
        console.log(666) //因为前面的代码发生错误,所以此处代码不会执行,不会打印 
    } catch(error){
        console.log(error.message) // Cannot read properties of null (reading 'style')
        //try-catch 可以拦截捕获错误,但是不会中断后面代码的执行
        //如果要中断后面的代码执行
        //1.return  不会中断finally代码执行
        //2.throw new Error('msg') 不会中断finally代码执行
    }
    finally{
        //不管前面代码对不对或者前面有return/throw抛出错误,都会执行这里的代码
        console,log('会执行')
    }
    console.log(999)
}
fn()
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 44. this指向(复习)

//this指向在定义的时候不能确定,只有在调用执行的时候才能确定!
//1. 全局作用域 / 普通函数调用 /定时器  this指向的是window

console.log(this) // window

function fn(){
console.log(this) // window
}
fn()

setTimeout(function(){
    console.log(this) // window
},1000)

//2. 对象方法调用中,谁调用这个方法,this就指向谁
const obj ={
    name:'Amy',
    age:6,
    sayHi:function(){
        console.log(this)
    }
}
obj.sayHi()  // obj{}
let fun = obj.sayHi
fun() // window

//3.事件注册的时候,this指向被绑定的元素
const btn = document.querySelector('button')
btn.addEventListener('click',function(e){
    console.log(this) // 绑定事件的元素
    console.log(e.target) // 触发事件的元素
    console.log(e.currentTarget) //同this相同,绑定事件的元素
})

//4.构造函数中的this指向 实例对象
function Parent(name,age){
    this.name = name
    this.age = age
    console.log(this)  // p:('灰灰',9), p1:('超超',19)
}
const p = new Parent('灰灰'9)
const p2 = new Parent('超超'19)

//5.原型方法里面,this指向的是实例对象
Parent.prototype.sayHi = function(){
    console.log('hi')
    console.log(this) // p:('灰灰',9)
}
p.sayHi()
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

# 45.箭头函数的特殊之处

//1.箭头函数没有prototype属性,也没有原型对象
let a = () => {}
console.log(a.prototype) // undefined

//2. 不能使用new 调用箭头函数,即箭头函数不能作为构造函数
let B = () => {}
let c = new B() // 报错:B is not a constructor
console.log(c)  // 上面的代码报错,不会执行此处代码 

//3. 箭头函数没有arguments对象, 利用剩余参数rest接收
let d = (first,...abc) => {
    //console.log(arguments)  // 报错 Unexpected identifier 'Error'
    console.log(first,abc) //1 (3) [2, 3, 4]    
}
d(1,2,3,4)

//4. 箭头函数本身没有this 
//箭头函数的this指向在定义的时候就确定了,指向的是上层作用域中的this
let msg = '666' // 注意!!! let / const 声明的变量不会挂载在window上
let obj = {
    msg:'777',
    sayHi:function(){
        console.log('sayHi',this.msg)
    },
    sayHello:() =>{
        //对象不能形成作用域,所以此处的this指向的是window
        console.log('sayHello',this.msg)
    }
}
obj.sayHi() // sayHi 777 普通函数作为对象的方法调用时,指向对象

obj.sayHello() //sayHello undefined  //let / const 声明的变量不会挂载在window上

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

# 46.改变this指向的方法

# 46.1 fn.call()

//1. 可以改变this的指向
//2. 调用函数,立即执行,返回执行结果
//3. fn.call()第一个参数就是让this指向哪个对象,后面紧跟的是参数列表

//eg1
const obj = {
    msg:"hello world"
}
function fn(x, y){
    console.log(this)
    return x + y
}
const res = fn(1, 2) //调用fn函数 打印this : window
console.log(res) // 3

const res2 = fn.call(obj, 1, 2)  //调用fn函数 打印this 改变this指向 : obj
console.log(res2) //3


//eg2
const obj2 = {
    name:'hw',
    getName(){
        console.log(this)
        console.log(this.name)
    }
}
const obj3 = {
    name:'iphone'
}
//改变getName里面的this指向,让它指向obj3
obj2.getName.call(obj3) //可以不接受后面的参数 {name: 'iphone'} iphone
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

# 46.2 fn.apply()

//1. 改变this指向
//2. 调用函数,立即执行,返回执行结果
//3. 第一个参数是this指向的对象,第二个参数是数组
const obj = {
    msg:"hello world"
}
function fn(x, y){
    console.log(this)
    return x + y
}
const res = fn(1,2) // window
console.log(res) // 3

const res2 = fn.apply(obj,[1,2]) // obj
console.log(res2) // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 46.3 fn.bind()

//1. 改变this指向
//2. bind的返回值是一个函数,这个函数里面的this就是传入的第一个参数
//3. bind不是立即执行,需要手动调用
const obj = {
    msg:"hello world"
}
function fn(x, y){
    console.log(this)
    return x + y
}
const fun = fn.bind(obj,1,2) //返回改变this指向后的fn函数 不会执行
console.log(fun) //ƒ fn(x, y) {
                                //console.log(this)
                                //return x + y
                               //}

//手动调用执行函数
const res = fun() // obj
console.log(res) // 3

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

# 46.4 fn.call() / fn.apply() / fn.bind()的区别(面试题)⭐

1.都是改变this的指向
2.call接受的是参数列表, apply接受的是数组
3.call 和 apply是立即执行的,bind返回的是一个函数,需要手动调用
1
2
3

# 47. 防抖: debounce

//防抖的含义
//当持续触发事件时,一定时间内没有再触发事件,回调函数才会执行一次
//如果设定的延迟时间到来之前,又触发了事件,就重新执行。

//防抖的应用场景
//1. 搜索框输入查询的时候
//2. 鼠标连续点击按钮提交等
<input type="text">
    <script>
        const input = document.querySelector('input')
        const sendMsg = function(){
            console.log('发送请求')
        }
        const debounce =(fn, ms = 0) =>{
            let timerId
            return function(){
                clearTimeout(timerId)
                timerId = setTimeout(() =>{
                    fn.call(this)
                },ms)
            }
        }
        input.addEventListener('keyup',debounce(sendMsg,500))
    </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 48. 防抖debounce传参

<input type="text">
    <script>
        const input = document.querySelector('input')
        const sendMsg = function(){
            console.log('发送请求')
        }
        const debounce =(fn, ms = 0) =>{
            let timerId
            return function(...args){
                clearTimeout(timerId)
                timerId = setTimeout(() =>{
                    fn.call(this,...args)
                    //fn.apply(this,args)
                },ms)
            }
        }
        input.addEventListener('keyup',debounce(sendMsg,500))
    </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 49. 定时器中this指向

 function foo(){
            console.log(this, 'foo')
        }

        const obj = {
            name:'test',
            fn:function(){
                console.log(this, '---定时器外层的this') // obj
                setTimeout(function(){
                    console.log(this, 'function')  // window
                    // 在定时器的回调函数中,又调用了一个函数
                    // foo调用执行的时候,它里面的this和定时器是不是箭头函数没有关系
                    foo() //widow
                }, 1000);
                setTimeout(() => {
                    console.log(this, '箭头函数')  // obj
                    foo() // window
                }, 2000)
            }
        }
        obj.fn()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 50. 节流:throttle

# 50.1 节流的含义和应用场景

//节流:throttle 含义
// 减少一段时间内,事件的触发频率

//应用场景
//1.浏览器窗口缩放 : resize事件
//2.scroll滚动事件 / mousemove事件等

1
2
3
4
5
6
7

# 50.2 节流 -- 定时器版本

 <script>
        const box = document.querySelector('.box')
        let i = 1 
        const move = () => {
            // 只要鼠标一移动,i就会变化
            box.innerHTML = i++
        }
        //需求:每隔100ms只执行一次
        //整个程序 ,应该只有一个定时器在走
        const throttle = (fn, ms = 0){
            let timerId
            return function(){
                if(!timerId){
                    timerId = setTimeout(() => {
                        // 箭头函数没有arguments对象,找的是上层函数中的arguments
                        fn.apply(this,arguments)
                        //fn.apply(this,args)
                        timerId = null // undefined // 0
                    },ms)
                }
            }
        }
        box.addEventListener('mousemove',throttle(move,100))
</script>

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

# 50.3 节流 -- 时间戳的版本

<script>
        const box = document.querySelector('.box')
        let i = 1 
        const move = () => {
            // 只要鼠标一移动,i就会变化
            box.innerHTML = i++
        }
        //需求:每隔100ms只执行一次
        const throttle = (fn,ms) =>{
            let pre = 0 // 记录一个开始时间
            return function(...args){
                let now = + new Date()
                if(now - pre >= ms){
                    fn.apply(this,args)
                    // 当我们执行完一次之后,可以把当前时间,作为下一次计算的开始时间
                    pre = Date.now()
                }
            }
        }
        box.addEventListener('mousemove',throttle(move,100))
</script>        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 51. 引入lodash库 节流防抖

<div class="box"></div>
    <script src="./lodash.min.js"></script>
    <script>
        const box = document.querySelector('.box')

        let i = 1 
        const move = () => {
            box.innerHTML = i++
        }
        // 节流 _.throttle(fn, ms)
        // 防抖 _.debounce(fn, ms)

        // box.addEventListener('mousemove', _.throttle(move, 10))
        box.addEventListener('mousemove', _.debounce(move, 300))
     </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 52.ES6 类Class

# 52.1 利用类创建一个对象

class Star{
    constructor(name,age){
        this.name = name
        this.age =age
    }
}
// new 一个实例对象
const hx = new Star('Push',19)

// 注意点
// 构造函数和类同名了,不能重复声明
// 1. constructor() 是类的默认方法,只要通过new 创建一个实例,就会执行里面的代码
// 2. 一个类里面必须要有constructor()方法,如果不写,一个空的constructor会默认添加
// 3. constructor可以接收传递过来的参数,默认返回实例对象(this)
// 4. 类必须使用 new 来调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 52.2 类添加公共方法

class Star {
            // 1. 公共的属性写在constructor里面
            constructor(name, age){
                this.name = name 
                this.age = age 
            }
            // 2. 类里面可以直接写方法,方法不需要加function关键字
            sing(song){
                console.log(song)
            }
            // 3. 多个方法之间不需要逗号隔开
            dance(){
                console.log('跳舞')
            }
        }

        const ldh = new Star('刘德华', 19)
        console.log(ldh)
        ldh.sing('冰雨')

        console.log(ldh.__proto__ === Star.prototype)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 52.3 类里面的this指向

class Star {
            // 1. constructor 里面的this 指向的是创建 new 的实例对象
            constructor(name, age){
                this.name = name 
                this.age = age 
                console.log(this) // Star {name: '航桑', age: 19}
            }
            // 2. 类的方法内部,如果含有this, 它默认指向的是类的实例
            sing(song){
                console.log(song)
                console.log(this) //Star {name: '航桑', age: 19}
            }
            dance(){
                console.log('跳舞')
            }
        }

        const handsome = new Star('航桑', 19)
        handsome.sing('冰雨')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 52.4 使用类的注意事项

 // 1.  ES6中  类 没有变量提升,所以必须先声明类,再通过类实例化对象。
        // const ldh = new Star('刘德华', 18) //报错 Cannot access 'Star' before initialization
        class Star {
            constructor(name, age){
                this.name = name 
                this.age = age 
                // console.log(this)
                // sing()
                this.sing()
            }
            // 2. 公共的属性和方法,在类里面使用的时候,前面一定要记得加this
            sing(){
                console.log('唱冷冷的冰雨')
                console.log(this.name)
            }
            dance(){
                console.log('跳舞')
            }
        }
        const ldh = new Star('刘德华', 18)
        // 想要new Star 的时候,就去调用执行sing方法
        // ldh.sing()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 52.5 类的本质

class Star {

        }

        console.log(typeof Star)  // function

        // 1. 类的本质是一个函数,我们可以简单的认为,类就是构造函数的另一种写法。
        // 2. 类有原型
        console.log(Star.prototype) // obj

        // 3. 我们也可以直接在类的原型上添加方法(不推荐),公共方法一般写在类里面
        Star.prototype.sing = function(){
            console.log('唱歌')
        }
        console.log(Star.prototype)
        // 4. 通过类创建的实例,有__proto__属性,可以访问到原型
        const ldh = new Star()
        console.log(ldh.__proto__)

        console.log(ldh.__proto__ === Star.prototype)
        console.log(Star.prototype.constructor === Star)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 52.6 类的继承

// 寄生式组合继承 
        // ===> extends ES6 类的继承  完美的 perfect~~~
        // 继承: 让子类拥有父类的属性和方法

        // 定义一个父类
        class Animal {
            constructor(name, age){
                this.name = name 
                this.age = age
            }

            run(){
                console.log('奔跑')
            }
        }

        // 写一个子类Dog 继承 Animal这个父类
        class Dog extends Animal {
            // 子类里面没有写属性和方法,但是 通过extends,直接继承了父类的name和age
            // 子类 创建的实例,可以调用父类的方法 (继承父类的属性和方法)

        }

        const dog = new Dog('西瓜', 3) //
        console.log(dog)
        dog.run() //奔跑
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

# 53. super关键字

// 定义一个父类
        class Father {
            constructor(){}
        }

        // 定义一个son继承Father
        class Son extends Father {
            constructor(){
                super()
            }
        }
        // 1. 子类实现继承的时候必须在constructor方法里面,调用super(), 否则会报错。
        //  ===> 如果子类里面写了constructor() ,就必须要写super()
        const p = new Son()

        // 2. 如果子类没有写constructor(), 这个方法会默认添加,并且里面还会默认调用super()

        class Son extends Father {
        }

        // 等同于
        class Son extends Father {
            constructor(){
                super()
            }
        }

        // 正常情况下,我们都会在子类里写constructor,并且写super()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 53.1 super关键字注意事项

// 定义父类
        class Father {
            // 这里的constructor叫做构造函数
            constructor(x, y) {
                this.x = x 
                this.y = y
                console.log(this)
            }
            // 求和 
            sum(){
                console.log(this.x + this.y)
            }
        }

        // 定义一个子类,继承父类
        class Son extends Father {
            // 1. super() 必须在子类的this前面调用
            // 2. super() 代表了父类的构造函数
            // 3. super() 里面的this 指向的是谁呢?
            //  ===> 指向的是子类的实例,Son的实例 p这个对象
            constructor(x, y, color){
                super(x, y) // ==> super(x, y)  父类的constructor(x, y)
                // 如果在这里又写this.x = x 
                // super这个方法后面加的属性,是子类自己的一些属性了
                // this.x = x + 2 
                // this.y = y + 2
                // this.color = color
                // super.sum()
               
            }

            minus(){
                console.log(this.x  - this.y)
            }

            // 4. 怎么理解super()要在this前面调用?
            // 我们现在是不是在实现继承,让子类拥有父类的属性和方法
            // 我们需要先继承父类的属性和方法,然后再扩展自己的属性和方法。
        }

        const p = new Son(5, 3, 'orange')
        // console.log(p)
        // p.sum()
        // p.minus()

        // 1. 子类不能往父类构造函数传参, 通过super() 就可以往父类构造函数传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Last Updated: 2/23/2023, 9:05:22 AM