当你看见 var a = 2; 这段程序时,很可能认为这是一句声明。但实际上,它是由 var a ; a=2 ; 这两句构成的, var a; 发生在编译时,a = 2;发生在运行时。
变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过(如果该变量已经存在,则会忽略本次声明)),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对他赋值。
作用域:根据名称查找变量的一套规则。
作用域链:由内向外访问,作用域查找会在找到的第一个匹配的标识符时停止。(遮蔽效应)
全局变量会自动成为全局对象的属性,因此可以通过window.a 来在嵌套的作用域中直接访问全局变量
JavaScript没有块级作用域(全局作用域、函数作用域和eval作用域 )。幸好,ES6改变了现状,引入了新的关键字let,const来形成块级作用域。
var foo = true;
if (foo) {
{
let a = 1;
var b = 2;
console.log(a); //1
}
}
var foo = true;
if (foo) {
{
let a = 1;
var b = 2;
}
console.log(a); // ReferenceError: a is not defined
}
const同样可以用来创建块作用域变量,但是其值是固定的,之后任何试图修改值的操作都会引起错误。
提升机制:
console.log( a );
var a = 2 ;
你可能会认为输出2 或者抛出ReferenceError异常。
但是,这两种猜想都不对, 输出来的是undefined。
正确的思路是:包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。
实际的执行流程如下:
var a ;
console.log(a);
a = 2;
再举一个例子:
foo();
function foo(){
console.log(a);//undefined
var a = 2;
}
实际的执行流程如下:
function foo(){
var a;
console.log(a); //undefined
a = 2;
}
foo();
注:函数表达式没有提升机制
foo(); //TypeError
var foo = function (){
console.log(a);
var a = 2;
}
实际的执行流程如下:
var foo;
foo(); //执行的时候还没有被赋值
foo = function (){
console.log(a);
var a = 2;
}
函数优先:函数声明和变量声明都会被提升。但是,函数会首先被提升,然后才是变量。
foo(); //1
var foo;
function foo () {
console.log(1);
}
foo = function () { console.log(2);
}
会输出1而不是2,这段代码会被引擎理解为如下形式:
function foo () {
console.log(1);
}
foo(); //1
foo = function () { console.log(2);
}
注意:var foo 尽管出现在function foo () {}声明之前,但它是重复声明,因此被忽略掉了。 ES3里,变量声明与函数声明会被提升(编译阶段)。函数声明冲突会覆盖,变量冲突会忽略。
接下来,我将写一些作用域相关的题型,以便自己以后查阅:
例1:var a = 10;
function aaa(){
a = 20; //声明一个全局变量
alert(a); //20
}
aaa();
例2: var a = 10;
function aaa(){
bbb();
alert(a); //10
function bbb(){ //这里的函数声明会被提升(提升到当前作用域的顶部)
var a = 10;
}
}
aaa();
例3: var a = 10;
function aaa(){
alert(a);
}
function bbb(){
var a = 20;
aaa(); //10,回过头来调用函数aaa, 并不是将函数aaa搬到这里执行
}
bbb();
例4:function aaa(){
var a = b = 10;
}
aaa();
alert(a); // ReferenceError: a is not defined
alert(b) //10
注:这里是因为var a = b = 10;的时候,a是通过var声明的,是局部变量,但是b没有用var声明,所以是全局变量
例5: var a = 10;
function aaa(a){
alert(a);
var a = 20;
}
aaa(a); //10
注:当参数跟局部变量重名的时候,优先级是等同的。(其实参数也是当前作用域的局部变量)
arguments:
一般模式下,arguments与参数绑定,及通过arguments[0] = 5可以修改传入的参数的值。但是,当修改没有传入的参数时
此时修改之后还是undefined
严格模式下,arguments只是传入参数的副本,通过arguments修改不会起作用。
同时,arguments.callee也是禁止使用的。
var a = 10;
function aaa(a){
arguments[0] = 20;
alert(a); //20
}
aaa(a);
var a = 10;
function aaa(a){
"use strict"
arguments[0] = 20;
alert(a); //10
}
aaa(a);
例6: var a = 10;
function aaa(a){
a += 3;
}
aaa(a);
alert(a); //10
例7: var a = [1,2,3];
function aaa(a){
a.push(4);
}
aaa(a);
alert(a); //[1,2,3,4]
注意:上面例6,例7的不同之处在于:例6传参是简单类型,例7传参是引用类型,而不管是简单类型还是引用类 型,传参都是赋值操作,但是,对于简单类型来说,赋值就是直接在栈区重新申请一块地址,然后各自指 向各自的地址,所以,修改其中一个的值不会影响另一个。但是,对于引用类型来说,变量存储在栈区, 只是存储了一个地址(引用),真正的数据存储在堆区,赋值操作只是完成了地址的赋值,它两最后都指 向了堆区的同一块地址,所以,一方改变该地址中的值,另一方的值也会被改变。
例8:var a = [1,2,3];
function aaa(a){
a = [1,2,3,4] //这个局部变量a指向了一块新地址
}
aaa(a);
alert(a); //[1,2,3]