JavaScript 高级 - 第四天

136 阅读4分钟

深浅拷贝

  1. 首先浅拷贝和深拷贝只针对像Object,Array这样的复杂对象,简单来说,浅拷贝只复制一层对象的属性,深拷贝则复制了所有的层级。 对于字符串类型,浅复制是对值的复制,对于对象来说,浅复制是对对象地址的复制,并没有开辟新的栈,也就是复制的结果是两个对象指向同一个同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变,而深复制则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
const obj = {
      uname: 'pink',
      age: 18,
      hobby: ['乒乓球', '足球'],
      family: {
        baby: '小pink'
      }
    }
    // 把对象转换为 JSON 字符串
    // console.log(JSON.stringify(obj))
    const o = JSON.parse(JSON.stringify(obj))
    console.log(o)
    o.family.baby = '123'
    console.log(obj)

异常处理

throw

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

<script>
  function counter(x, y) {

    if(!x || !y) {
      // throw '参数不能为空!';
      throw new Error('参数不能为空!')
    }

    return x + y
  }

  counter()
</script>

总结:

  1. throw抛出异常信息,程序也会终止执行
  2. throw后面跟得是错误提示信息
  3. Error对象配合throw使用,能够设置更详细的错误信息

try ... catch

<script>
   function foo() {
      try {
        // 查找 DOM 节点
        const p = document.querySelector('.p')
        p.style.color = 'red'
      } catch (error) {
        // try 代码段中执行有错误时,会执行 catch 代码段
        // 查看错误信息
        console.log(error.message)
        // 终止代码继续执行
        return

      }
      finally {
          alert('执行')
      }
      console.log('如果出现错误,我的语句不会执行')
    }
    foo()
</script>

总结:

  1. try...catch用于捕获错误信息
  2. 将预估可能发生错误的代码写在try代码段中
  3. 如果tyr代码段中出现错误后,会执行catch代码段,并截获到错误信息

this

默认值

this是JavaScript最具魅惑的知识点,不同应用场合this的取值可能有意想不到的结果,在次我们对以往学过的关于this默认的取值 情况进行归纳和总结。
普通函数
普通函数的调用方式决定了this的值,即【谁调用this的值指向谁】,如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this)  
  }
  // 函数表达式
  const sayHello = function () {
    console.log(this)
  }
  // 函数的调用方式决定了 this 的值
  sayHi() // window
  window.sayHi()
	

// 普通对象
  const user = {
    name: '小明',
    walk: function () {
      console.log(this)
    }
  }
  // 动态为 user 添加方法
  user.sayHi = sayHi
  uesr.sayHello = sayHello
  // 函数调用方式,决定了 this 的值
  user.sayHi()
  user.sayHello()
</script>

注:普通函数没有调用者时this值为window,严格模式下没有调用者时this的值为undefined
箭头函数
箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this!箭头函数中访问的this不过是箭头函数所在作用域this变量。

<script>
    
  console.log(this) // 此处为 window
  // 箭头函数
  const sayHi = function() {
    console.log(this) // 该箭头函数中的 this 为函数声明环境中 this 一致
  }
  // 普通对象
  const user = {
    name: '小明',
    // 该箭头函数中的 this 为函数声明环境中 this 一致
    walk: () => {
      console.log(this)
    },
    
    sleep: function () {
      let str = 'hello'
      console.log(this)
      let fn = () => {
        console.log(str)
        console.log(this) // 该箭头函数中的 this 与 sleep 中的 this 一致
      }
      // 调用箭头函数
      fn();
    }
  }

  // 动态添加方法
  user.sayHi = sayHi
  
  // 函数调用
  user.sayHi()
  user.sleep()
  user.walk()
</script>

在开发中【使用箭头函数前需要考虑函数中的this的值】,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数不推荐使用箭头函数,如下代码所示:

<script>
  // DOM 节点
  const btn = document.querySelector('.btn')
  // 箭头函数 此时 this 指向了 window
  btn.addEventListener('click', () => {
    console.log(this)
  })
  // 普通函数 此时 this 指向了 DOM 对象
  btn.addEventListener('click', function () {
    console.log(this)
  })
</script>

同样由于箭头函数this的原因,基于原型的面向对象也不推荐箭头函数,如下代码所示:

<script>
  function Person() {
  }
  // 原型对像上添加了箭头函数
  Person.prototype.walk = () => {
    console.log('人都要走路...')
    console.log(this); // window
  }
  const p1 = new Person()
  p1.walk()
</script>

this指向

以上归纳了普通函数和箭头函数中关于this,默认值的情形,不仅如此JavaScript中还允许指定函数中this的指向,有3个方法可以动态指定函数中this的指向: call
使用call方法调用函数,同时指定函数中this的值,使用方法如下代码所示:

<script>
  // 普通函数
  function sayHi() {
    console.log(this);
  }

  let user = {
    name: '小明',
    age: 18
  }

  let student = {
    name: '小红',
    age: 16
  }

  // 调用函数并指定 this 的值
  sayHi.call(user); // this 值为 user
  sayHi.call(student); // this 值为 student

  // 求和函数
  function counter(x, y) {
    return x + y;
  }

  // 调用 counter 函数,并传入参数
  let result = counter.call(null, 5, 10);
  console.log(result);
</script>

总结:

  1. call方法能够在调用函数的同时指定this的值
  2. 使用call方法调用函数时,第一个参数为this指定的值
  3. call方法的其余参数会依次自动传入函数作为函数的参数
    apply
    使用apply方法调用函数,同时指定函数中this的值,使用方法如下代码所示:
<script>
  // 普通函数
  function sayHi() {
    console.log(this)
  }

  let user = {
    name: '小明',
    age: 18
  }

  let student = {
    name: '小红',
    age: 16
  }

  // 调用函数并指定 this 的值
  sayHi.apply(user) // this 值为 user
  sayHi.apply(student) // this 值为 student

  // 求和函数
  function counter(x, y) {
    return x + y
  }
  // 调用 counter 函数,并传入参数
  let result = counter.apply(null, [5, 10])
  console.log(result)
</script>

总结:

  1. apply方法能够在调用函数的同时指定this的值
  2. 使用apply方法调用函数时,第一个参数为this指定的值
  3. apply方法 第 二个参数为数组,数组的单元值依次自动传入函数作为函数的参数
    bind
    bind方法并不会调用函数,而是创建了一个指定this值的新函数,使用方法如下代码所示:
<script>
  // 普通函数
  function sayHi() {
    console.log(this)
  }
  let user = {
    name: '小明',
    age: 18
  }
  // 调用 bind 指定 this 的值
  let sayHello = sayHi.bind(user);
  // 调用使用 bind 创建的新函数
  sayHello()
</script>

注:bind方法创建新的函数,与原函数唯一的变化是改变了this的值。

防抖节流

  1. 防抖(debounce)
    所谓防抖,就是触发事件后在n秒内函数只执行一次,如果在n秒内又触发了事件,则会重新计算函数的执行时间
<!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>
</head>

<body>
    <input type="text" id="txt">
    <script>
        /* 
        // 防抖
        1. 将事件处理函数单独封装
        2. 定义一个闭包(防抖函数)

        
        */
        // 1. 将事件处理函数单独封装
        function kd() {
            console.log(`hello`);
        }

        // 2. 定义一个闭包(防抖函数)
        function debounce(handler, t) {
            let timer = null;
            return function () {
                clearTimeout(timer)
                timer = setTimeout(handler, t)
            }
        }

        document.querySelector('#txt').addEventListener('keydown', debounce(dk, 200))

    </script>

    <script>
        /* 
                // 通过一个计时器,让连续触发的事件在仅执行最后1次
                let i = 0;
                document.querySelector('#txt').addEventListener('keydown',function(){
                    console.log(`第 ${++i} 次向服务器发起请求`);
                })
         */
    </script>
</body>

</html>

  1. 节流(throttle)
    所谓节流,就是指连续触发事件但是在n秒内只执行一次函数
<!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: 200px;
            height: 200px;
            background:#09f;
            color:#f90;
            font-size: 30px;
            text-align: center;
        }
    </style>
    
</head>
<body>
    <div class="box"></div>
    <script>
        // 节流方案实现的效果是什么?
        // 事件会被触发很多次,通过判断事件触的间隔时间控制事件处理函数在n秒内仅执行1次。
/* 
        function clk(){
            console.log('事件处理函数被执行');
        }

        function outer(){
            let t = 0;          
            function inner(){
                // 获取当前的时间,         1652692426768
                if(Date.now() - t > 2000){
                    clk();
                    t = Date.now()
                }

            }
            return inner;
        }

        let f = outer();

        document.querySelector('.box').addEventListener('mousemove', f)
 */




        // 1. 真正的事件处理函数
        function clk(){
            // 正确的事件处理函数
            console.log(1);
        }

        // 2. 这个函数就叫节流函数
        function outer(handle,time){
            // 3. 利用时间差,来控制真正的事件处理函数的调用
            // 3.1 先读取一个时间
            let t = Date.now()      // 1652692430769
            function inner(){
               // 3.2 再获取当前时间与3.1读取的时间判断 
                if(Date.now() - t > time){ ///1652692430769
                    // 3.3 如果达到时间间隔才调用真正的事件处理函数
                    handle();
                    // 3.4 使用当前的时间,去覆盖t
                    t = Date.now()
                }
            }
            return inner;
        }

        // 4. 绑定时,节流函数()
        document.querySelector('.box').addEventListener('click', outer(clk,2000))

    </script>
    <script>
        /* 
        // 思路:
            1. 将原来的事件处理函数,单独封装成一个独立的函数
            2. 定义一个节流函数(闭包)
            3. 在节流函数内,通过获取时间减,来调用真正的事件处理函数
            4. 注册事件时,注册节流函数
        */

    </script>
</body>
</html>