一、深浅拷贝
1.基本数据类型和引用数据类型的区别:
1. 基本数据类型的变量存储的是值
引用数据类型的变量存储的是地址值
2. 基本数据类型的变量存储的值在栈内存
引用数据类型的变量存储的值在堆内存
3. 基本数据类型的变量存储的是值和值之间相互不影响
引用数据类型的变量存储的值和值,在同用 一个地址的情况,其中一个的值发生变化,另一个也变
2. 深浅拷贝之发生在引用数据类型之间。
深拷贝: 拷贝的是值,源对象和拷贝的对象之间互不影响
浅拷贝: 拷贝的是地址,源对象和拷贝的对象存在公用一个地址的情况,所以相互之间会收到影响
3.浅拷贝
方法一:Object.assign()
方法
用于将一个或多个源对象的可枚举属性复制到目标对象。它只会进行浅拷贝。
const obj1 = {
a: 1,
b: {
c: 2
}
};
const obj2 = Object.assign({}, obj1);
obj2.a = 2; // obj1.a 仍然是 1
obj2.b.c = 3; // obj1.b.c 也变成 3
console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 2, b: { c: 3 } }
方法二:展开运算符...
使用展开运算符 ...
可以方便地创建一个对象的浅拷贝。
const obj1 = {
a: 1,
b: {
c: 2
}
};
const obj2 = {...obj1 };
obj2.a = 2; // obj1.a 仍然是 1
obj2.b.c = 3; // obj1.b.c 也变成 3
console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 2, b: { c: 3 } }
方法:Object.create()
使用 Object.create()
可以创建一个新对象,新对象的原型指向指定的对象,但这并不是传统意义上的拷贝,而是基于原型链的一个新对象。
const obj1 = {
a: 1,
b: {
c: 2
}
};
const obj2 = Object.create(obj1);
obj2.a = 2; // obj1.a 仍然是 1
obj2.b.c = 3; // obj1.b.c 也变成 3
console.log(obj1); // { a: 1, b: { c: 3 } }
console.log(obj2); // { a: 2 }
//===================for=================================
var arr = [12, [34, 56, 78], 543, 67, 78]
var brr = []
for (var i = 0; i < arr.length; i++) {
brr.push(arr[i])
}
// console.log(brr);
// 第一层是深拷贝
brr[0] = 20;
console.log(arr); // [12, [34, 56, 78], 67, 78]
console.log(brr); // [20, [34, 56, 78], 67, 78]
// 第二是浅拷贝
brr[1][0] = 43
console.log(arr);//[12, [43, 56, 78],, 67, 78]
console.log(brr);// [12, [43, 56, 78],, 67, 78]
// ===================arr.slice(0)===========================
var arr = [12, [34, 56, 78], 543, 67, 78]
var brr = arr.slice(0)
// 第一层是深拷贝
brr[0] = 20;
console.log(arr); // [12, [34, 56, 78],, 67, 78]
console.log(brr); // [20, [34, 56, 78],, 67, 78]
// 第二是浅拷贝
brr[1][0] = 43
console.log(arr);//[12, [43, 56, 78],, 67, 78]
console.log(brr);// [12, [43, 56, 78],, 67, 78]
// ===================arr.concat()===========================
var arr = [12, [34, 56, 78], 543, 67, 78]
var brr = arr.concat()
// 第一层是深拷贝
brr[0] = 20;
console.log(arr); // [12, [34, 56, 78],, 67, 78]
console.log(brr); // [20, [34, 56, 78],, 67, 78]
// 第二层是浅拷贝
brr[1][0] = 43
console.log(arr);//[12, [43, 56, 78],, 67, 78]
console.log(brr);// [12, [43, 56, 78],, 67, 78]
//--------------------------Array.from()----------------------
//Array.from() 方法可以用于创建一个新数组实例,它会浅拷贝类数组对象或可迭代对象。
const arr1 = [1, 2, 3, { a: 4 }];
const arr2 = Array.from(arr1);
arr2[0] = 0; // arr1[0] 仍然是 1
arr2[3].a = 5; // arr1[3].a 也变成 5
console.log(arr1); // [1, 2, 3, { a: 5 }]
console.log(arr2); // [0, 2, 3, { a: 5 }]
直接赋值和浅拷贝的区别:
直接复制的方法,只要是对象都会相互影响,因为是直接拷贝对象栈里面的地址
浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响
4.深拷贝
方法一:递归
函数递归:如果一个函数在内部自己调用自己,那么这个函数就是递归函数
由于递归函数很容易发生栈溢出错误,所以需要加推出条件return
let i = 0;
function fn() {
console.log(`调用${i} 次 `);
if (i >= 6) {
return
}
i++
fn()
}
fn()
利用setTimeout实现setInterval效果
function getTime() {
document.querySelector('#box').innerHTML = new Date().toLocaleString()
setTimeout(getTime, 1000)
getTime()
}
getTime()
const obj1 = {
a: 1,
b: {
c: 2
}
};
const obj2 = {};
function deepCopy(newObj, oldObj) {
for (let key in oldObj) {
if ((oldObj[key]).instanceof Array) {
newObj[key] = []
deepCopy(newObj[key], oldObj[key])
} else if ((oldObj[key]).instanceof Object) {
newObj[key] = {}
deepCopy(newObj[key], oldObj[key])
} else {
newObj[key] = oldObj[key]
}
}
}
deepCopy(obj2, obj1)
obj1.b.c = 6
console.log('obj2, obj1', obj2, obj1);
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj; // 基础情况,直接返回值
}
// 创建一个新对象
const newObj = Array.isArray(obj) ? [] : {};
// 递归拷贝每个属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
方法二:lodash
Lodash 简介 | Lodash中文文档 | Lodash中文网
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
var p = {
name: "zs",
age: 12,
wife: {
name: "zs的媳妇"
}
}
var newP = _.cloneDeep(p);
newP.wife.name = 'ls的媳妇'
console.log(p);
console.log(newP);
var arr = [12, [34], 5, 67, 78, 8]
var newArr = _.cloneDeep(arr);
arr[1][0] = 43;
console.log(newArr);
console.log(arr);
</script>
方法三:JSON.parse(JSON.stringify(obj))
var obj = { name: "zs", age: 18, wife: { name: "zs的wife", city: { name: '上海' } } }
var obj1 = JSON.parse(JSON.stringify(obj))
二、节流(技能冷却)
节流:单位时间内,频繁触发事件,只执行一次
使用场景:鼠标移动mousemove,页面尺寸缩放resize,滚动条滚动scroll等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: #ededed;
}
</style>
</head>
<body>
<div id="box"></div>
</body>
</html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
const box = document.querySelector('#box')
let i = 1;
function mouseMove() {
box.innerHTML = i++
}
box.addEventListener('mousemove', _.throttle(mouseMove, 3000))
</script>
核心思路:
a.声明一个定时器变量
b.当鼠标每次滑动都先判断是否有定时器,如果有定时器则不开开启新定时器
c.如果没有定时器则开启定时器,记得存到变量里面
定时器里面调用执行的函数
定时器里面要把定时器清空
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: #ededed;
}
</style>
</head>
<body>
<div id="box"></div>
</body>
</html>
<script>
const box = document.querySelector('#box')
let i = 1;
function mouseMove() {
box.innerHTML = i++
}
box.addEventListener('mousemove', throttle(mouseMove, 3000))
function throttle(fn, t) {
let timer = null;
return function() {
if (!timer) {
timer = setTimeout(() => {
fn()
clearTimeout(timer)
timer = null
}, t)
}
}
}
// let timer = null;
// timer = setTimeout(() => {
// clearTimeout(timer)
// console.log(timer)
// }, 1000)
//setTimeout中无法清除定时器,因为定时器还在运行
</script>
三、防抖(回城)
防抖:单位时间内,频繁触发事件,只执行最后一次,只要被打断就需要重来
使用场景:搜索框搜索输入,手机号、邮箱验证输入检测
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: #ededed;
}
</style>
</head>
<body>
<div id="box"></div>
</body>
</html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
const box = document.querySelector('#box')
let i = 1;
function mouseMove() {
box.innerHTML = i++
}
box.addEventListener('mousemove', _.debounce(mouseMove, 3000))
</script>
核心思路:
a.声明一个定时器变量
b.当鼠标每次滑动都先判断是否有定时器,如果有定时器则清除前面的定时器
c.如果没有定时器则开启定时器,记得存到变量里面
d.定时器里面调用执行的函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: #ededed;
}
</style>
</head>
<body>
<div id="box"></div>
</body>
</html>
<script>
const box = document.querySelector('#box')
let i = 1;
function mouseMove() {
box.innerHTML = i++
}
box.addEventListener('mousemove', debounce(mouseMove, 3000))
function debounce(fn, t) {
let timer = null;
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn()
}, t)
}
}
// debounce(mouseMove, 3000)调用函数拿到return结果
// debounce(mouseMove, 3000)=function(){}
</script>
性能优化 | 说明 | 使用场景 |
节流 | 单位时间内,频繁触发事件,只执行一次 | 鼠标移动mousemove,页面尺寸缩放resize,滚动条滚动scroll等 |
防抖 | 单位时间内,频繁触发事件,只执行最后一次 | 搜索框搜索输入,手机号、邮箱验证输入检测 |
四、节流案例
页面打开,可以记录上一次视频播放的位置
思路:
在ontimeupdate事件触发的时候,每隔1秒,就记录当前时间到本地存储
下次打开页面,onloadeddata事件触发,就可以从本地存储取出时间,让视频从取出的时间播放,如果没有就默认0s
获取当前时间video.currentTime
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: #ededed;
}
</style>
</head>
<body>
<div id="box">
<video src="https://v.itheima.net/LapADhV6.mp4" controls autoplay loop muted preload="auto" width="640" height="360"></video>
</div>
</body>
</html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
//ontimeupdate事件在视频/音频当前的播放位置发生改变时触发
//onloadeddata事件在当前帧的数据加载完成且没有足够的数据播放视频/音频的下一帧时触发
const video = document.querySelector('video')
video.ontimeupdate = _.throttle(() => {
console.log(video.currentTime);
localStorage.setItem('currentTime', video.currentTime)
}, 1000)
video.onloadeddata = () => {
video.currentTime = localStorage.getItem('currentTime') || 0
}
</script>
五、this指向
普通函数:谁调用this的值指向谁
1.全局函数 === window
2.回调函数 ===window
回调函数: 当一个函数作为另一个函数的参数, 那么这个函数就叫回调函数
console.log(this); //window
function fn() {
console.log(this); //window
}
fn()
setTimeout(function() {
console.log(this); //window
}, 1000)
3.在对象方法中 ===对象本身
4.事件中的 ===绑定事件的事件源
document.querySelector('button').addEventListener('click', function() {
console.log(this); //<button>按钮</button> ,指向button
})
const obj = {
sayHi: function() {
console.log(this);//{sayHi: ƒ},指向obj
}
}
obj.sayHi()
5.构造函数中 === new出来的对象
6.原型对象上对应的方法体内的this ===new出来的对象
let a;
let b;
function Star(name, age, sex) {
a = this;
this.name = name;
this.age = age;
}
const ldh = new Star('ldh', 55)
console.log(a === ldh);//true
//构造函数里面的 this就是实例对象
Star.prototype.sing = function() {
b = this;
console.log('唱歌');
};
ldh.sing()
console.log(b === ldh);//true
7.箭头函数===没有this 指向上级作用域
箭头函数:箭头函数中不存在this,箭头函数会默认绑定外层this的值,this引用的就是最近作用域中的this,向外层作用域中,一层一层查找this,直到有this的定义
const obj = {
sayHi: () => {
console.log(this); //window
}
}
obj.sayHi()
document.querySelector('button').addEventListener('click', () => {
console.log(this); //window
})
六、修改this的指向
有3个方法可以动态指定普通函数中的this指向
call(),apply(),bind()
1.fun.call(thisArg,arg1,arg2,...)
thisArg是在fun函数运行时指定的this值
返回值就是函数的返回值,因为它就是调用函数
2.fun.apply(thisArg,[arg1,arg2,...])
const obj = {
name: 'zs'
}
function fn(x, y) {
console.log(this); //window
console.log(x + y);
}
fn()
//调用函数,改变this指向
fn.apply(obj, [1, 2])
const arr = [11, 22, 33, 44, 55]
const max = Math.max.apply(Math, arr)
console.log('max', max);
3.fun.bind(thisArg,arg1,arg2,...)
bind()方法不会调用函数,
能改变函数内部的this指向
返回值是个函数,但是这个里面的this是更改过的this
const obj = {
name: 'zs'
}
function fn(x, y) {
console.log(this); //window
console.log(x + y);
}
fn()
//不会调用函数,能改变this指向
//返回值是个函数,但是这个函数里面的this是更改过的this
const fun = fn.bind(obj)
fun()
案例:点击按钮禁用,2秒后开启
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>发送短信</button>
</body>
</html>
<script>
const btn = document.querySelector('button')
btn.addEventListener('click', function() {
this.disabled = true
setTimeout(function() {
//在这个普通函数里,我们要this的指向由原来的window改为btn,可以使用箭头函数,也可以使用bind
this.disabled = false
}.bind(btn), 2000)
})
</script>
4.总结
call,apply,bind
相同点:都可以改变this的指向
区别点:
call和apply会调用函数,并且改变函数内部this的指向。
call和apply传递的参数不同,call传递多个参数,apply以数组的形式传递多个参数
bind不会调用函数,而是返回了一个this指向改变后的函数,需要调用这个函数。