JS进阶学习04
一、深浅拷贝
1.浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
1. 拷贝对象:Object.assgin() / 展开运算符 {...obj} 拷贝对象
2. 拷贝数组:Array.prototype.concat() 或者 [...arr]
>如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
<!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><script>const obj = {uname: "pink",age: 18,family: {baby: "小pink",},};// 浅拷贝// const o = { ...obj };// console.log(o); //Object { uname: "pink", age: 18, family: {…} }// o.age = 20;// console.log(o); //Object { uname: "pink", age: 20, family: {…} }// console.log(obj); //Object { uname: "pink", age: 18, family: {…} }const o = {};Object.assign(o, obj);o.age = 20;o.family.baby = "老pink";console.log(o); //Object { uname: "pink", age: 20, family: {…} }console.log(obj); //Object { uname: "pink", age: 18, family: {…} }</script></body>
</html>
2.深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
1. 通过递归实现深拷贝
2. lodash/cloneDeep
3. 通过JSON.stringify()实现
(1)函数递归
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
·简单理解:函数内部自己调用自己, 这个函数就是递归函数
·递归函数的作用和循环效果类似
· 由于递归很容易发生“栈溢出”错误(stack overflow),所以 必须要加退出条件 return

<!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><script>const obj = {uname: 'pink',age: 18,hobby: ['乒乓球', '足球'],family: {baby: '小pink'}}const o = {}// 拷贝函数function deepCopy(newObj, oldObj) {debuggerfor (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 = 20o.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>
</body></html>
(2)lodash
js库lodash里面cloneDeep内部实现了深拷贝
<!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><!-- 先引用 --><script src="./lodash.min.js"></script><script>const obj = {uname: "pink",age: 18,hobby: ["乒乓球", "足球"],family: {baby: "小pink",},};const o = _.cloneDeep(obj);console.log(o); //Object { uname: "pink", age: 18, hobby: (2) […], family: Object { baby: "老pink" } }o.family.baby = "老pink";console.log(obj); //Object { uname: "pink", age: 18, hobby: (2) […], family: Object { baby: "小pink" } }</script></body>
</html>
(3)JSON.stringify()
<!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><script>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); //Object { uname: "pink", age: 18, hobby: Array [ "乒乓球", "足球" ], family: {Object { baby: "123" }} }o.family.baby = "123";console.log(obj); //Object { uname: "pink", age: 18, hobby: Array [ "乒乓球", "足球" ], family: Object { baby: "小pink" } }</script></body>
</html>
二、异常处理
1.throw抛异常
2.try/catch捕获异常
3.debugger
相当于断点调试
三、处理this
1.this指向
(1)普通函数
普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
<!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><button>点击</button><script>// 普通函数: 谁调用我,this就指向谁console.log(this) // windowfunction 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></html>
(2)箭头函数
<script>console.log(this); // 此处为 window// 普通函数const sayHi = function () {console.log(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();// this指的是user// Object { name: "小明", walk: walk(), sleep: sleep(), sayHi: sayHi() }// user.sleep();// this指的是user(普通函数)// Object { name: "小明", walk: walk(), sleep: sleep(), sayHi: sayHi() }// hello// Object { name: "小明", walk: walk(), sleep: sleep(), sayHi: sayHi() }user.walk();// this指的是window(箭头函数)// Window// Window
</script>
<!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><button class="btn">点击</button><script></script></body><script>// DOM 节点const btn = document.querySelector(".btn");// 箭头函数 此时 this 指向了 windowbtn.addEventListener("click", () => {console.log(this); //Window});// 普通函数 此时 this 指向了 DOM 对象btn.addEventListener("click", function () {console.log(this);//<button class="btn">});</script>
</html>
2.改变this
(1)call()- this指向传入的参数
(2)apply()-传入必须是数组,this指向数组
(3)bind()-不会调用函数,但改变this
(4)总结- important
四、性能优化 (重要)
1.防抖
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
实现方式
(1)lodash提供的防抖来处理
<!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 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 防抖的写法 - 500ms后加一// 语法:_.debounce(fun,时间)box.addEventListener('mousemove', _.debounce(mouseMove, 500))</script>
</body></html>
(2)手写一个防抖函数来处理
手写防抖函数
核心是利用setTimeout()定时器来实现
1.声明定时器变量
2.每次鼠标移动(时间触发)的时候都要先判断是否有定时器,如果有先清除以前的定时器
3.如果没有定时器,则开启定时器,存入到定时器变量里面。
4.定时器里面写函数调用
<!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 i = 1 // 让这个变量++// 鼠标移动函数function mouseMove() {box.innerHTML = ++i// 如果里面存在大量操作 dom 的情况,可能会卡顿}// 防抖函数function debounce(fn, t) {let timeIdreturn function () {// 如果有定时器就清除if (timeId) clearTimeout(timeId)// 开启定时器 200timeId = setTimeout(function () {fn()}, t)}}// box.addEventListener('mousemove', mouseMove)box.addEventListener('mousemove', debounce(mouseMove, 200))</script>
</body></html>
2.节流
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
(1) lodash函数
<!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 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));</script></body>
</html>
(2)手写一个节流函数
手写一个节流函数- 每隔 500ms + 1
节流的核心就是利用定时器(setTimeout)来实现
1.声明一个定时器变量
2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器
3.如果没有定时器则开启定时器,记得存到变量里面
3.1 定时器里面调用执行的函数
3.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 i = 1; // 让这个变量++// 鼠标移动函数function mouseMove() {box.innerHTML = ++i;// 如果里面存在大量操作 dom 的情况,可能会卡顿}// 手写一个节流函数- 每隔 500ms + 1// 节流的核心就是利用定时器(setTimeout)来实现// 1.声明一个定时器变量// 2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器// 3.如果没有定时器则开启定时器,记得存到变量里面// 3.1 定时器里面调用执行的函数// 3.2定时器里面要把定时器清空// 节流函数function throttle(fn, t) {let timeId = null;return function () {if (!timeId) {timeId = setTimeout(function () {fn();// 清空定时器timeId = null;// 不用clearTimeout() // 原因:在setTimeout中是无法删除定时器多,因为定时器还在运作,所以使用timer=null 而不是clearTimeout(timer)}, t);}};}// box.addEventListener('mousemove', mouseMove)box.addEventListener("mousemove", throttle(mouseMove, 200));</script></body>
</html>
3.总结