JavaScript 进阶 - 第4天
深浅拷贝
浅拷贝
浅拷贝:把对象拷贝给一个新的对象,开发中我们经常需要复制一个对象
如果直接赋值,则复制的是地址,修改任何一个对象,另一个对象都会变化
常见方法:
- 拷贝对象:Object.assgin() / 展开运算符 {…obj} 拷贝对象
- 拷贝数组:Array.prototype.concat() 或者 […arr]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <body> <script>
const obj = { name: '佩奇', family: { father: '猪爸爸' } } const newObj = { ...obj } newObj.family.father = 'dad' console.log(newObj) console.log(obj) </script> </body>
|
浅拷贝注意:
- 如果是基本数据类型拷贝值
- 如果是引用数据类型拷贝的是地址
简单理解:如果是单层对象,没问题,如果有多层就有问题,还是会影响原来对象
深拷贝
深拷贝:拷贝多层,不再拷贝地址
常见方法:
- 通过 JSON 序列化实现
- lodash库 实现
- 通过递归实现
通过JSON序列化实现
JSON.stringify() 序列化为 JSON 字符串,然后再JSON.parse() 转回对象格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <body> <script>
const obj = { name: '佩奇', love: undefined, family: { father: '猪爸爸' }, hobby: ['跳泥坑', '唱歌'], sayHi() { console.log('我会唱歌') } } const newObj = JSON.parse(JSON.stringify(obj)) console.log(newObj) </script> </body>
|
缺点:function 或 undefined等,在序列化过程中会被忽略
js库 lodash实现深拷贝
官网地址:https://www.lodashjs.com/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <body> <script src="./js/lodash.min.js"></script> <script> const obj = { name: '佩奇', love: undefined, family: { father: '猪爸爸' }, hobby: ['跳泥坑', '唱歌'], sayHi() { console.log('我会唱歌') } } const newObj = _.cloneDeep(obj) newObj.family.father = 'dad' console.log(obj) console.log(newObj)
</script> </body>
|
通过递归实现深拷贝
递归:
所谓递归就是一种函数调用自身的操作
- 简单理解:函数内部自己调用自己, 就是递归,这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误(stackoverflow),所以记得添加退出条件 return
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <body> <script>
let i = 1 function fn() { console.log(`我是第${i}句话`) if (i >= 3) return i++ fn() } fn()
function timer() { const time = new Date().toLocaleString() console.log(time) setTimeout(timer, 1000) } timer()
</script> </body>
|
深拷贝思路:
- 深拷贝的核心是利用函数递归
- 封装函数,里面先判断拷贝的是数组还是对象
- 然后开始遍历
- 如果属性值是引用数据类型(比如数组或者对象),则再次递归函数
- 如果属性值是基本数据类型,则直接赋值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <body> <script> const obj = { name: '佩奇', family: { father: '猪爸爸' }, hobby: ['跳泥坑', '唱歌'], }
function cloneDeep(oldObj) { const newObj = Array.isArray(oldObj) ? [] : {}
for (let k in oldObj) { if (typeof oldObj[k] === 'object') { newObj[k] = cloneDeep(oldObj[k])
} else { newObj[k] = oldObj[k] } }
return newObj } const newObj = cloneDeep(obj) newObj.family.father = 'dad' console.log(newObj) console.log(obj) </script>
|
异常处理
了解 JavaScript 中程序异常处理的方法,提升代码运行的健壮性。
throw
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
总结:
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script> function counter(x, y) {
if(!x || !y) { throw new Error('参数不能为空!') }
return x + y }
counter() </script>
|
总结:
throw
抛出异常信息,程序也会终止执行
throw
后面跟的是错误提示信息
Error
对象配合 throw
使用,能够设置更详细的错误信息
try … catch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <script> function foo() { try { const p = document.querySelector('.p') p.style.color = 'red' } catch (error) { console.log(error.message) return
} finally { alert('执行') } console.log('如果出现错误,我的语句不会执行') } foo() </script>
|
总结:
try...catch
用于捕获错误信息
- 将预估可能发生错误的代码写在
try
代码段中
- 如果
try
代码段中出现错误后,会执行 catch
代码段,并截获到错误信息
debugger
相当于断点调试
处理this
了解函数中 this 在不同场景下的默认值,知道动态指定函数 this 值的方法。
改变this
JavaScript 中允许指定(改变)函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向
call
使用 call
方法调用函数,同时指定函数中 this
的值,使用方法如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <body> <script> const obj = { name: '佩奇' }
function fun(x, y) { console.log(this) return x + y } fun() console.log(fun.call(obj, 1, 2))
console.log(typeof '123') console.log(typeof []) console.log(typeof null)
console.log(Object.prototype.toString.call('123')) console.log(Object.prototype.toString.call(123)) console.log(Object.prototype.toString.call([])) console.log(Object.prototype.toString.call(null)) </script> </body>
|
总结:
call
方法能够在调用函数的同时指定 this
的值
- 使用
call
方法调用函数时,第1个参数为 this
指定的值
call
方法的其余参数会依次自动传入函数做为函数的参数
apply
使用 call
方法调用函数,同时指定函数中 this
的值,使用方法如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <body> <script> const obj = { name: '佩奇' } function fun(x, y) { console.log(this) console.log(x + y) } fun() fun.apply(obj, [1, 2])
console.log(Math.max(...[1, 2, 3]))
console.log(Math.max.apply(null, [8, 2, 3])) console.log(Math.min.apply(null, [8, 2, 3]))
</script> </body>
|
总结:
apply
方法能够在调用函数的同时指定 this
的值
- 使用
apply
方法调用函数时,第1个参数为 this
指定的值
apply
方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数
bind
bind
方法并不会调用函数,而是创建一个指定了 this
值的新函数,使用方法如下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <body> <button class="code">发送验证码</button> <script> const obj = { name: '佩奇' } function fun(x, y, z) { console.log(this) console.log(x + y + z) }
const fn = fun.bind(obj, 1, 2, 3)
fn()
const codeBtn = document.querySelector('.code') let flag = true codeBtn.addEventListener('click', function () { if (flag) { let i = 5 this.innerHTML = `05秒后重新获取` let timerId = setInterval(function () { i-- this.innerHTML = `0${i}秒后重新获取`
if (i === 0) { this.innerHTML = `重新获取` clearInterval(timerId) flag = true } }.bind(this), 1000) flag = false } }) </script> </body>
|
注:bind
方法创建新的函数,与原函数的唯一的变化是改变了 this
的值。
方法 |
相同点 |
传递参数 |
是否调用函数 |
使用场景 |
call |
改变this指向 |
传递参数列表 arg1, arg2… |
调用函数 |
Object.prototype.toString.call() 检测数据类型 |
apply |
改变this指向 |
参数是数组 |
调用函数 |
跟数组相关,比如求数组最大值和最小值等 |
bind |
改变this指向 |
传递参数列表 arg1, arg2… |
不调用函数 |
改变定时器内部的this指向 |
this指向
this的取值 不取决于函数的定义,而是取决于怎么调用的(this指向调用者)
- 全局内调用: fn() 指向window
- 对象内的方法调用:obj.fn() 指向调用对象
- 构造函数调用:newPerson() 指向实例对象
- 事件处理函数中调用:指向当前触发事件的DOM元素
- 特殊调用 比如 call、apply、bind可以改变this指向,fun.call(obj) 指向 obj
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <body> <button>点击</button> <script> function fn() { console.log(this) } fn()
const obj = { name: '佩奇', sayHi() { console.log(this) } } obj.sayHi()
function Person() { this.name = name console.log(this) } const zs = new Person()
document.querySelector('button').addEventListener('click', function () { console.log(this) })
const o = { name: '佩奇' } function fun() { console.log(this) } fun.call(o)
</script> </body>
|
性能优化
防抖(debounce)
防抖: 单位时间内,频繁触发事件,只执行最后一次
举个栗子:王者荣耀回城,只要被打断就需要重新来
使用场景:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <!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>利用防抖实现性能优化</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="./js/lodash.min.js"></script> <script> const box = document.querySelector('.box') let i = 1 function mouseMove() { box.innerHTML = i++ }
box.addEventListener('mousemove', _.debounce(mouseMove, 500)) </script> </body>
</html>
|
手写防抖函数
核心思路:
防抖的核心就是利用定时器 (setTimeout) 来实现
①:声明一个定时器变量
②: 当鼠标每次滑动都先判断是否有定时器了,如果有定时器先清除以前的定时器
③:如果没有定时器则开启定时器,记得存到变量里面
④:在定时器里面调用要执行的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <!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>防抖函数实现</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="./js/lodash.min.js"></script> <script> const box = document.querySelector('.box') let i = 1 function mouseMove() { box.innerHTML = i++ }
function debounce(fn, t) { let timer return function () { if (timer) clearTimeout(timer) timer = setTimeout(function () { fn() }, t) } } box.addEventListener('mousemove', debounce(mouseMove, 500))
</script> </body>
</html>
|
节流(throttle)
节流:单位时间内,频繁触发事件,只执行一次
举个栗子:
- 王者荣耀技能冷却,期间无法继续释放技能
- 和平精英 98k 换子弹期间不能射击
使用场景:
- 高频事件:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动scroll 等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <!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>利用防抖实现性能优化</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="./js/lodash.min.js"></script> <script> const box = document.querySelector('.box') let i = 1 function mouseMove() { box.innerHTML = i++ }
box.addEventListener('mousemove', _.throttle(mouseMove, 3000))
</script> </body>
</html>
|
手写节流函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| <!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>利用节流实现性能优化</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="./js/lodash.min.js"></script> <script> const box = document.querySelector('.box') let i = 1 function mouseMove() { box.innerHTML = i++ }
function throttle(fn, t) { let timer = null return function () { if (!timer) { timer = setTimeout(function () { fn() timer = null }, t) } } }
box.addEventListener('mousemove', throttle(mouseMove, 3000))
</script> </body>
</html>
|
性能优化 |
说明 |
使用场景 |
防抖 |
单位时间内,频繁触发事件,只执行最后一次 |
搜索框搜索输入、手机号、邮箱验证输入检测 |
节流 |
单位时间内,频繁触发事件,只执行一次 |
高频事件:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动scroll 等等 |