深度拷贝、异常、this、防抖与节流
深度拷贝
开发时复制一个对象会出现一些问题: 对象的属性值是对象,复制的时候只是复制了引用,修改复制的对象的属性值会影响原对象的属性值。
浅拷贝和深拷贝只针对引用数据类型。
浅拷贝
浅拷贝只是复制了对象的引用,修改复制的对象的属性值会影响原对象的属性值。
- 拷贝对象:
Object.assign({}, obj)
或者...obj
- 拷贝数组:
Array.prototype.concat()
或者[...arr]
但是浅拷贝会存在一些问题,比如拷贝对象的属性值是对象,修改拷贝对象的属性值会影响原对象的属性值。
因为浅拷贝只拷贝了最外面一层的对象,内部的对象只是拷贝了引用。
深拷贝
深拷贝是指完全复制一个对象,即使对象的属性值是对象,修改复制的对象的属性值不会影响原对象的属性值。
通过递归实现
- 递归很容易发生栈溢出,所以需要限制递归的深度,加退出条件
1
2
3
4
5
6
7
8
9
10function deepClone(newObj, oldObj) { // newObj是新对象,oldObj是旧对象
for (let key in oldObj) {
if (typeof oldObj[key] === 'object') {
newObj[key] = oldObj[key] instanceof Array ? [] : {}
deepClone(newObj[key], oldObj[key])
} else {
newObj[key] = oldObj[key]
}
}
}lodash/cloneDeep
- lodash是一个工具库,提供了很多常用的方法,cloneDeep是lodash提供的深拷贝方法
1
2
3const _ = require('lodash')
const obj = {name: 'Tom', age: 18, friend: {name: 'Jerry', age: 20}}
const obj2 = _.cloneDeep(obj)JSON.stringify()和JSON.parse()
缺点:不能拷贝函数、正则、undefined、symbol
优点:简单易用
1
2const obj = {name: 'Tom', age: 18, friend: {name: 'Jerry', age: 20}}
const obj2 = JSON.parse(JSON.stringify(obj))
异常处理
throw抛异常
- throw抛出异常信息后,程序也会终止执行,后面的代码不会执行。
- Error对象配合throw使用,可以自定义错误信息。
1
2
3
4
5
6function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为0')
}
return a / b
}try…catch捕获异常
- 拦截错误,提示浏览器提供的错误信息,但是不会终止程序的执行。
- 将预估可能发生错误的代码卸载try代码段中
- 如果try代码段出现错误后会执行catch代码段,并截获到错误信息
- finally不管是否有错误,都会执行
1
2
3
4
5
6
7
8
9
10try {
const p = document.querySelector('p')
p.innerHTML = 'hello'
} catch (e) {
console.log(e)
// throw new Error('error') // 如果这里抛出异常,后面的代码不会执行
// return
}finally {
alert('finally') // 不管有没有异常,都会执行
}debugger
- debugger是一个调试工具,可以在代码中设置断点,当代码执行到断点的时候会暂停执行,可以查看变量的值、调用栈等信息。
1
2
3
4
5
6
7function divide(a, b) {
debugger // 设置断点
if (b === 0) {
throw new Error('除数不能为0')
}
return a / b
}
处理this
普通函数的this指向
- 普通函数的调用方式决定了this的值,即谁调用this的值指向谁
- 普通函数是没有明确调用者时this的值为window,严格模式下没有调用者时this的值为undefined(‘use strict’)
箭头函数的this指向
- 箭头函数中不存在this,箭头函数会默认绑定外层this值,所以在箭头函数中的this值和外层的this值是一样的。
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到有this的定义
在开发中,使用箭头函数前需要考虑函数中的this值,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数。
构造函数、原型函数、DOM事件函数等不适用箭头函数
改变this
下面三个方法可以动态指定普通函数中this的值
call(),使用场景较少
- call()方法调用一个对象,用另一个对象替换当前对象
- call()方法可以接受多个参数,第一个参数是this的值,后面的参数是函数的参数
1
2
3
4
5
6// fun.call(thisArg,arg1,arg2) thisArg是在fun函数运行时指定的this值,arg1,arg2是fun函数的参数,返回值就是fun函数的返回值
function fn(a, b) {
console.log(this, a, b)
}
fn.call({name: 'Tom'}, 1, 2) // {name: 'Tom'} 1 2apply()
- apply()方法和call()方法类似,区别在于apply()方法接受的是一个数组
1
2
3
4
5
6// fun.apply(thisArg,[arg1,arg2]) thisArg是在fun函数运行时指定的this值,[arg1,arg2]是fun函数的参数,返回值就是fun函数的返回值
function fn(a, b) {
console.log(this, a, b)
}
fn.apply({name: 'Tom'}, [1, 2]) // {name: 'Tom'} 1 2bind()
- bind()方法不会调用函数,返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
- 适用于只是想改变this指向,并且不想调用这个函数的时候。
1
2
3
4
5
6
7// fun.bind(thisArg,arg1,arg2) thisArg是在fun函数运行时指定的this值,arg1,arg2是fun函数的参数,返回值是一个函数
function fn(a, b) {
console.log(this, a, b)
}
const newFn = fn.bind({name: 'Tom'}, 1, 2)
newFn() // {name: 'Tom'} 1 2
性能优化
防抖 debounce
单位时间内,频繁触发事件,只执行最后一次。
- 使用场景:搜索框搜索输入、手机号、邮箱验证输入检测
lodash库中的debounce方法
- lodash是一个工具库,提供了很多常用的方法,debounce是lodash提供的防抖方法
- 第一个参数是函数,第二个参数是延迟时间
- _.debounce(func,[wait=0],[option=])
1
2
3
4
5const _ = require('lodash')
function fn() {
console.log('hello')
}
const newFn = _.debounce(fn, 1000)
2.手写一个防抖函数
- 防抖函数的原理是通过定时器,延迟执行函数,如果在延迟时间内再次触发事件,清除定时器,重新设置定时器。
1 | function debounce(fn, delay) { |
节流 throttle
单位时间内,频繁触发事件,只执行一次。
- 使用场景:页面滚动、尺寸缩放、滚动条等高频事件
lodash库中的throttle方法
- 第一个参数是函数,第二个参数是延迟时间
- _.throttle(func,[wait=0],[option=])
1
2
3
4
5const _ = require('lodash')
function fn() {
console.log('hello')
}
const newFn = _.throttle(fn, 1000)手写一个节流函数
- 节流函数的原理是通过定时器,延迟执行函数,如果在延迟时间内再次触发事件,不执行函数。
1
2
3
4
5
6
7
8
9
10
11function throttle(fn, delay) {
let timer = null
return function() {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments) // 保证this指向
timer = null // 在setTimeout中无法删除定时器,因为定时器还在运作,不能用clearTimeout删除。
}, delay)
}
}
}
typeof、instanceof、Object.prototype.toString.call() 三者区别
1. ✅
typeof
1
2
3
4
5
6
7 typeof 1 // "number"
typeof 'hello' // "string"
typeof null // "object" ❌(历史遗留)
typeof undefined // "undefined"
typeof [] // "object"
typeof {} // "object"
typeof () => {} // "function"不适合判断复杂对象类型(如数组、正则、日期等)
2. ✅
instanceof
1
2
3
4
5 [] instanceof Array // true
new Date() instanceof Date // true
({}) instanceof Object // true
"abc" instanceof String // false(因为是原始值)
new String("abc") instanceof String // true原理:沿着
value.__proto__
一直向上找,看是否能找到Constructor.prototype
⚠️ 注意:
- 基本类型(如字符串)不是对象,不适用
- 在跨 iframe/窗口场景中
instanceof
判断会失效,因为构造函数不同
3. ✅
Object.prototype.toString.call(value)
这是最强大、最精准的方法,能判断几乎所有类型。
📌 常见类型返回值对照表:
类型 返回结果 undefined
[object Undefined]
null
[object Null]
Boolean
[object Boolean]
Number
[object Number]
String
[object String]
Symbol
[object Symbol]
BigInt
[object BigInt]
Object
[object Object]
Array
[object Array]
Function
[object Function]
Date
[object Date]
RegExp
[object RegExp]
Error
[object Error]
Map
[object Map]
Set
[object Set]
WeakMap
[object WeakMap]
WeakSet
[object WeakSet]
Promise
[object Promise]
Arguments
[object Arguments]
Int8Array
[object Int8Array]
Uint8Array
[object Uint8Array]
ArrayBuffer
[object ArrayBuffer]
✅ 使用方式封装:
1
2
3
4
5
6
7
8 function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
getType([]) // "Array"
getType(null) // "Null"
getType(() => {}) // "Function"
getType(new Map()) // "Map"
4. ✅
constructor
1
2
3
4
5 (123).constructor === Number // true
("abc").constructor === String // true
({}).constructor === Object // true
([]).constructor === Array // true
(new Date()).constructor === Date // true⚠️ 缺点:
constructor
可被人为修改(不安全)- 某些特殊对象可能没有
constructor
属性
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Comment