深入解读JavaScript中的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!"
 }
})();

这也是函数声明与函数表达式的主要区别。

(0)

相关推荐

  • JS中作用域和变量提升(hoisting)的深入理解

    作用域(Scoping) 对于Javascript初学者来说,一个最迷惑的地方就是作用域:事实上,不光是初学者.我就见过一些有经验的javascript程序员,但他们对scope理解不深.javascript作用域之所以迷惑,是因为它程序语法本身长的像C家族的语言.我对作用域的理解是只会对某个范围产生作用,而不会对外产生影响的封闭空间.在这样的一些空间里,外部不能访问内部变量,但内部可以访问外部变量. c语言的变量分为全局变量和局部变量,全局变量的作用范围是任何文件和函数访问(当然,对于非变量定

  • JavaScript Scoping and Hoisting 翻译

    你知道下面的JavaScript代码执行后会alert出什么值吗? 复制代码 代码如下: var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); 如果答案是"10"令你感到惊讶的话,那么下面这个会让你更加困惑: [/code] var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a); [/cod

  • 理解 JavaScript Scoping & Hoisting(二)

    Scoping & Hoisting var a = 1; function foo() { if (!a) { var a = 2; } alert(a); }; foo(); 上面这段代码在运行时会产生什么结果? 尽管对于有经验的程序员来说这只是小菜一碟,不过我还是顺着初学者常见的思路做一番描述: 1.创建了全局变量 a,定义其值为 1 2.创建了函数 foo 3.在 foo 的函数体内,if 语句将不会执行,因为 !a 会将变量 a 转变成布尔的假值,也就是 false 4.跳过条件分支,

  • JavaScript中Hoisting详解 (变量提升与函数声明提升)

    本文主要给大家介绍了关于JavaScript中Hoisting(变量提升与函数声明提升)的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 如何将 函数声明 / 变量 "移动" 到作用域的顶部. 术语 Hoisting(提升) 在很多 JavaScript 博文中被用来解释标识符的解析.其实 Hoisting(提升) 这个词是用来解释 变量 和 函数声明 是如何被提升到 函数或全局 作用域顶部的.你在任何的 JavaScript 文档中找不到这个术语,我们说的

  • JavaScript中变量提升 Hoisting

    因为我在写这文章的时候,百度里找资料,找到了园友的一篇文章,写的很好,可是我写了又不想放弃,所以就在里面拿了很多东西过来!~~ [翻译]JavaScript Scoping and Hoisting希望得到大家谅解. 一.案发现场 我们先看一段很简单的代码: 复制代码 代码如下: var v='Hello World'; alert(v); 这个没有疑问吧,弹出"Hello World".OK,我们继续. 我们在看一段Code: 复制代码 代码如下: var v='Hello Worl

  • JavaScript的变量声明提升问题浅析(Hoisting)

    一.变量声明提升 hoisting 英['hɔɪstɪŋ] 美['hɔɪstɪŋ] n. 起重,提升 v. 把-吊起,升起( hoist的现在分词 ) 先来看一个栗子 var cc = 'hello'; function foo(){ console.log(cc); var cc = 'world'; console.log(cc); } foo(); console.log(cc); 这里将会输出 undefined.'world' .'hello' 此处主要有两个知识点: 1.作用域 2.

  • 深入解读JavaScript中的Hoisting机制

    hoisting机制 javascript的变量声明具有hoisting机制,JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面. 先看一段代码 var v = "hello"; (function(){ console.log(v); var v = "world"; })(); 这段代码运行的结果是什么呢? 答案是:undefined 这段代码说明了两个问题, 第一,function作用域里的变量v遮盖了上层作用域变量v.代码做少

  • JavaScript中事件冒泡机制示例详析

    什么是冒泡? DOM事件流(event  flow )存在三个阶段:事件捕获阶段. 处于目标阶段. 事件冒泡阶段. 事件捕获(event  capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件. 事件冒泡(dubbed  bubbling):与事件捕获恰恰相反,事件冒泡顺序是由内到外进行事件传播,直到根节点. dom标准事件流的触发的先后顺序为:先捕

  • JavaScript中的this机制

    JavaScript有自己的一套this机制,在不同情况下,this的指向也不尽相同. 全局范围 console.log(this); //全局变量 全局范围使用this指向的是全局变量,浏览器环境下就是window. 注:ECMAScript5的strict模式不存在全局变量,这里的this是undefined. 函数调用中 function foo() { console.log(this); } foo(); //全局变量 函数调用中的this也指向全局变量. 注:ECMAScript5的

  • 深入解读JavaScript中的Iterator和for-of循环

    如何遍历一个数组的元素?在 20 年前,当 JavaScript 出现时,你也许会这样做: for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]); } for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]); } 自从 ES5 开始,你可以使用内置的 forEach 方法:

  • JavaScript中变量提升机制示例详解

    变量提升 JavaScript的变量提升有两种,用var声明的变量以及用function声明的变量. 用var声明的变量 我们先来看下面这段代码,a的值是多少 代码1 console.log(a); var a; 按照以往编程语言的思路来看,代码自上而下运行,按这种思路,会报错,因为执行到第2行时,变量a还没有定义,所以会报错a is not defined 然而事实上答案是undefined 好,抱着疑惑,我们看下面的代码 var a; console.log(a); 我们发现,这两段代码是一

  • 浅析JavaScript中的事件机制

     事件是什么 ? JavaScript与HTML交互是通过在用户或浏览器操纵页面上发生的事件进行处理. 当页面加载,这是一个事件.当用户点击一个按钮,这一下,也就是一个事件.事件的另一个例子是类似按下任意键,关闭窗口,调整窗口等. 开发者可以使用这些事件执行JavaScript编码响应,这引起按钮以关闭视窗,消息,以便显示给用户,要验证的数据,以及几乎任何其它类型的响应可以发生的. 事件是文档对象模型(DOM)第3级,每一个HTML元素的一部分有一套可以触发JavaScript代码事件. 例子:

  • 解读JavaScript中 For, While与递归的用法

    for循环: 复制代码 代码如下: for(i=start; i<end; i++) { } while循环: (注意, 若条件一直为真, 则会进入死循环, 浏览器就hang掉) 复制代码 代码如下: while (condition) {     //do something;     //change condition;} 递归: 使用for循环做substring 复制代码 代码如下: function substring(all, start, end) {    for(i=star

  • 在JavaScript中实现命名空间

    注:好久没写了,今天把我在公司内网写的文章拷一份,出来露露脸,刚转Web开发,所以开始学javascript! 在引入命名空间之前,一个令开发人员头疼的问题就是如何防止函数名/类名和其他人的冲突,在一个公司内部项目组之间可以通过命名预定(比如加前缀等)解决这个问题,但是把视线放到整个软件开发领域,在当今协作开发相当盛行的时代,这个问题却依然存在.在使用多个第三方框架或类库的时候,你唯一能作的就是祈祷它们的命名不要冲突,如果真正发生这种灾难的话,你唯一能作的就是放弃其中一个(注:可能是我孤陋寡闻,

  • JavaScript中的变量作用域介绍

    对于变量的作用域(scope),C.Java等语言采取的是"block scope"的方式.与之不同,JavaScript所采取的是"function scope"的方式 - 变量的作用域仅由所处的function决定,与if.for等逻辑块无关.比如,以下这个例子展示了JavaScript中与C.Java等语言不一样的行为: 复制代码 代码如下: function(){   var s = 42;//s is visible throughout function

  • Javascript中3种实现继承的方法和代码实例

    继承是我们在实现面向对象编程的时候很重要的一个手段.虽然我们讲不能过度继承,多利用组合代替继承,但是继承总是免不了的.这里要讨论的就是Javascript中的继承机制. Javascript中实际上是没有继承的概念的,但是我们可以通过一些手段来模仿实现它.这种继承实际上把一个对象复制到另外一个对象内部.你需要注意的是所有的本地类和宿主类是不能作为基类被继承的,主要是为了安全方面的考虑. Javascript中的继承大约有三类:1.对象冒充:2.原型继承:3.二者的混合. 一.对象冒充 其实对象冒

随机推荐