在JavaScript中我们对引用类型进行赋值时,我们会发现无论当我们修改赋值前的值还是赋值后的值,两个值都会发生改变。
例子如下:
let test = { course: 'web前端' } let newTest = test test.course = '测试' console.log('test', test) console.log('newTest', newTest)
我们发现控制台输出的test
和newTest
的值是一样的,结果如下:
test {course: '测试'} newTest {course: '测试'}
这是因为我们虽然对test.course
进行了重新赋值,但是因为对象时存储在堆内存中的,所以test
和newTest
会指向同一个内存地址,导致两个值的来源是同一个地方。
为了解决上面拷贝的问题,我们可以手动实现一个深拷贝。
var obj = { address: { city: 'hefei' }, arr: ['a', 'b', 'c'] } const deepObj = deepClone(obj) deepObj.address.city = 'fuyang' console.log(obj.address.city) console.log('obj', obj) console.log(deepObj) // 深拷贝 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) { // 保证 key 不是原型的属性 if (obj.hasOwnProperty(key)) { // 递归调用!!! result[key] = deepClone(obj[key]) } } // 返回结果 return result }
控制台结果如下:
var obj = { age: 55, address: { city: 'hefei' } } const deepObj = deepClone(obj) function deepClone(obj) { let newObj = JSON.stringify(obj) let newObj1 = JSON.parse(newObj) return newObj1 } obj.age = 66 console.log('obj',obj); console.log('deepObj',deepObj);
控制台输出结果如下:
如果想要快速实现深拷贝,我们可以JSON的方式,来回转一下就可以实现。
例如我们在输入框中输入内容,输入时会向后端请求数据接口,如果我们不进行防抖操作,那么我们每次输入的时候都会进行数据请求,这样不仅浪费性能而且会对浏览器造成压力。
实现防抖结果如下:
<!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> <input type="text"> <script> let inpCont = document.querySelector('input'); inpCont.oninput = debounce(function() { console.log(this.value); }, 1000) function debounce(fn, delay) { // timer是用来存放定时器的返回值 let timer = null return function() { if (timer !== null) { // 每当用户输入的时候我们就会把前一个 setTimeout 进行清除 clearTimeout(timer) } // 创建一个新的 setTimeout ,可以保证输入字符后的延迟时间内,如果还有字符输入的话, 就不会执行 fn 函数 timer = setTimeout(() => { fn.call(this); }, delay) } } </script> </body> </html>
原理就是当我们输入这个事件时,就会触发延时,如果我们在延迟时间内再次触发事件时,就会销毁前面的延迟并重新计算延时。如果在延迟时间内没有触发事件,那么就执行处理函数。
当我们滚动滚动条会不断触发滚动函数中的值或者文章在编辑过程中每一段时间进行自动保存都会用到节流,防止事件高频率触发。
实现方法如下:
<!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> body { height: 1800px; } </style> </head> <body> <script> window.onscroll = throttle(function() { console.log('滚动值11'); }, 1000) function throttle(fn, delay) { // 通过闭包保存一个标记,防止全局都可以进行访问该变量 let status = true return function() { if (status) { // 将外部传入的函数的执行放在 setTimeout 中 setTimeout(() => { fn.call(this) // 最后在 setTimeout 执行完成后,我们把标记设置为true时,就可以执行下一次循环了。 status = true }, delay) } status = false } } </script> </body> </html>
无论是防抖还是节流,我们都需要改变this
的指向,不然的话,不改变的话,我们会发现this
指向的是window
,而不是我们需要指向的内容。
call是一个函数的方法,可以用来调用函数。
function people() { // this指向的是window console.log(this) console.log('people的内容') } people()
此时代码中的this
指向的是window
,因为people()
相当于是window.people()
。所以this
指向的是window
。
如果我们定义一个param
,让people
去调用。如下
function people() { // this指向的是window console.log(this.age) console.log('people的内容') } let param = { age: 27 } people.call(param)
如果我们不是用call
改变this
,会发现输出的是undefined
而不是param
里面的age
内容。
我们除了改变this
,还可以传参,如果需要的参数不传的话,会显示undefined
。
<!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> <script> let param = { age: 27, action(run) { console.log('我每天都' + run) } } let test = { age: 25 } param.action.call(test) </script> </body> </html>
此时上面的代码输出的是undefined
,因为我们没有传参。所以我们需要传参。如下:
<!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> <script> let param = { age: 27, action(run) { console.log('我每天都' + run) } } let test = { age: 25 } param.action.call(test, '健身') </script> </body> </html>
apply
和call
不同之处就在于传参的类型不同,call
的传参是直接写在第一个参数后面就行,而apply
的参数是放在数组里面的。bind
和call
的写法上是一样的,但是bind
返回的是一个函数并不会执行函数,而call
会执行函数。
param.action.apply(test, ['健身']); param.action.call(test, '健身'); // 等价于 let litt = param.action.call(test, '健身'); litt()
我们可以使用call去实现一个继承。
<!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> <script> function Parent() { this.age = function() { console.log('我今年27岁了') } } function People() {} let people = new People() people.age(); </script> </body> </html>
此时上面的代码输出的是错误,因为people
没有age
方法。而Parent
中有age
方法,所以我们就需要让people
会继承Parent
中的age
方法。使用call
来解决,如下:
<!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> <script> function Parent() { this.age = function() { console.log('我今年27岁了') } } function People() { Parent.call(this) } let people = new People() people.age(); </script> </body> </html>
我们在People
中使用Parent.call(this)
可以让People
继承Parent
的age
方法。从而输出age
里面的内容了。