

博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端
变量提升 在代码执行中的优先级问题,闭包 在函数中对外部变量的捕获行为,都会在文中一一讲解。本篇内容 旨在帮助读者更好地理解 JavaScript 语言的独特行为,掌握 var、let 和 const 的适用场景,提升代码编写的可靠性以及阅读他人代码的能力。
JavaScript
有两个数组,数组 A 和数组 B。
['jojo', 'okko', '张三', '秃头', '帅小伙']。[] 或其他不确定的元素。7,其中 N = 7 - 数组A.length。7。例如:
['jojo', 'okko', '张三', '秃头', '帅小伙']['你好', '小脑斧', '大西瓜', '长得帅']['jojo', 'okko', '张三', '秃头', '帅小伙', '你好', '小脑斧']以下是完成上述要求的代码实现:
function fillArray(arrA, arrB) {
var maxLen = 7; // 定义目标数组的最大长度为 7
var arrALen = arrA.length; // 获取数组 A 当前的长度
return arrA.concat(arrB.slice(0, maxLen - arrALen));
// 使用 concat 方法将数组 A 和数组 B 的前 N 个元素合并成一个新数组
// 其中 N = maxLen - arrALen
}
console.log(fillArray(['jojo', 'okko', '张三', '秃头', '帅小伙'], ['你好', '小脑斧', '大西瓜', '长得帅']));
函数定义与参数:
fillArray(arrA, arrB),接收两个参数 arrA 和 arrB,分别表示固定的数组 A 和动态的数组 B。变量 maxLen:
maxLen 表示目标数组的最大长度,这里固定为 7。计算 arrALen:
arrA.length 获取数组 A 的当前长度,存储到变量 arrALen 中。截取数组 B 的前 N 个元素:
maxLen - arrALen,得出还需要从数组 B 中取出多少个元素,这里用 slice(0, maxLen - arrALen) 来截取数组 B 的前 N 个元素。合并数组:
concat 方法,将数组 A 与从数组 B 中截取的前 N 个元素合并,返回一个新的数组(不会修改原数组 A 和 B)。输出结果:
对于输入的数组 A 为 ['jojo', 'okko', '张三', '秃头', '帅小伙'],数组 B 为 ['你好', '小脑斧', '大西瓜', '长得帅'],最终输出为:
['jojo', 'okko', '张三', '秃头', '帅小伙', '你好', '小脑斧']7。slice 截取元素的方式,不会对原始数组 B 造成修改,保证了数据的完整性。concat 和 slice 的组合,代码显得简洁明了,一步到位地解决了数组合并的问题。以下是我们要分析的代码,这段代码涉及 JavaScript 的变量提升和作用域链特性:
console.log(x);
f1();
var x = 0;
function f1() {
f2();
var x = 10;
function f2() {
x = 20;
console.log(x);
}
}
console.log(x);
在 JavaScript 中,变量提升和函数声明提升的机制,会使代码的执行顺序变得更加复杂。以下是预解析后的代码形式,展示了变量和函数如何被提升:
var x;
function f1(){
var x;//局部变量x
function f2() {
x = 20;//局部变量x
console.log(x);//20
}
f2();
x = 10;//局部变量
}
console.log(x);//undefined;
f1();
x = 0;//全局变量x
console.log(x);//0
var 声明的变量会被提升到作用域顶部,但不会进行赋值。因此,var x 在预解析时被提升,但初始值为 undefined。f1 和 f2 也会被提升到作用域顶部,因此它们可以在声明之前调用。console.log(x):在这行代码中,变量 x 已被提升,但尚未赋值,所以输出 undefined。f1():进入函数 f1 后,首先调用 f2()。 f2() 中,x = 20 实际上修改的是 f1 作用域内的局部变量 x,由于 var x = 10 在 f1 内部,x 的作用域是 f1 函数级别的。console.log(x):输出 20,因为 f2() 修改了 f1 内部的局部变量 x。x = 0:为全局变量 x 赋值为 0。console.log(x):最后输出 0,因为此时全局变量 x 已被赋值为 0。undefined
20
0x 被提升,它在赋值之前的初始值为 undefined。f2 修改了 f1 内部的局部变量 x,展示了 JavaScript 的函数作用域链和变量查找规则。x 与函数内局部变量 x 并不冲突,且具有不同的作用域,这些特性使得 JavaScript 的执行行为更加灵活。以下代码涉及 JavaScript 中函数声明和变量声明同名时的提升行为:
console.log(a);
function a() {
a = 20;
}
a();
console.log(a);
var a = 30;
console.log(a);
在 JavaScript 中,函数声明和变量声明同名时,函数声明的优先级更高,以下是预解析后的代码形式:
var a;
function a(){
a = 20;//全局变量
}
console.log(a);//function a(){ a = 20; }
a();
console.log(a);//20
a = 30;
console.log(a);//30

a 被提升到作用域顶部,优先于变量 a 的声明。a 也被提升,但因为已经有同名的函数声明,所以它的作用被函数占据。console.log(a):因为 a 是函数声明提升的,所以输出整个函数体。a():调用函数 a,在函数内部执行 a = 20,将 a 的值修改为 20。这里的 a 指向的是全局的标识符。console.log(a):输出 20,因为 a 已经被赋值为 20。a = 30:将全局变量 a 赋值为 30。console.log(a):输出 30。function a() { a = 20; }
20
30a 的赋值,实际上是对全局范围内的 a 的修改。以下代码展示了 JavaScript 中的闭包现象,特别是在循环中对变量的捕获行为:
function f1() {
var arr = [];
for (var i = 0; i < 10; i++) {
arr.push(function () {
return i;
});
}
return arr;
}
console.log(f1()[3]);
console.log(f1()[3]());
在 JavaScript 中,var 声明的变量是函数作用域的,因此在循环中,所有闭包捕获的是同一个变量引用,而不是独立的值。以下是预解析后的代码及其解释:
function f1() {
var arr;
var i;
arr = [];
for (i = 0; i < 10; i++){
arr.push(function(){
return i;
})
}
return arr;
}
console.log(f1()[3]);
console.log(f1()[3]());
var 的作用域
f1 函数中,变量 i 是通过 var 声明的,这意味着 i 的作用域是整个 f1 函数,而不是块级作用域。i 的值已经增加到 10,所以所有闭包在执行时,访问的都是同一个变量 i,其值为 10。f1()[3]:这里直接访问数组中存储的第四个函数,结果是函数体本身,而不是调用结果。f1()[3]():在这里调用数组中的匿名函数,因为所有闭包引用的是共享的变量 i,而此时 i 的值为 10,所以输出 10。f1()[3]:当直接打印 f1()[3] 时,输出的是存储在数组 arr 中的函数本身,即函数体。这是因为 f1() 返回了包含函数的数组,访问 f1()[3] 仅仅是指向该函数,而并没有执行它。
f1()[3]():当执行 f1()[3]() 时,实际上调用了存储在数组中的匿名函数。由于这个函数捕获的是共享的变量 i,而在循环结束时,i 的值为 10,因此输出结果为 10。
为了让每个闭包捕获各自独立的 i 值,可以使用 let 声明或立即执行函数表达式(IIFE)来创建新的作用域。

letlet 是块级作用域,因此每次循环都会创建一个新的 i,从而保证每个闭包捕获的都是不同的 i 值。
function f1() {
var arr = [];
for (let i = 0; i < 10; i++) {
arr.push(function () {
return i;
});
}
return arr;
}
console.log(f1()[3]()); // 输出 3
通过立即执行函数表达式(IIFE),每次循环创建一个新的作用域,并将当前的 i 传递进去。
function f1() {
var arr = [];
for (var i = 0; i < 10; i++) {
(function (j) {
arr.push(function () {
return j;
});
})(i);
}
return arr;
}
console.log(f1()[3]()); // 输出 3
function () { return i; } // 原始代码输出结果(f1()[3])
10 // 原始代码中调用闭包函数的输出结果(f1()[3]())
3 // 使用 let 或 IIFE 修改后的输出结果
i,因此最终输出的都是相同的值。let 创建块级作用域:使用 let 可以为每次循环创建新的作用域,使得每个闭包能够捕获独立的变量。
通过本文对 JavaScript 中 作用域、闭包 和 变量提升 等概念的深入探索,我们可以更清楚地理解 JavaScript 的执行机制及其复杂之处。我们学习了如何在数组操作中动态添加元素,理解了 变量提升 的影响,并认识到 函数声明 与 变量声明 同名时的优先级。
此外,闭包与循环中的变量捕获问题,也让我们明白了 var 的缺陷及其在闭包中的表现。通过这些代码实例,我们不仅揭示了 JavaScript 代码的执行细节,还学会了如何规避由提升和闭包带来的常见问题。希望这些知识能帮助你编写出更加健壮、可维护的 JavaScript 代码,在项目开发中少走弯路