一 : 作用域的相关概念
首先看下 变量作用域 的概念:一个变量的作用域是程序源代码中定义这个变量的区域。————————《javascript权威指南》第六版
全局变量拥有全局作用域,函数体内定义的局部变量拥有函数作用域。
就个人理解,作用域(scope),顾名思义,是一块区域 或 领域 ,并且有某些对象(包括变量,属性,方法等)能够在这里起作用;作用域是在定义的时候决定的,和什么时候执行无关;这时候问题来了:
问题1:区域在那儿?
他是一个概念,是看不到摸不着的;在这个区域内原本是什么都没有的,但是与作用域密切相关的一个概念 执行上下文(EC),他却是与作用域相呼应,哪些变量、参数、this值等统统的会保存在上下文中(上下文是一个对象,变量参数等作为他的属性保存);全局作用域 对应 全局执行上下文,函数作用域 对应相应的 函数执行上下文; 而上下文是在代码执行的时候产生的,他保存在内存中,这个上下文中的变量等 则属于 其相对应的作用域(全局作用域或函数作用域);详见下一节【执行上下文】。
问题1:某些对象是谁?
这块区域中定义了谁,对象就有谁。全局作用域中定义了变量a和声明了函数 fun,那么a和fun就在全局作用域中起作用;fun域中定义了变量m ,则m 就在fun域中起作用(这时候变量a 也是起作用的,原理可查看后面作用域链 概念);另外还有this值,当前函数参数;主要就包含这三类对象。
问题2:这些对象啥时候起作用?
执行时,指全局函数执行时,生成执行上下文,变量等起作用;局部函数执行时,生成函数执行上下文,函数体内变量等起作用;详细查看 执行上下文。
js变量具有 的 向下透明 和 向上封闭 特性,向下透明,指fun,fn可以在当前作用域访问全局变量a,fn可以访问a,m,但在函数体内,局部变量的优先级高于同名上级变量。 向上封闭,指全局作用域 不可以访问 fun域中的变量m,更不可以访问 fn中的变量n,fun作用域也不可以访问他内部嵌套的fn域中变量n;还涉及到的一个概念就是 作用域链(scope chain):是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。
当全局作用域中没有函数定义时,作用域链上就只有一个对象,全局对象,这个对象定义了相关的变量信息;一般所谓链,都是指多个穿起来才叫链,这儿比较特殊,是只有一个元素的链。
当全局作用域中包含多个嵌套函数时,随着函数一级一级执行展开,作用域链将会变成由多个对象组成的链表;这个链表是动态的,随着代码的执行而不停的增加和释放内存。
上图可见,向下透明,向上封闭的原理,例如b变量保存的值是一个指针,那么在a域内,大家能看到的就是这个指针值,而不能看到b域内的变量,因此 b域 相对于a域是封闭的,也就是向上封闭;说到这儿,是不是感觉有点和闭包相似呢,闭包就是封闭的,只留一个接口(等同于a)给外界,包里都有啥不让别人直接看见。js之所以会出现闭包,可能就是因为这种存储机制造成的——变量保存为引用类型!闭包就是这样,但凡函数返回值是引用类型,都会产生闭包。
js是基于 词法作用域 的语言:通过阅读包含变量定义在内的数行源码,就能知道变量的作用域。
二 :作用域的分类
作用域 可分为两种,全局作用域 和 函数作用域;函数作用域是由函数创建的,至于全局作用域,我们也可以将全局作用域当作是 window函数 创建的,并且这个函数只有一个,而我们所写的所有代码都在这个函数之内。所以总结一句话:只有 函数 才能创建作用域
引申:这个全局函数 和 pareInt()等全局函数是什么关系呢?
默全局作用域从代码运行开始就一直存在着;
函数作用域:
变量在声明他们的函数体以及这个函数体内嵌套的任意函数体内都是有定义的。
在函数内声明的所有的变量在函数体内始终是可见的。
-------------《javascript权威指南》第六版
三个要点:所有变量 、 始终可见、都有定义
1、所有变量 ———— 函数参数 + 函数体变量+ for循环/if 语句/while语句 变量
如上图中所标出的所有变量,都 属于当前 函数作用域,这些变量向下透明,而向上封闭,既fn2函数体内可以访问fun函数以及全局变量,但是全局和fun却不能直接访问fn2中的变量。
注:一段带有大括号的代码,经常会被称为 代码块;而这种称呼,在Js中可能会带来误解;JS中 没有 块级作用域 ,这个概念与其他编程语言(如C等), 是不一样的;如果一定要用代码块来理解的话,js 只识别 全局代码块 和 函数代码块 当作其作用域。
例如下C++代码:
void fun(int x){ int a; //变量a的作用域在函数fun中 for(int i=0;i<=10;i++){i++} //变量i在块级作用域 for 代码块中 }
2、始终可见—————— 指变量在书写 声明源代码 之前已经可用
js中声明包含两种声明 变量声明(var) 和 函数声明 (function);变量声明时,只定义了变量名,类型不确定;函数声明时,将函数名当作变量名,类型是函数。
这个特性被称为 声明提前 ,即当前函数体内的声明所有变量(不涉及赋值)代码,都被提前到函数体顶部;通俗的说,就是指只要是在函数体内声明了变量,不论代码写在了什么位置,在函数体内的任意位置都可以访问到。事实上这和Js引擎规定如何执行代码顺序有关,他会先查找有关声明的关键词var 和 function 来保存在执行上下文中,这个工作是在 形成执行上下文 的初始化阶段 完成的,而赋值则是在
举例,执行如下代码:
function fn(){ console.log(a); //underfined var a=3; }fn();
等同于下面代码:
function fn(){ var a; //变量声明提前 console.log(a); //变量已声明未赋值时默认值是underfined a=3; }fn();
引申疑问:声明提前 这步工作 是在什么时候做的呢?
这就涉及到了另外一个概念,执行上下文环境 ,变量/函数的声明 工作都是在 代码执行的时候才做的;
例如 文件运行开始,就生成了 全局执行上下文环境,这个环境的生成分为两个阶段完成,分别是 初始化阶段 + 赋值阶段 ,那么声明提前 就包含在 初始化阶段中。详情见下一节。
3、都有定义—————— 变量在当前函数体内有定义
根据定义,变量在声明他们的函数体以及这个函数体内嵌套的任意函数体内都是有定义的,可见,函数体内不论包括啥函数,包括多少层函数,当前函数体内定义的变量一直在众多嵌套函数中是有效可用的;也就是上面提到的向下透明特性。