- 浅copy copy后的结果如果修改了 会影响之前的数据 copy前后 两个数据之前有关系
- 深copy copy后的结果如果修改了 不会影响之前的数据 copy前后 两个数据之前没有任何关系。说到深copy,通常不是指基本数据类型 是指引用数据类型
浅copy
copy后的结果如果修改了 会影响之前的数据 copy前后 两个数据之前有关系
<script>
let arr1 = [1,2,3];
let arr2 = arr1; // 浅copy
arr2[0] = 666;
console.log(arr1) //[666, 2, 3]
</script>
深copy
- 浅copy copy后的结果如果修改了 会影响之前的数据 copy前后 两个数据之前有关系
- 深copy copy后的结果如果修改了 不会影响之前的数据 copy前后 两个数据之前没有任何关系。说到深copy,通常不是指基本数据类型 是指引用数据类型
JSON.stringfy + JSON.parse
实现深copy非常简单的方案
- JSON.stringify()将对象转化为json字符串
- JSON.parse()将json字符串转化为对象
<script>
let obj = {
name: "wangcai",
arr: [1, 2, 3],
say: function () {
console.log("say...")
}
}
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj) //wagncai
obj.name = "xiaoqiang";
console.log(newObj) //wangcai
console.log(obj) //xiaoqiang
</script>
有缺点:如果数据中有方法,copy不过来
完整深copy
实现深copy一般只copy自己拥有的属性,不copy原型对象上的属性。
遍历对象使用for in
<script>
// 封装一个函数,实现深copy, 只copy自己有的属性,不copy原型上的属性
function deepClone(target){
let cloneTarget = {};
// 遍历数组使用for循环 或 forEach
// 遍历对象使用for in
for(let key in target){
// 并没有实现深copy, 修改一个,另一个也是会受影响
// cloneTarget[key] = target[key]
// 只copy自己的属性,不copy原型上的属性
if(target.hasOwnProperty(key)){
cloneTarget[key] = target[key]
}
}
return cloneTarget;
}
let obj = {
name:"wangcai",
arr:[1,2,3],
say:function () {
console.log("say...")
}
}
//let obj = {
// name:"wangcai",
// age:1
//} 如果是基本数据类型可以实现深copy
let newObj = deepClone(obj)
console.log(newObj)
obj.arr[1] = 666;
console.log(newObj)
</script>
上面的代码实现的深copy不完整。只能实现基本数据类型,不能实现引用数据类型。
<script>
// 如果说一个容器中的数据都是基本数据类型,下面的deepClone可以实现深copy
function deepClone(target){
if(typeof target !== "object") return target;
//返回的可能是对象,也可能是数组
let cloneTarget = new target.constructor
for(let key in target){
if(target.hasOwnProperty(key)){
// target[key] 可能还是引用数据类型
cloneTarget[key] = deepClone(target[key])
}
}
return cloneTarget;
}
let obj = {
name:"wangcai",
age:1,
score:[1,2,3]
}
// let obj = [1,2,3];
let newObj = deepClone(obj)
console.log(newObj)
obj.score[0] = 666;
console.log(newObj)
</script>
完整深copy
<script>
function deepClone(target){
// 对特殊情况的处理
if(target == null) return null;
if(target instanceof Date) return new Date(target);
if(target instanceof RegExp) return new RegExp(target);
// ....
// 递归的出口
if(typeof target !== "object") return target;
let cloneTarget = new target.constructor
for(let key in target){
if(target.hasOwnProperty(key)){
// target[key] 可能还是引用数据类型
cloneTarget[key] = deepClone(target[key])
}
}
return cloneTarget;
}
let reg = new RegExp('abc');
let newObj = deepClone(reg)
console.log(newObj)
</script>
数据劫持
数据劫持:Object.defineProperty()
指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
比较典型的是Object.defineProperty()
和 ES2016 中新增的Proxy
对象。数据劫持最著名的应用当属双向绑定
<script>
function update() {
console.log("更新视图了...")
} //劫持
function hijack(obj) {
if(typeof obj !== "object" || obj == null){
return obj;
}
for (let key in obj) {
// 把name和age做成响应式数据 获取/设置 我们都要知道
defineReactive(obj,key,obj[key]);
}
}
function defineReactive(obj,key,value){
// 精细化设置某个属性
Object.defineProperty(obj,key,{
get(){ //获取器
// console.log("获取了属性")
return value;
},
set(newValue){ //设置器
value = newValue
update();
}
})
}
let o = {name:"wc",age:10}
hijack(o)
console.log(o.name)
o.name = "xiaoqiang";
console.log(o.name)
/*console.log(o.name); // 获取name属性
o.name = "xx"; // 设置name属性*/
</script>
value有可能还是一个对象 递归侦测
function update() {
console.log("通知视图刷新...")
}
function hijack(obj){
if(typeof obj !== "object" || obj === null){
return obj;
}
for (let key in obj) {
defineReactive(obj,key,obj[key]);
}
}
function defineReactive(obj,key,value){
// value有可能还是一个对象 递归侦测
hijack(value); //o.xxx = {b:2}
Object.defineProperty(obj,key,{
get(){ // 获取器
return value;
},
set(newValue){ // 设置器
// 保证新设置的值和老值不一样,再去更新视图
if(value !== newValue){
hijack(newValue) //对应 o.xxx.b = 3;
value = newValue;
update();
}
})
}
let o = {name:"wangcai",age:10, xxx:{a:1}}
o.name = "wangcai"; // 修改前后的值一样,没有必须通知数据刷新
o.xxx = {b:2} // 这个地方会通知视图刷新 console.log("通知视图刷新...")
o.xxx.b = 3; // console.log("通知视图刷新...")
防抖节流
频繁去触发一个事件,有什么问题?
对浏览器来说
window.onscroll
window.onresize
window.onmousemove
频繁地触发一些事件,造成浏览器的性能问题。
解决:防抖 节流 目的:限制事件的频繁触发。
防抖 (规定事件,触发最后一次)
在函数需要频繁触发时,在规定的时间内,只让最后一次生效,前面的不生效。
适合多次事件一次响应的情况。
场景:
- 实时搜索联想
- 连续输入文字后,发送ajax请求
- 判断scroll是否滚动到底部
<button id="debounce">函数的防抖</button>
<script>
// 防抖 靠定时器
function handleClick(e) {
console.log("点击事件...")
}
function debounce(callback,delay){
let that = this;
let args = arguments;
return function () {
// 清除待执行的定时器任务
if(callback.timeoutId){
clearTimeout(callback.timeoutId)
}
callback.timeoutId = setTimeout(function () {
callback.apply(that,args)
delete callback.timeoutId;
},delay)
}
}
// 防抖 时间段内,最后一次执行才算数
document.getElementById("debounce").onclick = debounce(handleClick,2000);
// document.getElementById("debounce").onclick = handleClick;
</script>
节流 (一秒只能触发一次)
在函数需要频繁触发时,函数触发一次后,在规定时间,不会触发第二次,只有大于设定的周期后才会执行第二次。
适合多次事件按时间做平均分配触发。
场景:
- 调整窗口大小 resize
- 页面滚动 scroll
- 拖拽
- 抢购按钮 mousedown
<button id="throttle">函数的节流</button>
<script>
//函数handleClick(e)可以有事件对象
function handleClick() {
// console.log(e)
// console.log(Date.now())
console.log("点击事件...")
}
// 实现节点函数 // 前提是:频繁触发 节流目的:每隔一断时间,执行一次
function throttle(callback,delay){
// 第1次点击 立即调用
let start = 0;
return function () {
let current = Date.now();
// console.log(current)
if(current - start > delay){
//当有事件对象时,callback换为callback.apply(this,arguments)
callback()
start = current;
}
}
}
// document.getElementById("throttle").onclick = handleClick;
document.getElementById("throttle").onclick = throttle(handleClick,1000);
</script>
Date.new() 时间戳,时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数