继承、深浅拷贝、闭包、沙箱模式

141 阅读7分钟

认识继承

想让某个类可以使用另一个类的某些属性和它原型上的方法,就可以使用继承的方法

1、原型继承
利用自定义原型的方式去继承:
    当a这个类继承了b这个类的时候
        b叫做a的父类
        a叫做b的子类
    核心:子类的原型指向父类的实例化对象
        a.prototype = new b()
    优点:实现了继承
    缺点:继承了属性,但是不在自己身上
            失去了自己的原型
function Person(name) {
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }

        // 2. 
        function Stu(age) {
            this.age = age
        }
        Stu.prototype.abc = () => {
            console.log(123)
        }
        Stu.prototype = new Person('001')
        Stu.prototype.abc2 = () => {
            console.log(456)
        }
        
        const s = new Stu(18)

        // 需求 使用 Person 内部的 name 何 sayName 方法
        console.log(s)
        console.log(s.age)
        console.log(s.name) // 001
/**
 *  1. 去 对象内部查找 name 属性, 然后发现对象内部没有这个属性
 *  2. 去这个对象内部的 __proto__ 上查找, 对象的__proto__ 指向了自己构造函数的 原型
 *              也就是 指向了 Stu.prototype
 *  3. 因为我们手动修改了 Stu.prototype, 给他赋值为了 Person 构造函数的实例化对象
 *  4. 也就是说 我们 Stu.prototype 就指向了 Person的实例化对象
 *  5. Person 的实例化对象 内部 有一个属性叫做 name, 此时找到并返回
*/
2、借用构造函数继承
  • 核心:借用call方法修改父级构造函数内部this指向
  •   语法: 父类.call(this,'参数')
      优点:1.将属性继承在自己的身上
            2.  保留自己的原型
      缺点:只能继承父类的属性,不能继承父类原型上的方法
    
function Person(name) {
            this.abc = name
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }
        
        // let p1 = new Person('001')
        // console.log(window)
        
        function Stu(age) {
            this.age = age
            Person.call(this, '002')
            /**
             *  1. 通过 new 关键字调用 Stu 这个构造函数
             *  2. new 关键会在这个函数内部创建一个空对象, 并且 会把这个函数内部的 this 指定刚才创建的空对象
             *  3. 我们现在开始正常执行函数代码
             *      3.1 给这个对象 添加一个 age 属性, 并且给他赋值 为 参数 age      参数 age === 18
             *      3.2 调用 Perosn 这个 函数, 并通过 call 方法 改变这个函数内部的 this 指向,   this 指向了 刚才 new 关键字创建的 对象
             *          3.2.1   Person 函数开始执行
             *                  + 给这个对象 添加一个 abc 的属性, 并赋值为 参数 name        参数 name === '002'
             *                  + 给这个对象 添加一个 name 的属性, 并赋值为 参数 name       参数 name === '002'
             *  4. 现在函数执行完毕, 然后 new关键字 会将刚才创建的 对象 return
             * 
             *      5. 将这个对象 保存在了 变量 s 中
             *      6. 通过上述流程, 我们认为 这个对象中应该是 {age:18, abc: '002', name: '002'}
            */
        }

        const s = new Stu(18)
3、组合继承
1、利用原型继承到父类的原型上的方法
2、利用借用继承继承到父类的构造函数内部属性
3、语法:

优点:1、能继承到属性,并且是在自己对象内部
    2、能继承到父类原型的方法

缺点:在原型上有一套多余的属性
function Person(name) {
            this.abc = name
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }

        function Stu(age) {
            this.age = age

            // 2. 利用 借用继承 继承到 父类的 构造函数内部的 属性
            Person.call(this, 'QF001')
        }

        // 1. 利用 原型继承 继承到 父类的 原型上的方法
        Stu.prototype = new Person()

        const s = new Stu(18)

        console.log(s)
        console.log(s.name)
        /**
         * 查找对象内部属性的时候, 会先在对象内部查找, 找到直接使用, 并停止查找
        */
        s.sayName()
4、拷贝继承
核心:通过for in 遍历父类实例化对象上的所有属性,拷贝到子类上。
语法:在子类里面
        for(let key in p(父类实例化对象)){
            子类.prototype[key] = p[key]
        }
function Person(name) {
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }

        function Stu(age) {
            this.age = age
            const p = new Person('QF001')

            for (let key in p) {
                // console.log(key, p[key])
                Stu.prototype[key] = p[key]
            }
        }
        Stu.prototype.abc = () => {
            console.log(123)
        }

        const s = new Stu(18)

        // 需求 使用 Person 内部的 name 何 sayName 方法
        console.log(s)
        console.log(s.age)
        console.log(s.name)
        s.abc()
        s.sayName()
5、ES6类的继承
1、在书写子类时
        语法:class 子类类名 extends 父类类名
2、在书写 子类 constructor 需要在内部书写 super()

**注意**
                1、两个写法必须同时存在,才能完成继承
                2super()必须写在constructor最在开始位置**
    **类的继承除了可以继承class类,还能够继承ES5的构造函数**
        class Person {
            constructor(name) {
                this.name = name
            }
            sayName() {
                console.log('name')
            }
        }

        function Person (name) {
            this.name = name
        }
        Person.prototype.sayName = function () {
            console.log('name')
        }

        class Stu extends Person {
            constructor(age) {
                super('QF001')
                this.age = age
            }
        }

        const s = new Stu(18)
        console.log(s)
        console.log(s.age)
        console.log(s.name)
        s.sayName()
6、深浅拷贝

一定是引用数据类型(对象,数组,函数(很少))

  • 赋值:只要是引用数据类型,复制的时候,就是引用地址的传递
浅拷贝:
    遍历对象拿到所有的key和value
    赋给另一个对象
    如果所有的vlaue都是基本数据类型,那么浅拷贝完成后修改值不会影响到老对象。
    如果为引用数据类型,并且出现了多层结构,那么浅拷贝完成后只能靠被第一层的数据结构,多层的没办法处理,修改时还会被同时改变。、
1、自己手写 for in 循环遍历
        let o2 = {}
        for (let key in o1) {
            /**
             *  遍历 o1 对象所有的 key, 然后赋值给 对象 o2
             */
            console.log(key, o1[key])
            o2[key] = o1[key]
        }  
2/**
         *  Object.assign(新对象, 原始对象)
         *      将 原始对象 中所有的 key 全部浅拷贝到 新对象中, 然后返回一个 对象
        */
        通过 JS 提供的方法 完成浅拷贝
```js
        console.log(o1)
        console.log(o2)
        let o3 = Object.assign(o2, o1)
        let o2 = Object.assign({}, o1) 
深拷贝
    不管数据有多少层数据结构,百分百复制出来一份一模一样但毫不相关的数据。
    // JS 中提供的方法
    let o2 = JSON.parse(JSON.stringify(o1))
let o1 = {
            a: 1,
            b: 2,
            c: 3,
            d: {
                d1: '001',
                d2: '002',
                d3: '003'
            }
        }
        let o2 = {}
        function deepClone(target, origin) {
            // 将 origin 完全百分百的复制出来一份, 到 target 中
            for (let k in origin) {
                // console.log(k, origin[k])
                if (origin[k].constructor === Object) {
                    target[k] = {}
                    deepClone(target[k], origin[k])
                } else if (origin[k].constructor === Array) {
                    target[k] = []
                    deepClone(target[k], origin[k])
                } else {
                    target[k] = origin[k]
                }
            }
        }

        deepClone(o2, o1)
        // console.log(o1)
        // console.log(o2)
        o2.d.d2 = 'QF666'
        console.log(o2)
        console.log(o1)

        // JS 中提供的方法
        let o2 = JSON.parse(JSON.stringify(o1))

        o2.d.d3 = '9999999'
        console.log(o1)
        console.log(o2)        

函数的过程

1、定义
    在堆内存中开辟一段内存空间
    把函数体的内容完全百分百的照抄一份存放在内存空间中
    把内存空间的地址赋值给函数名
2、调用
    根据函数名内存储的地址去堆内存中找到
    会去运行内存中,开辟一段新的内存,用于运行函数(函数作用域)
    形参复值--->预解析--->函数代码全部执行结束
    函数执行完毕之后,这段内存空间会被销毁
不会被销毁的内容空间
1、函数向外部返回了一个 **引用数据类型**(返回的是应用数据类型的地址)
2. 外部有一个变量接受这个 返回值 (外部变量中有 变量 拿到 这个 引用地址)
3. 为了我们后续能够正常使用, JS 不会把这个函数的运行空间 销毁        
闭包

闭包 (重点!!! 看清楚我们调用的是那个函数)

   构成条件:
        1. 需要一个不会被销毁的函数执行空间
        2. 需要 直接 或 间接 的返回一个函数
        3. 内部函数使用着 外部函数的私有变量
    闭包的好处: 延长变量的作用域(使用时间)
        函数外部可以使用函数内部的变量了
    闭包的弊端: 因为闭包的存在就一定代表一个函数执行空间不会被销毁, 如果大量使用闭包, 会导致内存空间占据严重

沙箱模式

沙箱模式
    利用 间接 返回一个函数, 然后去拿到 外部函数内的私有变量

沙箱模式语法糖:ES6推出的沙箱模式的简写。