目录
1.深浅拷贝
浅拷贝和深拷贝只针对引用类型
1.1浅拷贝
浅拷贝:拷贝的是地址
常见方法:
- 拷贝对象:Object.assign() / 展开运算符{...obj}拷贝对象
- 拷贝数组:Array.prototype.concat() / [...arr]
<script>
const obj = {
name: 'bjt',
age: 18
}
// 浅拷贝
// 第一种方法:
// const o = {...obj}
// console.log(o) //{name: 'bjt', age: 18}
// o.age = 20
// console.log(o) //{name: 'bjt', age: 20}
// console.log(obj) //{name: 'bjt', age: 18}
// 第二种方法:
const o = {}
Object.assign(o, obj)
o.age = 20
console.log(o) //{name: 'bjt', age: 20}
console.log(obj) //{name: 'bjt', age: 18}
</script>
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址(简单解释:如果是单层对象,没问题,如果是多层会存在问题)
1.2深拷贝
深拷贝:拷贝的是对象,不是地址
常见方法:
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringify()实现
1.通过递归实现深拷贝
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
- 简单理解:函数内部自己调用自己,这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return
<script>
let i = 1
// fn就是递归函数
function fn() {
console.log(`这是第${i}次`)
if (i >= 6) {
return
}
i++
fn() //函数内部调用函数自己
}
fn()
</script>
常见使用场景:利用递归函数实现setTimeout模拟setInterval效果
<body>
<div></div>
<script>
function getTime() {
document.querySelector('div').innerHTML = new Date().toLocaleString()
setTimeout(getTime, 1000)
}
getTime()
</script>
</body>
<script>
const obj = {
uname: 'bjt',
age: 18,
hobby: ['basketball', 'football']
}
const o = {}
// 拷贝函数
function deepCopy(newObj, oldObj) {
for(let k in oldObj) {
// 处理数组问题
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
} else {
// k(属性名) uname oldObj[k] 属性值 bjt
// newObj[k] === o.uname
newObj[k] = oldObj[k]
}
}
}
deepCopy(o, obj)
console.log(o) //{uname: 'bjt', age: 18, hobby:['篮球', 'football']}
o.age = 20
o.hobby[0] = '篮球'
console.log(obj) //{uname: 'bjt', age: 18, hobby:['basketball', 'football']}
</script>
2.js库lodash里面cloneDeep内部实现深拷贝
<body>
<!-- 必须先引用 -->
<script src="./lodash.min.js"></script>
<script>
const obj = {
name: 'bjt',
age: 18,
hobby: ['basketball', 'football'],
family: {
baby: 'small-bjt'
}
}
const o = _.cloneDeep(obj)
console.log(o)
o.family.baby = 'small_白敬亭'
console.log(obj)
</script>
</body>
3.通过JSON.stringify()实现
<body>
<script>
const obj = {
name: 'bjt',
age: 18,
hobby: ['basketball', 'football'],
family: {
baby: 'small-bjt'
}
}
// 把对象转换成 JSON 字符串
// console.log(JSON.stringify(obj))
const o = JSON.parse(JSON.stringify(obj))
console.log(o)
o.family.baby = '123'
console.log(obj)
</script>
</body>
2.异常处理
2.1throw抛异常
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
<script>
function fn(x, y) {
if (!x || !y) {
// throw '没有参数传递进来'
throw new Error('没有参数传递进来')
}
return x + y
}
fn()
</script>
总结:
- throw抛出异常信息,程序也会终止执行
- throw后面跟的是错误提示信息
- Error对象配合throw使用,能够设置更详细的错误信息
2.2try/catch捕获异常
我们可以通过try / catch捕获错误信息(浏览器提供的错误信息)try试试 catch拦住finally最后
<body>
<p>123</p>
<script>
function fn() {
try {
//可能发送错误的代码 要写到try
const p = document.querySelector('.p')
p.style.color = 'red'
} catch(err) {
// 拦截错误,提示浏览器提供的信息,但是不中断程序
console.log(err.message)
// 可以配合throw使用
throw new Error('出现错误了')
// return
} finally {
//不管程序对错,都会执行
alert('弹出对话框')
}
//这里的代码还是会执行 除非在上面添加return
console.log(11)
}
fn()
</script>
</body>
总结:
- try...catch用于捕获错误信息
- 将预估可能发生错误的代码写在try代码段中
- 如果try代码段中出现错误后,会执行catch代码段,并截获到错误信息
- finally不管是否有错误,都会执行
2.3debugger
略
3.处理this
3.1this指向-普通函数
普通函数的调用方式决定了this的值,即“谁调用 this就指向谁”
<body>
<button>点击</button>
<script>
// 一、普通函数:谁调用 this就指向谁
// 1.
console.log(this) //window
// 2.
function fn() {
console.log(this) //window
}
// 完整写法是window.fn()
fn()
// 3.
// 完整写法window.setTimeout
setTimeout(function() {
console.log(this) //window
},1000)
// 4.
document.querySelector('button').addEventListener('click', function() {
console.log(this) //指向button
})
// 5.
const obj = {
sayHi: function() {
console.log(this) //指向obj
}
}
obj.sayHi()
</script>
</body>
普通函数没有明确调用者时,this指向window,严格模式下没有调用者this的值为undefined
3.2this指向-箭头函数
箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this
- 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this直到有this的定义
注意情况1:
在开发中【使用箭头函数前需要考虑函数中的this的值】,事件回调函数使用箭头函数时,this为全局的window,因此,DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数
// DOM节点
const btn = document.querySelector('.btn')
// 箭头函数 此时 this指向了window
btn.addEventListener('click', () => {
console.log(this)
})
// 普通函数 此时this指向了 DOM对象
btn.addEventListener('click', function() {
console.log(this)
})
注意情况2:
同样由于箭头函数this的原因,基于原型的面向对象也不推荐采用箭头函数
function Person() {
}
//原型对象上添加了箭头函数
Person.prototype.walk = () => {
console.log('走路')
console.log(this) //window
}
const p1 = new Person()
p1.walk()
总结:
- 函数内不存在this,沿用上一级的
- 不适用
- 构造函数,原型函数,dom事件函数等
- 适用
- 需要使用上层this的地方
- 适用正确的话,它会在跟多地方带来方便,后面对大量使用
3.3改变this
JavaScript中还允许指定函数中的this指向,有3个方法可以动态指定普通函数中的this指向
- call()
- apply()
- bind()
1.call()-了解
使用call方法调用函数,同时指定被调用函数中this的值
语法:
fun.call(thisArg, arg1, arg2, ...)
- thisArg:在fun函数运行时指定的this值
- arg1, arg2:传递的其他参数
- 返回值就是函数的返回值,因为它就是调用函数
2.apply()-理解
使用apply方法调用函数,同时指定被调用函中this的值
语法:
fun.apply(thisArg, [argsArray])
- thisArg:在fun函数运行时指定的this的值
- argsArray:传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
- 因此apply主要跟数组有关系,比如使用Math.max()求数组最大值
<script>
const obj = {
age: 18
}
function fun(x, y) {
console.log(this)
console.log(x + y)
}
fun.apply(obj, [1, 2])
// 使用场景:求数组最大值
const arr = [100, 20, 34, 35]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(Math, arr)
console.log(max, min)
</script>
3.bind()-重点
bind()方法不会调用函数,但是能改变函数内部this指向
语法:
fun.bind(thisArg, arg1, arg2, ...)
- thisArg:在fun函数运行时指定的this值
- arg1,arg2:传递的其他参数
- 返回由指定的this值和初始化参数改造的原函数拷贝(新函数)
- 因此当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向
// 需求:有一个按钮,点击就禁用,2秒以后自动开启
const btn = document.querySelector('button')
btn.addEventListener('click', function() {
this.disabled = true
setTimeout(function() {
// 这里的this本来是指向window
// 但是由于下面设置了bind方法,改变了this的指向
this.disabled = false
}.bind(btn), 2000) //这里可以写成btn或者this
})
call apply bind总结
相同点:
都可以改变函数内部的this指向
区别点:
call 和 apply 会调用函数,并且改变函数内部this指向
call 和 apply 传递的参数不一样,call传递参数arg1,arg2...形式,apply必须数组形式[arg]
bind 不会调用函数,可以改变函数内部的this指向
主要应用场景:
call 调用函数并且可以传递参数
apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变函数this指向,比如改变定时器内部的this指向
4.性能优化
4.1防抖(debounce)
单位时间内,频繁触发事件,只执行最后一次
使用场景:
- 搜索框搜索输入。只需用户最后一次输入完成,再发送请求
- 手机号、邮箱验证输入检测
<body>
<div class="box"></div>
<script src="./lodash.min.js"></script>
<script>
// 利用防抖实现性能优化
// 需求:鼠标在盒子上移动,里面的数字就会变化 +1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如dom操作,比如数据处理,可能造成卡顿
}
// 添加滑动事件
// box.addEventListener('mousemove', mouseMove)
// 利用lodash库实现防抖
// 语法:_.debounce(fun, 时间)
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
// 手写防抖函数
// 核心是利用setTimeout定时器来实现
// 1.先声明一个定时器变量
// 2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除以前的定时器
// 3.如果没有定时器,则开启定时器,存入到定时器变量里面
// 4.定时器里面写函数调用
function debounce(fn, t) {
let timer
return function() {
if (timer) clearTimeout(timer)
timer = setTimeout(function() {
fn()
}, t)
}
}
box.addEventListener('mousemove', debounce(mouseMove, 500))
</script>
</body>
4.2节流(throttle)
单位时间内,频繁触发事件,只执行一次
使用场景:
高频事件:鼠标移动(mousemove)、页面尺寸缩放resize、滚动条滚动scroll等等
<body>
<div class="box"></div>
<script src="./lodash.min.js"></script>
<script>
// 利用节流实现性能优化
// 需求:鼠标在盒子上移动,里面的数字就会变化 +1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量消耗性能的代码,比如dom操作,比如数据处理,可能造成卡顿
}
// 添加滑动事件
// box.addEventListener('mousemove', mouseMove)
// 利用lodash库实现防抖 - 500毫秒之后采取+1
// 语法:_.throttle(fun, 时间)
box.addEventListener('mousemove', _.throttle(mouseMove, 500))
// 手写节流函数
// 核心是利用setTimeout定时器来实现
// 1.先声明一个定时器变量
// 2.每次鼠标移动(事件触发)的时候都要先判断是否有定时器,如果有先清除以前的定时器
// 3.如果没有定时器,则开启定时器,存入到定时器变量里面
// 4.定时器里面写函数调用
function throttle(fn, t) {
let timer = null
return function() {
if (!timer) {
timer = setTimeout(function(){
fn()
timer = null
}, t)
}
}
}
box.addEventListener('mousemove', throttle(mouseMove, 500))
</script>
</body>