作用域
一、基本概念
作用域: 作用域说的是变量起作用的区域 或者范围。
作用: 变量在各自的作用域当中起作用。
二、局部变量与全局变量、局部作用域与全局作用域
局部变量 和全局变量 ,没有任何关系,两者是相互独立的个体。唯一的区别在于:全局变量 在哪都可以使用,而局部变量 只能在自己局部作用域 当中使用。
案例引入
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);
结果正如分析。
|