提升----你所不知道的JavaScript系列(3)
很多编程语言在执行的时候都是自上而下执行,但实际上这种想法在JavaScript中并不完全正确, 有一种特殊情况会导致这个假设是错误的。来看看下面的代码,
a = 2;
var a;
console.log( a );
console.log(a) 会输出什么呢?
有些人可能会认为是 undefined,因为 var a 声明在 a = 2 之后,他们自然而然地认为变量被重新赋值了,因此会被赋予默认值 undefined。但是,真正的输出结果是 2。
先不急为什么,我们再继续看另外一段代码,
console.log( a );
var a = 2;
鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码片段也会有同样的行为而输出 2。还有人可能会认为,由于变量 a 在使用前没有先进行声明,因此会抛出 ReferenceError 异常。
其实不然,两种猜测都是不对的。输出来的会是 undefined。
提升
引擎会在解释 JavaScript 代码之前首先对其进行编译,简单地说,任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前,说通常是因为JavaScript 中存在两个机制可以“欺骗” 词法作用域: eval(..) 和 with)。编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。这就是我们通常说的“提升”。
注:只有声明本身会被提升, 而赋值或其他运行逻辑会留在原地。
foo(); function foo() {
console.log( a ); // undefined
var a = 2;
}
每个作用域都会进行提升操作。所以 foo(..)函数自身也会在内部对 var a 进行提升(显然并不是提升到了整个程序的最上方)。在这里,你或许会发现,为什么代码里面是先调用 foo() ,再声明 foo() 这样的顺序,却不会报错。这是因为除了变量声明会在其作用域内提升之外,函数声明也具有相似的特效。因此这段代码可以暂时理解为下面的形式:
function foo() {
var a;
console.log( a ); // undefined
a = 2;
} foo();
可以看到,函数声明会被提升在作用域的顶部。但是有一点需要和变量声明提升做区别的是:变量提升只是提升了变量的声明,而变量赋值并没有被提升。但是,函数的声明有点不一样,函数体也会一同被提升。
所以上面的一段暂时性的代码实际上可以这样理解:
var foo = {
var a;
console.log( a ); // undefined
a = 2;
} foo();
foo 函数的声明(这个例子还包括实际函数的隐含值)被提升了,因此第一行中的调用可以正常执行。
然而并不是所有的函数都能提升!函数声明会被提升,但是函数表达式却不会被提升。
foo(); // 不是 ReferenceError, 而是 TypeError! var foo = function bar() {
// ...
};
上面这段程序中的变量标识符 foo() 被提升并分配给所在作用域,因此 foo() 不会导致 ReferenceError。但是 foo 此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo() 由于对 undefined 值进行函数调用而导致非法操作,因此抛出 TypeError 异常。
同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:
foo(); // TypeError
bar(); // ReferenceError var foo = function bar() {
// ...
};
这个代码片段经过提升后,实际上会被理解为以下形式:
var foo; foo(); // TypeError
bar(); // ReferenceError foo = function() {
var bar = ...self...
// ...
}
这里我们说到具名函数表达式,就顺便插如一点具名函数表达式的知识点。我们看看下面的例子:
function test() {
var fn = function fn1() {
log(fn === fn1); // true
log(fn == fn1); // true
}
fn();
log(fn === fn1); // Uncaught ReferenceError: fn1 is not defined
log(fn == fn1); // Uncaught ReferenceError: fn1 is not defined
} test();
看上面这例子,是不是很疑惑?
具名函数表达式,是带名字的函数赋值给一个变量,这个名字只在新定义的函数作用域内有效,因为规范规定了标示符不能在外围的作用域内有效。也就是说,这个函数名只能在此函数内部使用,可以理解为这个函数名成了函数体内部的一个变量。
这里还有一点需要注意的,函数定义了一个非标准的name属性,通过这个属性可以访问到给定函数指定的名字,这个属性的值永远等于跟在function关键字后面的标识符,匿名函数的name属性为空,而具名的函数表达式会修改到这个属性。
var foo = function(){
//...
};
console.log(foo.name); //foo var bar = function foobar(){
//...
};
console.log(bar.name); //foobar name值被修改
函数优先
函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个“重复” 声明的代码中)是函数会首先被提升,然后才是变量。
看一下下面的代码:
foo(); // var foo; function foo() {
console.log( 1 );
} foo = function() {
console.log( 2 );
};
会输出 1 而不是 2 ! 这个代码片段会被引擎理解为如下形式:
function foo() {
console.log( 1 );
} foo(); // foo = function() {
console.log( 2 );
};
var foo 尽管出现在 function foo()... 的声明之前,但它是重复的声明(因此被忽略了),因为函数声明会被提升到普通变量之前。尽管重复的 var 声明会被忽略掉, 但出现在后面的函数声明还是可以覆盖前面的。
foo(); // function foo() {
console.log( 1 );
} var foo = function() {
console.log( 2 );
}; function foo() {
console.log( 3 );
}
我们来看看下面这个,
function text1() {
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a); // ?
}
text1(); function text2() {
var a = 1;
function b() {
a = 10;
function a() {}
}
b();
console.log(a); // ?
}
text2();
想一想,这两段代码输出的结果会是什么?
结果都是1!为啥???
这里需要注意的是,在 function b() 中,function a() 由于存在函数提升,上述代码实际上的运行代码是这样子的,
function text{
var a = 1;
function b() {
var a = function(){};
a = 10;
//return; //这个return对这段代码没有任何影响
}
b();
console.log(a); 1
}
是不是很神奇~~~~所以在写代码的时候,就要特别注意了,不要因为 JavaScript 的提升机制导致很多莫名其妙的bug出来。
最后还有一个要强调一下,由于一个普通块内部的函数声明通常会被提升到所在作用域的顶部,这个过程不会像下面的代码暗示的那样可以被条件判断所控制:
foo(); // "b" var a = true;
if (a) {
function foo() { console.log("a"); }
}
else {
function foo() { console.log("b"); }
}
function hoistVariable() {
if (!foo) {
var foo = 5;
}
console.log(foo); //
} hoistVariable();
小结:
我们习惯将 var a = 2; 看作一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a和 a = 2 当作两个单独的声明, 第一个是编译阶段的任务,而第二个则是执行阶段的任务。这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候,否则会引起很多危险的问题!
理解变量提升和函数提升可以使我们更了解这门语言,更好地驾驭它,但是在开发中,我们不应该使用这些技巧,而是要规范我们的代码,做到可读性和可维护性。具体的做法是:无论变量还是函数,都必须先声明后使用。
如果对于新的项目,可以使用let替换var,会变得更可靠,可维护性更高。值得一提的是,ES6中的class声明也存在提升,不过它和let、const一样,被约束和限制了,其规定,如果再声明位置之前引用,则是不合法的,会抛出一个异常。
所以,无论是早期的代码,还是ES6中的代码,我们都需要遵循一点,先声明,后使用。
提升----你所不知道的JavaScript系列(3)的更多相关文章
- js值----你所不知道的JavaScript系列(6)
1.数组 在 JavaScript 中,数组可以容纳任何类型的值,可以是字符串.数字.对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的) .----<你所不知道的JavaS ...
- js类型----你所不知道的JavaScript系列(5)
ECMAScirpt 变量有两种不同的数据类型:基本类型,引用类型.也有其他的叫法,比如原始类型和对象类型等. 1.内置类型 JavaScript 有七种内置类型: • 空值(null) • 未定义( ...
- 闭包----你所不知道的JavaScript系列(4)
一.闭包是什么? · 闭包就是可以使得函数外部的对象能够获取函数内部的信息. · 闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. · 闭包就 ...
- let和const----你所不知道的JavaScript系列(2)
let 众所周知,在ES6之前,声明变量的关键字就只有var.var 声明变量要么是全局的,要么是函数级的,而无法是块级的. var a=1; console.log(a); console.log( ...
- LHS 和 RHS----你所不知道的JavaScript系列(1)
变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后在运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值.----<你所不知道的Ja ...
- 你所不知道的JavaScript数组
相信每一个 javascript 学习者,都会去了解 JS 的各种基本数据类型,数组就是数据的组合,这是一个很基本也十分简单的概念,他的内容没多少,学好它也不是件难事情.但是本文着重要介绍的并不是我们 ...
- 你所不知道的javascript数组特性
工作中,我们经常使用js的数组,但是,下面的东西你见过吗? 1,文本下标: var a=[]; a[-1]=1; 你想过数组的下标为负数的情况吗?我们对数组的下标规定从0开始.但是上面那么写也还是可以 ...
- JavaScript中你所不知道的Object(二)--Function篇
上一篇(JavaScript中你所不知道的Object(一))说到,Object对象有大量的内部属性,而其中多数和外部属性的操作有关.最后留了个悬念,就是Boolean.Date.Number.Str ...
- 你所不知道的html5与html中的那些事第三篇
文章简介: 关于html5相信大家早已经耳熟能详,但是他真正的意义在具体的开发中会有什么作用呢?相对于html,他又有怎样的新的定义与新理念在里面呢?为什么一些专家认为html5完全完成后,所有的工作 ...
随机推荐
- 《R数据挖掘入门》彩色插图(第8章)
图8.4 图8.5 图8.6 图8.7
- .NET 控制Windows文件和目录访问权限研究(FileSystemAccessRule)
前一段时间学习了.net 控制windows文件和目录权限的相关内容,期间做了一些总结.想把这方面的研究跟大家分享,一起学习.其中不免得有些用词不太标准的地方,希望大家留言指正,我加以修改. 首先,我 ...
- [20180713]关于hash join 测试中一个疑问.txt
[20180713]关于hash join 测试中一个疑问.txt --//上个星期做的测试,链接: http://blog.itpub.net/267265/viewspace-2157424/-- ...
- EasyUI报错 $(...).accordion is not a function
参考资料: https://stackoverflow.com/questions/9017634/accordion-is-not-a-function 原因:加载了2次jquery js文件
- Android中SELinux的TE简介【转】
转自:https://blog.csdn.net/murphykwu/article/details/52457667 selinux的概念如上一篇链接所示: http://www.cnblogs.c ...
- 鸟哥的 Linux 私房菜Shell Scripts篇(四)
12.4 条件判断式 只要讲到『程式』的话,那么条件判断式,亦即是『 if then 』这种判别式肯定一定要学习的!因为很多时候,我们都必须要依据某些资料来判断程式该如何进行.举例来说,我们在上头的a ...
- [MapReduce_1] 运行 Word Count 示例程序
0. 说明 MapReduce 实现 Word Count 示意图 && Word Count 代码编写 1. MapReduce 实现 Word Count 示意图 1. Map:预 ...
- svg 认识及动画
svg在线中文教程 https://svg.brucewar.me/ http://www.zhangxinxu.com/wordpress/tag/svg/ http://svgtrick.com ...
- 解决 Mac 突然没有声音
前言 偶尔早上上班时发现Mac突然没有声音了,不明所以.虽然重启也可以,但是也不免太麻烦了.也许是人品不太好,遇到过多次这种情况,就在快要砸了的自己mac的时候,脑中灵光一闪,难道这是因为核心音频守护 ...
- 阿里八八β阶段Scrum(3/5)
今日进度 叶文滔: 添加了侧边栏调用数据库用户名的功能,因为对Navigation View的不熟悉,走了很多弯路,尝试了三种方法才成功调用. 俞鋆: 研究了几个图像识别的api,最终决定使用ocr. ...