大家好,相信很多朋友在面试过程中遇到过这些问题,也查阅了很多相关资料,发现很多的教学都是copy复制,完美的展现了程序员的“优良传统”~开个小玩笑嘿嘿,今天我们就去看看这些问题到底是怎么个事!在工作中什么情况下会遇到?如何解决他们产生的问题~那么废话不多说!我们走起!
1、作用域的概念
什么是作用域?什么情况下我们会遇到?很简单,同学们看一下代码就懂了
// 定义一个名为outer函数
function outer() {
// 函数内部定义一个名为X,值为10的变量
var x = 10;
// 在函数内部定义内部函数
function inner() {
var y = 5; // 在内部函数中定义名为Y,值为5的函数
console.log(x + y); // 输出 15
}
// 在outer函数中调用inner函数
inner();
}
// 执行outer函数
outer();
// 错误提示 :在外部无法调用函数内部的变量和内部函数,因为超出了作用域,变量X和Y只在函数内部中使用。
在这个例子中,outer
函数中定义了一个变量x
。然后,outer
函数内部又定义了一个函数inner
,并在inner
函数内部定义了一个变量y
。在inner
函数中,我们可以访问并使用来自outer
函数作用域的变量x
。当我们调用outer
函数时,它会执行inner
函数并输出x
和y
的和,即15
。
这个案例展示了JavaScript的作用域链特性,即内部函数可以访问外部函数中定义的变量。在这个例子中,inner
函数可以访问和使用outer
函数中定义的变量x
。这种嵌套的作用域关系允许我们在函数内部创建私有变量,同时保护这些变量不被外部访问到。
2、原型链的概念
原型链是什么?可以解决什么问题?我们从这两个问题出发!
原型链是 JavaScript 中的一个重要概念,它决定了对象之间如何继承属性和方法。
当我们声明一个函数时,会自动生成prototype等价于obj._proto_,它指向了另一个对象,我们称之为原型对象。当我们访问一个对象的属性或方法时,JavaScript 引擎会首先在该对象本身上查找,如果找不到,则继续在原型对象上查找,如果还找不到,则继续在原型对象的原型对象上查找,依次类推,直到找到属性或方法或达到原型链的末端。
当我们把原型串联在一起的时候,它就称之为“原型链”,原型链的顶端为null~
解决了对象共享属性和方法
3、闭包的概念
什么是闭包?在什么情况下我们会用到?闭包给我们带来什么好处?闭包的缺点在哪?如何解决?我们先来看 两个经典的案例!
一、案例一:
function outer() {
var x = 10;
function inner() {
console.log(x);
}
return inner;
}
var innerFunc = outer();
innerFunc(); // 输出 10
在上面的代码中,inner
函数被定义在outer
函数内部,并且在outer
函数的外部被调用。
当调用outer
函数时,它返回了内部函数inner
的引用。由于inner
函数的作用域链仍然保留着对outer
函数作用域的引用,所以在调用inner
函数时,它仍然可以访问和使用outer
函数的局部变量x
。
这就是闭包的经典案例,通过闭包,我们可以在函数外部访问到函数内部的变量。
二、案例二:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 期望的输出:0,1,2,3,4
}, 1000);
}
这段代码中,就会遇到一个问题,我们期望的输出是0,1,2,3,4,每隔1秒输出一个数字。然而,实际上输出的结果是5,5,5,5,5。这是因为setTimeout是异步的,js会先执行同步代码,后执行异步代码,当计时器到期时,for循环已经执行完毕,并且i的值已经变为5。因此,在闭包函数中访问的是同一个i,它的值始终是5, 重点来了我们如何解决呢? 有聪明的小伙伴说:我可以用es6中的let啊,对!我也是这么想的,既然人家都已经解决了,还非得问这些问题,很无奈啊,所以我们还得知其所以然,我们就当做没有se6,我们就是远古的开发战士!如何解决这个问题?
三、闭包所带来的问题
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i); // 输出的结果为 0, 1, 2, 3, 4
}, 1000);
})(i);
}
在这个修正后的代码中,通过使用立即执行函数(IIFE)创建了一个新的作用域,并将当前的i的值作为参数传入该函数。这样,在每次循环中,都会创建一个新的闭包,而不是共享同一个变量i。
大家请仔细观看上面的代码还会有问题吗?3秒钟回答~
是的,依然还是存在使用闭包后存在的问题
1、我们所声明的变量会存在内存中,无法被回收掉,所以会导致大量的内存占用。我们应该优化一下~
解决方案:需要把闭包的函数设置为Null
2、内存泄露的问题只会出现在远古版本的IE浏览器(这个可回答,可不回答,如果面试官问到了,一定要把内存泄露在特定的浏览器上才会出现的问题指出!)
闭包的总结:
闭包是指一个函数内部定义的函数,该函数可以访问外部函数的变量。由于闭包会保留外部函数的变量和作用域链,因此可能会导致内存占用问题。
以下是一些解决闭包带来的内存占用问题的方法:
1. 避免过度使用闭包:只有在必要的情况下使用闭包,避免不必要的内存占用。
2. 及时释放闭包:当不再使用闭包时,手动将其置为null,以释放内存。
3. 使用箭头函数:箭头函数是匿名函数的简写形式,它没有自己的this值和arguments对象,并且不能作为构造函数使用。由于箭头函数没有自己的作用域,因此不会产生闭包,也不会导致内存占用问题。
4. 使用事件解绑:当使用闭包来处理事件时,确保在不需要时解绑事件,避免事件处理函数一直保留在内存中。
5. 使用模块化开发:将代码拆分成多个模块,通过导入和导出来引用变量,避免使用闭包。
6. 使用WeakMap对象:使用WeakMap对象来存储闭包中的变量,WeakMap对象的键是弱引用,当其他对象不再引用某个对象时,该对象会被垃圾回收。
总之,要解决闭包带来的内存占用问题,需要合理使用闭包,及时释放闭包,使用箭头函数等方法。
4、浅拷贝/深拷贝
浅拷贝:只复制引用,并没有复制真正的值
例如:
let arr1 = [1,2,3]
let arr2 = arr1
arr1[0] = 4
arr2[1] = 5
conslo.log(arr1,arr2)
// 输出结果
// [4,5,3]
深拷贝: 深拷贝是指创建一个完全独立的对象,与原对象拥有相同的值但是不共享任何引用。
例如:
function deepCopy(obj) {
// 首先检查传入的参数是否是对象
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 创建一个空对象来保存拷贝的结果
let copy = {};
// 遍历原对象的属性
for (let key in obj) {
// 使用递归来拷贝每个属性的值
copy[key] = deepCopy(obj[key]);
}
return copy;
}
该方法首先检查传入的参数是否是对象,如果不是对象则直接返回参数。然后创建一个空对象用来保存拷贝的结果。接着遍历原对象的属性,并使用递归来拷贝每个属性的值。最后返回拷贝的结果对象。
例如,假设有一个对象 obj
:
let obj = {
name: 'John',
age: 25,
address: {
street: '123 Main St',
city: 'New York',
state: 'NY'
}
};
可以使用 deepCopy
方法来深拷贝 obj
:
let copy = deepCopy(obj);
console.log(copy);
输出结果将是一个与 obj
值相同但是完全独立的对象。
补充一条:localstorage\ sessionstorage\ cookie的区别?
共同点:在客户端存放特定的数据
区别:
sessionStorage.setItem(key,value)
localStorage.setItem(key,value)
document.cookie
localstorage:持久化存储页面关闭再次打开页面依然存在数据。
sessionstorage:当浏览器关闭就会失效。
cookie:可自定义cookie的到期时间,默认为半个小时。
小伙伴们看完后记得给一个免费的赞叭~你们的鼓励对我真的很重要 : )