理解javascript作用域链

JavaScript的作用域: 作用域,在维基百科上解释是:在电脑程序设计中,作用域(scope,或译作有效范围)是名字(name)与实体(entity)的绑定(binding)保持有效的那部分计算机程序。
简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在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等等。
局部作用域(函数作用域) 局部作用域在函数内创建,在函数内可访问,函数外不可访问。
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,因为变量是不能够删除的。

函数声明:函数的声明有三种方式
(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的函数。
注意 :如果变量名和函数名声明时相同,函数优先声明。
JavaScript作用域链 作用域就是变量与函数的可访问范围。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。
执行环境(execution context) 每个函数运行时都会产生一个执行环境,而这个执行环境怎么表示呢?js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。
全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。
js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。
举个例子:

上面代码执行情况演示:

理解javascript作用域链
文章图片
1.jpg
一般来说,当某个环境中的所有代码执行完毕后,该环境被销毁(弹出环境栈),保存在其中的所有变量和函数也随之销毁(全局执行环境变量直到应用程序退出,如网页关闭才会被销毁)
但是像上面那种有内部函数的又有所不同,当outer()函数执行结束,执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。
我们再来看一段代码:
name="lwy"; function t(){ var name="tlwy"; function s(){ var name="slwy"; console.log(name); } function ss(){ console.log(name); } s(); ss(); } t();

当执行s时,将创建函数s的执行环境(调用对象),并将该对象置于链表开头,然后将函数t的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显
name是"slwy"。
但执行ss()时,作用域链是: ss()->t()->window,所以name是”tlwy"
下面看一个很容易犯错的例子:

当文档加载完毕,给几个按钮注册点击事件,当我们点击按钮时,会弹出什么提示框呢?
很容易犯错,对是的,三个按钮都是弹出:"Button4",你答对了吗?
当注册事件结束后,i的值为4,当点击按钮时,事件函数即function(){ alert("Button"+i); }这个匿名函数中没有i,根据作用域链,所以到buttonInit函数中找,此时i的值为4,
【理解javascript作用域链】所以弹出”button4“。

    推荐阅读