作用域、变量提升的知识点,面试时会经常遇到
作用域的分类
作用域是指一个变量的作用范围
- 全局作用域:作用范围整个 script 标签内部,页面打开时创建,关闭时销毁
- 函数作用域:作用范围函数内部,随着函数调用结束而销毁
全局变量
- 定义在函数外的变量,都是全局变量
- 在函数内部,不使用 var 声明的变量 也是是全局变量
- 全局变量有全局作用域,可以在页面任意位置访问,包括函数中
- 全局变量在页面关闭后销毁
局部变量
- 在函数内部,使用 var 声明的变量是局部变量
- 在函数内部,不使用 var 声明的变量 是全局变量
- 函数的形参也是属于局部变量,只能在函数内部访问
- 局部变量,只能在函数内部访问
- 局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁
作用域生命周期
- 全局作用域在页面关闭后销毁
- 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
- 每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的
作用域的范围
全局变量可以在任意位置访问,局部变量只能在函数内部访问
var a = 'aaa'; // 全局变量
function foo() {
var b = 'bbb'; // 局部变量
c = 'ccc'; // 全局变量
console.log(a); // 打印结果:aaa 说明全局变量可在函数中访问
}
foo();
console.log(b); // 报错:Uncaught ReferenceError: b is not defined 说明局部变量只能在函数内访问
函数内访问全局变量
在函数作用域操作一个变量时,会先在函数内寻找,如果有就直接使用(就近原则),如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错 ReferenceError
在函数中要访问全局变量可以使用window对象(比如说,全局作用域和函数作用域都定义了变量a,如果想访问全局变量,可以使用window.a
)
var a = 'aaa'; // 创建全局变量a
var b = 'bbb';
function foo() {
var a = 111; // 创建局部变量a
console.log(a); // 打印结果:111 就近原则 优先使用局部变量a
console.log(b); // 打印结果:bbb 就近原则 局部没有使用全局
console.log(window.a); // // 打印结果:aaa 强制使用全局变量a
}
foo();
作用域链:内部函数访问外部函数的变量,采用的是链式查找的方式来决定取哪个值,这种结构称之为作用域链。查找时,采用的是就近原则
var num = 10;
function fn() {
// 外部函数
var num = 20;
function fun() {
// 内部函数
console.log(num);
}
fun();
}
fn(); // 20
window对象
在全局作用域中有一个全局对象window,window对象可以在页面任意位置直接访问
- 创建的全局变量都会作为window对象的属性保存,所有的全局变量都是window对象的属性
- 创建的函数都会作为window对象的方法保存
var a = 'aaa'; // 相当于 var a; window.a = 'aaa';
function foo() {
a = 111; // 修改变量a的值 就近原则 这里修改全局变量a 相当于 window.a = 111;
b = 222; // 创建全局变量b 相当于 window.b = 222;
var c = 'ccc'; // 创建局部变量
c = 333; // 修改变量c的值 就近原则 这里修改局部变量c 和window无关
console.log(a); // 打印结果:111 相当于 window.a
console.log(window.a); // // 打印结果:111 强制使用全局变量a 因为被修改了所以是111
}
// 创建的函数都会作为window对象的方法保存
window.foo();
console.log(b); // 函数内隐式声明的全局变量b b必须在调用函数后才会被声明
//console.log(c); // c is not defined c局部变量已经随着函数运行结束销毁了
window.alert('哈哈');
变量提升
JS的代码是从上到下执行的,但下面代码并没有报错
console.log(a); // undefined 并没有报错
var a = 1;
使用var关键字声明的变量,会在所有的代码执行之前被声明
/*
console.log(a); // undefined 并没有报错
var a = 1;
*/
// 上方代码相当于
var a;
console.log(a);
a = 1;
如果声明变量时不使用var关键字,则变量不会被声明提前
console.log(window.a); // undefined 对象的属性如果不存在会报undefined
console.log(a); // ReferenceError: a is not defined
a = 1; // 这里也相当于 window.a 只不过没有var不会被声明提前
举例4:
foo();
function foo() {
if (false) {
var i = 123;
}
console.log(i);
}
打印结果:undefined。注意,打印结果并没有报错,而是 undefined。这个例子,再次说明了:变量 i 在函数执行前,就被提前声明了,只是尚未被赋值。
例4中, if(false)
里面的代码虽然不会被执行,但是整个代码有解析的环节,解析的时候就已经把 变量 i 给提前声明了。
总结:
既然JS中存在变量提升的现象,那么,在实战开发中,为了避免出错,建议先声明一个变量,然后再使用这个变量。
函数的声明提前
函数声明:
- 使用
函数声明
的形式创建的函数function foo(){}
,会在所有的代码执行之前就被创建完成,不仅仅是声明提前 - 所以,使用
函数声明
的形式创建的函数,可以先调用函数,再定义
/*
fun1(); // 虽然 函数 fn1 的定义是在后面,但是因为被提前定义了, 所以此处可以调用函数
function fun1() {
console.log('我是函数 fun1');
}
*/
// 以上代码相当于
function fun1() {
console.log('我是函数 fun1');
}
console.log(fun1); // 输出函数体
fun1(); // 我是函数 fun1
函数表达式:
- 使用
函数表达式
创建的函数var foo = function(){}
,不会被提前创建 - 如果函数表达式有使用var,会提前声明变量
/*
console.log(fun1); // undefined
fun1(); // TypeError: fun1 is not a function
var fun1 = function () {
console.log('我是函数 fun1');
}
*/
// 以上代码相当于
var fun1;
console.log(fun1); // undefined
fun1(); // TypeError: fun1 is not a function
fun1 = function () {
console.log('我是函数 fun1');
}
JS没有块级作用域(ES6之前)
在其他编程语言中(如 Java、C#等),存在块级作用域,由{}
包括起来。比如在 Java 语言中,if 语句里创建的变量,只能在if语句内部使用:
if(true){
int num = 123;
system.out.print(num); // 123
}
system.out.print(num); // 报错
但是,在 JS 中没有块级作用域(ES6之前)。举例如下:
if(true){
var num = 123;
console.log(123); //123
}
console.log(123); //123(可以正常打印)