- 深浅拷贝
- 异常处理
- 处理this
- 性能优化
一. 深浅拷贝
深浅拷贝只针对引用类型
1.1 浅拷贝
- 拷贝的是地址
- 常见方法
(1)拷贝对象:Object.assign() / 展开运算符 {...obj}
(2)拷贝数组:Array.prototype.concat() 或者 [...arr]
- 浅拷贝存在的问题
① 简单数据类型拷贝的是值,引用数据类型拷贝的是地址
② 赋值的时候不会影响原来的值,如果是简单数据类型拷贝值,引用数据类型拷贝的是地址
③ 如果是单层对象,就没问题,如果是多层对象就有问题
改变o 的值不会影响obj的值
如果是多层对象,改变o的值就会影响到obj的值
- 直接赋值和浅拷贝有什么区别
① 直接赋值,只要是对象,就会互相影响,因为会直接拷贝对象栈里面的地址
② 浅拷贝如果是一层对象,不会相互影响,如果是多层对象,还是会互相影响
- 浅拷贝怎么理解
① 拷贝对象后,里面的属性值是简单数据类型直接拷贝值
② 如果属性值是引用数据类型则拷贝的是地址
1.2 深拷贝
- 深拷贝:拷贝的是对象,不是地址
- 常见方式
① 通过递归实现深拷贝
② loadash/cloneDeep
③ 通过JSON.stringify()实现
- 通过递归实现深拷贝
① 函数递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
② 就是自己调自己
③ 递归容易发生“栈溢出"错误,所以必须要加退出条件return
let i = 1
function fn () {
console.log(`这是第${i}次`)
if (i >= 6) {
return
}
i++
fn()
}
fn()
- 递归实现
const obj = {
uname :'pink',
age: 18,
hobby:['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = {}
function deepCopy(newObj, oldObj) {
// 在代码中直接打断点
debugger
for (let k in oldObj) {
// 处理数组的问题
// 一定先写数组,再写对象,因为数组属于对象
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
} else {
newObj[k] = oldObj[k]
}
// k 属性名 属性值 oldObj[k]
}
}
deepCopy(o, obj)
o.age = 20
o.hobby[0] = '篮球'
o.family.baby = '小hi'
console.log(o)
console.log(obj)
- 使用lodash实现深拷贝
<script src="./lodash.min.js"></script>
<script>
const obj = {
uname :'pink',
age: 18,
hobby:['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = _.cloneDeep(obj)
console.log(o)
o.family.baby = '老pink'
console.log(obj)
</script>
- 利用JSON.stringfy
const obj = {
uname :'pink',
age: 18,
hobby:['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
// 1.先转换为字符串,然后再转回对象,就会生成一个新的对象,跟原来的对象没有关系,
// 修改里面的值不会影响原来的数据
const o = JSON.parse(JSON.stringify(obj))
console.log(o)
o.family.baby = '123'
console.log(obj)
二.异常处理
- throw抛异常
- try/catch 捕获异常
- debugger
2.1 throw抛异常
① 异常处理是指预估代码在执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续执行
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error对象配合throw使用,能够设置更详细的错误信息
function fn(x, y) {
if (!x || !y) {
// throw '没有参数传递进来'
// 精确到行
throw new Error('没有参数传递进来')
}
return x + y
}
2.2 catch捕获异常
① 代码示例
function fn(){
try {
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (err) {
// 只是拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
console.log(err.message)
// 加return 中断程序
// return
throw new Error('选择器错误了')
}
// 不管程序对不对,一定会执行的代码
finally {
alert('弹出对话框')
}
//
console.log(111)
}
fn()
② 说明
- 可能发送错误的代码写到try中
- 如果try中的代码出现错误后,会执行catch中的代码段,并截获到错误信息
- catch会拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
- finally无论程序对错,一定会执行
2.3 debugger
作用:断点调试
三.this指向
3.1 普通函数的this指向
- 普通函数的调用方式决定了this的值,即【谁调用this的值就指向谁】
- 普通函数没有明确调用者时this的值为window, 严格模式(use strict)下没有调用者时this的值为undefined
3.2 箭头函数的this指向
- 箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
- 箭头函数中的this会默认绑定上一层的this值
- 会向外层作用域一层一层查找this, 直到有this的定义
3.3 注意情况
- DOM事件回调函数如果里面需要DOM对象的this, 不推荐使用箭头函数
- 基于原型的面向对象也不推荐采用箭头函数
- 构造函数,原型函数,DOM事件等不适用
- 适用于需要使用上层this的地方
3.4 改变this的指向
- call()
- apply()
- bind()
1.call()
- 使用call方法调用函数,同时执行被调用函数中this的值
- 语法:fun.call(thisArg, arg1, arg2)
- 返回值就是函数的返回值,因为它就是调用函数
const obj = {
uname: 'pink'
}
function fn (x, y) {
console.log(x + y)
console.log(this) // obj
}
// 1.调用函数
// 2.改变this指向
fn.call(obj, 1, 2)
2.apply()
- 使用apply方法调用函数,同时指定被调用函数中this的值
- 语法: fun.apply(thisArg, [argsArray])
- thisArg:在fun函数运行时指定的this值
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- apply主要跟数组有关系,比如使用Math.max() 求数组的最大值
const obj = {
uname: 'pink'
}
function fn (x, y) {
console.log(this)
console.log(x + y)
}
// 1.调用函数 2.改变this指向 3.第三个参数要放一个数组
fn.apply(obj, [1, 2])
求数组最大值
const arr = [2, 4, 5]
const max = Math.max.apply(Math, arr)
console.log(max)
console.log(Math.max(...arr))
3.bind()
- bind() 方法不会调用函数,但是能改变函数内部this指向
- 语法:fun.bind(thisArg, arg1, arg2)
- thisArg:在 fun函数运行时指定this的值
- arg1, arg2:传递的其他参数
- 返回由指定的this值和初始化参数改造的 原函数拷贝(新函数)
- 只是向改变this指向,并不想调用这个函数的时候,可以使用bind
- 如下代码,如果不调用fun的话只是去调用bind,fn函数是不会调用的
const obj = {
uname: 'pink'
}
function fn () {
console.log(this)
}
// 返回值是个函数,但是这个函数里面的this是更改过的obj
const fun = fn.bind(obj)
// console.log(fun)
fun()
4. 相同点
- 都可以改变函数内部的this指向
5.区别点
- call 和 apply 会调用函数,并且改变函数内部this指向
- call和apply传递的参数不一样,call传递参数arg1, arg2...的形式,apply 必须传数组形式
- bind不会调用函数,可以改变函数内部this指向
6.主要应用场景
- call 调用函数并且可以传递参数
- apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但还想改变this指向
四. 防抖
- 防抖:单位时间内,频发触发事件,只执行最后一次
- 使用场景
① 搜索框搜索输入,只需用户最后一次输入完,再发送请求
② 手机号,邮箱验证输入检测
- 需求实现:鼠标在盒子上移动,鼠标停止500ms之后,里面的数字才会变化 +1
① losash 提供的防抖来处理
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如dom操作,数据处理,可能造成卡顿
}
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
② 手写一个防抖函数来处理
(1)定时器变量
(2)先判断是否有定时器,如果有定时器,先清除以前的定时器
(3)如果没有定时器则开启定时器,记得存到变量里面
(4)在定时器里面调用要执行的函数
function debounce (fn, t) {
let timer
// return 返回一个匿名函数
return function () {
if (timer) clearTimeout(timer)
timer = setTimeout(function () {
fn() // 加小括号调用
}, t)
}
}
box.addEventListener('mousemove', debounce(mouseMove, 500))
五. 节流
- 单位时间内,频繁触发事件,只执行一次
- 举例:王者荣耀冷却技能
- 使用场景:鼠标移动mousemove 页面尺寸缩放resize 滚动条滚动scroll
- 实现方式
① 使用lodash实现
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
// // 500毫秒之内只会执行一次
box.addEventListener('mousemove', _.throttle(mouseMove, 500))
② 自己写一个节流函数
// 1.声明一个定时器变量
// 2.当鼠标每次滑动都先判断是否有定时器,如果有定时器就不开启
// 3.如果没有定时器则开启定时器
// 4.定时器里面调用要执行的函数
// 5.定时器里面要把定时器清空
function throttle(fn, t) {
let timer = null
return function() {
if (!timer) {
timer = setTimeout(function() {
fn()
// 清空定时器
// 不能使用clearTimeout(timer), 在setTimeout中无法使用clearTimeout来清除定时器
timer = null
}, t)
}
}
}
box.addEventListener('mousemove', throttle(mouseMove, 500))
节流防抖总结