1. 作用域(Scope)的概念
作用域指一个变量的作用范围。在 js 中,一共有两种作用域:
全局作用域:直接编写在 script 标签中的 JS 代码,都在全局作用域。
全局作用域在页面打开时创建,在页面关闭时销毁。
在全局作用域中有一个全局对象 window,它由浏览器创建我们可以直接使用。
函数作用域:函数体内部
2. 全局变量和局部变量
2.1 全局变量
在全局作用域中:
创建的变量都会作为 window 对象的属性保存。
创建的函数都会作为 window 对象的方法保存。
全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问的到。
2.2 局部变量
在函数体内部的声明的变量
如果一个变量在函数体内部声明,则该变量只能在该函数体内使用,在函数体外不可引用该变量。
var y = 10; // 全局变量
function foo() {
var x = 1; //局部变量
console.log(y); // 10
}
foo();
console.log(x); // 报错
// 也就是说,函数体内部声明的,在函数体外部是不能使用的。
// 在全局作用域下声明的全局变量,可以在页面任意部分访问得到。
3. 作用域的上下级关系(作用域链)
当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。
如果没有则向上一级作用域中寻找,直到找到全局作用域。
如果全局作用域中依然没有找到,则会报错 。
function foo() {
var x = 1;
function bar() {
var y = x + 1; // 会找到 foo函数里边创建的 x 使用
var z = 100;
}
var z = y + 1; // 找不到y, 不可以访问bar函数的变量y!
function ins() {
x++; // 会找到 foo函数的变量 x
y++; // 找不到y,不可以访问 bar函数的变量y
var z = 1000;
}
// 注意 bar 和 ins 中声明的 变量z 是相互独立的,互不影响
}
var x = 10;
bar();
function foo() {
console.log(x);
// 找x,先在自身函数作用域查找,到自己上一级作用域 (此时是全局作用域)查找
// 虽然foo函数是在bar函数里边调用的,但是 bar函数并不是foo的上一层作用域
// 因为作用域是在 定义函数时确定的
}
function bar() {
var x = 30;
foo(); // 运行foo函数
}
4. 变量和函数声明提升
4.1 预解析
JavaScript代码的执行是由浏览器中的JavaScript解析器来执行的。JavaScript解析器执行JavaScript代码的时候,分为两个过程:预解析过程和代码执行过程
预解析过程:
把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
把函数的声明提升到当前作用域的最前面,函数声明代表函数整体,所以函数提升后,函数名代表整个函数
先提升var,再提升function。
预解析会把变量和函数的声明在代码执行之前执行完成。
预解析过程就是变量提升的过程。
4.2 变量声明提升
- 使用 var 关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前(预解析)被声明(但是不会赋值)
- 但是如果声明变量时不是用 var 关键字(比如直接写 a = 1),则变量不会被声明提前。(不推荐)
console.log(a); // 打印 undefined ,说明a已经被声明了,只是没有赋值
var a = 10;
console.log(b); // b is not defined 没有声明提升
b = 2; // 此时 b 相当于 window.b (全局变量)
console.log(b); // 2
以上代码相当于:
var a;
console.log(a);
a = 10;
console.log(b);
b = 2;
console.log(b);
4.3 函数声明提升
使用函数声明的形式创建的函数
function foo(){}
,会被声明提前。也就是说,整个函数会在所有的代码执行之前就被创建完成,所以我们可以在函数声明之前,调用函数。使用函数表达式创建的函数
var foo = function(){}
,不会被声明提前,所以不能在声明前调用。很好理解,因为此时foo
被声明了,且为 undefined,并没有把function(){}
赋值给foo
。
foo(); //执行
bar(); //报错 此时 bar 为undefined
function foo() {
console.log("我是foo函数");
}
var bar = function() {
console.log("我是bar函数");
};
在函数作用域也有声明提前的特性:会提升到该函数作用域的最前边。
5. 练习
function f1() {
var num = 123;
function f2() {
var num = 0;
console.log(num); // 站在目标出发,一层一层的往外查找0
}
f2();
}
var num = 456;
f1();
var a = 1;
function fn1() {
var a = 2;
var b = "22";
fn2();
function fn2() {
var a = 3;
fn3();
function fn3() {
var a = 4;
console.log(a); //4
console.log(b); //22
}
}
}
fn1();
var boy = 20,
girl = 18,
marry = 22;
function love(marry) {
// 指定的形参marry,相当于在函数内部创建了变量 marry , var marry;
boy = 24; // 修改了全局变量 boy 改成了 24
var girl = 22; // 创建了局部变量 girl
marry = 25; // 修改的是函数内部的marry
console.log(marry); // 25
}
love(22);
console.log(boy, girl, marry); // 24,18,22
f1();
function f1() {
var b = 9;
console.log(b); // 9
}
var num = 10;
fun();
function fun() {
console.log(num);//undefined
var num = 20;
console.log(num);//20
}
var b = 1;
function fun1() {
b = 2; //修改了全局变量 b
function fun2() {
var b = 3;
console.log(b); //3
}
fun2();
console.log(b); //2
}
fun1();
console.log(b); //2
var b = 1;
function fun1(b) {
//var b;
//b = 'yy';
b = 2; // 相当于修改了参数b
console.log(b); //2
}
fun1('yy');
console.log(b); //1
var b = 2;
function test2() {
window.b = 3; //修改了全局变量 b
console.log(b); //3
}
test2();
console.log(b); //3
c = 5;
function test3() {
window.c = 3; //修改了全局变量 c
console.log(c); //找到局部变量 c,所以是undefined
var c;
console.log(window.c); //3
}
test3();
console.log(c); //3