热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JS全局变量局部变量与hoisting

严格模式和非严格模式函数的声明可以是有条件的,比如嵌套在if语句中,有的浏览器会将这种有条件的声明看成无条件的,不论条件是true还是false,我们最好不要这样写;<

严格模式和非严格模式

  • 函数的声明可以是有条件的,比如嵌套在 if 语句中,有的浏览器会将这种有条件的声明看成无条件的,不论条件是true还是false,我们最好不要这样写;
<script type="text/Javascript">
//非严格模式
if(true){
function find(){
console.log(456);
}
}
find();//456

if(false){
function cantFind(){
console.log(123);
}
}
cantFind();//TypeError: cantFind is not a function
script>
  • 在严格模式下,只允许在全局作用域或函数作用域的顶层声明函数。也就是说,不允许在非函数的代码块内声明函数。
<script type="text/Javascript">
'use strict'
//严格模式下
if(true){
function find(){
console.log(456);
}
}
find();//ReferenceError: cantFind is not defined
script>

函数的概念区分

  • 函数声明(function statement),使用function关键字声明一个函数,再指定一个函数名,叫函数声明。如:function fnName(){};
  • 匿名函数(anonymous functions),使用function关键字声明一个函数,但是没有命名,这个函数就叫匿名函数,写法就是:function(){};
  • 函数表达式(function expression),把一个匿名函数当做值传给一个变量,叫函数表达式,这是最常见的函数表达式语法形式。如:var fnName = function(){};

声明、定义、初始化

  • 声明的意思是宣称一个变量名的存在,定义则为这个变量分配存储空间,初始化则是给该变量名的存储空间赋予初始值;
  • Javascript中,变量没有固定类型,其存储空间会随着初始化(并赋值)而变化;

hoisting机制

  • Javascript代码在执行时,一般是一行一行往下执行的,但是这里面又有一个变量声明提升的概念;
  • Javascript引擎在执行时,会把所有变量声明和函数声明都提升到当前作用域的最前面(hoisting机制);

变量声明提升

<script type="text/Javascript">
//console.log(a);//a is not defined a没有声明
console.log(b);//undefined 没找到b(没有初始化赋值),但已经声明了
var b = 'hello world!'; //声明并初始化b赋值'hello world!'
console.log(b);//hello world!
script>

上面代码中 a 和 b 的区别就是b声明提前了,a没有声明;所以上面代码相当于:

<script type="text/Javascript">
var b;//声明b
// console.log(a);//a is not defined a没有声明
console.log(b);//undefined
b = 'hello world!'; //初始化并赋值'hello world!'
console.log(b);//hello world!
script>

hoisting机制对函数名同样有效 当然是在某种意义上的 总体来说会将函数名提前到当前作用域的最开始的地方 如果遇到相同的函数名和变量名时 变量名会被相同的函数名替换掉 但是函数名却不会被相同的变量名顶替掉

<script type="text/Javascript">
play();//函数声明提升了,之所以可以正常运行,是因为定义也被提前了
function play(){
console.log('hello world!');//hello world!
}
script>

相当于

<script type="text/Javascript">
function play(){
console.log('hello world!');//hello world!
}
play();
script>

说明
把一个函数作为值传给一个变量,这时变量会被声明提升,但函数不会,它只是一个函数表达式,我们只需要它的返回值给变量;

  如果它是一个具名函数表达式,那么函数表达式的标识符(函数名)在外部作用域是找不到的,只在内部作用于能找到;

或者

可以像函数声明一样为函数表达式指定一个名字,但这并不会使函数表达式成为函数声明。命名函数表达式的名字不会进入名字空间,也不会被提升。

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

命名函数表达式的名字只在该函数的作用域内部有效。

变量名解析顺序

  同一作用域下,如果变量名和函数名相同,那么函数名会覆盖变量名,不论它们在代码的顺序如何;但是它们的初始化赋值是按顺序执行的;
  同一作用域下应避免使用相同变量名;

<script type="text/Javascript">
console.log(a);//function a(){console.log('我是函数');} 函数名和变量名相同,函数名会覆盖变量名

var a = 'hello world!';
function a(){
console.log('我是函数');
};

console.log(a);//hello world!
a();//TypeError: a is not a function 类型错误
script>

上面代码中,a的值最后是字符串,原因就在于:

变量的赋值是按顺序执行的;
函数声明提升会把表达式也提前;
相同变量名,后面的赋值会覆盖前面的;

相当于

<script type="text/Javascript">
console.log(a);//function a(){console.log('我是函数');} 函数名和变量名相同,函数名会覆盖变量名

function a(){
console.log('我是函数');
};
var a = 'hello world!';

console.log(a);//hello world!
a();//TypeError: a is not a function 类型错误
script>

全局变量和局部变量

<script type="text/Javascript">
var a = 1;
function test()
{

window.alert(a);
var a = 2;
window.alert(a);
}
test();
script>

所以上述代码可以解释为在test()内声明局部变量a 并且拥有比在test()作用域内比全局函数a有更高的优先级 同时根据hoisting机制 声明了a 但是没有定义 所以第一个结果是undefined 第二个弹出2

总结下就是
一个页面里直接定义在script标签下的变量是全局变量即属于window对象的变量,按照Javascript作用域链的原理,当一个变量在当前作用域下找不到该变量的定义,那么Javascript引擎就会沿着作用域链往上找直到在全局作用域里查找。

首先这段程序涉及到了以下三个概念 执行环境 变量对象 作用域链 js的执行环境分全局的(浏览器的话就是window执行环境)和function执行环境,变量对象是用来保存执行环境下的变量和方法的,而作用域链上放着一个一个的变量对象形成一个链条。 这段代码的执行过程应该是这样的 首先进入全局执行环境 建立该执行环境下的变量对象A(保存有该执行环境下的x和一个匿名方法),再往下执行到匿名方法的执行环境 建立变量对象B(保存有该执行环境下的x),而js的当前执行环境的变量对象永远放在作用域链的最前端,所在执行第一个alert(x), 就会找当前执行环境的变量对象B是否保存有x, 而事实上是有的,但alert(x)之前没有给x赋值,所出得到的结果就是undefined, 如果变量对象B中不存在x,那么程序就会顺着作用域链找上一个变量对象A里是否有x.

js的变量有两种作用域全局变量和局部变量。没有使用 var 声明的变量和在function之外声明的变量都是全局变量,是window属性之一;使用 var 声明的变量属于所在函数,不管在函数的哪个位置出现,等价于在函数一开始声明。局部变量比同名全局变量的优先级高,所以局部变量会隐藏同名的全局变量。要想访问被隐藏的全局变量就加上 window. 前缀。
js没有块作用域,在语句块之后,在函数结束之前,语句块定义的变量都是可以访问的。比如:for(var idx =0; idx<2;idx++){} alert(idx); 结果显示为2。

    <script type="text/Javascript">
var a = 1;
function hehe()
{

window.alert(window.a);
var a = 2;
window.alert(window.a);
}
hehe();
script>

这样结果就是1,1

举个栗子

var a = 10;
function test(){
a = 100;
console.log(a);
console.log(this.a);
var a;
console.log(a);
}
test();

100 10 100
解析:Javascript在执行前会对整个脚本文件的声明部分做完整分析(包括局部变量),从而确定变量的作用域,所以在函数test执行前,由于第6行声明了局部变量a,所以函数内部的a都指向已经声明的局部变量,所以第4行输出100。第5行输出this.a,我们都知道,函数内部的this指针指向的是函数的调用者,在这里函数test被全局对象调用,所以this指针指向全局对象(这里即window),所以this.a = window.a,一开始生命了全局变量a=10,所以第5行输出结果为10。第7行输出结果为100,因为局部变量a在第3行已经被赋值了100,所以直接输出局部变量a的值。

var a = 100;
function test(){
console.log(a);
var a = 10;
console.log(a);
}
test();

undefined 10
解析:看了第1个例子,可能有同学会认为输出结果是10 10,但是结果却不是10 10,为什么呢?仔细看第1个例子解析的第一句话,Javascript在执行前会对整个脚本文件的声明部分做完整分析(包括局部变量),但是不能对变量定义做提前解析,在这个函数中,执行第3行前,可以认为已经声明了变量a,但是并没有定义(这里即赋值),所以第3行输出结果为undefined,执行第4行a =10后,变量a的值就为10,所以第5行输出结果为10。

var a = 100;
function test(){
console.log(a);
a = 10;
console.log(a);
}
test();
console.log(a);

100 10 10
解析:我们知道在函数内部,一般用var声明的为局部变量,没用var声明的一般为全局变量,在test函数内,a=10声明了一个全局变量,所以第3行的a应该输出全局变量的值,而在函数执行之前已经声明过一个全局变量并赋值100,所以这里第上输出100。第4行给全局变量a 重新赋值10,所以全局变量a的值变成10,所以第5行输出10。而在函数test外部,第8行输出全局变量a的值,因为全局变量被重新赋值为10,所以输出结果即为10。

<script type="text/Javascript">
b = c;
console.log(b);
console.log(c);
b();
console.log(a);
function c() {
a = 1, b = 2, c = 3;
};
script>

函数c还没运行,所以里面的变量是不知道的,但是函数c已经声明提前了,又因为第一行b=c,所以b和c都是函数c ;

  函数c运行之后,里面的变量a,b,c被发现是全局变量,并且给它们赋值,所以外部能找到a=1,并且你还会发现b=2; c=3;

  如果函数c不曾运行过,那么是找不到它们的 ;

<script type="text/Javascript">
b = function c() {
a = 1, b = 2, c = 3;
};

b();
console.log(a);
console.log(b);
console.log(c);
script>

 函数b运行之后,发现了里面的全局变量,并且给它们赋值a=1; b=2;

  但是有个特殊的变量c,c是函数表达式的名字,函数的名字,规范说明函数名不能被改变,所以这里设置c=3其实是对函数名赋值,是无效的,也不会被当做全局变量;而且函数表达式的标识符只有内部作用域可以找到,所以外部是找不到的,报错ReferenceError: c is not defined 引用错误;

作用域和this

var a = 10;
function test(){
a = 100;
console.log(a);
console.log(this.a);
var a;
console.log(a);
}
test();
var a = 100;function test(){
console.log(a);
var a = 10;
console.log(a);
}
test();
var a = 100;function test(){
console.log(a);
a = 10;
console.log(a);
}

test();
console.log(a);

在非严格环境下,以上三个代码分别输出什么?碰到这种题目我也是头晕眼花,稍有不慎就掉坑了。当然实际业务中不会出现这样的代码,但还是相当有必要以这样的代码来检查对 Javascript 理解的程度。
this 的用法参照阮一峰老师的博客,主要分为三种情况,但总的原则是指向调用函数的那个对象。

全局环境:调用函数的对象实际为 window ,所以函数内的 this 指向 window ;
构造函数:通过构造函造函数生成了一个新对象,this 指向这个新对象。
对象的方法:函数作为对象的某个方法调用, this 就指向这个上级对象。

故第一道题中属于全局环境, this 指向 window ,所以输出结果为:100,10,100;
第二道题输出结果为:undefined,10;第三道题输出结果为:100,10,10;

其实 一句话 谁调用this指向谁

再说hoisting

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";
})();//这种函数看jQuery的源码 括号里写了windows 作为入口。

声明、定义与初始化的补充

声明宣称一个名字的存在,定义则为这个名字分配存储空间,而初始化则是为名字分配的存储空间赋初值。
用C++来表述这三个概念

extern int i;//这是声明,表明名字i在某处已经存在了
int i;//这是声明并定义名字i,为i分配存储空间
i = 0;//这是初始化名字i,为其赋初值为0

Javascript中则是这样

var v;//声明变量v
v = "hello";//(定义并)初始化变量v

因为Javascript为动态语言,其变量并没有固定的类型,其存储空间大小会随初始化与赋值而变化,所以其变量的“定义”就不像传统的静态语言一样了,其定义显得无关紧要。

其实 我觉得 如果在函数作用域内声明时 如果没有找见var 标记的变量声明 仅仅是一个变量名也是可以当做声明的

不然这个怎么解释好呢

var a = 10;
function test(){
a = 100; //可以看成全局变量的声明 但是优先级没有局部变量var a 高
console.log(a);
console.log(this.a);
var a;
console.log(a);
}
test();

JS函数作用域

引用于 http://blog.csdn.net/yueguanghaidao/article/details/9568071

var scope="global";  
function t(){
console.log(scope);
var scope="local"
console.log(scope);
}
t();

(PS: console.log()是firebug提供的调试工具,很好用,有兴趣的童鞋可以用下,比浏览器+alert好用多了)

第一句输出的是: “undefined”,而不是 “global”

第二讲输出的是:”local”

你可能会认为第一句会输出:”global”,因为代码还没执行var scope=”local”,所以肯定会输出“global”。

我说这想法完全没错,只不过用错了对象。我们首先要区分Javascript的函数作用域与我们熟知的C/C++等的块级作用域。

在C/C++中,花括号内中的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的。而Javascript压根没有块级作用域,而是函数作用域.

所谓函数作用域就是说:-》变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。

所以根据函数作用域的意思,可以将上述代码重写如下:

var scope="global";  
function t(){
var scope;
console.log(scope);
scope="local"
console.log(scope);
}
t();
我们可以看到,由于函数作用域的特性,局部变量在整个函数体始终是由定义的,我们可以将变量声明”提前“到函数体顶部,同时变量初始化还在原来位置。

为什么说Js没有块级作用域呢,有以下代码为证:

var name="global";  
if(true){
var name="local";
console.log(name)
}
console.log(name);

都输出是“local”,如果有块级作用域,明显if语句将创建局部变量name,并不会修改全局name,可是没有这样,所以Js没有块级作用域。

现在很好理解为什么会得出那样的结果了。scope声明覆盖了全局的scope,但是还没有赋值,所以输出:”undefined“。

所以下面的代码也就很好理解了。

function t(flag){  
if(flag){
var s="ifscope";
for(var i=0;i<2;i++)
;
}
console.log(i);
console.log(s);
}
t(true);

输出:2 ”ifscope”

JS变量作用域

function t(flag){  
if(flag){
s="ifscope";
for(var i=0;i<2;i++)
;
}
console.log(i);
}
t(true);
console.log(s);

就是上面的翻版,知识将声明s中的var去掉。

程序会报错还是输出“ifscope”呢?

让我揭开谜底吧,会输出:”ifscope”

这主要是Js中没有用var声明的变量都是全局变量,而且是顶层对象的属性。

所以你用console.log(window.s)也是会输出“ifconfig”

当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说无法通过delete运算符删除

var name=1 ->不可删除

sex=”girl“ ->可删除

this.age=22 ->可删除

作用域链

不写了 待完工


推荐阅读
author-avatar
一个都不等
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有