深度拷贝

  • 开发时复制一个对象会出现一些问题: 对象的属性值是对象,复制的时候只是复制了引用,修改复制的对象的属性值会影响原对象的属性值。

  • 浅拷贝和深拷贝只针对引用数据类型。

浅拷贝

浅拷贝只是复制了对象的引用,修改复制的对象的属性值会影响原对象的属性值。

  • 拷贝对象:Object.assign({}, obj) 或者 ...obj
  • 拷贝数组:Array.prototype.concat() 或者 [...arr]

但是浅拷贝会存在一些问题,比如拷贝对象的属性值是对象,修改拷贝对象的属性值会影响原对象的属性值。

因为浅拷贝只拷贝了最外面一层的对象,内部的对象只是拷贝了引用。

深拷贝

深拷贝是指完全复制一个对象,即使对象的属性值是对象,修改复制的对象的属性值不会影响原对象的属性值。

  1. 通过递归实现

    • 递归很容易发生栈溢出,所以需要限制递归的深度,加退出条件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function 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]
    }
    }
    }
  2. lodash/cloneDeep

    • lodash是一个工具库,提供了很多常用的方法,cloneDeep是lodash提供的深拷贝方法
    1
    2
    3
    const _ = require('lodash') 
    const obj = {name: 'Tom', age: 18, friend: {name: 'Jerry', age: 20}}
    const obj2 = _.cloneDeep(obj)
  3. JSON.stringify()和JSON.parse()

    • 缺点:不能拷贝函数、正则、undefined、symbol

    • 优点:简单易用

    1
    2
    const obj = {name: 'Tom', age: 18, friend: {name: 'Jerry', age: 20}}
    const obj2 = JSON.parse(JSON.stringify(obj))

异常处理

  1. throw抛异常

    • throw抛出异常信息后,程序也会终止执行,后面的代码不会执行。
    • Error对象配合throw使用,可以自定义错误信息。
    1
    2
    3
    4
    5
    6
    function divide(a, b) {
    if (b === 0) {
    throw new Error('除数不能为0')
    }
    return a / b
    }
  2. try…catch捕获异常

    • 拦截错误,提示浏览器提供的错误信息,但是不会终止程序的执行。
    • 将预估可能发生错误的代码卸载try代码段中
    • 如果try代码段出现错误后会执行catch代码段,并截获到错误信息
    • finally不管是否有错误,都会执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    try {
    const p = document.querySelector('p')
    p.innerHTML = 'hello'
    } catch (e) {
    console.log(e)
    // throw new Error('error') // 如果这里抛出异常,后面的代码不会执行
    // return
    }finally {
    alert('finally') // 不管有没有异常,都会执行
    }
  3. debugger

    • debugger是一个调试工具,可以在代码中设置断点,当代码执行到断点的时候会暂停执行,可以查看变量的值、调用栈等信息。
    1
    2
    3
    4
    5
    6
    7
    function 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的值

  1. 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 2
  2. apply()

    • 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 2
  3. bind()

    • 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

单位时间内,频繁触发事件,只执行最后一次。

  • 使用场景:搜索框搜索输入、手机号、邮箱验证输入检测
  1. lodash库中的debounce方法

    • lodash是一个工具库,提供了很多常用的方法,debounce是lodash提供的防抖方法
    • 第一个参数是函数,第二个参数是延迟时间
    • _.debounce(func,[wait=0],[option=])
    1
    2
    3
    4
    5
    const _ = require('lodash')
    function fn() {
    console.log('hello')
    }
    const newFn = _.debounce(fn, 1000)

2.手写一个防抖函数

  • 防抖函数的原理是通过定时器,延迟执行函数,如果在延迟时间内再次触发事件,清除定时器,重新设置定时器。
1
2
3
4
5
6
7
8
9
10
11
function debounce(fn, delay) {
let timer = null
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments) // 保证this指向
}, delay)
}
}

节流 throttle

单位时间内,频繁触发事件,只执行一次。

  • 使用场景:页面滚动、尺寸缩放、滚动条等高频事件
  1. lodash库中的throttle方法

    • 第一个参数是函数,第二个参数是延迟时间
    • _.throttle(func,[wait=0],[option=])
    1
    2
    3
    4
    5
    const _ = require('lodash')
    function fn() {
    console.log('hello')
    }
    const newFn = _.throttle(fn, 1000)
  2. 手写一个节流函数

    • 节流函数的原理是通过定时器,延迟执行函数,如果在延迟时间内再次触发事件,不执行函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function 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 属性