1.直接复制对象的问题
const obj = {
uname: 'pink',
age: 18
}
const o = obj
console.log(o)
o.age = 20
console.log(o)
console.log(obj)
2.浅拷贝
浅拷贝:拷贝的是地址
常见方法:
1. 拷贝对象:Object.assgin() / 展开运算符 {...obj} 拷贝对象
2.拷贝数组:Array.prototype.concat() 或者 [...arr]

代码示码:
const obj = {
uname: 'pink',
age: 18,
family: {
baby: '小pink'
}
}
浅拷贝
// const o = { ...obj }
// console.log(o)
// o.age = 20
// console.log(o)
// console.log(obj)
const o = {}
Object.assign(o, obj)
o.age = 20
o.family.baby = '老pink'
console.log(o)
console.log(obj)

直接赋值和浅拷贝有什么区别?
直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响
3.深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
1. 通过递归实现深拷贝
2. lodash/cloneDeep
3. 通过JSON.stringify()实现


案例:利用递归函数实现 setTimeout 模拟 setInterval效果
<body>
<div></div>
<script>
function getTime() {
document.querySelector('div').innerHTML = new Date().toLocaleString()
setTimeout(getTime, 1000)
}
getTime()
</script>
</body>

需求:每隔一秒,显示当前时间
function show() {
setTimeout(function () {
console.log(new Date().toLocaleString());
show()
}, 1000)
}
show()

案例 浅烤贝
<script>
const obj = {
uname: 'pink',
age: 17
}
const o = {}
function deepCopy(newOld, oldOld) {
for (let k in oldOld) {
newOld[k] = oldOld[k]
}
}
deepCopy(o, obj)
console.log(o)
o.age = 20
console.log(obj)
</script>

案例 深烤贝
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
const o = {}
function deepCopy(newObj, oldObj) {
debugger
for (let k in oldObj) {
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k])
} else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k])
}
else {
newObj[k] = oldObj[k]
}
}
}
deepCopy(o, obj)
console.log(o)
o.age = 20
o.hobby[0] = '篮球'
o.family.baby = '老pink'
console.log(obj)
console.log([1, 23] instanceof Object)
</script>

4.lodash里面cloneDeep内部实现了深拷贝

5.通过JSON.stringify()实现

实现深拷贝三种方式
1.自己利用递归函数书写深拷贝,先数组后对象
2.利用js库 lodash里面的 _.cloneDeep()
3.利用JSON字符串转换

6.throw 抛异常
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
<script>
function fn(x, y) {
if (!x || !y) {
throw new Error('没有参数传递过来')
}
return x + y
}
console.log(fn())
</script>

总结:
1. throw 抛出异常信息,程序也会终止执行
2. throw 后面跟的是错误提示信息
3. Error 对象配合 throw 使用,能够设置更详细的错误信息
7.try/catch 捕获错误信息


8.debugger

9.普通函数this指向
普通函数的调用方式决定了 this 的值,即【谁调用 this 的值指向谁】
没有调用指向window
严格模式下指向 undefined
<body>
<button>点击</button>
<script>
console.log(this)
function fn() {
console.log(this)
}
window.fn()
window.setTimeout(function () {
console.log(this)
}, 1000)
document.querySelector('button').addEventListener('click', function () {
console.log(this)
})
const obj = {
sayHi: function () {
console.log(this)
}
}
obj.sayHi()
</script>
</body>

10.this指向-箭头函数
箭头函数中的 this 与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this !
1. 箭头函数会默认帮我们绑定外层 this 的值,所以在箭头函数中 this 的值和外层的 this 是一样的
2.箭头函数中的this引用的就是最近作用域中的this
3.向外层作用域中,一层一层查找this,直到有this的定义
注意情况1:
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window
因此DOM事件回调函数如果里面需要DOM对象的this,则不推荐使用箭头函数

注意情况2:
同样由于箭头函数 this 的原因,基于原型的面向对象也不推荐采用箭头函数

11.改变this call()
<script>
const obj = {
uname: 'pink'
}
function fn(x, y) {
console.log(this)
console.log(x + y)
}
fn.call(obj, 1, 2)
</script>


12..改变this apply()

const obj = {
age: 18
}
function fn(x, y) {
console.log(this)
console.log(x + y)
}
fn.apply(obj, [1, 2])

const arr = [100, 44, 77]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(null, arr)
console.log(max, min)
console.log(Math.max(...arr))
13.bind()
语法:
fun.bind(thisArg, arg1, arg2, ...)
thisArg:在 fun 函数运行时指定的 this 值
arg1,arg2:传递的其他参数
返回由指定的 this 值和初始化参数改造的 原函数拷贝
因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind,比如改变定时器内部的this指向
1. bind 不会调用函数
2. 能改变this指向
3. 返回值是个函数, 但是这个函数里面的this是更改过的obj
const obj = {
age: 18
}
function fn() {
console.log(this)
}
const fun = fn.bind(obj)
fun()
需求,有一个按钮,点击里面就禁用,2秒钟之后开启
<button>点击按钮</button>
<script>
document.querySelector('button').addEventListener('click', function () {
this.disabled = true
window.setTimeout(function () {
this.disabled = false
}.bind(this), 2000)
})
</script>
14.call apply bind 总结
相同点:
都可以改变函数内部的this指向.
区别点:
call 和 apply 会调用函数, 并且改变函数内部this指向.
call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2..形式 apply 必须数组形式[arg]
bind 不会调用函数, 可以改变函数内部this指向.
主要应用场景:
call 调用函数并且可以传递参数
apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
15.节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
案例 :利用节流来处理-鼠标滑过盒子显示文字
要求: 鼠标在盒子上移动,里面的数字就会变化+1
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = ++i
}
function throttle(fn, t) {
let startTime = 0
return function () {
let now = Date.now()
if (now - startTime >= t) {
fn()
startTime = now
}
}
}
box.addEventListener('mousemove', throttle(mouseMove, 500))
</script>
</body>

16.防抖
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = ++i
}
function debounce(fn, t) {
let timeId
return function () {
if (timeId) clearTimeout(timeId)
timeId = setTimeout(function () {
fn()
}, t)
}
}
box.addEventListener('mousemove', debounce(mouseMove, 200))
</script>
</body>
节流和防抖的区别是?
节流: 就是指连续触发事件但是在 n 秒中只执行一次函数,比如
可以利用节流实现 1s之内 只能触发一次鼠标移动事件
防抖:如果在 n 秒内又触发了事件,则会重新计算函数执行时间
节流和防抖的使用场景是?
节流: 鼠标移动,页面尺寸发生变化,滚动条滚动等开销比较大的情况下
防抖: 搜索框输入,设定每次输入完毕n秒后发送请求,如果期间还有输入,则从新计算时间
17.Lodash 库 实现节流和防抖


<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script src="./lodash.min.js"></script>
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = ++i
}
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
</script>
</body>
案例:页面打开,可以记录上一次的视频播放位置
