深入解读JavaScript中的Hoisting机制
本文深入解析JavaScript中的Hoisting(提升)机制,阐述变量与函数声明如何被提升至当前作用域顶部。通过对比函数声明与函数表达式的差异,解析名字解析优先级及命名函数表达式的特性,帮助开发者理解undefined现象并避免常见陷阱。
Hoisting机制概述
Hoisting机制
JavaScript的变量声明具备Hoisting(提升)特性。在JavaScript引擎执行代码时,会将所有变量的声明语句提升至当前作用域的最顶端。
首先观察一段示例代码
var v = "hello";
(function(){
console.log(v);
var v = "world";
})();
这段代码的运行结果是什么? 答案是:undefined 该结果揭示了两个关键点: 第一,在function作用域内部,变量v遮蔽了外层作用域的同名变量v。代码稍作调整
var v = "hello";
if(true){
console.log(v);
var v = "world";
}
输出结果为”hello”,这表明JavaScript不存在块级作用域。在JavaScript中,函数是唯一拥有独立作用域的结构。
第二,在function作用域内部,变量v的声明发生了提升。因此,原始代码等效于:
var v = "hello";
(function(){
var v; //declaration hoisting
console.log(v);
v = "world";
})();
声明、定义与初始化的区别
声明、定义与初始化
声明用于宣告一个名称的存在,定义则为该名称分配存储空间,而初始化则是为已分配的存储空间赋予初始值。 以C++语言来表述这三个概念
extern int i;//这是声明,表明名字i在某处已经存在了 int i;//这是声明并定义名字i,为i分配存储空间 i = 0;//这是初始化名字i,为其赋初值为0 在javascript中则是这样
var v;//声明变量v v = "hello";//(定义并)初始化变量v 由于javascript是动态语言,其变量没有固定类型,存储空间大小随初始化和赋值动态变化,因此变量的“定义”概念不像传统静态语言那样严格,其定义环节显得相对次要。
声明提升原理
声明提升
当前作用域内的所有声明(包括变量和函数)都会被提升至作用域的最前端。
(function(){
var a = "1";
var f = function(){};
var b = "2";
var c = "3";
})();
变量a、f、b、c的声明会被提升至函数作用域的最前端,类似于以下形式:
(function(){
var a,f,b,c;
a = "1";
f = function(){};
b = "2";
c = "3";
})();
请注意,函数表达式并未被提升,这是函数表达式与函数声明的主要区别。进一步分析二者的差异:
(function(){
//var f1,function f2(){}; //hoisting,被隐式提升的声明
f1(); //ReferenceError: f1 is not defined
f2();
var f1 = function(){};
function f2(){}
})();
在上述代码中,函数声明f2被提升,因此在前面调用f2是可行的。虽然变量f1也被提升,但f1提升后的值为undefined,其真正的初始值是在执行到函数表达式赋值处时才被赋予。因此,只有声明部分会被提升。
名字解析顺序与优先级
名字解析顺序
在javascript中,一个名字(name)通过四种方式进入作用域(scope),其优先级顺序如下: 1、语言内置:所有的作用域中都有 this 和 arguments 关键字 2、形式参数:函数的参数在函数作用域中都是有效的 3、函数声明:形如function foo() {} 4、变量声明:形如var bar;
名字声明的优先级如上所示,这意味着如果一个变量的名字与函数的名字相同,那么函数的名字会覆盖变量的名字,无论其在代码中的书写顺序如何。但名字的初始化却是按其在代码中书写的顺序进行的,不受上述优先级的影响。参考代码:
(function(){
var foo;
console.log(typeof foo); //function
function foo(){}
foo = "foo";
console.log(typeof foo); //string
})();
如果形式参数中存在多个同名变量,那么最后一个同名参数会覆盖其他同名参数,即使最后一个同名参数并未定义。
上述名字解析优先级存在例外情况,例如可以覆盖语言内置的名字arguments。
命名函数表达式特性
命名函数表达式
可以为函数表达式指定一个名字,就像函数声明一样,但这并不会使该函数表达式转变为函数声明。命名函数表达式的名字不会进入外部名字空间,也不会被提升。
f();//TypeError: f is not a function foo();//ReferenceError: foo is not defined var f = function foo(){console.log(typeof foo);}; f();//function foo();//ReferenceError: foo is not defined 命名函数表达式的名字仅在该函数的作用域内部有效。
再来看以下示例:
var myval = "my global var";
(function() {
console.log(myval); // log "my global var"
})();
上述代码显然会输出 "my global var",但如果将上述代码按如下方式稍作修改:
var myval = "my global var";
(function() {
console.log(myval); // log "undefined"
var myval = "my local var";
})();
执行结果是输出了一个 undefined,出现这个结果的原因就是变量的声明被提升了,上述代码等同于如下形式:
var myval = "my global var";
(function() {
var myval;
console.log(myval); // log "undefined"
myval = "my local var";
})();
被提升的仅仅是变量的声明部分,并没有立即进行初始化,因此会输出 undefined。
然而这种提升机制不仅体现在普通变量上,同样也体现在函数上。例如以下这段代码无法正确执行:
(function() {
fun(); // Uncaught TypeError: undefined is not a function
var fun = function() {
console.log("Hello!");
}
})();
因为它等价于:
(function() {
var fun;
fun(); // Uncaught TypeError: undefined is not a function
fun = function() {
console.log("Hello!");
}
})();
由于函数的声明同样被提升而没有立即初始化,因此会导致错误。
当然,这种定义函数的方式称为“函数表达式”,会存在提升机制;如果是如下的这种“函数声明”方式,则完全没有提升机制方面的问题:
(function() {
fun();
function fun() {
console.log("Hello!"); // log "Hello!"
}
})();
这也是函数声明与函数表达式的主要区别。
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
