文章目录
- 一、深浅拷贝
- 1. 浅拷贝:object.assign;解构赋值
- 2. 深拷贝:递归函数、lodash的cloneDeep、JSON
- 二、异常处理
- 1. throw
- 2. try catch finally
- 三. this总结
- 1、this的指向
- 2、箭头函数this的指向
- 3、改变函数this的指向
- 四. 节流和防抖
- 1. 防抖(debounce)
- 2 节流throttle
- 3 节流案例
一、深浅拷贝
1. 浅拷贝:object.assign;解构赋值
给数据赋值时会存在的问题:
const peppa = {
name: 'peppa',
age: 6,
}
// 将peppa赋值给obj,
const obj = peppa
obj.age = 20 // obj修改数据
console.log(peppa.age); //20,peppa的数据也被修改了
因为引用数据类型在赋值时,赋给的是地址,也就是peppa
和obj
指向同一块内存;
解决方法:浅拷贝
Object.assign()
- 解构赋值
const peppa = {
name: 'peppa',
age: 6,
}
// 解构赋值
const pig1 = { ...peppa }
pig1.age = 7
console.log(peppa.age); // 6
// Object.assign方法
const pig2 = {}
Object.assign(pig2, peppa)
pig2.name = 'qiaozhi'
console.log(pig2.name); // qiaozhi
console.log(peppa.name); // peppa
问题:如果数据存在多层嵌套,浅拷贝还会出现赋值地址的问题
const peppa = {
name: 'peppa',
age: 6,
family: {
mother: 'pigMama',
father: 'pigPapa'
}
}
// pig1,pig2依旧为上段代码运行结果
pig2.family.mother = 'pig2Mama'
// 此时打印会发现,pig1和peppa里的mother数据都变成了pig2Mama
console.log(pig1);
console.log(peppa);
总结
- 浅拷贝在拷贝对象时,里面的属性值是简单数据类型则直接拷贝值
- 如果属性值是引用数据类型,则拷贝地址。
2. 深拷贝:递归函数、lodash的cloneDeep、JSON
方式一:通过递归函数实现深拷贝(简版,还有很多没考虑到。了解这种思想就行)
let obj = {
name: 'tom',
age: 10,
color: ['pink', 'skyblue', 'yellow'],
family: {
father: 'dad',
mother: 'mom'
}
}
function deepCopy (newObj, oldObj) {
for (const k in oldObj) {
if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
} else {
// k 属性名 oldObj[k] 属性值
newObj[k] = oldObj[k]
}
}
}
// 使用
let newObj = {}
deepCopy(newObj, obj)
方式二:JS库lodash里的cloneDeep实现深拷贝
官网:Lodash 简介 | Lodash中文文档 | Lodash中文网 (lodashjs.com)
// 引入 lodash库
<script src="./js/lodash.min.js"></script>
<script>
let obj = {
name: 'tom',
age: 10,
color: ['pink', 'skyblue', 'yellow'],
family: {
father: 'dad',
mother: 'mom'
}
}
// 实现深拷贝
const newObj2 = _.cloneDeep(obj)
</script>
方式三:JSON方法
let obj = {
name: 'tom',
age: 10,
color: ['pink', 'skyblue', 'yellow'],
family: {
father: 'dad',
mother: 'mom'
}
}
// 将obj转为字符串,是简单数据类型。然后再转为对象赋给newObj
const newObj = JSON.parse(JSON.stringify(obj))
深浅拷贝只针对引用类型,简单数据类型直接赋值的是值
- 直接赋值的方法,只要是对象,都会受影响,因为直接赋值获取的是栈里的地址值
- 浅拷贝如果是一层对象,不会有影响。若出现多层对象拷贝,则仍旧会拷贝地址值
- Object.assign方法
- 解构赋值
- 实现深拷贝的三种方法:
- 递归函数
- lodash的cloneDeep函数
- JSON转换字符串
二、异常处理
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
1. throw
throw:抛出异常信息,
function counter (x, y) {
if (!x || !y) {
throw new Error('参数不能为空!')
}
return x + y
}
counter()
2. try catch finally
try {
// 可能会发生错误的代码,要写到try
} catch (err) {
// 拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
throw new Error('xxx错误')
} finally {
// 无论程序对错,一定会执行的代码
}
三. this总结
1、this的指向
普通函数中,谁调用this,this就指向谁
console.log(this); // window
function fn () {
console.log('函数里的this', this); // window
}
fn() // window.fn()
setTimeout(function () {
console.log('延时函数里的this', this); // window
})
document.querySelector('button').addEventListener('click', function () {
console.log('按钮监听函数内的this');
console.log(this); // button
})
const obj = {
name: 'tom',
sayHi: function () {
console.log('对象里的函数的this', this);
}
}
obj.sayHi() // object
2、箭头函数this的指向
箭头函数中并不存在this,箭头函数中this的值就是最近作用域中的this。
向外层作用域中,一层一层查找this,直到有this的定义
注意情况一:事件回调函数使用箭头函数时,this为全局的window。
const btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
console.log(this); // window,而不是button
})
注意情况二:对原型对象的操作也不推荐采用箭头函数
function Person () {
}
// 原型对象上添加了箭头函数
Person.prototype.walk = () => {
console.log('人都要走路...');
console.log(this);
}
const p1 = new Person()
p1.walk
3、改变函数this的指向
- call()—了解
使用call方法调用函数,同时指定被调用函数中this的值.
语法:fun.call(thisArg,arg1,arg2)
thisArg
:在fun函数运行时指定的this值
arg1,arg2
:传递的其他参数
let obj = {
name: 'tom',
x: 10,
}
function fun (y) {
console.log('函数内的this', this);
// this指向中的x,参数y
return this.x + y
}
const res = fun.call(obj, 1)
console.log(res); // 11
- apply()
语法:fun.apply(thisArg,[argsArray])
thisArg
:在fun函数运行时指定的this值
argsArray
:传递的值,必须包含在数组里面
function fun2 (y1, y2) {
return this.x + y1 + y2
}
// obj还是上边那个obj
fun2.apply(obj, [1, 2]) // 13
// 可用在求数组最大值
const arr = [3, 5, 2, 9]
console.log(Math.max.apply(null, arr)); // 利用apply
console.log(Math.max.apply(Math, arr)); // 利用apply
console.log(Math.max(...arr)); // 利用展开运算符
- bind()
bind方法只会改变this指向,并不会在改变的同时调用这个函数
语法:bind(thisArg,arg1,arg2)
thisArg
:在fun函数运行时指定的this值
arg1,arg2
:传递的其他参数
**应用:**比如改变定时器内部的this指向
需求 :一个按钮,点击就禁用,两秒之后回复使用
const button = document.querySelector('button')
button.addEventListener('click', function () {
this.disabled = true, // 这里的this指向的是button
setTimeout(function () {
this.disabled = false
}.bind(button), 2000)
})
// 或者直接
document.querySelector('button').addEventListener('click', function () {
this.disabled = true,
setTimeout(function () {
this.disabled = false
}.bind(this), 2000)
})
总结
四. 节流和防抖
1. 防抖(debounce)
防抖:单位时间内,频繁触发事件,只执行最后一次。
使用场景:搜索框搜索输入。只需用户最后一次输入完,再发送请求。手机号、邮箱验证输入检测同理
如果每输入一个字就发送一次请求,十分消耗性能
案例:鼠标滑过盒子,盒子上的数字+1
<div>
<h1>1</h1>
</div>
<script>
let i = 0
function mouseMove () {
i++
document.querySelector('h1').innerHTML = i
// 如果里面存在大量消耗性能的代码,比如Dom操作,数据处理,则可能造成卡顿
}
document.querySelector('div').addEventListener('mousemove', mouseMove)
</script>
防抖优化: 鼠标停止移动500ms之后开始运行函数
方式一:lodash库实现防抖
语法:_.debounce(fun,时间)
document.querySelector('div').addEventListener('mousemove', _.debounce(mouseMove, 500))
方式二:手写一个防抖函数来处理
底层就是一个定时器
思路:
①:声明一个定时器变量
②:每次执行函数先判断有无定时器,有则清除以前的定时器
③:没有,则开启定时器,并存到变量里
④:在定时器里调用要执行的函数
function debounce (fn, t) {
let timer = null
return function () {
// 有定时器,则清除
if (timer) clearTimeout(timer)
timer = setTimeout(function () {
//调用fn(),就是mouseMove监听事件
fn()
}, t,)
}
}
document.querySelector('div').addEventListener('mousemove', debounce(mouseMove, 500))
2 节流throttle
节流:单位时间内,频繁触发事件,只执行一次。
触发一次事件,事件响应需要3秒,若在这3秒内再次触发,则会将当前触发事件响应完之后,再执行下一次事件响应。
还是鼠标划过盒子的案例
节流处理: 鼠标在盒子上移动,不管移动多少次,每隔500ms才+1(也就是500ms内,只执行一次)
方式一:lodash库
document.querySelector('div').addEventListener('mousemove', _.throttle(mouseMove, 500))
方式二:手写一个节流函数,节流的核心就是利用定时器setTimeout
来实现
思路:
①:变量定义一个计时器
②:鼠标滑过时判断是否有定时器,有定时器则不开启新定时器
③:没用定时器则开启新定时器并存到变量里面
(1) 定时器里调用要执行的函数
(2) 在定时器里把定时器清空
function throttle (fn, t) {
let timer = null
return function () {
if (!timer) {
// 为空则开启新定时器
timer = setTimeout(function () {
fn()
// 清空定时器
timer = null
}, t)
}
}
}
div.addEventListener('mousemove', throttle(mouseMove, 500))
注:为什么不用clearTimeout
清除定时器?
因为在定时器里清除定时器本身就是不合理的,所以定时器里是无法删除定时器的,如果执行clearTimeout(timer)
会发现,timer
仍旧有值。所以使用timer=null
来清空定时器。
let timer = null
timer = setTimeout(function () {
clearTimeout(timer)
console.log(timer); // clear清空之后,并不为null,所以要采用赋值操作清空定时器
}, t)
3 节流案例
播放视频,视频播放到一半,刷新界面,继续从上一次播放位置进行播放。需要记录当前的播放时间,timeupdate
是当播放位置发生变化就触发。而我们不需要触发的这么频繁,因此采用节流。
<script>
/*
timeupdate:事件在视频/音频当前的播放位置发生改变时触发
loadeddata:事件在当前帧的数据加载完成且还没有足够的数据播放视频或音频的下一帧时触发。
*/
// 1. 获取元素,对元素进行操作
const video = document.querySelector('video')
// 1.1 保存
video.addEventListener('timeupdate', _.throttle(() => {
// 保存当前的播放事件
localStorage.setItem('currentTime', video.currentTime)
}, 1000))
// 1.2 读取
video.addEventListener('loadeddata', () => {
// 获取当前时间,若没有,则默认为0s
video.currentTime = localStorage.getItem('currentTime') || 0
})
</script>