【JS笔记】JavaScript语法 《基础+重点》 知识内容,快速上手(七)

闭包

  • 闭包是我们函数的一种高级使用方式
  • 在聊闭包之前我们要先回顾一下 函数

函数的两个阶段

  • 函数有两个阶段
    1. 定义阶段
    2. 调用阶段

函数定义阶段

  1. 开辟一个 存储空间
  2. 把函数体内的代码一模一样的放在这个空间内(不解析变量)
  3. 把 存储空间 的地址给函数名

函数调用阶段

  1. 按照函数名的地址找到函数的 存储空间
  2. 形参赋值
  3. 预解析
  4. 将函数 存储空间 中的代码拿出来执行(才解析变量)

重新定义函数调用阶段

  1. 按照函数名的地址找到函数的 存储空间

  2. 形参赋值

  3. 预解析

  4. 在内存中开辟一个 执行空间

  5. 将函数 存储空间 中的代码拿出来在刚刚开辟的 执行空间 中执行

  6. 执行完毕后,内存中开辟的 执行空间 销毁

    function fn() {
      console.log('我是 fn 函数')
    }
    
    fn() 
    
    • 函数执行的时候会开辟一个 执行空间 (我们暂且叫他 xxff00
    • console.log('我是 fn 函数') 这个代码就是在 xxff00 这个空间中执行
    • 代码执行完毕以后,这个 xxff00 空间就销毁了

函数执行空间

  • 每一个函数会有一个 存储空间
  • 但是每一次调用都会生成一个完全不一样的 执行空间
  • 并且 执行空间 会在函数执行完毕后就销毁了,但是 存储空间 不会
  • 那么这个函数空间执行完毕就销毁了,还有什么意义呢?
    • 我们可以有一些办法让这个空间 不销毁
    • 闭包,就是要利用这个 不销毁的执行空间

垃圾回收gc(自动)

c 手动 管理内存

垃圾回收机制
oc object-c (swift ios app)-》早期的oc 引用计数  
java(安卓app)

gc-》
基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:

能从根(全局)访问到的变量 都会被标记,  如果没有被标记的对象,垃圾回收机制 将这些东西回收掉


1)问什么是垃圾

一般来说没有被引用的对象就是垃圾,就是要被清除, 
  有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。

2)如何检垃圾

一种算法是标记 标记-清除 算法,

函数执行空间不销毁

  • 函数的 执行空间 会在函数执行完毕之后销毁

  • 但是,一旦函数内部返回了一个 引用数据类型,并且 在函数外部有变量接受 的情况下

  • 那么这个函数 执行空间 就不会销毁了

    function fn() {
      const obj = {
          name: 'Jack',
          age: 18,
          gender: '男'
      }
      
      return obj
    }
    
    const o = fn()
    
    • 函数执行的时候,会生成一个函数 执行空间 (我们暂且叫他 xxff00
    • 代码在 xxff00 空间中执行
    • 在 xxff00 这个空间中声名了一个 对象空间(xxff11
    • 在 xxff00 这个执行空间把 xxff11 这个对象地址返回了
    • 函数外部 0 接受的是一个对象的地址没错
      • 但是是一个在 xxff00 函数执行空间中的 xxff11 对象地址
      • 因为 o 变量一直在和这个对象地址关联着,所以 xxff00 这个空间一直不会销毁
    • 等到什么时候,执行一句代码 o = null
      • 此时, o 变量比在关联在 xxff00 函数执行空间中的 xxff11 对象地址
      • 那么,这个时候函数执行空间 xxff00 就销毁了

闭包

  • 闭包就是利用了这个函数执行空间不销毁的逻辑
  • 有几个条件组成闭包

不销毁的空间

  • 闭包的第一个条件就是利用了不销毁空间的逻辑

  • 只不过不是返回一个 对象数据类型

  • 而是返回一个 函数数据类型

    function fn() {
        function b() {}
      return b
    }
    
    const f = fn()
    
    • f 变量接受的就是一个 fn的执行空间 中的 函数

内部函数引用外部函数中的变量

  • 涉及到两个函数

  • 内部函数要查看或者使用着外部函数的变量

    function fn() {
      let num = 100
      
      // 这个函数给一个名字,方便写笔记
      return function a() {
          num++
          console.log(num)
          return  num
          
      }
    }
    const f = fn()
    
    
     

调用fn,f 就会是 a函数



- `fn()` 的时候会生成一个 `xxff00` 的执行空间
- 再 `xxff00` 这个执行空间内部,定义了一个 `a` 函数的 **存储空间** `xxff11`
- 全局 f 变量接受的就是 `xxff00` 里面的 `xxff11` 
- 所以 `xxff00` 就是不会销毁的空间
- 因为 `xxff00` 不会销毁,所以,定义再里面的变量 num 也不会销毁
- 将来 `f()` 的时候,就能访问到 num 变量

### 闭包的特点

- 为什么要叫做特点,就是因为他的每一个点都是优点同时也是缺点
1. 作用域空间不销毁
   - 优点: 因为不销毁,变量页不会销毁,增加了变量的生命周期
   - 缺点: 因为不销毁,会一直占用内存,多了以后就会导致内存溢出
2. 可以利用闭包访问在一个函数外部访问函数内部的变量
   - 优点: 可以再函数外部访问内部数据
   - 缺点: 必须要时刻保持引用,导致函数执行栈不被销毁
3. 保护私有变量
   - 优点: 可以把一些变量放在函数里面,不会污染全局
   - 缺点: 要利用闭包函数才能访问,不是很方便

### 闭包概念(背诵全文)

- 有一个 A 函数,再 A 函数内部返回一个 B 函数
- 再 A 函数外部有变量引用这个 B 函数
- B 函数内部访问着 A 函数内部的私有变量
- 以上三个条件缺一不可


## 原型

- 原型的出现,就是为了解决 **构造函数的缺点**
- 也就是给我们提供了一个给对象添加函数的方法
- 不然构造函数只能给对象添加属性,不能合理的添加函数就太 LOW 了



### prototype

- **每一个函数天生自带一个成员,叫做 prototype,是一个对象空间**

- 即然每一个函数都有,构造函数也是函数,构造函数也有这个对象空间

- 这个 `prototype` 对象空间可以由函数名来访问

```javascript
function Person() {}

console.log(Person.prototype) // 是一个对象
  • 即然是个对象,那么我们就可以向里面放入一些东西
function Person() {}

Person.prototype.name = 'prototype'
Person.prototype.sayHi = function () {}
  • 我们发现了一个叫做 prototype 的空间是和函数有关联的

  • 并且可以向里面存储一些东西

  • 重点: 在函数的 prototype 里面存储的内容,不是给函数使用的,是给函数的每一个实例化对象使用的

  • 那实例化对象怎么使用能?

__proto__

  • 每一个对象都天生自带一个成员,叫做 __proto__,是一个对象空间

  • 即然每一个对象都有,实例化对象也是对象,那么每一个实例化对象也有这个成员

  • 这个 __proto__ 对象空间是给每一个对象使用的

  • 当你访问一个对象中的成员的时候

    • 如果这个对象自己本身有这个成员,那么就会直接给你结果
    • 如果没有,就会去 __proto__ 这个对象空间里面找,里面有的话就给你结果
    • 未完待续。。。
  • 那么这个 __proto__ 又指向哪里呢?

    • 这个对象是由哪个构造函数 new 出来的
    • 那么这个对象的 __proto__ 就指向这个构造函数的 prototype
    function Person() {}
    
    var p1 = new Person()
    
    console.log(p1.__proto__ === Person.prototype) // true
    
    • 我们发现实例化对象的 __proto__ 和所属的构造函数的 prototype 是一个对象空间
    • 我们可以通过构造函数名称来向 prototype 中添加成员
    • 对象在访问的时候自己没有,可以自动去自己的 __proto__ 中查找
    • 那么,我们之前构造函数的缺点就可以解决了
      • 我们可以把函数放在构造函数的 prototype 中
      • 实例化对象访问的时候,自己没有,就会自动去 __proto__ 中找
      • 那么也可以使用了
    function Person() {}
    
    Person.prototype.sayHi = function () {
      console.log('hello Person')
    }
    
    var p1 = new Person()
    p1.sayHi()
    
    • p1 自己没有 sayHi 方法,就会去自己的 __proto__ 中查找
    • p1.__proto__ 就是 Person.prototype
    • 我们又向 Person.prototype 中添加了 sayHi 方法
    • 所以 p1.sayHi 就可以执行了
  • 到这里,当我们实例化多个对象的时候,每个对象里面都没有方法

    • 都是去所属的构造函数的 protottype 中查找
    • 那么每一个对象使用的函数,其实都是同一个函数
    • 那么就解决了我们构造函数的缺点
    function Person() {}
    
    Person.prototype.sayHi = function () {
      console.log('hello')
    }
    
    var p1 = new Person()
    var p2 = new Person()
    
    console.log(p1.sayHi === p2.sayHi)
    
    • p1 是 Person 的一个实例
    • p2 是 Person 的一个实例
    • 也就是说 p1.__proto__ 和 p2.__proto__ 指向的都是 Person.prototype
    • 当 p1 去调用 sayHi 方法的时候是去 Person.prototype 中找
    • 当 p2 去调用 sayHi 方法的时候是去 Person.prototype 中找
    • 那么两个实例化对象就是找到的一个方法,也是执行的一个方法
  • 结论

    • 当我们写构造函数的时候
    • 属性我们直接写在构造函数体内
    • 方法我们写在原型上

原型链

  • 我们刚才聊过构造函数了,也聊了原型
  • 那么问题出现了,我们说构造函数的 prototype 是一个对象
  • 又说了每一个对象都天生自带一个 __proto__ 属性
  • 那么 构造函数的 prototype 里面的 __proto__ 属性又指向哪里呢?

一个对象所属的构造函数

  • 每一个对象都有一个自己所属的构造函数

  • 比如: 数组

    // 数组本身也是一个对象
    var arr = []
    var arr2 = new Array()
    
    • 以上两种方式都是创造一个数组
    • 我们就说数组所属的构造函数就是 Array
  • 比如: 函数

    // 函数本身也是一个对象
    var fn = function () {}
    var fun = new Function()
    
    • 以上两种方式都是创造一个函数
    • 我们就说函数所属的构造函数就是 Function

constructor

  • 对象的 __proto__ 里面也有一个成员叫做 constructor
  • 这个属性就是指向当前这个对象所属的构造函数

链状结构

  • 当一个对象我们不知道准确的是谁构造的时候,我们呢就把它看成 Object 的实例化对象
  • 也就是说,我们的 构造函数 的 prototype 的 __proto__ 指向的是 Object.prototype
  • 那么 Object.prototype 也是个对象,那么它的 __proto__ 又指向谁呢?
  • 因为 Object 的 js 中的顶级构造函数,我们有一句话叫 万物皆对象
  • 所以 Object.prototype 就到顶了,Object.prototype 的 __proto__ 就是 null

原型链的访问原则

  • 我们之前说过,访问一个对象的成员的时候,自己没有就会去 __proto__ 中找
  • 接下来就是,如果 __proto__ 里面没有就再去 __proto__ 里面找
  • 一直找到 Object.prototype 里面都没有,那么就会返回 undefiend
  • Object.prototype.__proto__ 的原型是null

继承

  • 继承是和构造函数相关的一个应用
  • 是指,让一个构造函数去继承另一个构造函数的属性和方法
  • 所以继承一定出现在 两个构造函数之间

一个小例子

  • 我们之前说,构造函数(类)是对一类行为的描述
  • 那么我们类这个概念其实也很抽象
  • 比如:
    • 我们说 国光 / 富士 都是 苹果的品种,那么我们就可以写一个 苹果类 来实例化很多品种出来
    • 而 苹果 /  这些东西都是水果的一种,那么我们就可以写一个 水果类
    • 说过的统一特点就是  / 水分大 ,而不同的水果有不同的特征
    • 那么我们就可以让 苹果类 来继承 水果类 的内容,然后再用 水果类 去实例化对象
    • 那么实例化出来的就不光有 苹果类 的属性和方法,还有 水果类 的属性和方法

继承的作用

  • 其实说到底,到底什么是继承

  • 我们之前说,在我们书写构造函数的时候,为了解决一个函数重复出现的问题

  • 我们把构造函数的 方法 写在了 prototype 上

  • 这样,每一个实例使用的方法就都是来自构造函数的 prototype 上

  • 就避免了函数重复出现占用内存得到情况

  • 那么,如果两个构造函数的 prototype 中有一样的方法呢,是不是也是一种浪费

  • 所以我们把构造函数䣌 prototype 中的公共的方法再次尽心提取

  • 我们准备一个更公共的构造函数,让构造函数的 __proto__ 指向这个公共的构造函数的 prototype

常见的继承方式

  • 我们有一些常见的继承方式来实现和达到继承的效果

  • 我们先准备一个父类(也就是要让别的构造函数使用我这个构造函数的属性和方法)

    function Person() {
        this.name = 'Jack'
    }
    
    Person.prototype.sayHi = function () {
        cosnole.log('hello')
    }
    
  • 这个 Person 构造函数为父类

  • 让其他的构造函数来继承他

  • 当别的构造函数能够使用他的属性和方法的时候,就达到了继承的效果

原型继承(原型链继承)
  • 原型继承,就是在本身的原型链上加一层结构

    function Student() {}
    Student.prototype = new Person()
    
借用构造函数继承
  • 把父类构造函数体借用过来使用一下而已

    function Student() {
      Person.call(this)
    }
    
组合继承
  • 就是把 原型继承 和 借用构造函数继承 两个方式组合在一起

    function Student() {
      Person.call(this)
    }
    Student.prototype = new Person()
    

ES6 的继承

  • es6 的继承很容易,而且是固定语法

    // 下面表示创造一个 Student 类,继承自 Person 类
    class Student extends Person {
        constructor () {
            // 必须在 constructor 里面执行一下 super() 完成继承 
            super()
        }
    }
    
  • 这样就继承成功了

  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旺旺大力包

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值