JS高阶 - day04
一、深浅拷贝
深浅拷贝只针对引用类型
1.1 浅拷贝
拷贝对象之后
如果里面的属性值是简单数据类型直接拷贝值
如果属性值是引用数据类型则拷贝的是地址
(多层对象会有问题,第二层即以后就相当于直接赋值)
拷贝对象:Object.assgin() / 展开运算符 {...obj} 拷贝对象
拷贝数组:Array.prototype.concat() 或者 [...arr]
以拷贝对象为例
// 浅拷贝只对于第一层有用, // 缺点:对于对象里面套对象的话,内置对象还是赋的地址,修改新的也是会修改旧的 // 对单层没问题,对多层就会有问题 const obj = { uname: '温小仙', age: 20 } // 直接赋值的形式会导致修改赋值后的对象也会造成源对象的修改 // 采用浅拷贝的形式 // 1. 展开运算符 const o = {...obj} console.log(o) o.age = 18 // 改变 0 不会影响 obj console.log(o,obj) console.log('----------------------------------------') // 2. 使用Object.assign() const o1 = {} Object.assign(o1,obj) console.log(o1) o1.age = 1999 console.log(o1,obj) console.log('----------------------------------------') // 多层 const obj2 = { uname: '温小仙', age: 20, game: { play: '云顶之奕' } } const o3 = {...obj2} console.log(o3) o3.game.play = '三国杀' console.log(o3,obj2)
1.2 深拷贝
深拷贝:拷贝的是对象。
深拷贝的三种常见方法:
1. 通过递归实现深拷贝
2. lodash/cloneDeep实现
3. 通过JSON.stringify()实现
1.2.1 通过递归
函数递归:
函数内部自己调用自己, 这个函数就是递归函数
递归函数的作用和循环效果类似
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return
const obj = { uname: '温小仙', age: 20, game: { play: '云顶之奕' }, hobby:['跑步','听歌'] } //递归的形式进行深拷贝 const o = {} //拷贝函数 function deepCopy(newObj,oldObj){ // 遍历对象 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 { // k是属性名 oldObj[k] 是属性值 // newObj[k] === o.uname/o.age/o.game newObj[k] = oldObj[k] } } } deepCopy(o,obj) console.log(o) o.game.play = '三国杀' console.log(o) console.log('-------------') console.log(obj)
1.2.2 通过Lodash的cloneDeep
js库lodash里面cloneDeep内部实现了深拷贝
<!--从上往下执行,要先引入JS的外部库,lodash--> <script src="lodash.min.js"></script> <script> const obj = { uname: '温小仙', age: 20, game: { play: '云顶之奕' }, hobby:['跑步','听歌'] } // 通过JS外部库lodash的 _.cloneDeep() const o = _.cloneDeep(obj) console.log(o) o.game.play = `三国杀` console.log(obj) </script>
1.2.3 通过JSON.stringify()
const obj = { uname: '温小仙', age: 20, game: { play: '云顶之奕' }, hobby:['跑步','听歌'] } // 利用JSON实现深拷贝 // 1.把对象转化为JSON字符串 // 先转为简单数据类型,就和之前的对象没有任何关系了 //JSON.stringify(obj) // 2.在将字符串转化为对象 const o = JSON.parse(JSON.stringify(obj)) // 注 这个方法对函数没有用,要注意对象里面的方法
二、异常处理
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
2.1 throw 抛异常
throw抛异常
throw 抛出异常信息,程序也会终止执行
throw 后面跟的是错误提示信息
Error 对象配合throw 使用,能够设置更详细的错误信息
// throw抛出异常 const counter = (x,y) => { if(!x || !y){ // throw抛出异常 throw new Error('参数不能为空!') } // 抛出异常后中断程序 return x + y } counter()
2.2 try /catch 捕获异常
try /catch 捕获异常:
try...catch 用于捕获错误信息
将预估可能发生错误的代码写在try代码段中
如果try代码段中出现错误后,会执行catch代码段,并截获到错误信息
finally 不管是否有错误,都会执行
// try/catch 捕获异常 function fn(){ try { // 可能发生错误的代码要写到这里面 const p = document.querySelector('.p') p.style.color = 'red' }catch (e) { // 拦截错误,只有发生错误之后提示浏览器提供的错误信息 但是本身不会中断程序的执行 console.log(e.message) // throw 来抛出异常 来中断程序,return 也可以 throw new Error(`${e.message}`) }finally { // 不管try里面的程序对不对一定会执行的代码 alert('弹出对话框') } console.log(1) } fn()
2.3 debugger
debugger会中断代码的执行,让开发者能够检查当前的执行环境,它类似于在代码中设置了一个断点。
三、处理this
3.1 this指向
对于普通函数:
非严格模式下 谁调用this的值指向谁
严格模式下 this指向undefined
对于箭头函数:(箭头函数中并不存在this)
箭头函数会默认帮我们绑定外层this 的值,
箭头函数中的this引用的就是最近作用域中的this
会向外层作用域中,一层一层查找this,直到有this的定义
<body> <button>点击</button> <script> // 严格模式下,普通函数this指向undefined // 在非严格模式下 // 普通函数 指向调用者 console.log(this)//window //window.console.log(this) function fn(){ console.log(this)//window } fn()// window.fn() setTimeout(function (){ console.log(this) // window },10000) // window.setTimeout document.querySelector('button').addEventListener('click',function (){ console.log(this) // 指向调用者 button }) const obj = { sayHi: function (){ console.log(this) // 指向调用者 obj }, sayHello: () => console.log(this)//window } obj.sayHi() // 对象作用域没有this,函数作用域中才有 this // 箭头函数本没有this,它的this是上一层的this 即调用者的调用者 obj.sayHello() //这里,箭头函数的上一级是对象obj,对象obj的调用者是window, </script> </body>
3.2 改变this
3.2.1 call()(了解即可)
call()调用函数,同时指定被调用函数中this的值
语法:
fun.call(thisArg, arg1, arg2, ...)
thisArg:在fun 函数运行时指定的this 值
arg1,arg2:传递的其他参数
返回值就是函数的返回值,因为它就是调用函数
<script> function fn(x,y){ console.log(this)// 改变之后就从window 变成了 obj console.log(x + y) } const obj = { uname:'温小仙' } // call 调用函数并改变指向,还可以传进去参数 fn.call(obj,1,2) </script>
3.2.2 apply()(理解)
apply()调用函数,同时指定被调用函数中this的值
语法:
fun.apply(thisArg, [argsArray])
thisArg:在fun函数运行时指定的this 值
argsArray:传递的值,必须包含在数组里面
返回值就是函数的返回值,因为它就是调用函数
apply 主要跟数组有关系,比如使用Math.max() 求数组的最大值
<script> function fn(x,y){ // 接收的时候参数要和数组里面的一一对应 console.log(this)// 改变之后就从window 变成了 obj console.log(x + y) } const obj = { uname:'温小仙' } // apply 调用函数并改变指向,但是第二个参数必须是数组 // 传的时候参数必须是数组 fn.apply(obj,[1,2]) // 返回值,因为其本身就是调用函数,所以返回值就是函数的返回值 // 使用场景 求数组最大值 第一个参数也可以写null,无影响 const arr = [1,2,3,4,589,564,7889,4] const max = Math.max.apply(Math,arr) console.log(max) // 展开运算符求数组最大值 console.log(Math.max(...arr)) </script>
3.2.3 bind()(重点)
bind() 不会调用函数。但是能改变函数内部this 指向
语法:
fun.bind(thisArg, arg1, arg2, ...)
thisArg:在fun 函数运行时指定的this 值
arg1,arg2:传递的其他参数(可选)
返回由指定的this 值和初始化参数改造的原函数拷贝(新函数)
当我们只是想改变this 指向,并且不想调用这个函数的时候,可以使用bind
<body> <button>发送验证码</button> <script> // bind 方法不会调用函数,但是能改变函数内部this的指向 // 最重要 function fn(){ console.log(this)// 改变之后就从window 变成了 obj } const obj = { uname:'温小仙' } //改变指向 // 返回值是个函数,指定this的值和原函数的拷贝(新函数) const fun = fn.bind(obj) fun() // 需求 有一个按钮,点击里面就禁用,2秒钟之后就打开 const button = document.querySelector('button') button.addEventListener('click',function (){ // 禁用按钮 this.disabled = true // 第一种 //setTimeout(() => this.disabled = false,2000) //第二种写法 //const time = timeOut.bind(button) //setTimeout(time,2000) // 第三种 setTimeout(function (){ this.disabled = false }.bind(button),2000) // 还有其他,主要是搞定this的指向 }) /* function timeOut(){ this.disabled = false }*/ </script> </body>
3.2.4 三者对比
相同点:
都可以改变函数内部的this指向
不同点:
call 和apply 会调用函数, 并且改变函数内部this指向
call 和apply 传递的参数不一样, call 传递参数aru1, aru2..形式 apply 必须数组形式[arg]
bind 不会调用函数, 可以改变函数内部this指向
主要应用场景
call 调用函数并且可以传递参数
apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向
四、性能优化
Lodash库里面有节流和防抖的函数
4.1 节流
节流(throttle)就是指连续触发事件但是在n秒中只执行一次函数
使用场景:
轮播图点击效果 、 鼠标移动、页面尺寸缩放resize、滚动条滚动等都可以加节流
<body> <div class="box"></div> <script src="lodash.min.js"></script> <!--下面这个是节流-----类似技能的CD--> <script> const box = document.querySelector('.box') let i = 1 function mouseMove(){ box.innerHTML = `${i++}` console.log(new Date().toLocaleString()) } // 利用Lodash库里面的节流函数 _.throttle(function,time) //box.addEventListener('mousemove',_.throttle(mouseMove,3000)) // 自己手写 定义定时器 --间歇函数 function throttle(fn,t){ let timeId return function (){ if (!timeId){ timeId = setInterval(function (){ fn() // 清空定时器 // 应为在定时器里面无法清除定时器所以不能使用clearTimeout(timeId),应该使用timeId = null timeId = null },t) } } } box.addEventListener('mousemove',throttle(mouseMove,3000)) </script> </body>
4.2 防抖
防抖(debounce)就是指触发事件后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间
使用场景:搜索框防抖等
<body> <div class="box"></div> <script src="lodash.min.js"></script> <!--下面这个是防抖-----类似回城--> <script> // 利用防抖实现性能 优化 // 需求,鼠标在盒子上移动,里面的数组就会变化 + 1 const box = document.querySelector('.box') let i = 1 function mouseMove(){ box.innerHTML = `${i++}` } // 我自己的想法 // 利用定时器setTimeout写 防抖处理 /* let timeId function mouseMove(){ if(timeId) clearTimeout(timeId) timeId = setTimeout(function (){ box.innerHTML = `${i++}` },500) box.addEventListener('mousemove',mouseMove) }*/ // Lodash的防抖函数 // _.debounce(mouseMove(),500,true) // 会返回一个新的函数 //box.addEventListener('mousemove',_.debounce(mouseMove,500,true)) // Pink老师,利用定时器setTimeout写 防抖处理 function debounce(fn, t) { let timeId return function() { if (timeId) clearTimeout(timeId) timeId = setTimeout(fn, t) } } box.addEventListener('mousemove',debounce(mouseMove,500)) </script> </body>