06 - ECMAScript 6 - 04. 深浅拷贝、异常处理、处理this、性能优化、综合案例

ECMAScript 6

深浅拷贝

深浅拷贝

  • 开发中经常需要复制一个对象,若直接赋值会有以下问题
    const obj = {
        uname: 'pink',
        age: 18
    }
    const o = obj
    console.log(o)  // {age: 18 uname: "pink"}
    
    o.age = 20
    console.log(o)  // {age: 20 uname: "pink"}
    console.log(obj)  // {age: 20 uname: "pink"}
    
  • 浅拷贝、深拷贝都只针对引用类型

直接赋值 vs 浅拷贝 vs 深拷贝

  • 直接赋值:只要是对象,都会相互影响,因为直接拷贝对象栈里的地址
  • 浅拷贝:若只是一层对象,不相互影响;若是多层对象,会互相影响

浅拷贝

  • 拷贝的是 地址
  • 常见方法
    ① 拷贝对象:Object.assign() 或 展开运算符 {...obj}
    ② 拷贝数组:Array.prototype.concat()[...arr]
    const obj = {
        uname: 'pink',
        age: 18
    }
    // Object.assign()
    const o = {}
    Object.assign(o, obj)
    o.age = 20
    console.log(o)  // {age: 20 uname: "pink"}
    console.log(obj)  // {age: 18 uname: "pink"}
    
    // {...obj}
    const o1 = { ...obj }
    o1.age = 20
    console.log(o1)  // {age: 20 uname: "pink"}
    console.log(obj)  // {age: 18 uname: "pink"}
    
  • 存在问题:若是简单数据类型拷贝值,引用数据类型拷贝的是地址
    【即若是单层对象,则ok;若是多层对象,则有问题】
    【拷贝对象后,里面的属性值若是简单数据类型,则直接拷贝值;若是引用数据类型,则拷贝地址】
    const obj = {
        uname: 'pink',
        age: 18,
        family: {
            baby: '小pink'
        }
    }
    // Object.assign()
    const o = {}
    Object.assign(o, obj)
    o.age = 20
    o.family.baby = '老pink'
    console.log(o)  // {age: 20, family: {baby: '老pink'}, uname: "pink"}
    console.log(obj)  // {age: 18, family: {baby: '老pink'}, uname: "pink"}
    
    // {...obj}
    const o1 = { ...obj }
    o1.age = 20
    o1.family.baby = '老pink'
    console.log(o1)  // {age: 20, family: {baby: '老pink'}, uname: "pink"}
    console.log(obj)  // {age: 18, family: {baby: '老pink'}, uname: "pink"}
    

深拷贝

  • 拷贝的是 对象【非地址】
  • 常见方法
    ① 递归【易出现“栈溢出(stack overflow),故必须加推出条件 return”】
    lodashcloneDeep
    JSON.stringfy()
  • 递归
    <div></div>
    <script>
        // 利用递归函数实现 setTimeout 模拟 setInterval 效果
        // 输出当前时间 new Date().toLocaleString()
        function getTime() {
            document.querySelector('div').innerHTML = new Date().toLocaleString()
            setTimeout(getTime, 1000)
        }
        getTime()
    </script>
    
    <div></div>
    <script>
        const obj = {
            uname: 'pink',
            age: 18,
            hobby: ['乒乓球', '足球'],
            family: { baby: '小pink' }
        }
        const o = {}
        function deepCopy(newObj, oldObj) {
            for (let k in oldObj) {
                // k 属性名    oldObj[k] 属性值
                // k 是变量,只能用中括号    给 newObj 添加了一个 k 属性
                if (oldObj[k] instanceof Array) {
                    newObj[k] = []
                    deepCopy(newObj[k], oldObj[k])
                } else if (oldObj[k] instanceof Object) {
                    newObj[k] = {}
                    deepCopy(newObj[k], oldObj[k])
                } else {
                    newObj[k] = oldObj[k]
                }
            }
        }
        deepCopy(o, obj)
        console.log(o)
        o.age = 20
        o.hobby[0] = '篮球'
        o.family.baby = '老pinkb'
        console.log(obj)
    </script>
    
  • lodash
    <script src="./lodash.min.js"></script>
    <script>
        const obj = {
            uname: 'pink',
            age: 18,
            hobby: ['篮球', '足球'],
            family: {
                baby: '小baby'
            }
        }
        // 语法:_.cloneDeep(要被克隆的对象)
        const o = _.cloneDeep(obj)
        console.log(o)
        o.family.baby = '老pink'
        console.log(obj)
    </script>
    
  • JSON.stringfy()
    const obj = {
        uname: 'pink',
        age: 18,
        hobby: ['篮球', '足球'],
        family: {
            baby: '小baby'
        }
    }
    const o = JSON.parse(JSON.stringify(obj))
    console.log(o)
    o.family.baby = '老pink'
    console.log(obj)
    

异常处理

throw 抛异常

  • 预估代码执行过程中可能发生的错误,最大程度地避免错误的发生导致整个程序无法继续运行
  • 总结
    ① throw 抛出异常信息,程序也会终止执行
    ② throw 后面跟的是错误提示信息
    ③ Error 对象配合 throw 使用,能够设置更详细的错误信息
  • 示例
    function counter(x, y) {
        if (!x || !y) {
            // throw '没有参数传递进来'
            throw new Error('参数不能为空')
        }
        return x + y
    }
    console.log(counter())
    

try / catch 捕获异常

  • 总结
    ① try … catch 用于捕获错误信息
    ② 将预估可能发生错误的代码写在 try 代码段中
    ③ 如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到错误信息
    ④ finally 不管是否有错误,都会执行
  • 示例
    function foo() {
        try {
            const p = document.querySelector('.p')
            p.style.color = 'red'
        } catch (error) {
            // 拦截错误:提示浏览器提供的错误信息,但是不中断程序的执行
            console.log(error.message)
            throw new Error('你看看,选择器错误了吧')
            // return
        }
        finally {
            // 不管有没有 catch,一定会执行的代码
            alert('执行')
        }
        console.log('如果出现错误,我的语句不会执行')
    }
    foo()
    

debugger

  • 示例
    const arr = [1, 3, 5]
    const newArr = arr.map((item, index) => {
        debugger
        console.log(item)  // 当前元素
        console.log(index)  // 当前元素索引号
        return item + 10  // 当前元素 + 10
    })
    console.log(newArr)  // [11, 13, 15]
    

处理this

this 指向

普通函数 this 指向
  • 普通函数的调用方式决定了 this 的值【即谁调用,指向谁】
    普通函数没有明确调用者时,this 值为 window;严格模式下没有调用者时,this 值为 undefined
  • 示例
    <button>点击</button>
    <script>
    // 普通函数
    function sayHi() {
        console.log(this)
    }
    // 函数表达式
    const sayHello = function () {
        console.log(this)
    }
    sayHi()  // window
    sayHello()  // window
    
    
    const user = {
        name: '小明',
        walk: function () {
            console.log(this)
        }
    }
    user.sayHi = sayHi
    user.sayHello = sayHello
    user.sayHi()  // user
    user.sayHello()  // user
    
    
    'use strict'
    function fn() {
        console.log(this)
    }
    fn()  // undefined
    
    
    document.querySelector('button').addEventListener('click', function () {
        console.log(this)  // button
    })
    </script>
    
箭头函数 this 指向
  • 箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响。事实上,箭头函数中并 不存在 this
    ① 箭头函数默认绑定外层 this 的值,故在箭头函数中 this 的值和外层的 this 是一样的
    ② 箭头函数中的 this 引用的就是最近作用域中的 this
    ③ 向外层作用域中,一层一层查找 this,直达有 this 的定义
  • 注意事项
    ① DOM 事件回调函数如果里面需要 DOM 对象的 this,不推荐使用箭头函数
    ② 基于原型的面向对象不推荐使用箭头函数
    ③ 构造函数不推荐使用箭头函数
    ④ 字面量对象中函数不推荐使用箭头函数
  • 示例
    <button class="btn">点击</button>
    <script>
        console.log(this)  //window
    
        console.log('---------------------------------')
    
        const sayHi = function () {
            console.log(this)  // 箭头函数中的 this 为函数声明环境中 this 一致
        }
        sayHi()  // window
        const user = {
            name: '小明',
            walk: () => {
                console.log(this)
            }
        }
        user.sayHi = sayHi
        user.sayHi()  // user
    
        console.log('---------------------------------')
    
        const btn = document.querySelector('.btn')
        btn.addEventListener('click', () => {
            console.log(this)  // window
        })
        btn.addEventListener('click', function () {
            console.log(this)  // button
        })
    
        console.log('---------------------------------')
    
        function Person() {
        }
        Person.prototype.walk = () => {
            console.log('人都要走路...')
            console.log(this)  // window
        }
        const p1 = new Person()
        p1.walk()
    </script>
    

改变 this

  • JS 中允许指定函数中 this 的指向,有 3 个方法可动态指定普通函数中 this 的指向
call()
  • 使用 call 方法调用函数,同时指定被调用函数中 this 的值
  • fun.call (thisArg, arg1, arg2, ...)
    thisArg:在 fun 函数运行时指定的 this 值
    arg1, arg2:传递的其他参数
    ③ 返回值就是函数的返回值,因为它就是调用函数
  • 示例
    const obj = {
        uname: 'pink'
    }
    function fn(x, y) {
        console.log(this)
        console.log(x + y)
    }
    fn.call(obj, 1, 2)  // 3
    
apply()
  • 使用 apply 方法调用函数,同时指定被调用函数中 this 的值
  • fun.apply (thisArg, [argsArray])
    thisArg:在 fun 函数运行时指定的 this 值
    argsArray:传递的值,必须包含在 数组 里【apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值】
    ③ 返回值就是函数的返回值,因为它就是调用函数
    注:apply 不改变指向时,写 自己 或 null,但不可省略
  • 示例
    function counter(x, y) {
        return x + y
    }
    let result = counter.apply(null, [5, 10])
    console.log(result)  // 15
    
    const obj = {
        age: 18
    }
    function fn(x, y) {
        console.log(this)  // {age: 18}
        console.log(x + y)  // 3
    }
    fn.apply(obj, [1, 2])
    
    // 求数组最大值的两个方法
    const arr = [3, 5, 2, 9]
    // apply不改变指向时,写自己或null,但不可省略
    console.log(Math.max.apply(null, arr))  // 9,利用apply
    console.log(Math.max.apply(Math, arr))  // 9,利用apply
    
    console.log(Math.max(...arr))  // 9,利用展开运算符
    
bind()
  • bind 方法不会调用函数,但能改变函数内部中 this 指向【如 改变定时器内部的 this 指向】
  • fun.bind (thisArg, arg1, arg2, ...)
    thisArg:在 fun 函数运行时指定的 this 值
    arg1, arg2:传递的其他参数
    ③ 返回由指定的 this 值和初始化参数改造的 原函数拷贝(新函数)
  • 示例
    function sayHi() {
        console.log(this)
    }
    let user = {
        name: '小明',
        age: 18
    }
    let sayHello = sayHi.bind(user)
    sayHello()
    
    <button>发送短信</button>
    <script>
        const obj = {
            age: 18
        }
        function fn() {
            console.log(this)
        }
        // 返回值是个函数,但此函数里的 this 是更改过的 obj
        const fun = fn.bind(obj)
        console.log(fun)
        fun()  // {age: 18}
    
        // 需求:点击按钮即禁用,2秒钟后开启
        const btn = document.querySelector('button')
        // 法一
        btn.addEventListener('click', function () {
            this.disabled = true  // 此处 this 指向 btn
            window.setTimeout(function () {
                this.disabled = false
            }.bind(btn), 2000)
        })
        // 法二
        document.querySelector('button').addEventListener('click', function () {  // 不可改为箭头函数
            this.disabled = true
            window.setTimeout(function () {  // 可改为箭头函数
                this.disabled = false
            }.bind(this), 2000)
        })
    </script>
    

性能优化

防抖 debounce

  • 单位时间内,频繁触发事件,只执行最后一次
  • 使用场景:
    ① 搜索框搜索输入。只需用户最后一次输入完,再发送请求
    ② 手机号、邮箱验证输入检测
  • 案例:鼠标在盒子上移动,里面的数字就会变化
    要求:鼠标在盒子上移动,鼠标停止 500ms 之后,里面的数字才会变化 +1
    方案:① lodash 提供的防抖来处理 ② 手写一个防抖函数来处理
    <body>
        <button class="box">按钮</button>
        <script src="./js/lodash.min.js"></script>
        <script>
            const box = document.querySelector('.box')
            let i = 1
            function mouseMove() {
                box.innerHTML = i++
                // 若存在开销较大操作,大量数据处理,大量 dom 操作,可能会卡
            }
            // box.addEventListener('mousemove', mouseMove)
    
            // 方案1:利用 lodash 库实现防抖,500 ms 之后 +1
            // 语法:_.debounce(fun, 时间)
            // 该函数会从上一次被调用后,延迟 wait 毫秒后调用 func 方法
            // box.addEventListener('mousemove', _.debounce(mouseMove, 500))
    
            // 方案2:手写防抖函数 (setTimeout)
            function debounce(fn, t) {
                let timer
                return function () {
                    if (timer)
                        clearTimeout(timer)
                    timer = setTimeout(function () {
                        fn()
                    }, t)
                }
            }
            // addEventListener 函数第二个参数有小括号,是调用
            // 若 debounce 函数中返回的不是函数,而是写在外面,则只运行一次
            box.addEventListener('mousemove', debounce(mouseMove, 500))
        </script>
    </body>
    

节流 throttle

  • 单位时间内,频繁触发事件,只执行一次
  • 使用场景:
    高频事件:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动 scroll 等
  • 案例:鼠标在盒子上移动,里面的数字就会变化
    要求:鼠标在盒子上移动,不管移动多少次,每隔 500ms 才 +1
    方案:① lodash 提供的节流函数来处理 ② 手写一个节流函数来处理
    <body>
        <button class="box">按钮</button>
        <script src="./js/lodash.min.js"></script>
        <script>
            const box = document.querySelector('.box')
            let i = 1
            function mouseMove() {
                box.innerHTML = i++
                // 若存在开销较大操作,大量数据处理,大量 dom 操作,可能会卡
            }
            // box.addEventListener('mousemove', mouseMove)
    
            // 方案1:利用 lodash 库实现节流,500 ms 之后 +1
            // 语法:_.throttle(fun, 时间)
            // 在 wait 秒内最多执行 func 一次的函数
            // box.addEventListener('mousemove', _.throttle(mouseMove, 500))
    
            // 方案2:手写防抖函数 (setTimeout)
            function throttle(fn, t) {
                let timer = null
                return function () {
                    if (!timer) {
                        timer = setTimeout(function () {
                            fn()
                            // 清空定时器
                            timer = null
                        }, t)
                    }
                }
            }
            box.addEventListener('mousemove', throttle(mouseMove, 500))
        </script>
    </body>
    
  • setTimeout 注意事项
    let timer = null
    timer = setTimeout(() => {
        clearTimeout(timer)
        console.log(timer)
    }, 1000)
    // 在 setTimeout 中无法删除定时器
    // 因为定时器还在运作,所以使用 timer = null,而非 clearTimeout(timer)
    

防抖 vs 节流

防抖
  • 说明:单位时间内,频繁触发事件,只执行最后一次
    【指触发事件后在 n 秒内函数只能执行一次。若在 n 秒内又触发了,则会重新计算函数执行时间】
  • 使用场景:搜索框搜索输入、手机号、邮箱验证输入检测
节流
  • 说明:单位时间内,频繁触发事件,只执行一次
    【指连续触发事件,但在 n 秒内只执行一次函数】
  • 使用场景:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动 scroll

综合案例

  • 页面打开,可以记录上一次的视频播放位置
  • 两事件:
    ontimeupdate 事件在视频/音频 (audio/video) 当前的播放位置发送改变时触发
    onloadeddata 事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频 (audio/video) 的下一帧时触发
  • 案例分析
    谁需要节流?
    ontimeupdate:触发频次太高了,我们可以设定 1 s 触发一次
  • 代码
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="referer" content="never">
    <title>综合案例</title>
    <style>
        * {
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }

        .container {
            width: 1200px;
            margin: 0 auto;
        }

        .video video {
            width: 100%;
            padding: 20px 0;
        }

        .elevator {
            position: fixed;
            top: 280px;
            right: 20px;
            z-index: 999;
            background: #fff;
            border: 1px solid #e4e4e4;
            width: 60px;
        }

        .elevator a {
            display: block;
            padding: 10px;
            text-decoration: none;
            text-align: center;
            color: #999;
        }

        .elevator a.active {
            color: #1286ff;
        }

        .outline {
            padding-bottom: 300px;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="header">
            <a href="http://pip.itcast.cn">
                <img src="https://pip.itcast.cn/img/logo_v3.29b9ba72.png" alt="">
            </a>
        </div>
        <div class="video">
            <video src="https://v.itheima.net/LapADhV6.mp4" controls></video>
        </div>
        <div class="elevator">
            <a href="javascript:;" data-ref="video">视频介绍</a>
            <a href="javascript:;" data-ref="intro">课程简介</a>
            <a href="javascript:;" data-ref="outline">评论列表</a>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
    <script>
        const video = document.querySelector('video')

        // 该事件触发时,每隔 1s,就记录当前时间到本地存储
        video.ontimeupdate = _.throttle(() => {
            // console.log(video.currentTime)
            localStorage.setItem('currentTime', video.currentTime)
        }, 1000)

        // 下次打开页面,从本地存储取出时间,让视频从取出的时间播放。若无,则默认为0
        video.onloadeddata = () => {
            video.currentTime = localStorage.getItem('currentTime')
        }
    </script>
</body>
  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值