博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端
变量提升
在代码执行中的优先级问题,闭包 在函数中对外部变量的捕获行为,都会在文中一一讲解。本篇内容 旨在帮助读者更好地理解 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
0
x
被提升,它在赋值之前的初始值为 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
30
a
的赋值,实际上是对全局范围内的 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)来创建新的作用域。
let
let
是块级作用域,因此每次循环都会创建一个新的 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 代码,在项目开发中少走弯路