JS第二十一次笔记

83 阅读6分钟

1 拷贝

1.1 复制拷贝

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>

    const obj = {
      name: "佩奇",
      age: 18

    }
    //赋值贝 意思就是复制地址,但指向的是同一个对象
    const newObj = obj;
    //直接赋值,如果指向的是复杂数据类型,会复制地址,但堆中的空间是同一个,所以修改一个,另一个也会改变
    newObj.name = "小红";
    console.log(obj);
    console.log(newObj);
  </script>

</body>

</html>

结果: 两个对象都被更改了
原因: 简单的赋值拷贝只单纯的复制了地址,但堆中的空间是同一个,所以修改其中一个的话,会影响所有对象

1.1 浅拷贝

image.png

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>


    //实现浅拷贝
    //1.拷贝对象
    const oldObj1 = {
      uname: '佩奇',
      age: 18
    }
    //1.1 利用Object.assign() 静态方法
    const newObj1 = {}
    Object.assign(newObj1, oldObj1)
    newObj1.uname = '小红'
    console.log(oldObj1)
    console.log(newObj1)

    console.log('-----------------')
    //1.2 展开运算符
    const oldObj2 = {
      uname: '乔治',
      age: 19
    }
    const newObj2 = { ...oldObj2 }
    newObj2.uname = '小黄'
    console.log(oldObj2)
    console.log(newObj2)

    console.log('-----------------')



    //2. 数组的浅拷贝
    const oldArr1 = [1, 2, 3, 4, 5]

    //2.1 concat 方法
    //先创建一个空数组,再把原数组中的元素依次添加到新数组中
    const newArr1 = []
    const newArr2 = oldArr1.concat(oldArr1)
    newArr2.push(60)
    console.log(oldArr1)
    console.log(newArr1)
    console.log(newArr2)

    //2.2 展开运算符
    const newArr3 = [...oldArr1]
    newArr3.push(90)
    console.log(oldArr1)
    console.log(newArr3)

  </script>

</body>

</html>

结果

image.png

image.png

1.2浅拷贝注意事项:

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    const person = {
      name: '张三',
      family: {
        father: '张爸爸',
        mother: '张妈妈'
      }
    }
    //利用浅拷贝,拷贝person对象
    const obj = { ...person }
    obj.name = '李四'//修改成功
    obj.family.father = '李爸爸'//修改失败,因为family是一个对象,浅拷贝无法实现浅拷贝
    console.log(person, obj)

  </script>
</body>

</html>

image.png

1.3 深拷贝

image.png

1.3.1 通过JSON序列化实现(开发中使用较多)

复杂数据类型-->字符串-->对象

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    const oldObj = {
      name: '张三',
      family: {
        father: '张爸爸',
        mother: '张妈妈'
      }
    }
    //深拷贝
    const newObj = JSON.parse(JSON.stringify(oldObj))

    newObj.name = '李四'
    newObj.family.father = '李爸爸'
    console.log(oldObj)
    console.log(newObj)
    //注意:JSON数据是不支持 undefined和 function的,所以用JSON时不会转化出undefined和function类型的数据
  </script>
</body>

</html>

image.png

1.3.2 js库lodash里面_.colneDeep内部实现了深拷贝

[lodash官网](Lodash 简介 | Lodash中文文档 | Lodash中文网)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script src="E:\DeskTop\js\lodash.min.js"></script>
  <script>
    const oldObj = {
      name: '张三',
      family: {
        father: '张爸爸',
        mother: '张妈妈'
      },
      hobby: ['抽烟', '喝酒', '烫头']
    }
    const newObj = _.cloneDeep(oldObj)
    newObj.family.father = '李爸爸'
    newObj.hobby[2] = '炸街'
    console.log(oldObj)
    console.log(newObj)

  </script>
</body>

</html>

结果:

image.png

1.3.3 通过递归实现深拷贝

image.png

1.3.3.1递归介绍:
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    // 递归函数一直执行下去的话会出现栈溢出的问题
    //所以要添加一个退出条件
    let num = 0;
    function fn() {
      num++;
      console.log('我被调用了');

      if (num > 30) {
        return//return 退出递归

      }
      fn()//函数内部调用,实现递归
    }
    fn()//函数内部调用,实现递归
  </script>
</body>

</html>

递归结果: image.png

1.3.3.2 通过递归函数实现深拷贝:

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    const person = {
      name: "佩奇",
      family: {
        father: '猪爸爸',
        mother: '猪妈妈'
      },
      hobby: [1, 2, 3]
      //需求: 利用 递归函数 实现深拷贝(简版)

    }
    function cloneDeep(old) {
      //1. 先判断old数据是数组还是对象 Array. isArray(参数) true/false
      const newObj = Array.isArray(old) ? [] : {}
      //2.核心:循环遍历旧数据,给新数据追加
      for (let k in old) {
        //k 属性
        //old[k] 属性对应的值
        //3. 当我们每次循环获取到数据的时候,先判断是不是复杂数据类型
        if (typeof old[k] === 'object') {//判断数值类型是什么
          //如果是复杂数据类型,就递归调用自己
          newObj[k] = cloneDeep(old[k])
        } else {//如果是简单数据类型,就进行浅拷贝
          //newObj['name']='张三'
          newObj[k] = old[k]
        }
      }

      return newObj
    }
    const obj = cloneDeep(person)

    obj.name = '李四'

    cloneDeep(person)
    console.log(obj, person)
  </script>
</body>

</html>

结果:

image.png

2 异常处理

image.png

2.1 throw异常

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    //异常抛出
    function getSum(x, y) {
      if (!x || !y) {
        throw new Error('参数不能为空')
      }
      return x + y;
    }
    console.log(getSum(1,));
  </script>
</body>

</html>

image.png

2.2 try/catch捕获错误信息

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    //try/catch异常捕获
    //try/catch和throw的不同是:try/catch无论是否有错误,都会继续执行(有finnaly)

    try {
      const div = document.querySelector('box')
      div.innerHTML = '123'
    } catch (error) {
      console.log(error)
    } finally {
      console.log('尝试是否执行finally')
    }
  </script>
</body>

</html>

image.png

2.3 debugger

image.png

image.png

3 处理this

image.png

3.1 改变this指向

3.1.1 call()方法

call()传递参数列表,如:(1,2,3)
const str = Object.prototype.toString.call([])
console.log(str.includes('Array'))//true
image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    const obj = {
      name: 'tom '
    }
    function fun(sing) {
      console.log(this.name + sing)
    }
    //fun()改变fun函数的this指向, 让thiszh指向obj
    fun.call(obj, 'winwin')
    console.log(obj)
    //注意:call方法需要被函数调用,
    //第一个参数就是需要改变的this指向,其余参数是原函数的参数

    //场景: 检测数据类型 typeof instanceof
    console.log(Object.prototype.toString.call(''))//[object String]
    console.log(Object.prototype.toString.call(123))//[object Number]
    console.log(Object.prototype.toString.call([]))//[object Array]
    console.log(Object.prototype.toString.call({}))//[object Object]
    //使用toString.call()方法,检测是否包含数组
    const str = Object.prototype.toString.call([])
    console.log(str.includes('Array'))//true
  </script>
</body>

</html>

结果:

image.png

3.1.2 apply()方法

apply()传递数组,如:([1,2,3])
image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    //改变this指向
    const obj = {
      name: '小明',
    }
    function fun(num) {
      console.log(this, num)
    }
    //apply()方法 会调用函数,并改变this指向(指向obj)
    fun.apply(obj, [10])
    //使用场景:使用到数组时也联用到apply()方法
    // 例如:Math.max()方法求最大值时
    //Math.max(1,2,3)//1,2,3为参数,返回3
    //Math.max([1,2,3])//NaN 表示数组中的值不能识别
    //(只要将数组指向任意对象甚至null即可)(max是函数,可以调用apply方法)
    Math.max.apply(null, [1, 2, 3])//1,2,3为参数,返回3
    console.log(Math.max.apply(null, [1, 2, 3]))
  </script>
</body>

</html>

结果:

image.png

3.1.3 bind()方法--重点

image.png

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    // bind方法:不调用函数,但是会改变this指向
    // 第一个参数是this指向,其余参数(可有可无)是原函数的参数
    //因为不会立即调用函数,所以但会一个原函数的拷贝(新函数),这个函数是改变了this指向的
    const obj = { name: 'zhangsan' }
    function fun() {
      console.log(this)
    }
    //bind方法会返回一个函数,所以此时用fn作为新函数接收
    const fn = fun.bind(obj)
    fn()

  </script>
</body>

</html>

image.png

按钮倒计时案例解释bind()(定时器)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button>按钮</button>
  <script>
    const btn = document.querySelector('button');
    let num = 3
    btn.addEventListener('click', function () {
      //禁用按钮
      this.disabled = true
      //修改计时器
      this.innerHTML = `${num--}秒后启用`
      num--
      setInterval(function () {
        this.innerHTML = `${num--}秒后启用`
        this.disabled = false
        //间隔setInterval的this指向是window,所以需要bind绑定this,使this指向按钮

      }.bind(btn), 1000)
    })
  </script>
</body>

</html>

image.png

4 性能优化

image.png

4.1 防抖

image.png

image.png

4.1.1 使用lodash中的_.debounce(函数名,时间(毫秒为单位))方法实现防抖

<!DOCTYPE html>
<html lang="en">

<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" />
  <title>Document</title>
  <style>
    .box {
      width: 500px;
      height: 500px;
      background-color: #ccc;
      color: #fff;
      text-align: center;
      font-size: 100px;
    }
  </style>
</head>

<body>
  <!-- 使用lodash 的debounce方法实现防抖 -->
  <div class="box"></div>
  <script src="E:\DeskTop\js\lodash.min.js"></script>
  <script>
    let num = 0
    function mouseMove() {
      num++
      box.innerHTML = num
    }
    const box = document.querySelector('.box')
    //_.debounce(函数名,时间)
    box.addEventListener('mousemove', _.debounce(mouseMove, 500))
  </script>
</body>

</html>

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

image.png

<!DOCTYPE html>
<html lang="en">

<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" />
  <title>Document</title>
  <style>
    .box {
      width: 500px;
      height: 500px;
      background-color: #ccc;
      color: #fff;
      text-align: center;
      font-size: 100px;
    }
  </style>
</head>

<body>
  <!-- 使用lodash 的debounce方法实现防抖 -->
  <div class="box"></div>

  <script>
    const box = document.querySelector('.box')
    let num = 0
    function mouseMove() {
      num++
      box.innerHTML = num
    }
    //自行声明一个函数
    function debounce(fn, time) {
      //1.定义一个定时器id
      let timeID = null
      //必须要return一个函数给debounce否则显示类型是undefined,
      // 下面事件绑定中的mousemove不能调用undefined
      return function () {
        if (timeID) clearTimeout(timeID)

        timeID = setTimeout(function () {
          fn()
        }, time)

      }

    }

    box.addEventListener('mousemove', debounce(mouseMove, 500))
  </script>
</body>

</html>

4.2 节流

image.png

4.2.1 lodash实现节流——throttle

时间设置为1000ms,无论mousemove动的多快,它最快也只能1000ms刷新一次num

<!DOCTYPE html>
<html lang="en">

<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" />
  <title>Document</title>
  <style>
    .box {
      width: 500px;
      height: 500px;
      background-color: #ccc;
      color: #fff;
      text-align: center;
      font-size: 100px;
    }
  </style>
</head>

<body>
  <!-- 使用lodash 的debounce方法实现防抖 -->
  <div class="box"></div>
  <script src="E:\DeskTop\js\lodash.min.js"></script>
  <script>
    const box = document.querySelector('.box')
    let num = 0
    function mouseMove() {
      num++
      box.innerHTML = num
    }


    box.addEventListener('mousemove', _.throttle(mouseMove, 1000))
  </script>
</body>

</html>

4.2.2 手动实现节流

<!DOCTYPE html>
<html lang="en">

<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" />
  <title>Document</title>
  <style>
    .box {
      width: 500px;
      height: 500px;
      background-color: #ccc;
      color: #fff;
      text-align: center;
      font-size: 100px;
    }
  </style>
</head>

<body>

  <div class="box"></div>

  <script>
    const box = document.querySelector('.box')
    let num = 0
    function mouseMove() {
      num++
      box.innerHTML = num
    }
    //自行声明一个函数
    //判断之前是否开启了定时器,如果开启了,就不要重新开启定时器
    //如果没有开启则开启定时器,并赋值定时器ID
    //在定时器里调用函数 定时器执行完成之后清空定时器
    function throttle(fn, time) {
      let timeID = null
      return function () {
        //null的布尔值为false 取反则表示真
        if (!timeID) {
          timeID = setTimeout(function () {
            fn()
            //函数调用完成,意为清空定时器
            timeID = null
          }, time)
        }
      }

    }

    box.addEventListener('mousemove', throttle(mouseMove, 500))
  </script>
</body>

</html>

4.3 节流防抖总结

image.png