JavaScript 高阶技巧
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
- Lodash 是第三方 JS 库,官网链接
- 引入 Lodash:https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js
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的值为windowfunction 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-
JavaScript 高阶技巧的更多相关文章
- 现代 CSS 高阶技巧,完美的波浪进度条效果!
本文是 CSS Houdini 之 CSS Painting API 系列第三篇. 现代 CSS 之高阶图片渐隐消失术 现代 CSS 高阶技巧,像 Canvas 一样自由绘图构建样式! 在上两篇中,我 ...
- 现代 CSS 高阶技巧,不规则边框解决方案
本文是 CSS Houdini 之 CSS Painting API 系列第四篇. 现代 CSS 之高阶图片渐隐消失术 现代 CSS 高阶技巧,像 Canvas 一样自由绘图构建样式! 现代 CSS ...
- JavaScript高阶函数之filter、map、reduce
JavaScript高阶函数 filter(过滤) 用法: 用于过滤,就是把数组中的每个元素,使用回调函数func进行校验,回调函数func返回一个布尔值,将返回值为 true 的元素放入新数组 参数 ...
- JavaScript高阶函数
所谓高阶函数(higher-order function) 就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数. 下面的例子接收两个函数f()和g(),并返回一个新的函数用以计算f(g ...
- JavaScript高阶函数 map reduce filter sort
本文是笔者在看廖雪峰老师JavaScript教程时的个人总结 高阶函数 一个函数就接收另一个函数作为参数,这种函数就称之为高阶函数 1.高阶函数之map: ...
- JavaScript高阶函数的应用
定义 高阶函数是指至少满足下列条件之一的函数: 函数可以作为参数被传递: 函数可以作为返回值输出. JavaScript语言中的函数显然满足高阶函数的条件,在实际开发中,无论是将函数当作参数传递,还是 ...
- JavaScript 高阶函数 + generator生成器
map/reduce map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果: function pow(x ...
- JavaScript 高阶函数
高阶函数的英文叫Higher-order function ,什么是高阶函数呢>? JavaScript的函数其实都指向某个变量.既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接 ...
- 浅析javascript高阶函数
什么是高阶函数:在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数: 1. 接受一个或多个函数作为输入: 2. 输出一个函数.在数学中它们也叫做算子(运算符)或泛函.微积分中的导数就是常见的例 ...
- JavaScript高阶函数map/reduce、filter和sort
map() 举例说明,比如我们有一个函数f(x)=x²,要把这个函数作用在一个数组[1,2,3,4,5,6,7,8,9]上. 由于map()方法定义在JavaScript的Array中,我们调用Arr ...
随机推荐
- 如何设计一个高性能的图 Schema
本文整理自青藤云安全工程师--文洲在青藤云技术团队内部分享,分享视频参考:https://www.bilibili.com/video/BV1r64y1R72i 图数据库的性能和 schema 的设计 ...
- AIGC下一步:如何用AI再度重构或优化媒体处理?
让媒资中"沉默的大多数"再次焕发光彩. 邹娟|演讲者 编者按 AIGC时代下,媒体内容生产领域随着AI的出现也涌现出更多的变化与挑战.面对AI的巨大冲击,如何优化或重构媒体内容生产 ...
- 智能升级:AntSK教你如何让聊天机器人实现智能联网操作
随着人工智能技术的飞速发展,智能体已经逐步融入到我们的日常生活中.不过,要想让智能体不仅能聊天,还能接入网络实时获取信息,为我们提供更多便利,所需技术的复杂性不得不让人瞩目.今天,我将和各位分享如何在 ...
- centos 7与8修改主机名的各种方法
hostname 查看当前系统主机名,知道当前主机名为localhost 当然在centos7特地添加了hostnamectl命令查看,修改主机名 使用hostnamectl set-hostname ...
- 虚拟机和开发板之间通过NFS互联
简介 NFS是Network File System的首字母缩写.它是一种分布式协议,使客户端可以访问远程服务器上的共享文件.它允许网络中的计算机之间通过TCP/IP网络共享资源. 配置过程 安装NF ...
- Vue3学习(二十三)- 保存文档内容正常显示
写在前面 情人节已经接近尾声了,虽然跟我没什么关系,但是我还是很渴望,能遇到一个良人相伴一生. 现在时间: 内心异常平静,相对吵闹我更喜欢安静的晚上,没人打扰,enjoy自己独处的时间! 保存内容显示 ...
- npm install 的执行顺序,和 安装包的源死磕
npm install 源的地址加载执行顺序 从近到远 lock文件 这里直接就记录了 包的下载地址 .npmrc 里面的内容 registry=http://registry.npm.xxxx.co ...
- 基于C语言的串口AT指令发送实例解析
一 知识点 1 AI指令后面一定要加 \n\r 2 注意AT指令里面待双引号的这种,要使用斜杠隔开. 二 源码: void Set_Pdu_Mode(void) { u8 a = 1; if(atKe ...
- tomcat正常启动,但网页拒绝连接的解决方法
当发生拒绝连接的时候 1.首先要排除端口的占用 上一篇文章已经详细介绍了,这里不再赘述tomcat端口配置 2.设置防火墙放行tomcat 3.配置环境变量 此电脑→属性→高级系统设置→环境变量 点击 ...
- 鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
一.RelativeContainer 1.概述 ArkUI组件中的RelativeContainer是一个相对定位的容器,可以用来将子组件按照相对位置布局. 在RelativeContainer中, ...