作用域是每种计算机语言最重要的基础之一,当然它也是JavaScript最重要的概念之一。要想真正的深入了解JavaScript,了解JavaScript的作用域链非常必要。现在让我们深入了解JavaScript作用域和作用域链的工作原理。
JavaScript的作用域是什么
简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种,局部作用域又称为函数作用域。
全局作用域
在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:
(1)程序最外层定义的函数或者变量
var a = "tsrot";
function hello(){
alert(a);
}
function sayHello(){
hello();
}
alert(a); //能访问到tsrot
hello(); //能访问到tsrot
sayHello(); //能访问到hello函数,然后也能访问到tsrot
(2)所有window对象的属性和方法
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。
(3)所有末定义直接赋值的变量(不推荐)
function hello(){
a = "tsrot";
var b = "hello tsrot";
}
alert(a); //能访问到tsrot
alert(b); //error 不能访问
局部作用域(函数作用域)
局部作用域在函数内创建,在函数内可访问,函数外不可访问。
function hello(){
var a = "tsrot";
alert(a);
}
hello(); //函数内可访问到tsrot
alert(a); //error not defined
作用域链是什么
了解作用域链之前我们要知道一下几个概念:
变量和函数的声明
函数的生命周期
Activetion Object(AO)、Variable Object(VO)
变量和函数的声明
在JavaScript引擎解析JavaScript代码的时候,首先,JavaScript引擎会把变量和函数的声明提前进行预解析,然后再去执行其他代码。
变量声明:变量的声明只有一种方式,那就是用var关键字声明,直接赋值不是一种声明方式。这仅仅是在全局对象上创建了新的属性(而不是变量)。它们有一下区别:
(1)因为它只是一种赋值,所以不会声明提前
alert(a); // undefined
alert(b); // error "b" is not defined
b = 10;
var a = 20;
2)直接赋值形式是在执行阶段创建
alert(a); // undefined, 这个大家都知道
b = 10;
alert(b); // 10, 代码执行阶段创建
var a = 20;
alert(a); // 20, 代码执行阶段修改
(3)变量不能删除(delete),属性可以删除
a = 10;
alert(window.a); // 10
alert(delete a); // true
alert(window.a); // undefined
var b = 20;
alert(window.b); // 20
alert(delete b); // false
alert(window.b); // 仍然为 20,因为变量是不能够删除的。
但是,这里有一个意外情况,就是在“eval”的上下文中,变量是可以删除的:
eval('var a = 10;');
alert(window.a); // 10
alert(delete a); // true
alert(window.a); // undefined
有些debug工具也是可以删除的,因为它们使用了 eval()方法来执行代码的。
函数声明:函数的声明有三种方式
(1)function name( ){ }直接创建方式
function add(a,b){
return a+b;
}
add(5,4);
(2)new Funtion构建函数创建
var add=new Function("a", "b", "return a+b;");
add(4,5);
(3)给变量赋值匿名函数方法创建
var add = function(a,b){
return a+b;
}
add(4,5);
后面两种方法,在声明前访问时,返回的都是一个undefined的变量。当然,在声明后访问它们都是一个function的函数。
注意:如果变量名和函数名声明时相同,函数优先声明。
alert(x); // function
var x = 10;
alert(x); // 10
x = 20;
function x() {};
alert(x); // 20
函数的生命周期
函数的的生命周期分为创建和执行两个阶段。
在函数创建阶段,JS解析引擎进行预解析,会将函数声明提前,同时将该函数放到全局作用域中或当前函数的上一级函数的局部作用域中。
在函数执行阶段,JS引擎会将当前函数的局部变量和内部函数进行声明提前,然后再执行业务代码,当函数执行完退出时,释放该函数的执行上下文,并注销该函数的局部变量。
什么是AO、VO
英文解释:
AO:Activetion Object(活动对象)
VO:Variable Object(变量对象)
VO对应的是函数创建阶段,JS解析引擎进行预解析时,所有的变量和函数的声明,统称为Variable Object。该变量与执行上下文相关,知道自己的数据存储在哪里,并且知道如何访问。VO是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:
变量 (var, 变量声明);
函数声明 (FunctionDeclaration, 缩写为FD);
函数的形参
举个例子:
function add(a,b){
var sum = a + b;
function say(){
alert(sum);
}
return sum;
}
// sum,say,a,b 组合的对象就是VO,不过该对象的值基本上都是undefined
AO对应的是函数执行阶段,当函数被调用执行时,会建立一个执行上下文,该执行上下文包含了函数所需的所有变量,该变量共同组成了一个新的对象就是Activetion Object。该对象包含了:
函数的所有局部变量
函数的所有命名参数
函数的参数集合
函数的this指向
举个例子:
unction add(a,b){
var sum = a + b;
function say(){
alert(sum);
}
return sum;
}
add(4,5);
// 我用JS对象来表示AO
// AO = {
// this : window,
// arguments : [4,5],
// a : 4,
// b : 5,
// say : ,
// sum : undefined
// }
JavaScript作用域链
在 JavaScript 中,函数也是对象,实际上,JavaScript 里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供 JavaScript 引擎访问的内部属性。其中一个内部属性是 [[Scope]],由 ECMA-262 标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
1.在函数创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量。
2.函数执行时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。
3.这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端。
4.当运行期上下文被销毁,活动对象也随之销毁。
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
网友评论