js高阶 -继承-深浅拷贝-闭包-沙箱模式+(沙箱的语法糖)

82 阅读5分钟

ES6继承

    // 父类
        class Person {
            constructor (name, age) {
                this.name = name
                this.age = age
            }

            sayHi () {
                console.log('hi')
            }
        }
    // 继承
        class Stu extends Person {
            constructor (id, ...args) {
                super(...args)
                this.id = id
            }
        }
        const s1 = new Stu(10086, '张三', 18)
        console.log('实际的对象: ', s1)
  • 子类

    • 在 实现 ES6 类的继承的时候, 书写方法有几个要求

        1. class 子类的类名 extends 要继承的父类类名 {}
        1. constructor 内部 必须书写 super
        • 他的作用可以在调用的时候对他进行传参, 传递的参数会传递到 父类中
      • 注意: 哪怕父类不需要任何参数, 我们的 super 也需要书写

        • 并且需要书写在 constructor 内部的 第一行
      • 当前的方法只能给 class 类使用

        • 但是可以继承到构造函数中的内容, 也就是说父类是构造函数或者class都可以

深浅拷贝 (只考虑引用数据类型, 当前案例中 只考虑 数组和对象)

  • 赋值
    • 因为引用数据类型在赋值的时候 是将地址传递过去了
    • 所以赋值后是将一个地址给到了一个新的变量, 如果你对这个地址内做任何操作
    • 那么两个变量都会互相影响
  • 浅拷贝
    • 通过浅拷贝复制一个引用数据类型到另外一个变量中
    • 其中这个数据内的基本数据类型修改的时候不会互相影响
    • 但是其中的 引用数据类型再修改的时候会互相影响
  • 深拷贝
    • 基于原本的数据, 复制出一个一摸一样的, 但是内部数据的修改, 不会互相影响
        const obj = {
            name: '我是对象obj',
            info: {
                width: 100,
                height: 101
            },
            arr: [100, 200, 300]
        }

        // 深拷贝 
        const newObj = JSON.parse(JSON.stringify(obj))

        // 修改基本数据类型
        newObj.name = '新的名字'
        obj.name = '一个更新的名字'

        // 修改引用数据类型
        newObj.info.width = 999
        obj.arr[0] = 'QF001'

        console.log('原对象obj: ', obj)
        console.log('深拷贝后的对象newObj: ', newObj)

        // 深拷贝 
        const obj = {
            name: '我是对象obj',
            info: {
                width: 100,
                height: 101
            },
            arr: [100, 200, 300]
        }
        const newObj = {}
        function deepCopy(target, origin) {
            /**
             *  target 目标对象
             *  origin 原对象
             *
             *  当前函数执行完毕后, 会基于origin 创建一个和他一摸一样的对象, 放到 target 中
            */

            // 下边两个写法功能一样, 但是 第一行操作的是形参不依赖外界变量, 第二行操作的是全局变量, 需要操作外界的变量
            // target.name = '函数内部添加的'
            // newObj.name = '12345678'

            for (let key in origin) {
                // console.log(key, origin[key])

                if (Object.prototype.toString.call(origin[key]) === '[object Object]') {
                    // 说明当前的值是 对象
                    target[key] = {}
                    deepCopy(target[key], origin[key])

                } else if (Object.prototype.toString.call(origin[key]) === '[object Array]') {
                    // 说明当前的值是 数组
                    target[key] = []
                    deepCopy(target[key], origin[key])

                } else {
                    // 说明当前的值是 基本数据类型
                    target[key] = origin[key]
                }
            }
        }
        deepCopy(newObj, obj)
        修改基本数据类型
        newObj.name = '新的名字'
        obj.name = '一个更新的名字'
        修改引用数据类型
        newObj.info.width = 999
        obj.arr[0] = 'QF001'
        console.log('原对象obj: ', obj)
        console.log('深拷贝后的对象newObj: ', newObj)

闭包

  • 书写闭包, 需要创建一个不会销毁的内容空间
  • 需要 直接/间接 返回一个函数
  • 内部函数, 需要访问外部函数内创建的局部变量
  • 优点: 延长了变量的使用时间
  • 缺点: 每一个闭包都会创建一个不会销毁的内存空间
    • 如果闭包书写的太多, 那么这个不会被销毁的空间就越来越多
    • 就可能会造成页面/程序的卡顿

沙箱模式

  • 是 JS 中 利用 闭包 完成的一个设计模式

  • 设计模式: 为了解决某一类问题的最优化的写法, 但不是万能

function outter() {

            let a = 100
            let b = 99

            const obj = {
                getA() {
                    return a
                },
                setA(val) {
                    a = val
                },
                getB() {
                    return b
                },
                setB(val) {
                    b = val
                }
            }

            return obj
        }

        /**
         *  此时调用函数会开启一个执行空间 (XF666)
         *      内部有对应的局部变量 a 和 b
         * 
         *  所以我们现在 res_1 使用的变量是来自 执行空间 XF666
        */

        const res_1 = outter()
        console.log(res_1.getA())   // 100
        res_1.setA(999)
        console.log(res_1.getA())   // 999


        /**
         *  此时调用函数会开启一个执行空间 (XF777)
         *      内部有对应的局部变量 a 和 b
         * 
         *  所以我们现在 res_2 使用的变量来自 执行空间 XF777
        */

        const res_2 = outter()
        console.log(res_2.getA())   // (999错误) (100正确)


        // 当前案例 可以帮助我们思考上述的问题
        // const obj_1 = {
        //     a: 100,
        //     b: 99
        // }
        // const obj_2 = {
        //     a: 100,
        //     b: 99
        // }
        // obj_1.a = 999
        // console.log(obj_2.a)    // ???
        // 案例结束位置========================================

沙箱模式语法糖

 /**
         *  沙箱模式语法糖
         * 
         *   语法糖: 在保证功能不变的情况下, 尽可能的简化我们的代码
        */

        // 基础版
        function fn1() {
            let a = 100
            
            return {
                getA () {
                    return a
                },
                setA (val) {
                    a = val
                }
            }
        }

        const res1 = fn1()
        // console.log(res1)
        // console.log(res1.getA())    // 100
        // res1.setA(999)
        // console.log(res1.getA())    // 999


        // 语法糖 优化版
        function fn2() {
            let a = 1
            let b = 'QF001'

            return {
                /**
                 *  将来对象中如果有人访问了 属性 a, 那么就会触发 get a 这个方法
                 *  如果有人修改了 属性a, 那么就会自动触发 set a 这个方法
                */
                get a () { return a },
                set a (val) { a = val },
                get b () { return b }
            }
        }
        
        const res2 = fn2()

        console.log(res2)
        // 用了语法糖之后的简化写法
        // console.log(res2.a) // 1
        // res2.a = 666
        // 用了语法糖之后的简化写法
        // console.log(res2.a) // 666

        console.log(res2.b) // 'QF001'
        res2.b = 'QF999'    // 当前没有给 属性b 设置 set 方法, 所以修改是无效的
        console.log(res2.b) // 'QF001'