首页 > 编程语言 >JavaScript 高阶技巧

JavaScript 高阶技巧

时间:2024-03-12 12:33:05浏览次数:21  
标签:function console 技巧 函数 JavaScript const 高阶 o2 log

0x01 深浅拷贝

  • 开发中经常需要拷贝(复制)一个对象,如果直接赋值,则对拷贝对象的修改会影响到源对象

    const o1 = {
      a: 1,
      b: 2
    }
    const o2 = o1
    console.log(o2) // { a: 1, b: 2 }
    
    o2.a = 3
    console.log(o1) // { a: 3, b: 2 }
    console.log(o2) // { a: 3, b: 2 }
    

    原因在于,直接赋值的方法是在拷贝对象数据在栈中的地址,即两个变量操作同一个位置的数据

  • 深拷贝与浅拷贝只针对引用类型

(1)浅拷贝

  • 浅拷贝只将对象或数组的第一层进行复制,其他层级复制的是所存储的内存地址

a. 对象

  • 方法:Object.assign(to, from){...obj}

  • 案例:

    const o1 = {
      a: 1,
      b: 2
    }
    
    const o2 = {}
    Object.assign(o2, o1)
    console.log(o2) // { a: 1, b: 2 }
    o2.a = 3
    console.log(o1) // { a: 1, b: 2 }
    console.log(o2) // { a: 3, b: 2 }
    
    const o3 = {...o1}
    console.log(o3) // { a: 1, b: 2 }
    
  • 浅拷贝仅拷贝第一层数据,而不会深入

    const o1 = {
      a: 1,
      b: {
        c: 2
      }
    }
    
    const o2 = {}
    Object.assign(o2, o1)
    
    o2.b.c = 3
    
    console.log(o1) // { a: 1, b: { c: 3 } }
    console.log(o2) // { a: 1, b: { c: 3 } }
    

b. 数组

  • 方法:Array.prototype.concat()[...array]

  • 案例:

    const a1 = [1, 2, 3]
    
    const a2 = a1.concat([])
    console.log(a2) // [ 1, 2, 3 ]
    
    const a3 = [...a1]
    console.log(a3) // [ 1, 2, 3 ]
    

(2)深拷贝

  • 深拷贝会构造一个新的复合数组或对象,遇到引用所指向的引用数据类型会继续执行拷贝

  • 常见方法:

a. 递归方法

  • 递归应用举例:通过 setTimeout 模拟 setInterval 效果实现页面中的时钟每秒刷新

    document.body.appendChild(document.createElement('div'))
    function getTime() {
    document.querySelector('div').innerHTML = new Date().toLocaleString()
    setTimeout(getTime, 1000)
    }
    getTime()
    
  • 基于递归方法的深拷贝案例:

    const o1 = {
      a: 1,
      b: {
        c: 2
      }
    }
    const o2 = {}
    
    function deepCopy(newObj, oldObj) {
      // 遍历oldObj中的所有属性
      for (let key in oldObj) {
        // 如果属性值是对象,则递归进行深度复制
        if (typeof oldObj[key] === 'object') {
          newObj[key] = {} // 为newObj创建一个空对象作为属性值
          deepCopy(newObj[key], oldObj[key]) // 递归调用deepCopy函数复制属性值中的所有属性
        } else {
          // 如果属性值不是对象,直接复制
          newObj[key] = oldObj[key]
        }
      }
    }
    
    deepCopy(o2, o1)
    console.log(o2)	// { a: 1, b: { c: 2 } }
    o2.b.c = 3
    console.log(o1)	// { a: 1, b: { c: 2 } }
    console.log(o2)	// { a: 1, b: { c: 3 } }
    

b. Lodash

const o1 = {
  a: 1,
  b: {
    c: 2
  }
}

const o2 = _.cloneDeep(o1)

console.log(o2)

c. JSON 方法

const o1 = {
  a: 1,
  b: {
    c: 2
  }
}

const o2 = JSON.parse(JSON.stringify(o1))

console.log(o2)

0x02 异常处理

  • 定义:预估代码运行过程中可能发生的错误,使用特定的方法对这些错误进行合适的处理
  • 意义:有助于提高代码健壮性

(1)throw 抛出异常

function division(x, y) {
  if(!x || !y) {
    throw "The parameter cannot be empty"
  }
  if(y === 0) {
    throw new Error("Divisor cannot be zero")
  }
}
division()
  • throw 抛出异常信息,程序也会中止
  • Error 对象常配合 throw 使用,能够设置更详细的错误消息

(2)try...catch 捕获异常

document.body.appendChild(document.createElement('p'))
function fun() {
  try {
    // Correct: document.querySelector('p').style.color = 'red'
    document.querySelector('.p').style.color = 'red'
  } catch (e) {
    console.log("Catch a error: ", e.message)
  } finally {
    console.log("Finally")
  }
}
fun()
  • 用于捕获错误信息
  • try 中写入可能会发生错误的代码
  • catch 中写入捕获错误后的处理
  • finally 中写入无论是否出错都会执行的代码

(3)debugger

const btn = document.createElement('button')
btn.onclick = function () {
  debugger
}
btn.textContent = 'Debug'
document.body.appendChild(btn)
  • 使用 debugger 可以进入逐步调试

0x03 处理 this

(1)this 指向

a. 普通函数

  • 普通函数的调用方式决定了 this 的值

    const obj = {
      a: function fun() {
        console.log(this);
      }
    }
    obj.a() // {a:f}
    
  • 没有明确的调用方法时,this 的值为 window

    function fun() {
      console.log(this) // [object Window]
    }
    fun()
    
  • 严格模式下没有明确的调用方法时,this 的值为 undefined

    'use strict'
    function fun() {
      console.log(this) // undefined
    }
    fun()
    

b. 箭头函数

  • 箭头函数不存在 this

    • 箭头函数会默认绑定外层 this 的值
    • 箭头函数中的 this 引用的是最近作用域中的 this
    • 箭头函数会向外层作用域中一层层查找 this,直至找到有 this 的定义
    const obj = {
      a: () => {
        console.log(this)
      }
    }
    obj.a()	// [object Window]
    
  • 在开发中,使用箭头函数前需要考虑函数中 this 的值

    const btn = document.createElement("button")
    btn.textContent = "Click"
    btn.addEventListener("click", function() {
      console.log(this) // <button>Click</button>
    })
    btn.addEventListener("click", () => {
      console.log(this) // [object Window]
    })
    document.body.appendChild(btn)
    
  • 基于原型的面向对象不推荐采用箭头函数

    function Obj() {}
    Obj.prototype.a = () => {
      console.log(this)
    }
    const obj = new Obj()
    obj.a()	// [object Window]
    

(2)改变 this

  • 有三个方法可以动态指定普通函数中 this 的指向

a. call()

  • 语法:call(thisArg, arg1, arg2, ...)

    • thisArg:在函数运行时,指定 this 的值
    • arg1, arg2, ...:传参
    • 返回值就是函数的返回值
  • 举例

    const obj = { a: 0 }
    function fun(x, y) {
      console.log(x, y, this) // 1 2 {a: 0}
    }
    fun.call(obj, 1, 2)
    

b. apply()

  • 语法:fun.apply(thisArg, [argsArray])

    • thisArg:在函数运行时,指定 this 的值
    • argsArray:传参,必须包含在数组里面
    • 返回值就是函数的返回值
    • 因此 apply 主要跟数组有关系
  • 举例

    function sum(x, y) {
      console.log(this) // [object Window]
      return x + y
    }
    console.log(sum.apply(null, [1, 2])) // 3
    console.log(Math.max.apply(Math, [1, 2, 3])) // 3
    

c. bind()

  • bind 方法不会调用函数,但是也可以改变函数内部的 this 指向

  • 语法:bind(thisArg, arg1, arg2, ...)

    • thisArg:在函数运行时,指定 this 的值
    • arg1, arg2, ...:传参,必须包含在数组里面
    • 返回值由指定的 this 值和初始化参数改造的原函数拷贝
  • 举例

    const obj = { a: 0 }
    function fun(x, y) {
      console.log(x, y, this)
    }
    fun.bind(obj, 1, 2)() // 1 2 {a: 0}
    

0x04 性能优化

(1)防抖

  • 防抖(debounce):单位时间内,频繁触发事件,只执行最后一次

    • 触发事件后,在 \(n\) 秒内函数只能执行一次,如果在 \(n\) 秒内再次被触发,则重新计算函数执行时间
  • 使用场景:常用于输入事件的处理中,以减少不必要的计算或操作

  • 举例:鼠标在盒子上移动,每 500ms 盒内数字加一

    const box = document.createElement('div')
    document.body.appendChild(box)
    
    let cnt = 1
    function add() {
      box.innerHTML = cnt++
    }
    
    function debounce(func, timeMs) {
      let timer
      return function () {
        if (timer) clearTimeout(timer)
        timer = setTimeout(function () {
          func()
        }, timeMs)
      }
    }
    
    box.addEventListener("mousemove", debounce(add, 500))
    
  • 防抖函数的封装说明

    /**
     * @param {Function} func 要执行的函数。
     * @param {number} timeMs 延迟的时间,单位为毫秒。
     * @returns {Function} 返回一个新的函数,该函数具有防抖功能。
     */
    function debounce(func, timeMs) {
      let timer // 用于存储定时器的变量
    
      // 返回一个新的函数,该函数会延迟执行传入的func函数
      return function () {
        if (timer) clearTimeout(timer) // 如果存在定时器,则清除,以防止之前设定的执行被触发
    
        // 设定一个新的定时器,当延迟时间过去后,执行func函数
        timer = setTimeout(function () {
          func()
        }, timeMs)
      }
    }
    

(2)节流

  • 节流(throttle):单位时间内,频繁触发事件,只执行一次

    • 连续触发事件,但在 \(n\) 秒内仅执行一次函数
  • 使用场景:常用于高频事件的处理中,以减少不必要的性能消耗

  • 举例:鼠标在盒子上移动,每 500ms 盒内数字加一

    const box = document.createElement('div')
    document.body.appendChild(box)
    
    let cnt = 1
    function add() {
      box.innerHTML = cnt++
    }
    
    function throttle(func, timeMs) {
      let timer = null
      return function () {
        if (!timer) {
          timer = setTimeout(function() {
            func()
            timer = null
          }, timeMs)
        }
      }
    }
    
    box.addEventListener("mousemove", throttle(add, 500))
    
  • 节流函数封装说明

    /**
     * @param {Function} func 要节流的函数
     * @param {number} timeMs 节流的时间间隔(毫秒)
     * @returns {Function} 返回一个新函数,新函数将控制原函数在指定时间间隔内只执行一次
     */
    function throttle(func, timeMs) {
      let timer = null // 利用闭包保存一个定时器变量
    
      return function () {
        // 如果定时器不存在,则设置定时器
        if (!timer) {
          timer = setTimeout(function() {
            func() // 在指定时间间隔后执行原函数
            timer = null // 执行后重置定时器变量
          }, timeMs)
        }
      }
    }
    

-End-

标签:function,console,技巧,函数,JavaScript,const,高阶,o2,log
From: https://www.cnblogs.com/SRIGT/p/18068048

相关文章

  • 跨端轻量JavaScript引擎的实现与探索
    一、JavaScript1.JavaScript语言JavaScript是ECMAScript的实现,由ECMA39(欧洲计算机制造商协会39号技术委员会)负责制定ECMAScript标准。ECMAScript发展史:时间版本说明1997年7月ES1.0发布当年7月,ECMA262标准出台1998年6月ES2.0发布该版本修改完全符合ISO......
  • 轻松驾驭Python格式化:5个F-String实用技巧分享
    F-String(格式化字符串字面值)是在Python3.6中引入的,它是一种非常强大且灵活的字符串格式化方法。它允许你在字符串中嵌入表达式,这些表达式在运行时会被求值并转换为字符串,这种特性使得F-String在编写Python代码时能够更简洁、更直观地处理字符串。本文总结了5个实用的F-String技......
  • Java HashMap 和 HashSet 的高效使用技巧
    JavaHashMapHashMap是一种哈希表,它存储键值对。键用于查找值,就像数组中的索引一样。HashMap的优势在于它可以使用任何类型作为键,并且查找速度很快。创建HashMap//导入HashMap类importjava.util.HashMap;publicclassMain{publicstaticvoidmain(String[]a......
  • JavaScript逆向之有道翻译加解密全过程解析
    本篇文章用于解析有道翻译中的加解密全过程url:https://fanyi.youdao.com/index.html#/加密访问网址,输入框中随便输入一个英文单词,查看触发流量包,只看Fetch/XHR类型的。这里主要关注webtranslate的这条,请求参数和响应数据都是有加密的,主要了解其的加解密逻辑。根据url定位......
  • [JavaScript] JavaScript的时间与时区
    0序言第1章节,原创,主要关注:JS的时间、时区国际化问题第2章节,主要来自于第1篇参考文献(系对第1章节的基础知识的夯实)1JavaScript时间、时区的国际化(案例)本案例等同于问题:JavaScript从浏览器根据不同时区获取时间的问题假定现在有两台Windows笔记本电脑。Step1设置......
  • VBA调试技巧
    我们写VBA代码的时候,很少会一次性就能通过,往往执行报错,然后找BUG这里我们就说说一些常用的VBA代码调试技巧一,代码书写上的误区1,喜欢嵌套写代码因为公式写的比较多,我们往往喜欢嵌套来书写代码比如,下面案例Subtest()Dimarrarr=Range("a1:b"&Cells(Rows.Count,1).......
  • 9个提高代码运行效率的小技巧你知道几个?
    我们写程序的目的就是使它在任何情况下都可以稳定工作。一个运行的很快但是结果错误的程序并没有任何用处。在程序开发和优化的过程中,我们必须考虑代码使用的方式,以及影响它的关键因素。通常,我们必须在程序的简洁性与它的运行速度之间做出权衡。今天我们就来聊一聊如何优化程序的......
  • LLM 加速技巧:Muti Query Attention
    前言 MQA是19年提出的一种新的Attention机制,其能够在保证模型效果的同时加快decoder生成token的速度。在大语言模型时代被广泛使用,很多LLM都采用了MQA,如Falcon、PaLM、StarCoder等。本文转载自DeephubImba作者:FlorianJune仅用于学术分享,若侵权请联系删除欢迎关注......
  • JavaScript 打包器esbuild的基础使用
    esbuild是一种类似于webpack的极速JavaScript打包器。esbuild项目主要目标是:开辟一个构建工具性能的新时代,创建一个易用的现代打包器。先安装esbuildnpmiesbuild-g-g代表全局范围检查esbuild的版本esbuild--version命令行构建esbuildsrc\app.jsx--bundle--outfi......
  • JavaScript 常用取整、四舍五入方法汇总
    https://juejin.cn/post/7184257661339172919  2023-01-0314,624阅读2分钟 在JavaScript中,对数值进行四舍五入操作的场景有以下几种:向上取整:ceil向下取整:floor四舍五入:round固定精度:toFixed固定长度:toPrecision取整:parseInt1.向上取整:ceilceil是天花板......