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 浅拷贝
<!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>
结果
1.2浅拷贝注意事项:
<!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>
1.3 深拷贝
1.3.1 通过JSON序列化实现(开发中使用较多)
复杂数据类型-->字符串-->对象
<!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>
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>
结果:
1.3.3 通过递归实现深拷贝
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>
递归结果:
1.3.3.2 通过递归函数实现深拷贝:
<!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>
结果:
2 异常处理
2.1 throw异常
<!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>
2.2 try/catch捕获错误信息
<!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>
2.3 debugger
3 处理this
3.1 改变this指向
3.1.1 call()方法
call()传递参数列表,如:(1,2,3)
const str = Object.prototype.toString.call([])
console.log(str.includes('Array'))//true
<!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>
结果:
3.1.2 apply()方法
apply()传递数组,如:([1,2,3])
<!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>
结果:
3.1.3 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>
<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>
按钮倒计时案例解释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>
4 性能优化
4.1 防抖
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 手写一个防抖函数来处理
<!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 节流
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>