深拷贝
通过JSON
javascript
// JSON.parse(JSON.stringify(obj))我们一般用来深拷贝
// 其过程说白了 就是利用JSON.stringify 将js对象序列化(JSON字符串)
// 再使用JSON.parse来反序列化(还原)js对象
function deepClone(obj) {
if(typeof obj != 'object') return obj;
return JSON.parse(JSON.stringify(obj))
}
WARNING
但是在使用时应该注意一下几点
如果obj里面有时间对象
javascript// JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象 var test = { name: 'a', date: [new Date(1536627600000), new Date(1540047600000)], }; let b; b = JSON.parse(JSON.stringify(test)) // b = {"name":"a","date":["2018-09-11T01:00:00.000Z","2018-10-20T15:00:00.000Z"]}
如果obj里有RegExp、Error对象
javascript// 序列化的结果将只得到空对象 const test = { name: 'a', date: new RegExp('\\w+'), }; // debugger const copyed = JSON.parse(JSON.stringify(test)); test.name = 'test' console.error('ddd', test, copyed) //输出:ddd,{"name":"test","date":{}},{"name":"a","date":{}}
如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失
javascriptconst test = { name: 'a', date: function hehe() { console.log('fff') }, }; // debugger const copyed = JSON.parse(JSON.stringify(test)); test.name = 'test' console.error('ddd', test, copyed) //输出: ddd,{"name":"test"},{"name":"a"}
如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
javascriptconst test = { name: NaN, date: function hehe() { console.log('fff') }, }; // debugger const copyed = JSON.parse(JSON.stringify(test)); console.log( test, copyed) //输出:{"name":null},{"name":null}
JSON.stringify()只能序列化对象的可枚举的自有属性
javascript// 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor function Person(name) { this.name = name; console.log(name) } const liai = new Person('liai'); // 输出:lihai const test = { name: 'a', date: liai, }; // debugger const copyed = JSON.parse(JSON.stringify(test)); test.name = 'test' console.error('ddd', test, copyed) // 输出:ddd,{"name":"test","date":{"name":"liai"}},{"name":"a","date":{"name":"liai"}}
通过递归
一个基本的递归循环实现
javascript// 定义一个深拷贝方法 function deepClone(obj = {}){ // 判断传入的数据是否是引用类型(一般为对象或者数组) if(typeOf obj !== 'object' || obj == null){ // 如果不是,那么返回该数据 return obj; } // 定义一个拷贝后的结果,用来当返回值 let _result; // 判断结果值为数组还是对象 if(obj instanceOf Array){ _result = [] }else{ _result = {} } // 遍历传入的对象,并赋值 for(let key in obj){ // 判断是否为自己本身的属性 if(obj.hasOwnProperty(key)){ // 如果是,赋值(递归复制深层) _result[key] = deepClone(obj[key]) } } // 返回结果数据 return _result }
vuex源码中的DeepCopy
javascriptfunction find(list, f) { return list.filter(f)[0] } function deepCopy(obj, cache) { const Constructor = obj.constructor // typeof null的返回值为object,所以可以直接省略 if (typeof obj !== 'object') { return obj } else if (Constructor === RegExp) { return new Constructor(obj) } else if (Constructor === Date) { return new Constructor(obj.getTime()) } if (cache === void 0) cache = [] // just return if obj is immutable value if (obj === null || typeof obj !== 'object') { return obj } // if obj is hit, it is in circular structure var hit = find(cache, function (c) { return c.original === obj }) if (hit) { return hit.copy } var copy = Array.isArray(obj) ? [] : {} // put the copy into cache at first // because we want to refer it in recursive deepCopy cache.push({ original: obj, copy: copy }) Object.keys(obj).forEach(function (key) { copy[key] = deepCopy(obj[key], cache) }) return copy } const obj1 = { x: 1 } const obj2 = { x: 2 } obj1.fn = function add() { return 's' } obj1.reg = /\s+/g obj1.time = new Date() const obj3 = deepCopy(obj1); console.log(obj1) console.log(obj2) console.log(obj3) /* 输出 > {"x":1,"reg":{},"time":"2022-08-02T07:18:15.517Z"} > {"x":2} > {"x":1,"reg":{},"time":"2022-08-02T07:18:15.517Z"} */
防抖 & 节流
防抖
案例
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> <style> * { margin: 0; padding: 0; } #content { width: 200px; height: 200px; line-height: 200px; background-color: #ccc; margin: 0 auto; font-size: 60px; text-align: center; color: #000; cursor: pointer; } </style> </head> <body> <div id="content"></div> <script> /* 连续onmousemove在最后一次触发changeNum函数, 多余的处理函数的都会被clearTimeout掉 */ let num=1 let oDiv= document.getElementById('content') let changeNum=function () { oDiv.innerHTML=num++ } let deBounce = function (fn,delay){ let timer=null return function (...args) { if(timer) clearTimeout(timer) timer = setTimeout(()=>{ fn(...args) },delay) } } oDiv.onmousemove=deBounce(changeNum,500) // or let _deBounce = deBounce(changeNum,500) oDiv.onmousemove=function(){ _deBounce() } </script> </body> </html>
underscore 库 debounce 源码
javascript_.debounce = function(func, wait, immediate) { var timeout, result var later = function(context, args) { timeout = null if (args) result = func.apply(context, args) } var debounced = restArguments(function(args) { if (timeout) clearTimeout(timeout) if (immediate) { var callNow = !timeout timeout = setTimeout(later, wait) if (callNow) result = func.apply(this, args) } else { timeout = _.delay(later, wait, this, args) } return result }) debounced.cancel = function() { clearTimeout(timeout) timeout = null } return debounced }
防抖使用场景
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> </head> <body> <input type="text" /> <!-- 防抖场景 --> <script> // 防抖函数 let deBounce = (fn, delay) => { let timer = null return function(...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { fn(...args) }, delay) } } let oInput = document.getElementsByTagName('input')[0] // 模拟请求 let ajax = message => { let json = { message } console.log(JSON.stringify(json)) } let doAjax = deBounce(ajax, 200) // 键盘弹起执行 oInput.addEventListener('keyup', e => { doAjax(e.target.value) }) </script> </body> </html>
节流源码
案例
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> </head> <body></body> <button>点击</button> <script> /* * 连续点击只会1000执行一次btnClick函数 */ let obutton = document.getElementsByTagName('button')[0] // 如果用箭头函数,箭头函数没有arguments,也不能通过apply改变this指向 function btnClick() { console.log('我响应了') } /* 方法1: 定时器方式实现 缺点:第一次触发事件不会立即执行fn,需要等delay间隔过后才会执行 */ let throttle = (fn, delay) => { let flag = false return function(...args) { if (flag) return flag = true setTimeout(() => { fn(...args) flag = false }, delay) } } /* 方法2:时间戳方式实现 缺点:最后一次触发回调与前一次的触发回调的时间差小于delay,则最后一次触发事件不会执行回调 */ let throttle = (fn, delay) => { let _start = Date.now() return function(...args) { let _now = Date.now(), that = this if (_now - _start > delay) { fn.apply(that, args) start = Date.now() } } } // 方法3:时间戳与定时器结合 let throttle = (fn, delay) => { let _start = Date.now() return function(...args) { let _now = Date.now(), that = this, remainTime = delay - (_now - _start) if (remainTime <= 0) { fn.apply(that, args) } else { setTimeout(() => { fn.apply(that, args) }, remainTime) } } } /* 方法4:requestAnimationFrame实现 优点:由系统决定回调函数的执行机制,60Hz的刷新频率,每次刷新都会执行一次回调函数,不 会引起丢帧和卡顿 缺点:1.有兼容性问题2.时间间隔有系统决定 */ let throttle = (fn, delay) => { let flag return function(...args) { if (!flag) { requestAnimationFrame(function() { fn.apply(that, args) flag = false }) } flag = true } } obutton.onclick = throttle(btnClick, 1000) </script> </html>
underscore 库 throttle 源码
javascript_.throttle = function(func, wait, options) { var timeout, context, args, result var previous = 0 if (!options) options = {} var later = function() { previous = options.leading === false ? 0 : _.now() timeout = null result = func.apply(context, args) if (!timeout) context = args = null } var throttled = function() { var now = _.now() if (!previous && options.leading === false) previous = now var remaining = wait - (now - previous) context = this args = arguments if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout) timeout = null } previous = now result = func.apply(context, args) if (!timeout) context = args = null } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining) } return result } throttled.cancel = function() { clearTimeout(timeout) previous = 0 timeout = context = args = null } return throttled }