JavaScript高级 第四天
一、深浅拷贝
对对象属性进行赋值,容易改变原来属性的值
eg:
const pink = {
name:'pink老师',
age:18
}
const red = pink
console.log(red) //{name:'pink老师',age:18}
red.name = 'red老师'
console.log(red) //{name:'red老师',age:18}
console.log(pink) //{name:'red老师',age:18}
1.浅拷贝
浅拷贝:拷贝的是地址
常见方法:
-
拷贝对象: Object.assgin () / 展开运算符 obj } 拷贝对象
-
拷贝数组: Array.prototype.concat () 或者 arr
eg:
const pink = {
name:'pink老师',
age:18
}
const red = {}
Object.assign(red,pink)
console.log(red) //{name:'pink老师',age:18}
red.name = 'red老师'
console.log(red) //{name:'red老师',age:18}
console.log(pink) //{name:'pink老师',age:18}
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址
简单理解: 如果是单层对象,没问题,如果有多层就有问题
eg:
family:{
mother:'pink妈妈'
}
2.深拷贝
深拷贝:拷贝的是对象,不是地址
常见方法:
-
通过 递归实现深 拷贝
-
lodash cloneDeep
-
通过 JSON.stringify 实现
函数递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
由于递归很容易发生“栈溢出”错误( stack overflow ),所以必须要加退出条件 return
eg:
let num=1
function fn() {
console.log()
if(num>=6){
return
}
num++
fn()
}
fn()
案例
利用递归函数实现 setTimeout 模拟 setInterval 效果
需求:
①:页面每隔一秒输出当前的时间
②:输出当前时间可以使用: new Date(). toLocaleString ()
function getTime() {
const time = new Date().toLocaleDateString()
console.log(time)
setTimeout(getTime, 1000)
}
getTime()
通过递归函数实现深拷贝
eg:
const o = {}
function deepCopy(new,old){
for(let k in old){
new[k]=old[k]
}
}
deepCopy(o,obj)
js库lodash里面cloneDeep内部实现深拷贝
语法:
_.cloneDeep(要被克隆的对象)
eg:
const obj = {
uname: 'pink',
age: 18,
hobby: ['篮球', '足球'],
family: {
baby: '小pink'
}
}
const o = _.cloneDeep(obj)
console.log(o)//[uname: 'pink',age: 18,hobby: ['篮球', '足球'],family: {baby: '小pink'}
o.family.baby = '老pink'
console.log(obj)//[uname: 'pink',age: 18,hobby: ['篮球', '足球'],family: {baby: '小pink'}
通过 JSON.stringify 实现
语法:
JSON.parse(JSON.stringify(要被克隆的对象))
eg:
const obj = {
uname: 'pink',
age: 18,
hobby: ['篮球', '足球'],
family: {
baby: '小pink'
}
}
const o =JSON.parse(JSON.stringify(obj))
...
二、异常处理
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
1.throw抛异常
eg:
function counter(x,y){
if(!x || !y){
throw mew Error('参数不能为空!')
}
return x+y
}
counter()
-
throw 抛出异常信息,程序也会终止执行
-
throw 后面跟的是错误提示信息
-
Error 对象配合 throw 使用,能够设置更详细的错误信息
2.try/catch 捕获异常
eg:
function foo(){
try{
const p = document.querySelector('.p')
p.style.color= 'red'
}catch(error){
console.log(error.message)
return
}
finally{
alert('执行')
}
console.log('如果出现错误,我的语句不会执行')
}
foo()
-
try...catch 用于捕获错误信息
-
将预估可能发生错误的代码写在 try 代码段中
-
如果 try 代码段中出现错误后,会执行 catch 代码段,并截获到 错误信息
4.finally 不管是否有错误,都会执行
3.debugger
在代码块里面直接添加debugger,类似与断点调试,借助于浏览器提供的错误信息
三、处理this
this指向
1.普通函数——this指向
普通函数的调用方式决定了 this 的值,即 【 谁调用 this 的值指向谁 】
普通函数没有明确调用者时this 值为 window ,严格模式下没有调用者时 this 的值为 undefined
eg:
function sayHi() {
console.log(this)
}
const sayHello = function () {
console.log(this)
}
sayHi() //window
window.sayHi() //window
sayHello() //window
'use strict'
function fn() {
console.log(this)
}
fn() //undefined
2.箭头函数——this指向
箭头函数中的this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this!
1.箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
2.箭头函数中的 this 引用的就是最近作用域中的 this
3.向外层作用域中,一层一层查找 this,直到有 this 的定义
即this为上一层this的定义
eg:
console.log(this) //window
const sayHi=function(){
console.log(this) // window
}
const user = {
name: 'xiaom',
walk: () => {
console.log(this)
}
}
user.walk() //window
注意
在开发中 【 使用箭头函数前需要考虑函数中 this 的值 】 ,事件回调函数使用箭头函数时 this 为全局的 window
因此DOM 事件回调函 数 如果里面需要 DOM 对象的 this ,则不 推荐使用箭头函数
同样由于箭头函数this 的原因,基于原型的面向对象也不推荐采用箭头函数
总结
不适用构造 函数, 原型函数,dom事件函数等等
适用需要使用上层this 的地方,其余尽量不要在箭头函数内使用到this
改变this
JavaScript中还允许指定函数中 this 的指向,有 3 个方法可以动态指定普通函数中 this 的指向
1.call()——了解
使用call 方法调用函数,同时指定被调用函数中 this 的值
语法:
fun(函数名).call(thisArg,arg1,arg2,...)
-
thisArg:在fun函数运行时指定的this值
-
arg1,arg2:传递的其他参数
-
返回值就是函数的返回值,因为其是在调用函数
eg:
const obj = {
name: 'pink'
}
function fn(x, y) {
console.log(this) //指向obj {name:'pink'}
console.log(x + y)
}
fn.call(obj, 1, 2)
2.apply()——理解
使用apply 方法调用函数,同时指定被调用函数中 this 的值
语法:
fun(函数名).apply(thisArg,[argsArray])
-
thisArg:在fun函数运行时指定的this值
-
argsArray:传递的值,必须包含在数组里面
-
返回值就是函数的返回值,因为其是在调用函数
-
apply与数组有关系,eg使用Math.max()求数组的最大值
eg:
function counter(x,y){
return x+y
}
let result = counter.apply(null,[5,10])
console.log(result)
//求数组最大值
const arr=[3,5,2,9]
console.log(Math.max.apply(null,arr)) //9
3.bind()——重点
bind() 方法不会调用函数,但是能改变函数内部 this 指向
语法:
fun(函数名).bind(thisArg,arg1,arg2,...)
-
thisArg:在fun函数运行时指定的this值
-
arg1,arg2:传递的其他参数
-
返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
-
当我们只想改变this指向,并且不想调用这个函数的时候,可以使用bind,eg改变定时器内部的this指向
eg:
function sayHi() {
console.log(this)
}
let user = {
name: 'xiaom',
age: 18
}
let sayHello = sayHi.bind(user) //this指向user
sayHello()
四、性能优化
1.防抖
防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
eg:搜索框输入,设定每次输入完毕 n 秒后发送请求,如果期间还有输入,则从新计算时间
案例
利用防抖来处理-鼠标滑过盒子显示文字
鼠标在盒子上移动,鼠标停止之后, 500ms 后里面的数字就会变化 +1
利用防抖的方式实现
利用定时器实现,当鼠标滑过,判断有没有定时器,还有就清除,以最后一次滑动为准开启定时器
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
function debounce(fn, t = 500) {
let timeId
return function () {
if (timeId) clearTimeout(timeId)
timeId = setTimeout(function () {
fn()
}, t)
}
}
box.addEventListener('mousemove', debounce(mouseMove, 500))
2.节流
节流,就是指连续触发事件但是在 n 秒中只执行一次函数
在轮播图点击效果、鼠标移动、页面尺寸缩放 resize 、滚动条滚动可以加节流
eg:当一张轮播图完成切换需要300ms,不加节流效果,快速点击,会迅速切换,而添加节流效果,不管快速点击多少次,300ms内,只能切换一张图片
案例
利用节流来处理鼠标滑过盒子显示文字
鼠标在盒子上移动,里面的数字就会变化 +1
利用节流的方式,鼠标经过, 500ms ,数字才显示
利用时间相减:移动后的时间 - 刚开始移动的时间 >= 500ms ,才去执行 mouseMove 函数
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
function throttle(fn, t = 500) {
let startTime = 0
return function () {
let now = Date.now()
if (now - startTime >= t) {
fn()
startTime = now
}
}
}
box.addEventListener('mousemove', throttle(mouseMove, 500))
也可使用Lodash库实现节流和防抖
节流:
box.addEventListener('mousemove', _.throttle(mouseMove, 500))
防抖:
box.addEventListener('mousemove', _.debounce(mouseMove, 500))