高阶技巧
深浅拷贝
只针对引用类型
浅拷贝
拷贝对象后,里面的属性值是简单数据类型直接拷贝值,如果属性值是引用数据类型则拷贝的是地址
常见语法:
1.拷贝对象:Object.assgin(新变量,被拷贝对象)
或const 变量名 = {...对象}
2.拷贝数组:Array.prototype.concat()
或[...arr]
注意:如果是简单数据类型拷贝值,引用数据类型拷贝的是地址(如果是单层对象没有问题,如果是多层对象就有问题)
深拷贝
拷贝的是对象,不是地址
常见方法:
- 通过递归实现深拷贝
函数递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
由于递归容易发生“栈溢出”错误。所以必须要加退出条件return
const obj = {
uname:'pink',
age:18,
hobby:['乒乓球','足球'],
family:{
baby:'xiaop'
}
}
const o = {}
// 拷贝函数
// 深拷贝,拷贝的新对象不会影响旧对象,遇到数组和对象就再次调用递归函数,先数组再对象
function deepCopy(newObj,oldObj) {
for(let k in oldObj) {
// 一定先写数组,在写对象,因为万物皆对象
// 处理数组的问题
if (oldObj[k] instanceof Array) {
newObj[k] = []
// 函数递归
deeepCopy(newObj[k],oldObj[k])
}
// 处理对象的问题
else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k],oldObj[k])
} else {
// k属性名,oldObj[k]属性值
// newObj[k] === o.uname
newObj[k] = oldObj[k]
}
}
}
deepCopy(o,obj)// 函数调用两个参数
o.age = 20
console.log(o)
o.hobby[0] = '篮球'
o.family.baby = 'oldp'
console.log(obj)
- lodash/cloneDeep
JavaScript库lodash里面cloneDeep内部实现了深拷贝
语法:const deep = _.cloneDeep(object)
<!-- 先引用 -->
<script src="./lodash.min.js"></script>
<script>
const obj = {
uname:'pink',
age:18,
hobby:['乒乓球','足球'],
family:{
baby:'xiaop'
}
}
const o = _.cloneDeep(obj)
o.family.baby = 'oldp'
console.log(obj)
</script>
- 通过JSON.stringfy()实现
const obj = {
uname:'pink',
age:18,
hobby:['乒乓球','足球'],
family:{
baby:'xiaop'
}
}
// JSON.stringify() 把对象转换为JSON字符串
// JSON.parse() 把JSON字符串转换为对象
const o = JSON.parse(JSON.stringify(obj))
o.family.baby = 'oldp'
console.log(obj)
异常处理
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
throw抛异常
1.throw抛出异常信息,程序也会终止执行
2.throw后面跟的是错误提示信息
3.Error对象配合throw使用,能够设置更详细的错误信息
function fn(x,y) {
if (!x || !y) {
// throw '没有参数传递进来'
throw new Error('没有参数传递进来')
}
return x + y
}
try/caych捕获异常
通过try/catch捕获错误信息(浏览器提供的错误信息)
1.try…catch用于捕获错误信息
2.将预估可能发生了错误的代码写在try代码段中
3.如果try代码段中出现错误后,会执行catch代码段,并截获到错误信息
4.finally不管是否错误,都会执行
function fn() {
try {
// 可能发生错误的代码要写到try
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (err) {
// 拦截错误,提示浏览器提供的错误信息,但是不中断程序的执行
console.log(err.message)
// 需要return中断程序,可以用throw代替
throw new Error('错误')
} finally {
// 不管你程序对不对,一定会执行的代码
alert('弹出对话框')
}
}
debugger
在代码中需要调试的地方写debugger,打开控制台后自动来到debugger的位置,类似于断点
处理this
this指向
- 普通函数
普通函数的调用方式决定了this的值,即谁调用就指向谁
普通函数没有明确调用者时this值为window,严格模式下没有调用者时this的值为undefined - 箭头函数
箭头函数中的this与普通函数完全不同,也不受调用方式影响,事实上箭头函数中并不存在this
1.箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的
2.箭头函数中的this引用的就是最近作用域中的this
3.向外层作用域中,一层一层查找this,直到有this的定义
注意:
1.在开发中使用箭头函数前需要考虑函数中this的值,事件回调函数使用箭头函数时,this为全局变量时,this为全局的window
因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
2.同样由于箭头函数this的原因,基于原型的面向对象也不推荐采用箭头函数
改变this
- call()
使用call()方法调用函数,同时指定被调用函数中的this的值
语法:fn.call(thisArg,arg1,arg2,...)
thisArg:在fn函数运行时指定的this值
arg1,arg2:传递的其他参数
返回值就是函数的返回值,因为它就是调用函数 - apply()
使用apply()方法调用函数,同时指定被调用函数中的this的值
语法:fn.apply(thisArg,[argsArray])
thisArg:在fn函数运行时指定的this值
argsArray:传递的值,必须包含在数组里面
返回值就是函数的返回值,因为它就是调用函数
因此apply()主要跟数组有关系,比如使用Math.max()求数组的最大值
const obj = {
age:18
}
function fn(x,y) {
console.log(this)// {age:18}
console.log(x + y)// 3
}
// 调用函数
// 改变this指向
fn.apply(obj,[1,2])
// 3.返回值 就是函数的返回值
// 使用场景:求数组最大值
const arr = [1,2,3]
const max = Math.max.apply(Math,arr)
// console.log(Math.max(...arr))
console.log(max)// 3
- bind()(重点)
bind()方法不会调用函数,但是能改变函数内部this指向
语法:fn.bind(thisArg,arg1,arg2...)
thisArg:在fn函数运行时指定的this的值
arg1,arg2:传递的其他参数
返回由指定的this值和初始化参数改造的原拷贝函数(新函数)
因此当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变计时器内部的this指向
const obj = {
age:18
}
function fn() {
console,log(this)
}
// 返回值是个函数,但是这个函数里面的this是更改过的
const fn = fn.bind(obj)
// console.log(fn)
fn()// {age:18}
<body>
<button>发送</button>
<script>
// 需求,有一个按钮,点击里面就禁用,2秒钟之后开始
const btn = document.querySelector('button')
btn.addEventListener('click', function() {
// 禁用按钮
this.display = true//this指向btn
setTimeout(function() {
// 在这个普通函数里面,要this由原来的window改为btn
this.disabled = false
}.bind(this),2000)//这个this是上面指向btn的this
})
</script>
</body>
性能优化
防抖
防抖:单位时间内,频繁触发事件,只执行最后一次
使用场景:
1.搜索框搜索输入。只需要用户最后一次输入完,再发送请求
2.手机号、邮箱验证输入检测
常用方法:
- lodash提供的防抖来处理
语法:_.debounce(func,[wait=0],[options=])
func:要防抖的函数
[wait=0]:需要延迟的毫秒数
[options=]:选项对象 - 手写防抖函数
核心思路:利用定时器(setTimeout)来实现
例如:
<style>
.box {
width:500px;
height:500px;
background-color:#ccc;
color:#fff;
text-align:center;
font-size:100px;
}
</style>
<body>
<div class="box"></div>
<script src='./lodash.min.js'>
<script>
// 利用防抖实现性能优化
// 需求:鼠标在盒子上移动,里面的数字就会变化+1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如DOM操作、数据处理,可能造成卡顿
}
// 添加事件
// box.addEventListener('mousemove',mouseMove)
// 利用lodash库实现防抖 500毫秒之后+1
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()//加小括号调用fn函数
},t)
}
}
box.addEventListener('mousemove',debounce(mouseMove,500))
</script>
</body>
节流
节流:单位时间内,频繁触发事件,只执行一次
使用场景:
高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll
常用方法:
- lodash提供的节流函数来处理
语法:_.throttle(func,[wait=0],[options=])
func:要节流的函数
[wait=0]:需要节流的毫秒数
[options=]:选项对象
[options.leading=true]:指定调用在节流开始前
[options.leading=false]:指定调用在节流结束后 - 手写一个节流函数来处理
核心思路:利用定时器(setTimeout)来实现
例如:
<style>
.box {
width:500px;
height:500px;
background-color:#ccc;
color:#fff;
text-align:center;
font-size:100px;
}
</style>
<body>
<div class="box"></div>
<script src='./lodash.min.js'>
<script>
// 利用节流实现性能优化
// 需求:鼠标在盒子上移动,里面的数字就会变化+1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如DOM操作、数据处理,可能造成卡顿
}
// 添加事件
// box.addEventListener('mousemove',mouseMove)
// 利用lodash库实现节流 500毫秒之后+1
box.addEventListener('mousemove',_.throttle(mouseMove,500))
// 手写函数部分
// 1.声明一个定时器变量
// 2.当鼠标每次滑动都先判断是否有定时器,如果有定时器则不开启新定时器
// 3.如果没有定时器则开启定时器,记得存到变量里面
// 3.1定时器里面调用执行的函数
// 3.2定时器里面要把定时器清空
function throttle(fn,t) {
let timer = null
return function() {
if(!timer) {
timer = setTime(function() {
fn()
// 清空定时器
// 在setTimeout中是无法删除定时器的,因为定时器还在运作,故不使用clearTimeront(timer)
timer = null
},t)
}
}
}
box.addEventListener('mousemove',throttle(mouseMove,500))
</script>
</body>