Description
http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html
http://www.laruence.com/2009/05/28/863.html
https://www.zhihu.com/question/36393048
读了上面三篇文章,对一直很困惑的作用域有了进一步的了解。真的很感谢博主们用心的写的博文和知友的回答。现在把里面能理解到相关知识都整合一起,方便以后理解。
有几个结论很重要
- JavaScript里一切都是对象
- 需要分析函数在代码中被调用的位置,而不是函数声明的位置。只有仔细分析这个调用位置才能知道this代表什么。
javascript的预编译( JS的预编译是以段为处理单元的)
<script>
//并不会弹出function的。因为这是两个script标签。
alert(typeof eve); //结果:undefined
</script>
<script>
function eve() {
alert('I am Laruence');
}
</script>
JS在执行每一段JS代码之前, 都会首先处理var关键字和function定义式(函数定义式和函数表达式).
在调用函数执行之前, 会首先创建一个变量对象, 然后搜寻这个函数中的局部变量定义,和函数定义, 将变量名和函数名都做为这个变量对象的同名属性, 对于局部变量定义,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined.
对于函数的定义:函数声明和函数表达式两种的区别
alert(typeof eve); //结果:function
alert(typeof walle); //结果:undefined
function eve() { //函数定义式
alert('I am Laruence');
};
var walle = function() { //函数表达式
}
alert(typeof walle); //结果:function
这就是函数定义式和函数表达式的不同, 对于函数定义式, 会将函数定义提前. 而函数表达式, 会在执行过程中才计算.(这就是说函数的声明的定义函数的方式,有一个重要的特征就是函数声明提升,其意思是在执行代码之前,就会读取函数的声明)
JavaScript的作用域链
注意到, 因为函数对象的[[scope]]属性是在定义一个函数的时候决定的, 而非调用的时候。
function func(lps, rps){
var name = 'laruence';
}
此时我们只有一个执行环境那就是全局执行环境。里面有一个func对象,其值是未定义的。func()函数未调用之前会创建一个执行环境-------而每个执行环境都有与之关联的变量对象(变量对象就是执行环境中包含了所有变量和函数的对象。另外变量对象是后台的,保存在内存中的,代码无法直接访问的)----------这个变量对象的有一个特殊的内部属性[scope]--------这个属性就是的就是作用域链的值。(这些关系都是静态的话,还没有开始执行,这只是这个函数拥有的关系)。
function func(lps, rps){
var name = 'laruence';
}
func();
我们调用func()函数----------会创建一个执行环境-------而每个执行环境都有与之关联的活动对象(变量对象就是执行环境中包含了所有变量和函数的对象。另外活动对象是后台的,保存在内存中的,代码无法直接访问的)----------这个活动对象的有一个特殊的内部属性[scope]--------这个属性就是
的就是作用域链的值。此时全部执行环境的活动变量处于第二位,当前的执行环境的活动对象处于第一位。
在发生标识符解析的时候, 就会逆向查询当前scope chain列表的每一个活动对象的属性,如果找到同名的就返回。找不到,那就是这个标识符没有被定义。
作用域链例子
function returnfunc (propertyName) {
return function (obj) {
return obj[propertyName];
};
}
var savefunc = returnfunc("name");
var result = savefunc({name:"Picasso"});
alert(result);
以上代码的最开始的作用域链和执行环境:
我们先开始调用returnfunc()函数,马上会创建一个包含returnfunc()变量对象的行环境,作用域链开始变化,如下图:
随后returnfunc()函数会返回它内部的匿名函数,当匿名函数被返回后,整个作用域链和执行环境又发生了变化:
我们看到匿名函数(闭包)被添加到了最作用域链的最前端,returnfunc()的执行环境被销毁,但我们注意到returnfunc()函数的活动对象仍然在被引用(匿名函数仍在访问propertyName参数),因此returnfunc()函数的变量对象仍然在内存中,成为活动对象。这就是为什么匿名函数就能访问returnfunc()函数定义的所有变量和全局环境定义的变量,毕竟returnfunc()的活动对象仍然保持“激活”状态。
知道匿名函数被销毁,returnfunc()函数活动对象才被销毁
js预编译的例子(这个例子很重要,要重点理解)
var name = 'laruence';
function echo() {
alert(name);
var name = 'eve';
alert(name);
alert(age);
}
echo();
/*
其结果为:
undefined
eve
[脚本出错]
*/
因为js预编译----- 在调用函数执行之前, 会首先创建一个变量对象, 然后搜寻这个函数中的局部变量定义,和函数定义, 将变量名和函数名都做为这个变量对象的同名属性, 对于局部变量定义,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined. 这句话很重要,要再提醒一遍。
开始执行echo()函数的时候,js预编译的时候,name设置了undefine,执行的echo()函数的时候,函数内部定义的变量name赋值了,即找到了,所以弹出eve,而age根本就没定义,所以会报错。