本文深入解析JavaScript引擎在编译阶段的预处理机制,详细阐述变量提升与函数提升的执行顺序。通过对比var声明与function声明在预编译块中的行为,探讨同名变量、参数与函数的优先级判定。重点分析立即执行函数及常规函数中参数传递与函数体赋值的覆盖关系,帮助开发者理解并解决因提升机制导致的命名冲突与意外结果。
前言
在JavaScript引擎编译代码的过程中,存在一个关键的预处理阶段。该阶段会对源代码进行初步分割与处理,为后续执行做准备。
源程序 ---> 预处理块+ 剩余代码块
经过预处理阶段处理后的代码块,其执行优先级高于剩余的普通代码块。
什么是变量提升,函数提升
变量提升是指:当代码中使用var关键字声明变量时,该变量的声明动作会被JavaScript引擎提取至预处理阶段优先执行。这种机制使得使用var定义的变量具备变量提升特性。
函数提升是指:当代码中使用function关键字声明函数时,该函数的声明动作同样会被提取至预处理阶段优先执行。这种机制使得使用function定义的函数具备函数提升特性。
判断是否发生提升的依据在于:如果在代码的正常执行顺序中,某个变量或函数尚未声明就被调用,则可能触发了提升机制。
变量提升
在变量提升机制下,变量名会被放入预编译的词法环境中,初始值为undefined。例如:var a(undefined)。
console.log(a);
var a = 1;上述代码会触发变量提升,其实际执行逻辑等价于以下代码结构:
{// 模拟预处理块
var a;
}
// 正常执行的剩余代码块
console.log(a); // undefined
a = 1;由于变量提升的存在,变量a在打印语句执行前已被声明,因此输出结果为undefined。
函数提升
在函数提升机制下,整个函数体(包括函数名和函数体)会被放入预编译的词法环境中。例如:function b(){}。
console.log(b);
function b(){
console.log('b')
};上述代码会触发函数提升,其实际执行逻辑等价于以下代码结构:
{// 模拟预处理块
function b(){
console.log('b')
};
}
// 正常执行的剩余代码块
console.log(b);所有使用function关键字声明的函数均存在函数提升特性。这意味着可以在函数声明之前调用该函数(即先使用函数,后实现函数)。
关于变量提升和函数提升的判断
在理解基本的变量提升和函数提升概念后,接下来需要区分两者的区别,并分析当二者同时存在时的优先级关系。
1.变量赋值的函数
console.log(a);
var a = function(){
console.log('a')
};显然,这是由var关键字声明的变量,因此它具备变量提升特性,只不过该变量的初始值是一个函数体。
{// 模拟预处理块
var a;
}
// 正常执行的剩余代码块
console.log(a); // undefined
a = function(){
console.log('a')
};因此,该变量a在打印时会显示为undefined。
2.优先级判断
console.log(b);
var b = function(){
console.log('b1')
};
function b(){
console.log('b2')
};
console.log(b);函数提升的优先级高于变量提升。当出现同名变量和函数的声明时,在预处理块中最终都会表现为函数声明。
{// 模拟预处理块
var b;
function b(){
console.log('b2')
};
}
// 正常执行的剩余代码块
console.log(b);// 能打印'b2'的函数
b = function(){
console.log('b1')
};
console.log(b);// 能打印'b1'的函数除了上述优先级的理解方式外,还可以将function声明拆分为两个部分:先声明函数名,再赋值函数体。而变量的声明过程介于这两者之间。
{// 模拟预处理块
function b
var b;
b = function(){
console.log('b2')
};
}
// 正常执行的剩余代码块
console.log(b);// 能打印'b2'的函数
b = function(){
console.log('b1')
};
console.log(b);// 能打印'b1'的函数tips:以上两种方式的分析结果一致,采用任意一种分析方式均可。
3.同名参数名,变量名,函数名的优先级判断
针对同名参数名与变量名的判断:
var foo = 'hello';
(function(foo){
console.log(foo);
var foo = foo || 'world';
console.log(foo);
})(foo);
console.log(foo);此处仅分析立即执行函数内部的预处理过程。
var foo = 'hello';
(function(foo){
// 模拟立即执行函数内的预处理块
{
var foo;
foo = foo; //传入的参数('hello')
}
// 正常执行的剩余代码块
console.log(foo);
foo = foo || 'world';
console.log(foo);
})(foo);
console.log(foo);因此,最后连续打印了3个hello。传入的参数会在预处理阶段被声明并赋值给同名变量,由于foo已不为undefined,后续赋值操作不会将其变为world。
当同名参数名、变量名和函数名同时存在时:
function fn (m){
console.log(m);
var m = 1;
function m(){
}
console.log(m);
}
fn(10)同样存在两种理解方式:
function fn (m){
// 模拟预处理块
{
var m;
m = m;// 传入的参数(10)
function m(){
}
};
}
// 正常执行的剩余代码块
console.log(m);// function(){}
m = 1;
console.log(m);// 1
}
fn(10)function fn (m){
// 模拟预处理块
{
function m
var m;
m = m;// 传入的参数(10)
m = function(){
}
};
}
// 正常执行的剩余代码块
console.log(m);// function(){}
m = 1;
console.log(m);// 1
}
fn(10)可以看到,该函数的输出结果与传入的参数无关。因为传入的参数处理发生在函数提升之前(即函数体赋值之前),导致传入的参数值被覆盖。
总结
由此可以得出同名参数名、变量名和函数名的预处理顺序为:
变量提升---参数传递---函数提升 (声明函数名---变量提升---参数传递---函数体赋值)
因此,最终该名称会被函数声明所占据,这也是函数提升优先级最高的原因。
完整代码和运行结果展示
// 变量提升,将变量名放入预编译的词法环境 var a(undefined)
console.log(a);
var a = function(){
console.log('a')
};
console.log(a);
// 函数提升,将函数体放入预编译的词法环境 function b(){}
console.log(b);
var b = function(){
console.log('b1')
};
function b(){
console.log('b2')
};
console.log(b);
// function b 函数名提升
// var b 变量名提升
// b = (){} 函数体初始化
// 因为函数体初始化总在变量名之后,所以每次都是优先预编译成函数
// 结合自执行函数
var foo = 'hello';
(function(foo){
console.log(foo);
var foo = foo || 'world';
console.log(foo);
})(foo);
console.log(foo);
// 依次输出 hello hello hello
// 预编译后
var foo = 'hello';
(function (foo) {
var foo; // undefined;
foo= 'hello'; //传入的foo的值
console.log(foo); // hello
foo = foo || 'world';// 因为foo有值所以没有赋值world
console.log(foo); //hello
})(foo);
console.log(foo);// hello,打印的是var foo = 'hello' 的值(变量作用域)
// function m(){
// console.log('m');
// };
// var m;
// m =1;
// function m(){
// }
// console.log(m)
function fn (m){
console.log(m);
var m = 1;
function m(){
}
console.log(m);
}
fn(10)
// function m
// var m
// 参数m
// m = (){}
// m = 1
// function > parmas > var
到此这篇关于js进阶语法之变量提升、函数提升以及参数的命名冲突问题解决的文章就介绍到这了,更多相关js变量、函数提升及参数命名冲突内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!