作用域
一、基本概念
作用域: 作用域说的是变量起作用的区域或者范围。
作用: 变量在各自的作用域当中起作用。
二、局部变量与全局变量、局部作用域与全局作用域
局部变量和全局变量,没有任何关系,两者是相互独立的个体。唯一的区别在于:全局变量在哪都可以使用,而局部变量只能在自己局部作用域当中使用。
案例引入
var a = 10;
var b = 20;
function f1()
{
a = 20;
b = 10;
console.log(a,b)
}
f1();
console.log(a,b);
我们知道,当我们在定义变量的时候,都会给前面加一个var,但是也有不加var的情况。而这种情况又根据环境不同分为以下两者情况:
- 如果是在
全局:没有定义过的变量,不加var,那么必须给这个变量赋值;而且赋值相当于给这个变量加var,赋值完后它是一个地地道道的全局变量。 - 如果是在
局部(如函数内部):没有定义过的变量,而且所处函数也没有一个参数是它,此时定义这个变量不加var,首先要看函数外部全局是否定义过这个变量。如果全局定义过,那么这个变量就不是在初始化,而是在操作全局变量。如果没有定义过,相当于在全局加var定义了这个变量。
所以,这就是最终结果a = 20 , b = 10的原因。
案例分析
先说结论:JS的作用域,跟调用位置无关,而是跟定义位置有关。
var num = 10;
function fun()
{
var num = 20;
fun2();
}
function fun2()
{
console.log(num);
}
fun();
举例说明
-
例一 var num = 0;
function f1()
{
var num = 1;
function f2()
{
var num = 2;
function f3()
{
var num = 3;
function f4()
{
var num = 4;
console.log(num);
}
f4();
}
f3();
}
f2();
}
f1();
在函数f4内使用var定义了一个变量num,这个num的作用域就是f4函数。 -
例二 var num = 0;
function f1()
{
var num = 1;
function f2()
{
var num = 2;
function f3()
{
var num = 3;
function f4()
{
console.log(num);
}
f4();
}
f3();
}
f2();
}
f1();
删掉f4内的num变量,使用console.log进行输出时,发现输出的是函数f3内的同名num变量。 -
例三 var num = 0;
function f1()
{
function f2()
{
function f3()
{
function f4()
{
console.log(num);
}
f4();
}
f3();
}
f2();
}
f1();
删除函数f1、f2、f3、f4内的num变量,而保留 全局环境中的num变量,发现此时输出的是全局的num变量的值。 -
例四
function f1()
{
function f2()
{
function f3()
{
function f4()
{
console.log(num);
}
f4();
}
f3();
}
f2();
}
f1();
连同全局下的num变量一并删掉,再次进行输出,发现程序报错,找不到 num这个变量。 -
例五 function f1()
{
function f2()
{
function f3()
{
function f4()
{
num = 4;
console.log(num);
}
f4();
}
f3();
}
f2();
}
f1();
console.log(num);
不使用var,在f4内定义num变量,结果正如我们所说,如果全局没有定义过这个变量,那么这一步相当于在全局加var定义了这个变量。
分析结论:
由以上的五个例子,结合我们所说的“函数作用域的嵌套关系是由定义位置决定的”,我们可以得到一个简单结论:
嵌套作用域里的变量在查找的时候,首先从自己的作用域当中去查找。
如果自己的作用域内没有,则会往上一级作用域当中去查找。一直查找到函数外部的全局作用域。
只要找到,立即停止往上找,直接输出。如果没有找到,会一直往上找,如果最后查到函数外部的全局还没有的话,则报错。
这个查找的顺序过程,我们称其为“作用域链”。
案例说明
var num = 10;
function fun()
{
var num = 20;
function fun2()
{
console.log(num);
}
fun2();
}
fun();
最重要的一条规则: 作用域的嵌套关系是由定义位置决定的,作用域链是在定义时就随即出现的,而不是由调用位置决定的。
三、图解全局环境与局部环境作用过程(内存是怎么表现的)
var a = 10;
function f1()
{
var b = 11;
};
f1();

预解析
预解析,又被称为声明提升、变量提升。
两个基本原则:
-
变量 预解析只会解析带var的变量,如果不带var,则不进行预解析。 -
函数
-
function f1(){} (字面量定义) 如果是这种写法,函数整体会提升。 整体提升后,要当成完全体看待,则可看成var f1 = function(){}整体提升。 -
var f1 = function(){}(函数表达式定义) 如果是这种写法,只会提升var f1,不会提升函数的表达式。
预解析有什么效果
-
变量 全局当中所有带var的变量,以及使用字面量定义的函数,都会提升到全局的最上方。而最重要的是:变量的声明会被提升到当前作用域的最上面,但变量的赋值不会提升。 -
函数 函数当中所有带var的变量,以及使用字面量定义的函数,都会提升到这个函数的局部环境的最上方。而最重要的是:函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
通过案例来理解预解析的效果
-
alert(a);
a = 0;
-
alert(a);
var a = 0;
alert(a);
根据变量的声明会被提升到当前作用域的最上面,但变量的赋值不会提升的原则,其实它的代码在预解析之后是这个样子: var a
console.log(a)
a = 0
console.log(a)
所以是先输出undefined,后输出0。 -
alert(a);
var a = '我是变量';
function a(){ alert('我是函数'); }
alert(a);
解析过程:
-
开始解析,全局扫描,提升位置。 -
提升位置后的新代码相当于: var a;
function a(){ alert('我是函数'); }
alert(a);
a = '我是变量';
alert(a);
-
又因为,function a(){ alert('我是函数'); }的完全形式为var a =function(){ alert('我是函数'); },所以会把上面的var a; 给覆盖掉。 -
alert(a);
a++;
alert(a);
var a = '我是变量';
function a(){ alert('我是函数'); }
alert(a);
解析后的新代码相当于: var a;
function a(){ alert('我是函数'); }
alert(a);
a++;
alert(a);
a = '我是变量';
alert(a);
-
alert(a);
var a = 0;
alert(a);
function fn()
{
alert(a);
var a = 1;
alert(a);
}
fn();
alert(a);
-
alert(a);
var a = 0;
alert(a);
function fn()
{
alert(a);
a = 1;
alert(a);
}
fn();
alert(a);
预解析的优先级(判断预解析的终极方法)
先去解析函数,函数如果有同名会发生覆盖。
再去解析var的变量,如果变量跟函数有同名,真正的情况是忽略。(下面赋值的时候才管你)
案例
alert(a);
var a = '我是变量';
function a(){ alert('我是函数') }
alert(a);
function a(){ alert('我是函数2')}
var a = '哈哈';
alert(a);
分析:
function a(){ alert('我是函数') }
function a(){ alert('我是函数2')}
var a;
var a;
alert(a);
a = '我是变量';
alert(a);
a = '哈哈';
alert(a);
结果正如分析。
|