在js语言中,没有类似于c语言这样的块级作用域。
js语言中的顶级作用域为window对象范围内,称为全局作用域,在全局作用域中声明的变量为全局变量。
js函数范围内的变量只能在函数内部使用,函数外部无法使用,这样的变量为局部变量。
js函数可以嵌套,多个函数的嵌套构成了作用域的层层嵌套,这称为js中的作用域链。
js作用域链变量访问规则:
(1)当前作用域内存在要访问的变量时,则使用当前作用域中的变量。
(2)当前作用域中不存在要访问的变量时,则会到上一层作用域中寻找,直到全局作用域。
1.函数作用域
var scope="global"; function t(){ console.log(scope); var scope="local" console.log(scope); } t();
第一句输出的是: "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没有块级作用域。
因此一下代码就很好理解了
function t(flag){ if(flag){ var s="ifscope"; for(var i=0;i<2;i++) ; } console.log(i); console.log(s); } t(true);
2.变量作用域
先看这段代码
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)也是会输出“ifscope"
3.Js作为属性的变量
当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符来删除。如果你没有使用严格模式并给一个未声明的变量赋值的话,javascript会自动创建一个全局变量。
以这种方式创建的变量是全局对象的正常可配置属性,并可以删除它们
var a =1; b =2; this.b2 = 3; delete a; //不可删除 delete b; //可删除 delete this.b2 //可删除
尽管var声明的全局变量是属于window对象的属性(在浏览器中),但依然是无法删除的,因为这种属性的configurable=false,因此不能delete掉。可通过 Object.getOwnPropertyDescriptor()
获取
4.作用域链
name="lwy"; function t(){ var name="tlwy"; function s(){ var name="slwy"; console.log(name); } function ss(){ console.log(name); } s(); ss(); } t();
当执行s时,将创建函数s的执行环境(调用对象),并将该对象置于链表开头,然后将函数t的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显-name是"slwy"
但执行ss()时,作用域链是: ss()->t()->window,所以name是”tlwy"
例子:
<html> <head> <script type="text/javascript"> function buttonInit(){ for(var i=1;i<4;i++){ var b=document.getElementById("button"+i); b.addEventListener("click",function(){ alert("Button"+i);},false); } } window.οnlοad=buttonInit; </script> </head> <body> <button id="button1">Button1</button> <button id="button2">Button2</button> <button id="button3">Button3</button> </body> </html>
当文档加载完毕,给几个按钮注册点击事件,当我们点击按钮时,会弹出什么提示框呢?
三个按钮都是弹出:"Button4"
当注册事件结束后,i的值为4,当点击按钮时,事件函数即function(){ alert("Button"+i);}这个匿名函数中没有i,根据作用域链,所以到buttonInit函数中找,此时i的值为4
5.块级作用域let概念
块作用域的用处:变量的声明应该距离使用的地方越近越好。并最大限度的本地化。避免污染。
let声明的变量可以将变量绑定到所在的任意作用域中。({ .. }内部)
function aaa(){ console.log(b); let b=1; console.log(b); } aaa()
let声明的变量并不会变量提升
const-固定常量,不可被修改,否则报错。
for(var i=1;i<=3;i++){ setTimeout(function(){ console.log(i); //输出:4,4,4 },0) }
既然延时器时间设置为0,那么应该执行一遍循环就应该立即打印出一个i,但是最终的打印结果为:4,4,4。之所以会出现上面的结果,正是因为js代码是单线程应用。
在执行过程中,先遇到for循环,for循环先进入线程。当i=1时,循环走到setTimeOut后,此时的for循环还没有执行完成,setTimeOut就会被放入一个地方(线程池)等待执行。此时for循环继续执行,当i=2时,for循环仍没有执行完,这时的setTimeOut仍会被放在线程池中等待执行……依次类推,直到for循环转完3遍后,for循环执行完了,此时线程空闲了,在线程池中等待执行的setTimeOut依次执行打印i,而for循环执行完成后,i变成了4,所以打印出了三个4。
如果想要改变上面的状况可以运用以下代码:
//将var变为let for(let i=1; i<=3; i++){ setTimeout(function(){ console.log(i); //输出的结果为1,2,3 },0); }
for循环中的变量变成块儿作用域:
因为for循环头部的let不仅将i绑定到for循环快中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了;这个匿名函数的参数作用域 和 for参数的作用域 不一样,是利用了这一点来完成的。
6.例题
a = 100; function demo(e){ arguments[0]=2; function e(){} console.log(e); if(a){ var b = 123; } a=10; var a; console.log(b); function c(){}; var c; f =123; console.log(c); } var a ; demo(1); console.log(a); console.log(f);
输出:2 undefined function c(){} 100 123
理解:1.if条件不成立时,{}里面的变量依旧会提升。
2.函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被变量赋值后覆盖(不会重复提升,但会重新赋值)