上次谈过原型链的概念,其中我们会有个疑问对于一个原型链而言,肯定会存在所取值的范围规定,那么我们能否跨过这些范围去取值?又该如何取?
在这里,就要谈到 作用域的问题。
作用域
简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
全局作用域
在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:
(1)最外层函数和在最外层函数外面定义的变量拥有全局作用域,例如:
var authorName="芷若初荨";
function doSomething(){
var blogName="Cecilia620";
function innerSay(){
alert(blogName);
}
innerSay();
}
alert(authorName); //芷若初荨
alert(blogName); //脚本错误
doSomething(); //Cecilia620
innerSay() //脚本错误
(2)所有末定义直接赋值的变量自动声明为拥有全局作用域,例如:
function doSomething(){
var authorName="芷若初荨";
blogName="Cecilia620";
alert(authorName);
}
doSomething(); //Cecilia620
alert(blogName); //芷若初荨
alert(authorName); //脚本错误
(3)所有window对象的属性拥有全局作用域
一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等。
函数作用域(局部作用域)
一般在函数内部,例如下列代码中的blogName和函数innerSay都只拥有局部作用域。
function doSomething(){
var blogName="芷若初荨";
function innerSay(){
alert(blogName);
}
innerSay();
}
alert(blogName); //脚本错误
innerSay(); //脚本错误
作用域链
在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
例如定义下面这样一个函数:
function add(num1,num2) {
var sum = num1 + num2;
return sum;
}
执行此函数会创建一个“执行上下文”的内部对象,那么执行上下文是什么?
执行上下文
先来看个代码,如下:
分析:第一句报错,a没有定义,当然报错,第二句、第三句输出都是undefined,说明浏览器在执行console.log(a)时,已经知道了a是undefined,在第三句中,但却不知道a是10。
实际上,在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些“准备工作”,其中就包括对变量的声明,而不是赋值。变量赋值是在赋值语句执行的时候进行的。可用下图模拟:
这是第一种情况,让我们再来看一种,如下:
大家看到这句代码会发现这是直接对其this赋值,并没有声明;
还有第三种情况,如下:
在上述代码中,我们可以看到函数声明和函数表达式,实质上函数表达式就是属于变量声明,函数声明的过程属于赋值的过程,结合看来,这就是JS解释器根据一定的顺序在查找定义的变量和函数,那么这个顺序到底是什么呢?
在这里又要提到变量对象这个概念,
*变量对象(VO)
实际上简单来说,它就是知道数据存储在哪里以及该如何获取的一种机制,同时它也是执行上下文的属性。我们常见的声明新的变量和函数的过程其实就是在VO中创建新的和变量以及函数名对应的属性和属性值的过程。
VO按照以下顺序填充:
**函数参数(若未传入,初始化该参数值为undefined)
**函数声明(若发生命名冲突,会发生覆盖)
**变量声明(初始化变量值为undefined,若发生命名中途,会选择忽略)。
大家来看段代码:
console.log('EC0');
function funEC1(){
console.log('Ec1');
var funEC2=function (){
console.log('EC2');
var funEC3=function(){
console.log('EC3');
};
funEC3();
}
funEC2();
}
funEC1();
//输出结果为:EC0 EC1 EC2 EC3
如下图所示: