变量的原理
目录
- 变量的分类
- 作用域
- 作用域链
- 声明提升
- 作用域与作用域链的底层原理(扩展)
变量的分类
-
引入案例
var a = 1; function demo() { var b = 2; console.log(`demo:${a}`); console.log(`demo:${b}`); } demo(); console.log(`外部:${a}`); console.log(`外部:${b}`);
-
概念
- JavaScript根据变量定义的位置不同,将变量分为全局变量和局部变量;
-
分类
- 全局变量
- 定义在函数外部的变量,被称为全局变量(只针对var);
- 局部变量
- 定义在函数内部的变量,被称为局部变量(属于函数级局部变量);
- 全局变量
-
注意
-
如果一个变量未声明定义,直接赋值使用,该变量会自动成为全局变量;
-
function demo(){ b=2; console.log(`demo:${b}`); } demo(); console.log(`外部:${b}`);
-
作用域
-
概念
- 变量作用域指变量的作用范围;
- 全局变量和局部变量的作用域各不相同,分别为全局作用域和局部作用域;
-
分类
-
全局作用域
- 全局变量拥有全局作用域,在当前页面都可以使用(不考虑模块化编程);
-
局部作用域
-
局部变量拥有局部作用域,只能在当前函数内部使用;
-
每个函数都有自己的作用域,各自相互独立;
-
function demo1(){ var a=1; console.log(`demo1:a=${a}`); console.log(`demo2:aa=${aa}`); } function demo2(){ var aa=2; console.log(`demo2:aa=${aa}`); console.log(`demo1:a=${a}`); } demo1(); demo2();
-
-
作用域链
-
引入案例
var a=1; function outer(){ var a=2; console.log(`outer:a=${a}`); function inner(){ var a=3; console.log(`inner:a=${a}`); } inner(); } outer(); console.log(`外部:a=${a}`);
-
概念
-
作用域链决定了作用域嵌套时,变量的访问顺序(优先级);
-
访问顺序(优先级)
-
采用就近作用域原则
-
先在当前代码所在的作用域中查找变量,如果存在,则直接使用,否则,查找上一层作用域是否存在,存在,则直接使用,否则继续查找上一层作用域,直到全局作用域中也未找到目标变量,则程序报错;
-
var a=1; function outer(){ var a=2; var b=4; function inner(){ var a=3; console.log(`inner:a=${a}`);//inner console.log(`inner:b=${b}`);//inner->outer console.log(`inner:c=${c}`);//inner->outer->全局->报错 } inner(); console.log(`outer:a=${a}`);//outer } outer(); console.log(`外部:a=${a}`);//全局
-
作用域链关系在函数定义时就决定了,与函数调用关系无关
-
var a=1; function demo(){ var a=2; var b=4; test(); console.log(`demo:a=${a}`);//demo } function test(){ var a=3; console.log(`test:a=${a}`);//test console.log(`test:b=${b}`);//test->全局 } demo();
-
-
声明提升
-
引入案例
-
console.log(a); var a=1; demo(); function demo(){ console.log('demo'); }
-
概念
-
在作用域中定义变量和函数时,会发生声明提升现象;
-
在作用域中声明的变量和函数,会被提升到当前作用域顶部,可直接访问,与声明位置无关;
-
demo(); function demo() { // var a = 1; console.log('demo',a); var a; } // console.log(a);
-
作用域底层原理(扩展)
-
引入案例
-
var a=1; var b = 2; function a() { console.log('函数执行'); } // console.log(a); a();
-
概念
- 在JavaScript代码执行前,会自动进行全局扫描,并根据扫描的内容自动创建一系列
作用域对象
,用于存储各个作用域中可访问的变量和函数;
- 在JavaScript代码执行前,会自动进行全局扫描,并根据扫描的内容自动创建一系列
-
作用域对象
-
GO(Global Object,全局对象)
-
存储全局作用域中可访问的变量和函数;
-
创建对象后初始化GO对象:
- 创建this、document、window等属性;
- 检查全局作用域中的
声明函数
,每个函数以GO对象的一个属性形式存在,属性名为函数名称,属性值为函数对象的引用(如果函数与函数之间名称冲突时,会发生覆盖); - 检查全局作用域中的var变量声明,每个var变量以一个GO对象属性形式存在,属性名为变量名称,属性值为
undifined
,当变量对应的属性名已经在GO对象中存在时,会忽略当前变量,不会创建新的属性;
-
案例一
var a=1; var b = 2; function a() { console.log('函数执行'); } a();
-
案例二
var a=1; var b = 2; var a=function(){ console.log('函数执行'); } a();
-
-
AO(Activation Object 执行对象,Variable Object 变量对象)
-
存储当前局部作用域中可访问的变量和函数;
-
每个作用域都拥有自己独自的AO对象;
-
创建AO对象后初始化对象:
- 创建
arguments
数组对象; - 检查当前作用域中的
声明函数
,每个函数以AO对象的一个属性形式存在,属性名为函数名称,属性值为函数对象的引用(如果函数与函数之间名称冲突时,会发生覆盖); - 检查当前作用域中的var变量声明,每个var变量以一个AO对象属性形式存在,属性名为变量名称,属性值为
undifined
,当变量对应的属性名已经在AO对象中存在时,会忽略当前变量,不会创建新的属性;
- 创建
-
案例
var a=1; function outer(){ var a=2; function inner(){ console.log('inner'); } var inner=3; inner(); } outer();
-
-
作用域链底层原理(扩展)
-
由一个GO对象和一系列AO对象组成的链式结构;
-
查找变量和函数时,会以当前代码所在的作用域对象为起点,以GO对象为终点,单向向上查找,直到查找到目标变量为止,如果查不到则程序报错;
-
案例
var a=1; var c=5; function outer(){ var a=2; var b=4; function inner(){ console.log('inner');//inner var a=3; console.log(a);//3 AO(inner) console.log(b);//4 AO(inner) -> AO(outer) console.log(c);//5 AO(inner) -> AO(outer) -> GO console.log(d);//报错 AO(inner) -> AO(outer) -> GO ->报错 } inner(); } outer();
腾讯面试题
var a = 100;
function fn() {
alert(a);//AO(fn) undefined
var a = 200;
alert(a);//AO(fn) 200
alert(b);//AO(fn) b函数
function b() {
var b = 1;
alert(a);//AO(b) -> AO(fn) 200
}
b();
}
fn();
alert(a);//100
var a;
alert(a);//100
function a() {
console.log('a函数');
}
var a = 300;
alert(a);//300
//undefined 200 函数 200 100 100 300