Pink老师JS进阶DAY4学习记录-深浅拷贝,异常处理,梳理this,性能优化
一. 深浅拷贝(复制对象)
1. 普通复制
会改变原对象的属性,因为直接复制是复制的的对象的地址,改变的是同一坨变量。
const obj = {
uname: 'pink',
age: 18
}
const o = obj
console.log(o)
o.age = 20
console.log(o)
console.log(obj) // ...obj.age = 20
2. 浅拷贝
可以解决上述问题,不会改变原对象的普通数据类型数据,但是仍然会改变原对象的引用数据类型,也是因为复制的是地址。
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) // 第二种,Object.assign()
o.age = 20
o.family.baby = '老pink'
console.log(o)
console.log(obj) // 发现原对象的family也被改了
3. 深拷贝
实现深拷贝有三种方式
- 递归
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] === 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)
这里就是在普通的浅拷贝的基础上加上遇到 数组 和 对象 两种情况的处理方式,也就是传入空数组/对象,原对象属性值(数组,对象)进去递归,需要注意数组一定要在对象前面,因为Array instanceof Object
为true。
- lodash
// 需要先引入库
<script src="./lodash.min.js"></script>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
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: '小pink'
}
}
// 把对象转换为 JSON 字符串
// console.log(JSON.stringify(obj))
const o = JSON.parse(JSON.stringify(obj))
console.log(o)
o.family.baby = '123'
console.log(obj)
二.异常处理
1. throw抛异常
会终止程序
function fn(x, y) {
if (!x || !y) {
// throw '没有参数传递进来'
throw new Error('没有参数传递过来')
}
return x + y
}
console.log(fn())
2. try / catch 捕获异常
try {
// 可能发送错误的代码 要写到 try
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (err) {
// 拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
console.log(err.message)
throw new Error('你看看,选择器错误了吧')
}
finally {
// 不管你程序对不对,一定会执行的代码
alert('弹出对话框')
}
console.log(11)
}
fn()
3. debugger
关键字,就是相当于一个断点,方便代码多的时候debug
三. 梳理this
1. this指向
普通函数的this指向它的调用者
箭头函数没有this,默认为外层this
所以DOM事件,原型函数,构造函数不要使用箭头函数
2. 改变this指向
有三种方法。call(了解),apply(掌握),bind(掌握)
- call
const obj = {
uname: 'pink'
}
function fn(x, y) {
console.log(this) // window
console.log(x + y)
}
// 1. 调用函数
// 2. 改变 this 指向
fn.call(obj, 1, 2) // 直接传入参数
- apply
const obj = {
age: 18
}
function fn(x, y) {
console.log(this) // {age: 18}
console.log(x + y)
}
// fn.apply(this指向谁, 数组参数)
fn.apply(obj, [1, 2]) // 传入参数必须为数组的形式
运用场景:求数组最大最小值
const arr = [100, 44, 77]
const max = Math.max.apply(Math, arr) // 100
const min = Math.min.apply(null, arr) // 44
const m = Math.max(...arr) // 之前学的方法
- bind
bind 不会调用函数
能改变this指向
返回值是个函数
const fun = fn.bind(obj) // 改变fn的this指向并赋值给fun
使用场景
// 需求,有一个按钮,点击里面就禁用,2秒钟之后开启
document.querySelector('button').addEventListener('click', function () {
// 禁用按钮
this.disabled = true
window.setTimeout(function () {
// 在这个普通函数里面,我们要this由原来的window 改为 btn
this.disabled = false
}.bind(this), 2000) // 这里的this 和 btn 一样
})
改变setTimeout的回调函数的this为事件源
四. 性能优化
1. 防抖
单位时间内,频繁触发事件,只执行最后一次
使用场景:
- 搜索框搜索输入。只需用户最后一次输入完再发送请求。
- 手机号、邮箱验证输入检测
2. 节流
单位时间内。频繁触发事件,只执行一次
使用场景:高频事件:mousemove, resize, scroll等等。
3.案例(鼠标滑动盒子内数+1)
防抖: 鼠标移动结束500ms后,数字才会发生变化
- lodash
const box = document.querySelector('.box')
let i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
box.innerHTML = ++i
// 如果里面存在大量操作 dom 的情况,可能会卡顿
}
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
- 手写
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))
节流: 鼠标在盒子上移动,不管移动多少次,每隔500ms才加1
- lodash
box.addEventListener('mousemove', _.throttle(mouseMove, 500))
- 手写
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))
4. 节流综合案例
因为节流之后会用得比较多,这里写一个综合案例
需求: 页面一打开,记录上一次播放位置
知识储备: 这里会用到ontimeupdate
和onloadeddata
两个事件,前者是在播放位置改变时触发,后者是打开页面时触发(在当前帧的数据加载完成且还没有足够的数据播放视频/音频的下一帧时触发)
思路:
-
在
ontimeupdate
事件触发的时候,每隔1秒钟,就记录当前时间到本地存储(因为ontimeupdate
触发的频次太高,这里用到节流) -
下次打开页面,
onloadeddata
事件触发,就可以从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s -
获得当前时间
video.currentTime
代码:
// 1. 获取元素 要对视频进行操作
const video = document.querySelector('video')
video.ontimeupdate = _.throttle(() => {
// console.log(video.currentTime) 获得当前的视频时间
// 2. 把当前的时间存储到本地存储
localStorage.setItem('currentTime', video.currentTime)
}, 1000)
// 3. 打开页面触发事件,就从本地存储里面取出记录的时间, 赋值给 video.currentTime
video.onloadeddata = () => {
// console.log(111)
video.currentTime = localStorage.getItem('currentTime') || 0
}