js高级第四天
一、深浅拷贝
- 浅拷贝:把对象拷贝给一个新的对象,开发中我们经常需要复制一个对象。如果直接赋值,则复制的是地址,修改任何一个对象,另一个对象都会变化。常见方法:拷贝对象:Object.assgin() / 展开运算符 {...obj} 拷贝对象;拷贝数组Array.prototype.concat() 或者 [...arr]。如果是基本数据类型拷贝值;如果是引用数据类型拷贝的是地址。
<script>
// 浅拷贝方法
// 1. 对象拷贝
// const obj = {
// name: '佩奇'
// }
// 1.1 Object.assign()
// const newObj = {}
// Object.assign(newObj, obj)
// // console.log(newObj)
// console.log(newObj === obj) // false
// newObj.name = '乔治'
// console.log(obj)
// console.log(newObj)
// 1.2 展开运算符
// const newObj = { ...obj }
// console.log(newObj === obj) // false
// newObj.name = '乔治'
// console.log(obj)
// console.log(newObj)
// // 2. 数组拷贝
// const arr = ['佩奇', '乔治']
// 2.1 concat 方法实现数组浅拷贝
// const arr1 = []
// const newArr = arr1.concat(arr)
// console.log(newArr)
// newArr[1] = '猪爸爸'
// console.log(arr)
// console.log(newArr)
// 2.2 展开运算符
// const newArr = [...arr]
// console.log(newArr)
// newArr[1] = '猪爸爸'
// console.log(arr)
// console.log(newArr)
// 3. 浅拷贝的问题如果遇到多层拷贝还是会影响原来的对象
const obj = {
name: '佩奇',
family: {
father: '猪爸爸'
}
}
const newObj = { ...obj }
// console.log(newObj)
newObj.family.father = 'dad'
console.log(newObj)
console.log(obj)
</script>
- 深拷贝
//JSON.stringify() 序列化为 JSON 字符串,然后再JSON.parse() 转回对象格式
<script>
// 深拷贝实现方式一:JSON序列化(常用的方式)
// const obj = {
// name: '佩奇',
// family: {
// father: '猪爸爸'
// },
// hobby: ['跳泥坑', '唱歌']
// }
// // console.log(JSON.stringify(obj))
// // console.log(JSON.parse(JSON.stringify(obj)))
// const newObj = JSON.parse(JSON.stringify(obj))
// console.log(newObj === obj) // false
// newObj.family.father = 'dad'
// console.log(obj)
// console.log(newObj)
// 注意事项:JSON.stringify序列化的时候会忽略 function undefined
const obj = {
name: '佩奇',
love: undefined,
family: {
father: '猪爸爸'
},
hobby: ['跳泥坑', '唱歌'],
sayHi() {
console.log('我会唱歌')
}
}
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
</script>
//js库 lodash实现深拷贝
<!-- 引入lodash库 -->
<script src="./js/lodash.min.js"></script>
<script>
const obj = {
name: '佩奇',
love: undefined,
family: {
father: '猪爸爸'
},
hobby: ['跳泥坑', '唱歌'],
sayHi() {
console.log('我会唱歌')
}
}
// lodash 库实现
const newObj = _.cloneDeep(obj)
// console.log(newObj)
newObj.family.father = 'dad'
console.log(obj)
console.log(newObj)
</script>
//通过递归实现深拷贝
<script>
// 1.利用函数递归打印3句话
let i = 1
function fn() {
console.log(`我是第${i}句话`)
if (i >= 3) return
i++
fn() // 递归
}
fn()
// 2. 练习 利用递归函数实现 setTimeout 每隔一秒钟输出当前时间
function timer() {
const time = new Date().toLocaleString()
console.log(time) // 输出当前时间
setTimeout(timer, 1000) // 函数递归
}
timer()
</script>
<script>
//1. 深拷贝的核心是利用函数递归
//2. 封装函数,里面先判断拷贝的是数组还是对象
//3. 然后开始遍历
//4. 如果属性值是引用数据类型(比如数组或者对象),则再次递归函数
//5. 如果属性值是基本数据类型,则直接赋值即可
// 递归实现深拷贝 - 简版实现对象和数组的拷贝
const obj = {
name: '佩奇',
family: {
father: '猪爸爸'
},
hobby: ['跳泥坑', '唱歌'],
}
// 封装深拷贝函数 cloneDeep()
function cloneDeep(oldObj) {
// 先判断拷贝的是数组还是对象
const newObj = Array.isArray(oldObj) ? [] : {}
// 遍历拷贝属性和值
for (let k in oldObj) {
// console.log(k) // k 是属性
// console.log(oldObj[k]) // oldObj[k] 是属性值
// 把旧对象的值给新对象的属性
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>
二、异常处理
- throw
<script>
function counter(x, y) {
if(!x || !y) {
// throw '参数不能为空!';
throw new Error('参数不能为空!')
}
return x + y
}
counter()
</script>
- try...catch
<script>
function foo() {
try {
// 查找 DOM 节点
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (error) {
// try 代码段中执行有错误时,会执行 catch 代码段
// 查看错误信息
console.log(error.message)
// 终止代码继续执行
return
}
finally {
alert('执行')
}
console.log('如果出现错误,我的语句不会执行')
}
foo()
</script>
- debugger
相当于断点调试
三、处理this
- 改变this:JavaScript 中允许指定(改变)函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向
<script>
// 1. 改变this指向 - call
const obj = { name: '佩奇' }
// call() 作用: 第一个调用函数 第二改变this指向
function fun(x, y) {
console.log(this)
// console.log(x + y)
return x + y
}
fun() // this 指向window
// fun.call(obj) // this 指向 obj 对象
// fun.call(obj, 1, 2) // this 指向 obj 对象
console.log(fun.call(obj, 1, 2)) // 返回值就是函数 返回值
// 2. call的应用场景 - 检测数据类型
// 2.1 typeof 检测数据类型不够精确的
console.log(typeof '123') // string
console.log(typeof []) // object
console.log(typeof null) // object
// 2.2 Object.prototype.toString() 返回的结果是[object xxx类型]
// console.log(Object.prototype.toString('123')) // [object Object]
console.log(Object.prototype.toString.call('123')) // [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(null)) // [object Null]
</script>
<script>
// 改变this指向apply
// 1. 基本使用
const obj = { name: '佩奇' }
function fun(x, y) {
console.log(this)
console.log(x + y)
}
fun()
// fun.apply() // 1. 作用1调用函数
// fun.apply(obj) // 2. 作用2 改变this指向 obj
fun.apply(obj, [1, 2]) // 参数必须是数组
// 2. 使用场景- 求数组的最大值/最小值
console.log(Math.max(...[1, 2, 3])) // 3
// apply 或者 call 如果不需要改变this指向 写 null
console.log(Math.max.apply(null, [8, 2, 3])) // 8
console.log(Math.min.apply(null, [8, 2, 3])) // 2
</script>
<body>
<button class="code">发送验证码</button>
<script>
const obj = { name: '佩奇' }
//改变this指向-bind方法
// 1. 基本使用
function fun(x, y, z) {
console.log(this)
console.log(x + y + z)
}
// fun()
// fun.bind() // bind不会调用函数
// const fn = fun.bind() // 返回的是对原来函数的拷贝
// console.log(fn)
// console.log(fn === fun) // false
// const fn = fun.bind(obj) // bind 可以改变this指向
const fn = fun.bind(obj, 1, 2, 3) //
fn() // 调用函数
// 2. 使用场景 - 不需要调用函数,但是又想改变函数内部的this指向
// 1. 发送短信5秒倒计时业务
const codeBtn = document.querySelector('.code')
let flag = true // 开关变量,用来防止多次点击
codeBtn.addEventListener('click', function () {
if (flag) {
// 1.2 利用定时器做倒计时效果 setInterval
let i = 5
// 点击之后立马变化文字
this.innerHTML = `05秒后重新获取`
// 定时器
let timerId = setInterval(function () {
i--
this.innerHTML = `0${i}秒后重新获取`
// 1.3 时间到了 就显示文字为 重新获取
if (i === 0) {
this.innerHTML = `重新获取`
// 停止定时器
clearInterval(timerId)
flag = true
}
}.bind(this), 1000)
// 关闭开关
flag = false
}
})
</script>
</body>
| 方法 | 相同点 | 传递参数 | 是否调用函数 | 使用场景 |
|---|---|---|---|---|
| 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
<body>
<button>点击</button>
<script>
// this指向总结
// 1. 普通函数
// 1.1 全局内调用
function fn() {
console.log(this) // window
}
fn()
// 1.2 对象内调用
const obj = {
name: '佩奇',
sayHi() {
console.log(this) // obj
}
}
obj.sayHi()
// 1.3 构造函数内this
function Person() {
this.name = name
console.log(this)
}
const zs = new Person()
// 1.4 事件处理函数中的this
document.querySelector('button').addEventListener('click', function () {
console.log(this)
})
// 1.5 特殊调用 call apply bind 可以改变this指向
const o = { name: '佩奇' }
function fun() {
console.log(this)
}
fun.call(o)
// 2. 箭头函数 没有this,是沿用上一级作用域的this
</script>
</body>
四、性能优化
- 防抖:单位时间内,频繁触发事件,只执行最后一次
<!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>
// 利用防抖实现性能优化
//需求: 鼠标在盒子上移动,里面的数字就会变化 + 1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如dom操作,比如数据处理,可能造成卡顿
}
// 添加事件
// box.addEventListener('mousemove', mouseMove)
// 利用lodash库实现防抖 - 500毫秒之后采取+1
// 语法: _.debounce(fun, 时间)
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
</script>
</body>
</html>
<!-- 手写防抖函数 -->
<!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>
// 利用防抖实现性能优化
//需求: 鼠标在盒子上移动,里面的数字就会变化 + 1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如dom操作,比如数据处理,可能造成卡顿
}
// box.addEventListener('mousemove', _.debounce(mouseMove, 500))
// 手写防抖函数
// 核心是利用 setTimeout定时器来实现
// 1. 声明定时器变量
// 2. 每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除以前的定时器
// 3. 如果没有定时器,则开启定时器,存入到定时器变量里面
// 4. 定时器里面写函数调用
function debounce(fn, t) {
let timer
// return 返回一个匿名函数
return function () {
// 2.3.4
if (timer) clearTimeout(timer)
timer = setTimeout(function () {
fn() // 加小括号调用 fn函数
}, t)
}
}
box.addEventListener('mousemove', debounce(mouseMove, 500))
// debounce(mouseMove, 500) // 调用函数
// debounce(mouseMove, 500) = function () { 2.3.4}
</script>
</body>
</html>
- 节流:单位时间内,频繁触发事件,只执行一次
<!-- 高频事件:鼠标移动 mousemove、页面尺寸缩放 resize、滚动条滚动scroll 等等 -->
<!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>
// 利用节流实现性能优化
//需求: 鼠标在盒子上移动,里面的数字就会变化 + 1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如dom操作,比如数据处理,可能造成卡顿
}
// box.addEventListener('mousemove', mouseMove)
// 利用lodash库实现节流 - 500毫秒之后采取+1
// 语法: _.throttle(fun, 时间)
box.addEventListener('mousemove', _.throttle(mouseMove, 3000))
</script>
</body>
</html>
<!-- 手写节流函数 -->
<!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>
// 利用节流实现性能优化
//需求: 鼠标在盒子上移动,里面的数字就会变化 + 1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如dom操作,比如数据处理,可能造成卡顿
}
// box.addEventListener('mousemove', mouseMove)
// 利用lodash库实现节流 -
// 语法: _.throttle(fun, 时间)
// box.addEventListener('mousemove', _.throttle(mouseMove, 3000))
// 手写一个节流函数- 每隔 500ms + 1
// 节流的核心就是利用定时器(setTimeout) 来实现
// 1.声明一个定时器变量
// 2.当鼠标每次滑动都先判断是否有定时器了,如果有定时器则不开启新定时器
// 3.如果没有定时器则开启定时器,记得存到变量里面
// 3.1定时器里面调用执行的函数
// 3.2定时器里面要把定时器清空
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 等等 |

浙公网安备 33010602011771号