1.作用域的概念
任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
局部作用域和全局作用域
在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
引入:
<body>
<script>
//1.变量,函数作用的范围???
//2.变量为什么前置访问undefined,而函数可以任何地方调用????
</script>
<script>
alert(a) //报错Uncaught ReferenceError: a is not defined(为什么???)
</script>
<script>
alert(a) //undefined
var a = 3;
alert(a); //3
</script>
<script>
alert(a) //3
</script>
</body>
全局作用域(变量):整个程序都有效,即整个代码中都可以调用(特殊:变量前面没有添加var变成全局的属性,也是整个程序都有效)。
(1)全局作用域(全局变量):对整个script块内都是有效,也可以理解成作用于整个文档。(一般情况下一个文档只有一个script块,要是有多个script,那么全局变量会对所在的script块以及后面的script块起作用,在之前的script块输出变量会报错)(全局变量可以进入函数内)
var a=5;
function fn(){
return a;//a是全局变量,此处的a就是5,return把5返回给调用者,谁调用给谁
}
alert(fn());//5调用函数,并在alert显示
(2)特殊情况,如果声明的变量前面没有var关键字,变量变成了全局的属性(全局变量)
function fn(){
a=1;//全局属性,变量是window下面的属性。
return a;
}
fn();(函数必须调用否则,函数体等于不存在)
console.log(a);//1,a是全局属性,故函数外也可以获取a的值
变量是window下面的属性
上述的a就是window下面的属性,只不过正规写法是window.a,window被省略了。
var num=100;
console.log(num);//100
console.log(window.num);//100
window.document.write('abc');
局部作用域(变量):只对函数内部有效,即只能在本变量声明的函数内部调用,函数外部无法获取。
function fn() {
var num = 5;
return num;
}
alert(num)//报错,原因是num定义在函数体内,无法获取,
//Uncaught ReferenceError: num is not defined
function a() {
//alert(num); //报错:Uncaught ReferenceError: num is not defined
function b() {
var num = 4;
alert(num)//输出4
} b()
}a()
function a() {
var num = 5;
alert(num) //5
function b() {
var num = 4;
alert(num) //4
}b();
}a()
!在函数体内,局部变量的优先级高于同名的全局变量-作用域链
作用域链:内—外代码执行过程。
var a=1;//全局的
function fn(){
var a=2;//局部的//
console.log(a)//2
function fn1(){
var a=3;//局部的
console.log(a);//3从内到外寻找a的值输出
}
fn1();
}
fn();
console.log(a)//1
var a = 2;
function fn() {
a = 4;//将之前a=2的值覆盖;
b = 5;
console.log(a);
console.log(b);
}
fn()
console.log(a);
console.log(b)
2.JS的编译和执行
JS的解析过程分为两个阶段:预编译期(预处理)与代码逐行执行期。
第一阶段(预编译期):JS会对本代码块(script)中的所有声明的变量和函数进行处理(类似与C语言的编译,)但需要注意的是此时处理函数的只是声明式函数,而且变量也只是进行了声明但未进行初始化以及赋值。
第一阶段(预解析:浏览器编译执行代码之前,写完之后)
1、先找var和function关键字,如果找到var关键字,提前赋值undefined给变量名. 如果找到function,提前将整个函数赋值给函数名称。
2、如果函数和变量出现重名,函数优先。
3、函数的参数类似于变量,函数内部同样做预解析,支持预解析。
4、if语句和for语句里面的变量和函数做预解析提前赋值undefined
第二阶段(执行期):在编译后的基础上开始从上到下执行脚本,遇到错误时中断(函数声明直接跳过)。
例1
alert(a); //function a() {alert('不好');}//函数名和变量名重合,函数名优先
var a = 1;
alert(a); //1
var a = 2;
alert(a); //2
function a() {
alert('你好')
}//函数未调用,直接跳过
alert(a); //2
function a() {
alert('不好');
}//函数未调用直接跳过
alert(a); //2
例2
函数内部同样要做预解析。
*function fn(){
alert(a);//undefined
var a=1;
alert(a);//1
}
fn();*
例3
函数的参数类似于变量,函数内部同样做预解析,支持预解析。
var a = 1;
function fn(a) {//a是函数fn的形参,在这里a被赋了值,函数的形参只能作用于函数体内部
alert(a);//1
a = 2;//重新给形参a赋值
alert(a);//2
}
fn(a);
alert(a);//1 函数体内,虽然给a重新赋了值,但是函数体内的参数无法在函数体外获取,故此处的a的值还是函数体外的a
例4
if语句和for语句里面的变量和函数做预解析提前赋值undefined
function fn() {
alert(a);//undefined
if (false) {
var a = 1;
}
alert(a)//undefined
}
fn()
例5
alert(fn);//fn为if函数里面的函数的函数名,if里面的函数和变量预解析,提前被赋值undefined
if (true) {
function fn() {
alert(1)
}
}
alert(fn);// function fn() {alert(1)}//按顺序执行,函数名等于函数体,直接输出函数体
例6
alert(a);// function a() {alert(1);},函数名和变量重名,函数优先,函数名等于函数体,输出函数体
var a = 1;
alert(a);//1
function a() {
alert(1);
}//函数未被执行,因为未调用
alert(a);//1
a = 2;
alert(a);//2
var a = function() {
alert(2);
}//此处不是函数,是定义变量,属于赋值
alert(a)//function() {alert(2);}
例7
var a = 1;
function b(a) {
alert(a);//1,函数的形参a被赋了值,为1;
var a = 2;
alert(a);//2,a的值被覆盖了
}
b(a);//调用函数
alert(a)//1无法获取函数内a的值,直接打印函数外的a的值
例8
var x = 1;
var y = 2;
var z = 3;
sum(x, y)//-1,第一个函数被第二个函数覆盖,无论在什么地方调用,都只会执行第二个函数
function sum(x, y) {
alert(x + y)
}
sum(x, y);//-1 第一个函数被第二个函数覆盖,无论在什么地方调用,都只会执行第二个函数
function sum(x, y) {
alert(x - y)
}