继承 闭包 沙箱模式

87 阅读7分钟

继承 闭包 沙箱模式

一.原型继承

  • 利用自定义原型方式去继承
  • 核心: 就子类的原型指向父类的实例化对象
  • 优点: 实现了继承
  • 缺点: 继承了属性,但是不在自己身上,失去自身的原型

案例:

        
    /*
        当Stu这个类继承了Person这个类的时候
            我们把Person叫做Stu的父类
            把Stu叫做Person的子类
​
     */
        // 1. 
        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('张三')
        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) // QF001
        /*
            1.去对象内部查找name属性,然后发现对象内部没有这个属性
            2.去这个对象内部的__proto__上查找,对象的__proto__指向了自己构造函数的原型
                    也就是指向了 Stu.prototype
            3.因为手动修改了Stu.prototype,给他赋值为了Person构造函数的实例化对象
            4.也就是说我们Stu.prototype就指向了Person的实例化对象
            5.Person的实例化对象内部有一个属性叫做name,此时找到并返回
        */
        s.sayName()

二.借用构造函数继承

  • 核心: 借用call方法修改父类构造函数内部this指向
  • 优点: 将属性继承在自身,保留了自己原型
  • 缺点: 只能继承父类的属性,不能继承父类原型上的方法

案例:

        function Person(name) {
            this.abc = name
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }
         // let p1 = new Person('张三')
        // console.log(window)
        
        function Stu(age) {
            this.age = age
            Person.call(this, '李四')
            }
        const s = new Stu(18)

三.组合继承

  1. 利用原型继承 继承到父类的原型上的方法

  2. 利用借用继承 继承到父类的构造函数内部的属性

  3. 好处:

    • 能继承到属性,并且是在自身对象内部
    • 能够继承到父类原型上的方法

4.缺点: 在原型上有一套多余属性

案例:

​
        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, '张三')
        }
​
        // 1. 利用 原型继承 继承到 父类的 原型上的方法
        Stu.prototype = new Person()
​
        const s = new Stu(18)
​
        console.log(s)
        console.log(s.name)
        /**
         * 查找对象内部属性的时候, 会先在对象内部查找, 找到直接使用, 并停止查找
        */
        s.sayName()

四.拷贝继承

  • 核心: 通过for in 遍历父类实例化对象上的所有属性,拷贝到子类上,它能够遍历对象内部的属性,已经原型上的

案例:

   function Person(name) {
            this.name = name
        }
        Person.prototype.sayName = () => {
            console.log('name')
        }
​
        function Stu(age) {
            this.age = age
            const p = new Person('张三')
​
            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()

五.ES6类的继承

  1. 书写子类的时候 语法: class 子类类名 extends 父类类名

  2. 书写子类constructor 需要在内部书写 super()

  3. 注意:

    • 两个写法必须同时存在,才能完成继承
    • super() 必须在 constructor 开始位置

4.类的继承,除了能够继承class类,还能够继承ES5构造函数

案例:

        function Person (name) {
            this.name = name
        }
        Person.prototype.sayName = function () {
            console.log('name')
        }
​
        class Stu extends Person {
            constructor(age) {
                super('张三')
                this.age = age
            }
        }
​
        const s = new Stu(18)
        console.log(s)
        console.log(s.age)
        console.log(s.name)
        s.sayName()

六.浅拷贝与赋值

  1. 深浅拷贝

    • 一定是 引用数据类型
    • 对象,数组,函数(基本只有对象和数组

2.赋值

只要是引用数据类型,那么在赋值的时候,就是引用地址的传递

3.浅拷贝

  • 遍历对象拿到对象的每一个key与value
  • 然后赋值给另外一个对象
  • 如果所有的value都是基本数据类型,那么浅拷贝完成之后,修改新对象不会影响到老对象
  • 如果value有引用数据类型(出现了多层数据结构),那么浅拷贝只能拷走第一层数据结构,多层没有办法处理

案例:

        // 1. 赋值
        // const s1 = '123'
        // let s2 = s1   // 赋值
        // console.log(s2 === s1)
        // s2 = '456'
        // console.log(s2) // 456
        // console.log(s1) // 123
​
        // let o1 = { a: 1 }
        // let o2 = o1   // 赋值
        // console.log(o2 == o1)
        // o2.a = 999
        // console.log(o1)
        // console.log(o2)
​
        // let a1 = [1, 2, 3]
        // let a2 = a1   // 赋值
        // console.log(a2 == a1)
        // a2[0] = 999
        // console.log(a1)
        // console.log(a2)
        
        
        // 2. 浅拷贝
        let o1 = {
            a: 1,
            b: 2,
            c: 3,
            d: {
                d1: '001',
                d2: '002',
                d3: '003'
            }
        }
        // let o2 = {}
        // for (let key in o1) {
        //     /**
        //      *  遍历 o1 对象所有的 key, 然后赋值给 对象 o2
        //      */
        //     console.log(key, o1[key])
        //     o2[key] = o1[key]
        // }
        // console.log(o1) // {a: 1, b: 2, c: 3}
        // console.log(o2) // {a: 1, b: 2, c: 3}
        // console.log(o1 === o2)  // false
        // o2.b = 999
        // console.log(o1) // {a: 1, b: 2, c: 3}
        // console.log(o2) // {a: 1, b: 999, c: 3}
​
        // console.log(o1)
        // console.log(o2)
​
        // o2.d.d1 = 'QF666'
        // console.log(o1)
        // console.log(o2)
​
        // 通过 JS 提供的方法 完成浅拷贝
        // console.log(o1)
        // console.log(o2)
        /**
         *  Object.assign(新对象, 原始对象)
         *      将 原始对象 中所有的 key 全部浅拷贝到 新对象中, 然后返回一个 对象
        */
        // let o3 = Object.assign(o2, o1)
        let o2 = Object.assign({}, o1)
        console.log(o1)
        console.log(o2)
        

七.深拷贝

  1. 不管数据有多少层数据结构,百分百复制出来一份一模一样但是毫不相关的数据

案例:

  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. 在堆内存中,开辟一段内存空间(a)
    2. 把函数体的内容完全百分百照搬,存放在内存空间中(a)
    3. 把内存空间的地址(a)复制给函数名
  • 调用

    1. 根据函数名内存储的地址(a),去堆内存中找到对应函数
    2. 会去运行内存中,开辟一段新的内存,用于运行函数(函数作用域)
    3. 形参复制---预解析---函数代码全部执行结束
    4. 函数执行完毕之后,这段内存空间会被销毁

九.认识不会被销毁的内存空间

  1. 函数向外部返回了一个引用函数类型(返回的是引用数据类型的地址)
  2. 外部有一个变量接受这个 返回值(外部变量中有 变量拿到这个引用地址)
  3. 为了后续能够正常使用,js不会把这个函数的运行空间销毁

案例:

  function fn () {
            let obj = {
                a: 1,
                b: 2
            }
            return obj
        }
        let o1 = fn()
        // console.log(o1)
​
        o1 = 1000

十.闭包

  • 重点: 看清楚调用的是哪个函数

  • 构成条件:

    1. 需要一个不会被销毁的函数执行空间
    2. 需要直接或间接的返回一个函数
    3. 内部函数使用着 外部函数的私有变量
  • 闭包的好处: 延长变量的作用域(使用时间)

    函数外部可以使用函数内部的变量

  • 闭包的弊端: 因为闭包的存在就一定代表一个函数执行空间不会被销毁,如果大量使用闭包,会导致内存空间占据严重

案例:

           function outer() {
            let a = 100
            let str = 'QF001'
            let obj = {
                a: 1,
                b: 2
            }
            function inner() {
                return a
            }
            return inner
        }
​
        // 1. 调用 outer 的到了 内层函数 inner, 存到了 变量 res 中
        let res = outer()
​
        /**
         *  2. 调用 res 也就是 调用 内层函数 inner, 调用完成之后 因为函数内部 return str,
         *      所以我的到了一个 字符串
         *      然后 我将这个 字符串 存储到了 newStr 这个变量中
        */
        let newStr = res()
​
        // 3. 打印 newStr 得了 outer 函数中 str 变量
        console.log(newStr) // 100

十一.沙箱模式

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

案例:

    function outer() {
            let a = 100
            let str = '张三'
​
            const obj = {
                getA: function () {
                    return a
                },
                getStr: function () {
                    return str
                },
                setA(val) {
                    a = val
                }
            }
​
            return obj
        }
          // 1. 调用了 外部函数 outer , 这个函数内部会返回一个 对象 obj, 然后我将她存储到了 变量 res
        let res = outer()
        console.log(res)
​
        let num = res.getA()
        // console.log(num)    // 100
​
        let newStr = res.getStr()
        // console.log(newStr) // '张三'
​
        res.setA(999)
        // console.log(res.getA()) // 999
​
​
        // 2. 调用了 外部函数 outer, 这个函数内部会返回一个对象 obj, 然后我将它存储到了 变量 res2 中
        let res2 = outer()
        console.log(res2.getA())    // 100

十二.沙箱模式语法糖

  1. 沙箱模式语法糖: 再不影响功能的情况下,对我们的语法做一点简化操作 ES6
  2. 通过 getter 和 setter 帮助我们简化代码书写

案例:

     function outer() {
            let a = 1
            let b = 100
​
            const obj = {
                get a () {
                    return a
                },
                set a (val) {
                    a = val
                },
                get b () {
                    return b
                },
                set b (val) {
                    b = val
                }
            }
​
            return obj
        }
        let res = outer()
        // console.log(res.getA())  // 这样使用 有问题
        console.log(res)
        console.log(res.a)
        res.a = 999
​
        console.log(res.a)
​
​
        console.log(res.b)