JavaScript 进阶 第四天

  • 深浅拷贝
  • 异常处理
  • 处理this
  • 性能优化

一. 深浅拷贝

深浅拷贝只针对引用类型

1.1 浅拷贝

  • 拷贝的是地址
  • 常见方法

     (1)拷贝对象:Object.assign()   / 展开运算符 {...obj}    

     (2)拷贝数组:Array.prototype.concat() 或者 [...arr]

  • 浅拷贝存在的问题

      ① 简单数据类型拷贝的是值,引用数据类型拷贝的是地址

      ② 赋值的时候不会影响原来的值,如果是简单数据类型拷贝值,引用数据类型拷贝的是地址

      ③ 如果是单层对象,就没问题,如果是多层对象就有问题

 改变o 的值不会影响obj的值

JavaScript 进阶 第四天_第1张图片

 如果是多层对象,改变o的值就会影响到obj的值

JavaScript 进阶 第四天_第2张图片

  •  直接赋值和浅拷贝有什么区别

     ① 直接赋值,只要是对象,就会互相影响,因为会直接拷贝对象栈里面的地址

     ② 浅拷贝如果是一层对象,不会相互影响,如果是多层对象,还是会互相影响

  • 浅拷贝怎么理解

    ① 拷贝对象后,里面的属性值是简单数据类型直接拷贝值

    ② 如果属性值是引用数据类型则拷贝的是地址

1.2 深拷贝

  • 深拷贝:拷贝的是对象,不是地址
  • 常见方式

        ① 通过递归实现深拷贝

        ② loadash/cloneDeep

        ③ 通过JSON.stringify()实现

  • 通过递归实现深拷贝

       ① 函数递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数

       ② 就是自己调自己

       ③ 递归容易发生“栈溢出"错误,所以必须要加退出条件return

let i = 1
        function fn () {
            console.log(`这是第${i}次`)
            if (i >= 6) {
                return
            }
            i++
            fn()
        }
        fn()
  • 递归实现
        const obj = {
            uname :'pink',
            age: 18,
            hobby:['乒乓球', '足球'],
            family: {
                baby: '小pink'
            }
        }
        const o = {}

        function deepCopy(newObj, oldObj) {
            // 在代码中直接打断点
            debugger
            for (let k in oldObj) {
                // 处理数组的问题
                // 一定先写数组,再写对象,因为数组属于对象
                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]
                }
                // k 属性名  属性值 oldObj[k]
            }
        }
        deepCopy(o, obj)
        o.age = 20
        o.hobby[0] = '篮球'
        o.family.baby = '小hi'
        console.log(o)
        console.log(obj)

JavaScript 进阶 第四天_第3张图片

  • 使用lodash实现深拷贝
  
    
  • 利用JSON.stringfy 
        const obj = {
            uname :'pink',
            age: 18,
            hobby:['乒乓球', '足球'],
            family: {
                baby: '小pink'
            }
        }

        // 1.先转换为字符串,然后再转回对象,就会生成一个新的对象,跟原来的对象没有关系,
        // 修改里面的值不会影响原来的数据
        const o = JSON.parse(JSON.stringify(obj))
        console.log(o)
        o.family.baby = '123'
        console.log(obj)

二.异常处理

  • throw抛异常
  • try/catch 捕获异常
  • debugger

2.1 throw抛异常

① 异常处理是指预估代码在执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续执行

  • throw 抛出异常信息,程序也会终止执行
  • throw 后面跟的是错误提示信息
  • Error对象配合throw使用,能够设置更详细的错误信息
        function fn(x, y) {
            if (!x || !y) {
                // throw '没有参数传递进来'
                // 精确到行
                throw new Error('没有参数传递进来')
                
            }
            return x + y
        }

2.2 catch捕获异常

① 代码示例

        function fn(){
            try {
                const p = document.querySelector('.p')
                p.style.color = 'red'
            } catch (err) {
                // 只是拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
                console.log(err.message)
                // 加return 中断程序
                // return
                throw new Error('选择器错误了')
            }
            // 不管程序对不对,一定会执行的代码
            finally {
                alert('弹出对话框')
            }
            // 
            console.log(111)
        }
        fn()

② 说明

  • 可能发送错误的代码写到try中
  • 如果try中的代码出现错误后,会执行catch中的代码段,并截获到错误信息
  • catch会拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
  • finally无论程序对错,一定会执行

2.3 debugger

作用:断点调试

JavaScript 进阶 第四天_第4张图片

三.this指向 

3.1 普通函数的this指向

  • 普通函数的调用方式决定了this的值,即【谁调用this的值就指向谁】
  • 普通函数没有明确调用者时this的值为window, 严格模式(use strict)下没有调用者时this的值为undefined

3.2 箭头函数的this指向

  • 箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
  • 箭头函数中的this会默认绑定上一层的this值
  • 会向外层作用域一层一层查找this, 直到有this的定义

3.3 注意情况

  • DOM事件回调函数如果里面需要DOM对象的this, 不推荐使用箭头函数
  • 基于原型的面向对象也不推荐采用箭头函数
  • 构造函数,原型函数,DOM事件等不适用
  • 适用于需要使用上层this的地方

3.4 改变this的指向

  • call()
  • apply()
  • bind()

1.call()

  • 使用call方法调用函数,同时执行被调用函数中this的值
  • 语法:fun.call(thisArg, arg1, arg2)
  • 返回值就是函数的返回值,因为它就是调用函数
        const obj = {
            uname: 'pink'
        }
        function fn (x, y) {
            console.log(x + y)
            console.log(this)   // obj
        }

        // 1.调用函数
        // 2.改变this指向
        fn.call(obj, 1, 2)

2.apply()

  • 使用apply方法调用函数,同时指定被调用函数中this的值
  • 语法: fun.apply(thisArg, [argsArray])
  • thisArg:在fun函数运行时指定的this值
  • argsArray:传递的值,必须包含在数组里面
  • 返回值就是函数的返回值,因为它就是调用函数
  • apply主要跟数组有关系,比如使用Math.max() 求数组的最大值
        const obj = {
            uname: 'pink'
        }
        function fn (x, y) {
            console.log(this)
            console.log(x + y)
        }
        // 1.调用函数   2.改变this指向   3.第三个参数要放一个数组
        fn.apply(obj, [1, 2])

求数组最大值

const arr = [2, 4, 5]
const max = Math.max.apply(Math, arr)
console.log(max)
console.log(Math.max(...arr))

3.bind()

  • bind() 方法不会调用函数,但是能改变函数内部this指向
  • 语法:fun.bind(thisArg, arg1, arg2)
  • thisArg:在 fun函数运行时指定this的值
  • arg1, arg2:传递的其他参数
  • 返回由指定的this值和初始化参数改造的 原函数拷贝(新函数)
  • 只是向改变this指向,并不想调用这个函数的时候,可以使用bind
  • 如下代码,如果不调用fun的话只是去调用bind,fn函数是不会调用的
        const obj = {
            uname: 'pink'
        }
        function fn () {
            console.log(this)
        }

        // 返回值是个函数,但是这个函数里面的this是更改过的obj
        const fun = fn.bind(obj)
        // console.log(fun)
        fun()

4. 相同点

  • 都可以改变函数内部的this指向

5.区别点

  • call 和 apply 会调用函数,并且改变函数内部this指向
  • call和apply传递的参数不一样,call传递参数arg1, arg2...的形式,apply 必须传数组形式
  • bind不会调用函数,可以改变函数内部this指向

6.主要应用场景

  • call 调用函数并且可以传递参数
  • apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
  • bind 不调用函数,但还想改变this指向

四. 防抖

  • 防抖:单位时间内,频发触发事件,只执行最后一次

JavaScript 进阶 第四天_第5张图片

  • 使用场景

     ① 搜索框搜索输入,只需用户最后一次输入完,再发送请求

     ② 手机号,邮箱验证输入检测

  • 需求实现:鼠标在盒子上移动,鼠标停止500ms之后,里面的数字才会变化 +1

      ① losash 提供的防抖来处理

 const box = document.querySelector('.box')
    let i = 1
    function mouseMove() {
      box.innerHTML = i++
      // 如果里面存在大量消耗性能的代码,比如dom操作,数据处理,可能造成卡顿
    }

box.addEventListener('mousemove', _.debounce(mouseMove, 500))

      ② 手写一个防抖函数来处理

    (1)定时器变量

    (2)先判断是否有定时器,如果有定时器,先清除以前的定时器

    (3)如果没有定时器则开启定时器,记得存到变量里面

    (4)在定时器里面调用要执行的函数

function debounce (fn, t) {
      let timer
      // return 返回一个匿名函数
      return function () {
        if (timer) clearTimeout(timer)
        timer = setTimeout(function () {
          fn() // 加小括号调用
        }, t)
      }
    }
 box.addEventListener('mousemove', debounce(mouseMove, 500))

  五. 节流

  • 单位时间内,频繁触发事件,只执行一次
  • 举例:王者荣耀冷却技能
  • 使用场景:鼠标移动mousemove 页面尺寸缩放resize 滚动条滚动scroll
  • 实现方式

     ① 使用lodash实现

const box = document.querySelector('.box')
    let i = 1
    function mouseMove() {
      box.innerHTML = i++
    }
    // // 500毫秒之内只会执行一次
    box.addEventListener('mousemove', _.throttle(mouseMove, 500))

 ② 自己写一个节流函数

    // 1.声明一个定时器变量
    // 2.当鼠标每次滑动都先判断是否有定时器,如果有定时器就不开启
    // 3.如果没有定时器则开启定时器
    // 4.定时器里面调用要执行的函数
    // 5.定时器里面要把定时器清空
    function throttle(fn, t) {
        let timer = null
        return function() {
            if (!timer) {
                timer = setTimeout(function() {
                    fn()
                    // 清空定时器
                    // 不能使用clearTimeout(timer), 在setTimeout中无法使用clearTimeout来清除定时器
                    timer = null
                }, t)
            }
        }
    }
    box.addEventListener('mousemove', throttle(mouseMove, 500))

节流防抖总结

JavaScript 进阶 第四天_第6张图片

 

你可能感兴趣的:(javascript,开发语言,ecmascript)