JavaScript-深浅拷贝、异常处理、this、防抖节流
深浅拷贝
在开发中经常需要复制,就会出现这种情况
两个对象都指向同一个地址,所以修改一个的制,另一个也会变化
浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
- 拷贝对象:
Object.assgin()
/ 展开运算符{...obj}
拷贝对象 - 拷贝数组:
Array.prototype.concat()
或者[...arr]
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过
JSON.stringify()
实现
通过递归实现深拷贝:
1 | const obj = { |
js库lodash里面cloneDeep
内部实现了深拷贝:
1 | <script src="js/lodash.min.js"></script> |
通过JSON.stringify()
实现:
JSON.stringify(obj)
:将对象obj
序列化成一个JSON字符串。JSON.parse(...)
:将上一步得到的JSON字符串反序列化成一个新的JavaScript对象。
1 | const o = JSON.parse(JSON.stringify(obj)) |
异常处理
throw抛异常
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
1 | function counter(x, y) { |
总结:
throw
抛出异常信息,程序也会终止执行throw
后面跟的是错误提示信息Error
对象配合throw
使用,能够设置更详细的错误信息
try/catch捕获错误信息
我们可以通过try/catch捕获错误信息(浏览器提供的错误信息)
1 | function foo() { |
总结:
try...catch
用于捕获错误信息- 将预估可能发生错误的代码写在
try
代码段中 - 如果
try
代码段中出现错误后,会执行catch
代码段,并截获到错误信息 finally
不管是否有错误,都会执行
debugger
直接在代码中加入debugger,可以直接设置断点
处理this
this指向
普通函数
普通函数的调用方式决定this的值,就是谁调用的this
就指向谁
1 | function sayHi() { |
普通函数没有明确调用者时,this
值为window
,严格模式下没有调用者时this
值为undefined
箭头函数
箭头函数的this
与普通函数完全不一样,不受调用方式的影响,事实上箭头函数中并不存在this
- 箭头函数会默认帮我们绑定外层
this
的值,所以在箭头函数中this
的值和外层的this
是一样的 - 箭头函数中的
this
引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找
this
,直到有this
的定义
1 | const sayHi = () => { |
ChatGPT的解释:
user.sayHi()
: 在这里,sayHi
是一个箭头函数,它在全局上下文中被定义。因此,当user.sayHi()
被调用时,this
指向箭头函数定义时的上下文,即全局对象(在浏览器中是window
,在Node.js中可能是global
或globalThis
)。注意,即使sayHi
被作为user
对象的方法调用,由于它是箭头函数,这不会影响this
的值。user.hobby()
:hobby
是使用function
关键字定义的传统函数,并作为user
对象的方法被调用。因此,在hobby
函数内部,this
指向调用它的对象,即user
对象。同理,内部的箭头函数fn
捕获了hobby
函数中的this
,所以在fn
中this
也指向user
对象。user.word()
:word
是一个箭头函数,在user
对象的定义中捕获了它定义时的this
。由于user
对象字面量不形成单独的作用域,word
函数捕获的this
是在包含user
定义的外部作用域中的this
,也就是全局对象。
特殊情况
在开发中使用箭头函数前需要考虑函数中 this
的值,事件回调函数使用箭头函数时,this
为全局的 window
,因此DOM事件回调函数不推荐使用箭头函数
1 | // DOM 节点 |
同样由于箭头函数 this
的原因,基于原型的面向对象也不推荐采用箭头函数
1 | function Person() { |
总结:
- 函数内不存在this,沿用上一级的
- 不适用于构造函数、原型函数、DOM事件函数等
- 适用于需要使用上层this的地方
改变this
JavaScript中还允许指定函数中this
的指向,有3个方法可以动态指定普通函数中this
的指向
call()
apply()
bind()
call()
使用 call()
方法调用函数,同时指定函数中 this
的值
语法:fn.call(thisArg, arg1, arg2, ...)
thisArg
:在fn函数运行时指定的this值arg1
、arg2
:传递的其他参数- 返回值就是函数的返回值,因为他就是调用函数
1 | // 普通函数 |
总结:
call()
方法能够在调用函数的同时指定this
的值- 使用
call()
方法调用函数时,第1个参数为this
指定的值 call()
方法的其余参数会依次自动传入函数做为函数的参数
apply()
使用 apply()
方法调用函数,同时指定函数中 this
的值
语法:fn.apply(thisArg, [argsArray])
thisArg
:在fn函数运行时指定的this
值argsArray
:传递的值,必须包含在数组里面- 返回值就是函数的返回值,因为它就是调用函数
- 因此
apply()
主要跟数组有关系,比如使用Math.max()
求数组的最大值
1 | function sayHi() { |
总结:
apply()
方法与call()
方法几乎一致,只是第二个参数是否为数组的区别
bind()
bind
方法并不会调用函数,而是创建一个指定了 this
值的新函数
语法:fn.bind(thisArg, arg1, arg2, ...)
thisArg
:在 fn 函数运行时指定的this
值arg1
,arg2
:传递的其他参数- 返回由指定的
this
值和初始化参数改造的原函数拷贝(新函数) - 因此当我们只是想改变
this
指向,并且不想调用这个函数的时候,可以使用bind()
,比如改变定时器内部的this
指向
1 | function saythis() { |
三种方法的区别
相同点:
- 都可以改变函数内部的
this
指向
区别点:
call
和apply
会调用函数, 并且改变函数内部this
指向call
和apply
传递的参数不一样,call
传递参数arg1
,arg2..
形式,而apply
必须数组形式[arg]
bind
不会调用函数,可以改变函数内部this
指向
主要应用场景:
call
调用函数并且可以传递参数apply
经常跟数组有关系,比如借助于数学对象实现数组最大值最小值bind
不调用函数,但是还想改变this
指向,比如改变定时器内部的this
指向
性能优化
防抖
防抖(debounce),就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
例如之前的商品放大镜案例,鼠标放入盒子就会在旁边出现放大效果,离开200ms后消失,但是如果在200ms之内鼠标再次返回盒子,则不会出现放大效果,所以需要设定重新计算执行时间
1 | //监听鼠标经过和离开 |
节流
节流(throttle),就是指连续触发事件但是在 n 秒中只执行一次函数
假如一张轮播图完成切换需要300ms, 不加节流效果,快速点击,则嗖嗖嗖的切换,加上节流效果,不管快速点击多少次,300ms时间内,只能切换一张图片。