你不知道的JavaScript--Item6 var预解析与函数声明提升(hoist )
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)和增加代码的可读性。在你看到代码后,你可以根据初始化的值知道这些变量大致的用途。
系列文章导航:
3、你不知道的JavaScript–Item3 隐式强制转换
4、你不知道的JavaScript–Item4 基本类型和基本包装类型(引用类型)
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 )的更多相关文章
- javascript作用域、预解析笔记
1.作用域 一般情况下,一段代码中所用到的名字并不总是有效可用的, 而限定这个名字(变量)的可用性的代码范围就是这个名字的作用域,可用有效的减少变量名冲突 2.js的作用域(e ...
- JavaScript 函数声明与函数表达式的区别 函数声明提升(function declaration hoisting)
解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁.解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问).至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真的被 ...
- JavaScript函数声明提升
首先,JavaScript中函数有两种创建方式,即函数声明.函数表达式两种. 1.函数声明. function boo(){ console.log(123); } boo() 2.函数表达式. va ...
- JS执行顺序-函数声明提升、匿名函数、函数表达式
大方向上: JS 是按照 代码块 进行 编译.执行 的. 学习至: 1.变量声明提升 2.新唐的博客 3.js中匿名函数的创建与调用方法分析 4.前端圣经 - <高程三> 5.深入理解变量 ...
- Js 变量声明提升和函数声明提升
Js代码分为两个阶段:编译阶段和执行阶段 Js代码的编译阶段会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容 包括变量声明(var a)和函数声明(function a(){ ...
- js函数声明提升与变量提升
变量提升 变量提升: 在指定作用域里,从代码顺序上看是变量先使用后声明,但运行时变量的 “可访问性” 提升到当前作用域的顶部,其值为 undefined ,没有 “可用性”. alert(a); // ...
- 变量声明提升 Vs. 函数声明提升
1. 变量声明提升 先看以下代码: 1)var in_window = "a" in window; console.log(in_window); 2)var in_window ...
- JS变量声明提升和函数声明提升
JS代码在执行的时候会先找出执行代码中定义的变量和函数,对其进行声明. 例1:console.log(a); var a = 4; 此时输出undefined.a变量在执行console.log(a) ...
- JavaScript的变量预解析特性
JavaScript是解释型语言是毋庸置疑的,但它是不是仅在运行时自上往下一句一句地解析的呢?事实上或某种现象证明并不是这样的,通过<JavaScript权威指南>及网上相关资料了解到,J ...
随机推荐
- ExtAspNet页面跳转的方法
一:如果在Page_Load中则可以用Response.Redirect("ABC.aspx"); 二:在其它事件中可以用以下方法: protected void Button1_ ...
- Linux之ulimit详解(整理)
修改:一般可以通过ulimit命令或编辑/etc/security/limits.conf重新加载的方式使之生效通过ulimit比较直接,但只在当前的session有效,limits.conf中可以根 ...
- javascript加RoR实现JSONP
我们知道不同域中的js代码受同源策略的限制,不同域中的AJAX同样受此限制,不过使用html中的script远程脚本可以跳过该限制,下面我们实际看一下利用RoR和js如何实现所谓的JSONP. 这里只 ...
- rails小重构:将图片加入产品Model之二
在前面我们重构了product中图片的实现,但是还是有一些小问题.比如用户如果上传一个非图片格式的文件时的验证情况. 我们之前是将图片格式验证代码放在Picture类中: validates_form ...
- rails将类常量重构到数据库对应的表中之二
在博文之一中我们将Order中的常量重构到了数据库的表中,也做了一些测试,貌似一切都很完美.可是...梦魔还未开始啊!我们少做了一步测试,就是rake test! 结果惨不忍睹,所有测试都是E,全部出 ...
- 解决Visual Studio 2017隐藏“高级保存选项”命令
Visual Studio提供高级保存选项功能,它能指定特定代码文件的编码规范和行尾所使用的换行符.在Visual Studio 2017中,该命令没有默认显示在“文件”菜单中.用户需要手工设置,才能 ...
- java中文拼音字母排序
package com.yputil.util; import java.text.CollationKey;import java.text.Collator;import java.util.Ar ...
- JVM内存详解-阅读笔记
- andorid下从相册选取/拍照选取一张相片并剪切
http://www.2cto.com/kf/201401/270144.html 在Android编程中,从相册选取或是拍照选取一张照片然后对其进行剪切的需求非常的多 之前的一篇文章只说到如何从相册 ...
- mysql经典面试题
数据库优化:这个优化法则归纳为5个层次:1. 减少数据访问(减少磁盘访问)2. 返回更少数据(减少网络传输或磁盘访问)3. 减少交互次数(减少网络传输)4. 减少服务器CPU开销(减少CPU及内存开销 ...