JS高级第四天

121 阅读5分钟

1.直接复制对象的问题

const obj = {
  uname: 'pink',
  age: 18
}
const o = obj
console.log(o) // {uname: 'pink', age: 18}
o.age = 20
console.log(o) // {uname: 'pink', age: 18}
console.log(obj) // {uname: 'pink', age: 20}  会把obj里面的age值改变

2.浅拷贝

 浅拷贝:拷贝的是地址
 常见方法:
1. 拷贝对象:Object.assgin() / 展开运算符 {...obj} 拷贝对象
2.拷贝数组:Array.prototype.concat() 或者 [...arr]

image.png

代码示码:
 const obj = {
  uname: 'pink',
  age: 18,
  family: {
    baby: '小pink'
  }
}
 浅拷贝
// const o = { ...obj }
// console.log(o)
// o.age = 20
// console.log(o)
// console.log(obj)
const o = {}
Object.assign(o, obj)
o.age = 20
o.family.baby = '老pink'
console.log(o)
console.log(obj)

image.png

直接赋值和浅拷贝有什么区别?
直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响

3.深拷贝

首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
1. 通过递归实现深拷贝
2. lodash/cloneDeep
3. 通过JSON.stringify()实现

image.png

image.png

案例:利用递归函数实现 setTimeout 模拟 setInterval效果
 <body>
      <div></div>
 <script>
    function getTime() {
      document.querySelector('div').innerHTML = new Date().toLocaleString()
    setTimeout(getTime, 1000)
  }
    getTime()
 </script>
 </body>

image.png

需求:每隔一秒,显示当前时间

  //获取当前时间new Date().toLocaleString()
function show() {
  setTimeout(function () {
    console.log(new Date().toLocaleString());
    show()
  }, 1000)
}
show()

image.png

案例 浅烤贝
 <script>
      const obj = {
                   uname: 'pink',
                  age: 17
              }
      const o = {}
      //烤贝函数
    function deepCopy(newOld, oldOld) {
     for (let k in oldOld) {
       // k 是属性名uname oldOld[k] 是属性值18
       //newOld[k] ===o.uname
         newOld[k] = oldOld[k]
       }
      }
      deepCopy(o, obj) //函数调用两个参数   o新对象  obj旧对象
      console.log(o)
      o.age = 20
      console.log(obj)
 </script>
 

image.png

案例 深烤贝
   <script>
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] = []
      //  newObj[k] 接收 []  hobby
      //  oldObj[k]   ['乒乓球', '足球']
      deepCopy(newObj[k], oldObj[k])
    } else if (oldObj[k] instanceof Object) {
      newObj[k] = {}
      deepCopy(newObj[k], oldObj[k])
    }
    else {
      //  k  属性名 uname age    oldObj[k]  属性值  18
      // newObj[k]  === o.uname  给新对象添加属性
      newObj[k] = oldObj[k]
    }
  }
}
deepCopy(o, obj) // 函数调用  两个参数 o 新对象  obj 旧对象
console.log(o)
o.age = 20
o.hobby[0] = '篮球'
o.family.baby = '老pink'
console.log(obj)
console.log([1, 23] instanceof Object)
// 复习
// const obj = {
//   uname: 'pink',
//   age: 18,
//   hobby: ['乒乓球', '足球']
// }
// function deepCopy({ }, oldObj) {
//   // k 属性名  oldObj[k] 属性值
//   for (let k in oldObj) {
//     // 处理数组的问题   k 变量
//     newObj[k] = oldObj[k]
//     // o.uname = 'pink'
//     // newObj.k  = 'pink'
//   }
// }
  </script>
  

image.png

4.lodash里面cloneDeep内部实现了深拷贝

image.png

5.通过JSON.stringify()实现

image.png

实现深拷贝三种方式
1.自己利用递归函数书写深拷贝,先数组后对象
2.利用js库 lodash里面的 _.cloneDeep()
3.利用JSON字符串转换

image.png

6.throw 抛异常

异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
   <script>
        function fn(x, y) {
        if (!x || !y) {
       // throw '没有参数传递进来'
         throw new Error('没有参数传递过来')
       }

       return x + y
      }
       console.log(fn())
  </script>
  

image.png

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

7.try/catch 捕获错误信息

image.png

image.png

8.debugger

image.png

9.普通函数this指向

 普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
  没有调用指向window
  严格模式下指向 undefined
  
 <body>
      <button>点击</button>
 <script>
// 普通函数:  谁调用我,this就指向谁
console.log(this) // window
function fn() {
  console.log(this) // window    
}
window.fn()
window.setTimeout(function () {
  console.log(this) // window 
}, 1000)
document.querySelector('button').addEventListener('click', function () {
  console.log(this) // 指向 button
})
const obj = {
  sayHi: function () {
    console.log(this) // 指向 obj
  }
}
obj.sayHi()
 </script>
</body>

image.png

10.this指向-箭头函数

箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this1. 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
2.箭头函数中的this引用的就是最近作用域中的this
3.向外层作用域中,一层一层查找this,直到有this的定义
注意情况1:
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window
因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数  

image.png

注意情况2:
 同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数
 

image.png

11.改变this call()

<script>
     //原来的this指向window 加了call()以后this指向obj
       const obj = {
       uname: 'pink'
     }

    function fn(x, y) {
       console.log(this) // window
       console.log(x + y)
     }
    // 1. 调用函数  
    // 2. 改变 this 指向
    fn.call(obj, 1, 2)
 </script>
 

image.png

image.png

12..改变this apply()

image.png

//原来的this指向window 加了apply()以后this指向obj
const obj = {
  age: 18
}

function fn(x, y) {                 
  console.log(this) // {age: 18}
  console.log(x + y) // 3
}
fn.apply(obj, [1, 2])
// 1. 调用函数
// 2. 改变this指向 
//  fn.apply(this指向谁, 数组参数) 
// 3. 返回值   本身就是在调用函数,所以返回值就是函数的返回值

image.png

// 使用场景: 求数组最大值
//const max = Math.max(1, 2, 3)
//console.log(max) // 3
const arr = [100, 44, 77]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(null, arr)
console.log(max, min) // 100  44
// 使用场景: 求数组最大值
console.log(Math.max(...arr)) // 100

13.bind()

语法:
 fun.bind(thisArg, arg1, arg2, ...)
 
 thisArg:在 fun 函数运行时指定的 this 值
 arg1,arg2:传递的其他参数
 返回由指定的 this 值和初始化参数改造的 原函数拷贝
 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向
 
1. bind 不会调用函数 
2. 能改变this指向
3. 返回值是个函数,  但是这个函数里面的this是更改过的obj  

const obj = {
  age: 18
}
function fn() {
  console.log(this)
}
const fun = fn.bind(obj)
// console.log(fun) 
fun()
需求,有一个按钮,点击里面就禁用,2秒钟之后开启
<button>点击按钮</button>
<script>
  document.querySelector('button').addEventListener('click', function () {
  // 禁用按钮
  this.disabled = true
  window.setTimeout(function () {
    // 在这个普通函数里面,我们要this由原来的window 改为 btn
    this.disabled = false
  }.bind(this), 2000) // 这里的this 和 btn 一样
})
</script>

14.call apply bind 总结

相同点:
都可以改变函数内部的this指向.
区别点:
call 和 apply 会调用函数, 并且改变函数内部this指向.
call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]
bind 不会调用函数, 可以改变函数内部this指向.
主要应用场景:
call 调用函数并且可以传递参数
apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.

15.节流(throttle)

 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
 
案例 :利用节流来处理-鼠标滑过盒子显示文字
要求: 鼠标在盒子上移动,里面的数字就会变化+1
<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 i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
  box.innerHTML = ++i
  // 如果里面存在大量操作 dom 的情况,可能会卡顿
}
// console.log(mouseMove)
// 节流函数 throttle 
function throttle(fn, t) {
  // 起始时间
  let startTime = 0
  return function () {
    // 得到当前的时间
    let now = Date.now()
    // 判断如果大于等于 500 采取调用函数
    if (now - startTime >= t) {
      // 调用函数
      fn()
      // 起始的时间 = 现在的时间   写在调用函数的下面 
      startTime = now
    }
  }
}
box.addEventListener('mousemove', throttle(mouseMove, 500))

// throttle(mouseMove, 500) === function () { console.log(1) }


// box.addEventListener('mousemove', function () {
//   // 得到当前的时间
//   let now = Date.now()
//   // 判断如果大于等于 500 采取调用函数
//   if (now - startTime >= t) {
//     // 调用函数
//     fn()
//     // 起始的时间 = 现在的时间   写在调用函数的下面
//     startTime = now
//   }
// })
</script>
</body>

image.png

16.防抖

所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
   <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 i = 1  // 让这个变量++
// 鼠标移动函数
function mouseMove() {
  box.innerHTML = ++i
  // 如果里面存在大量操作 dom 的情况,可能会卡顿
}
// 防抖函数
function debounce(fn, t) {
  let timeId
  return function () {
    // 如果有定时器就清除
    if (timeId) clearTimeout(timeId)
    // 开启定时器 200
    timeId = setTimeout(function () {
      fn()
    }, t)
  }
}
// box.addEventListener('mousemove', mouseMove)
box.addEventListener('mousemove', debounce(mouseMove, 200))

 </script>
 </body>
节流和防抖的区别是?
节流: 就是指连续触发事件但是在 n 秒中只执行一次函数,比如
可以利用节流实现 1s之内 只能触发一次鼠标移动事件
防抖:如果在 n 秒内又触发了事件,则会重新计算函数执行时间
节流和防抖的使用场景是?
节流: 鼠标移动,页面尺寸发生变化,滚动条滚动等开销比较大的情况下
防抖: 搜索框输入,设定每次输入完毕n秒后发送请求,如果期间还有输入,则从新计算时间 
17.Lodash 库 实现节流和防抖

image.png

image.png

 <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 src="./lodash.min.js"></script>
<script>
const box = document.querySelector('.box')
let i = 1  // 让这个变量++
// 鼠标移动函数
function mouseMove() {
  box.innerHTML = ++i
  // 如果里面存在大量操作 dom 的情况,可能会卡顿
}

// box.addEventListener('mousemove', mouseMove)
// lodash 节流写法
// box.addEventListener('mousemove', _.throttle(mouseMove, 500))
// lodash 防抖的写法
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
</script>
</body>

案例:页面打开,可以记录上一次的视频播放位置

image.png