1、var 变量预编译

JavaScript 的语法和 C 、Java、C# 类似,统称为 C 类语法。有过 C 或 Java 编程经验的同学应该对“先声明、后使用”的规则很熟悉,如果使用未经声明的变量或函数,在编译阶段就会报错。然而,JavaScript 却能够在变量和函数被声明之前使用它们。下面我们就深入了解一下其中的玄机。

先来看一段代码:

(function() {
console.log(noSuchVariable);//ReferenceError: noSuchVariable is not defined
})();

运行上面代码立马就报错,不过,这也正是我们期望的,因为 noSuchVariable 变量根本就没有定义过嘛!再来看看下面的代码:

(function() {
console.log(declaredLater); //undefined
var declaredLater = "Now it's defined!";
console.log(declaredLater);// "Now it's defined!"
})();

首先,上面这段代码是正确的,没有任何问题。但是,为什么不报错了?declaredLater 变量是在调用语句后面定义的啊?为什么居然输出的是 undefined?

这其实是 JavaScript 解析器搞的鬼,解析器将当前作用域内声明的所有变量和函数都会放到作用域的开始处,但是,只有变量的声明被提前到作用域的开始处了,而赋值操作被保留在原处。上述代码对于解析器来说其实是如下这个样子滴:

(function() {
var declaredLater; //声明被提前到作用域开始处了!
console.log(declaredLater); // undefined
declaredLater = "Now it's defined!"; //赋值操作还在原地!
console.log(declaredLater);//"Now it's defined!"
})();

这就是为什么上述代码不报异常的原因!变量和函数经过“被提前”之后,declaredLater 变量其实就被放在了调用函数的前面,根据 JavaScript 语法的定义,已声明而未被赋值的变量会被自动赋值为 undefined ,所以,第一次打印 declaredLater 变量的值就是 undefined,后面我们对 declaredLater 变量进行了赋值操作,所以,第二次再打印变量就会输出Now it’s defined!。

再来看一个例子:

var name = "Baggins";
(function () {
console.log("Original name was " + name);// "Original name was undefined"
var name = "Underhill";
console.log("New name is " + name);// "New name is Underhill"
})();

上述代码中,我们先声明了一个变量 name ,我们的本意是希望在第一次打印 name 变量时能够输出全局范围内定义的 name 变量,然后再在函数中定义一个局部 name 变量覆盖全局变量,最后输出局部变量的值。可是第一次输出的结果和我们的预期完全不一致,原因就是我们定义的局部变量在其作用域内被“提前”了,也就是变成了如下形式:

var name = "Baggins";
(function () {
var name; //注意:name 变量被提前了!
console.log("Original name was " + name);// "Original name was undefined"
name = "Underhill";
console.log("New name is " + name);//"New name is Underhill"
})();

由于 JavaScript 具有这样的“怪癖”,所以建议大家将变量声明放在作用域的最上方,这样就能时刻提醒自己注意了。

2、函数声明“被提前”

前边说的是变量,接下来我们说说函数。

函数的“被提前”还要分两种情况,一种是函数声明,第二种是函数作为值赋值给变量,也即函数表达式

先说第一种情况,上代码:

isItHoisted();//"Yes!"
function isItHoisted() {
console.log("Yes!");
}

如上所示,JavaScript 解释器允许你在函数声明之前使用,也就是说,函数声明并不仅仅是函数名“被提前”了,整个函数的定义也“被提前”了!所以上述代码能够正确执行。

再来看第二种情况:函数表达式形式。还是先上代码:

definitionHoisted();// "Definition hoisted!"
definitionNotHoisted();// TypeError: undefined is not a function
function definitionHoisted() {
console.log("Definition hoisted!");
}
var definitionNotHoisted = function () {
console.log("Definition not hoisted!");
};

我们做了一个对比,definitionHoisted 函数被妥妥的执行了,符合第一种类型;definitionNotHoisted 变量“被提前”了,但是他的赋值(也就是函数)并没有被提前,从这一点上来说,和前面我们所讲的变量“被提前”是完全一致的,并且,由于“被提前”的变量的默认值是 undefined ,所以报的错误属于“类型不匹配”,因为 undefined 不是函数,当然不能被调用。

总结

通过上面的讲解可以总结如下:

  • 变量的声明被提前到作用域顶部,赋值保留在原地
  • 函数声明整个“被提前”
  • 函数表达式时,只有变量“被提前”了,函数没有“被提前”

3、var的副作用

隐式全局变量和明确定义的全局变量间有些小的差异,就是通过delete操作符让变量未定义的能力。

  • 通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的。
  • 无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的。

这表明,在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete操作符删除的,而变量是不能的:

// 定义三个全局变量
var global_var = 1;
global_novar = 2; // 反面教材
(function () {
global_fromfunc = 3; // 反面教材
}()); // 试图删除
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true // 测试该删除
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"

在ES5严格模式下,未声明的变量(如在前面的代码片段中的两个反面教材)工作时会抛出一个错误。

4、单var形式声明变量

在函数顶部使用单var语句是比较有用的一种形式,其好处在于:

  • 提供了一个单一的地方去寻找功能所需要的所有局部变量
  • 防止变量在定义之前使用的逻辑错误
  • 少代码(类型啊传值啊单线完成)

    单var形式长得就像下面这个样子:
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body...
}

您可以使用一个var语句声明多个变量,并以逗号分隔。像这种初始化变量同时初始化值的做法是很好的。这样子可以防止逻辑错误(所有未初始化但声明的变量的初始值是undefined)和增加代码的可读性。在你看到代码后,你可以根据初始化的值知道这些变量大致的用途。


系列文章导航:

1、你不知道的JavaScript–Item1 严格模式

2、你不知道的JavaScript–Item2 浮点数精度

3、你不知道的JavaScript–Item3 隐式强制转换

4、你不知道的JavaScript–Item4 基本类型和基本包装类型(引用类型)

5、你不知道的JavaScript–Item5 全局变量

6、你不知道的JavaScript–Item6 var预解析与函数声明提升(hoist )

7、你不知道的JavaScript–Item7 函数和(命名)函数表达式

8、你不知道的JavaScript–Item8 函数,方法,构造函数调用

9、你不知道的JavaScript–Item9 call(),apply(),bind()与回调

10、你不知道的JavaScript–Item10 闭包(closure)

11、你不知道的JavaScript–Item11 arguments对象

12、你不知道的JavaScript–Item12 undefined 与 null

13、你不知道的JavaScript–Item13 理解 prototype, getPrototypeOf 和_ proto_

14、你不知道的JavaScript–Item14 使用prototype的几点注意事项

15、你不知道的JavaScript–Item15 prototype原型和原型链详解

16、你不知道的JavaScript–Item16 for 循环和for…in 循环的那点事儿

17、你不知道的JavaScript–Item17 循环与prototype最后的几点小tips

18、你不知道的JavaScript–Item18 JScript的Bug与内存管理

19、你不知道的JavaScript–Item19 执行上下文(execution context)

20、你不知道的JavaScript–Item20 作用域与作用域链(scope chain)

21、你不知道的JavaScript–Item21 漂移的this


持续更新中……………….

版权声明:本文为小平果原创文章,转载请注明:http://blog.csdn.net/i10630226

你不知道的JavaScript--Item6 var预解析与函数声明提升(hoist )的更多相关文章

  1. javascript作用域、预解析笔记

    1.作用域     一般情况下,一段代码中所用到的名字并不总是有效可用的,     而限定这个名字(变量)的可用性的代码范围就是这个名字的作用域,可用有效的减少变量名冲突     2.js的作用域(e ...

  2. JavaScript 函数声明与函数表达式的区别 函数声明提升(function declaration hoisting)

    解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁.解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问).至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真的被 ...

  3. JavaScript函数声明提升

    首先,JavaScript中函数有两种创建方式,即函数声明.函数表达式两种. 1.函数声明. function boo(){ console.log(123); } boo() 2.函数表达式. va ...

  4. JS执行顺序-函数声明提升、匿名函数、函数表达式

    大方向上: JS 是按照 代码块 进行 编译.执行 的. 学习至: 1.变量声明提升 2.新唐的博客 3.js中匿名函数的创建与调用方法分析 4.前端圣经 - <高程三> 5.深入理解变量 ...

  5. Js 变量声明提升和函数声明提升

    Js代码分为两个阶段:编译阶段和执行阶段 Js代码的编译阶段会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容 包括变量声明(var a)和函数声明(function a(){ ...

  6. js函数声明提升与变量提升

    变量提升 变量提升: 在指定作用域里,从代码顺序上看是变量先使用后声明,但运行时变量的 “可访问性” 提升到当前作用域的顶部,其值为 undefined ,没有 “可用性”. alert(a); // ...

  7. 变量声明提升 Vs. 函数声明提升

    1. 变量声明提升 先看以下代码: 1)var in_window = "a" in window; console.log(in_window); 2)var in_window ...

  8. JS变量声明提升和函数声明提升

    JS代码在执行的时候会先找出执行代码中定义的变量和函数,对其进行声明. 例1:console.log(a); var a = 4; 此时输出undefined.a变量在执行console.log(a) ...

  9. JavaScript的变量预解析特性

    JavaScript是解释型语言是毋庸置疑的,但它是不是仅在运行时自上往下一句一句地解析的呢?事实上或某种现象证明并不是这样的,通过<JavaScript权威指南>及网上相关资料了解到,J ...

随机推荐

  1. 第一个Polymer应用 - (1)创建APP结构

    原文链接: Step 1: Creating the app structure翻译日期: 2014年7月5日翻译人员: 铁锚在本节中,将使用一些预先构建好的Polymer元素来创建基本的应用程序结构 ...

  2. Slop One 算法

    Slope One 算法是由 Daniel Lemire 教授在 2005 年提出的一个 Item-Based 推荐算法. Slope One 算法试图同时满足这样的的 5 个目标: 易于实现和维护: ...

  3. 自定义UICollectionViewLayout 布局实现瀑布流

    自定义 UICollectionViewLayout 布局,实现瀑布流:UICollectionView和UICollectionViewCell 另行创建,这只是布局文件, 外界控制器只要遵守协议并 ...

  4. 自动布局Autoresizing与Autolayout

    一.关于iPhone屏幕的一些基本常识 1.ios屏幕适配的尺寸 iPhone的尺寸3.5inch.4.0inch.4.7inch.5.5inch iPad的尺寸7.9inch.9.7inch 2.点 ...

  5. Bash的一些零星笔记

    1.变量带入操作符 在脚本中,使用变量前做检查是很重要的.通过代入操作符,可以实现这方面的功能.比如当变量未赋值时为变量赋默认值,以及更多内容: ${parameter:-默认为空}:当paramet ...

  6. MySQL中遇到的几种报错及其解决方法

    MySQL中遇到的几种报错及其解决方法 1.[Err] 1064 - You have an error in your SQL syntax; check the manual that corre ...

  7. 关于在IJ中使用maven projects 的Lifecycle中打包package报expected START_TAG or END_TAG not TEXT

    报错指定到maven本地仓库下的settings.xml某一行,如下列JDK配置: <profiles> <profile>    <id>jdk-1.8</ ...

  8. Java获取当日的起始时间,结束时间,现在时间,是否在时间段中。

    当日的起始时间 public static Date getTodayStartTime() { Calendar todayStart = Calendar.getInstance(); today ...

  9. java数据库(MySQL)之增删改查

    1.查询数据 先救从简单的来吧,之前我们实现了将数据库表格信息读取到一个List集合中,数据库的查询,实 际上就是对这个集合的查询: public class Show { public static ...

  10. 基于NetMQ的TLS框架NetMQ.Security的实现分析

    基于NetMQ的TLS框架NetMQ.Security的实现分析 前言 介绍 交互过程 支持的协议 TLS协议 支持的算法 实现 握手 第一次握手 Client Hello 第二次握手 Server ...