执行环境:执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为;每一个执行环境都有一个与之关联的变量对象;函数中定义的所有变量和函数都保存在这个对象中;
某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁;
每个函数都有自己的执行环境,当执行流进入一个函数时候,函数的环境就会被推入一个环境栈中,而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境;
作用域链:当代码在环境中执行,会创建变量对象的一个作用域链,作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问;
变量对象:是与执行上下文相关的数据作用链,他是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明;
注意,函数表达式(与函数声明相对)不包含在变量对象之中;在全局上下文中变量对象就是全局对象本身;
如:
var foo = 10;
function bar() {} // function declaration, FD
(function baz() {}); // function expression, FE
console.log(
this.foo == foo, // true
window.bar == bar // true
);
console.log(baz); // ReferenceError, "baz" is not defined
之后,全局上下文的变量对象(variable objec,简称VO)将会拥有如下属性:
foo:10;
bar:<function>;
活动对象:
当一个函数被调用,一个特殊的对象叫活动对象将会被创建,这个对象中包含形参和那个特殊的arguments对象,(是一个形参的映射,但是值是通过索引来获取);活动对象之后会作为函数上下文的变量对象来使用;换句话说函数的变量对象也是一个同样简单的变量对象但是除了变量和函数声明外,它还存储了形参和arguments对象,并叫做活动对象;
function foo(x, y) {
var z = 30;
function bar() {} // FD
(function baz() {}); // FE
}
foo(10, 20);
函数foo的上下文中的活动对象(activation object,简称AO):
x:10;
y:20;
arguments:{0:10,1:20,...};
z:30;
bar:<function>;
延长作用域:
执行环境的作用域有两种,全局和局部(函数);
延长作用链:有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除;
1.try-catch语句的catch块;会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明;
2.width语句;会将指定的对象添加到作用域链中;
如:
<script>
function buildUrl(){
var qs='?debug=true';
with(location){
var url=href+qs;
}
return url;
}
var aa=buildUrl;
console.log(aa());
//22.html:17 http://localhost:63342/11.%20%E9%9D%A2%E5%90%91%
// E5%AF%B9%E8%B1%A1/01%20%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%AC%AC%E4%B8%
// 80%E5%A4%A9/01%20%E9%9D%A2%E5%90%91%E5%AF%
// B9%E8%B1%A1%E5%9F%BA%E7%A1%80/06%20%E9%9D%A2%E5%90%91%E
// 5%AF%B9%E8%B1%A1%E5%92%8C%E5%88%97%E8%A1%A8/22.html?debug=true
</script>
此函数中,with语句接收的是location对象,因此 其变量对象中就包含了location对象的所有属性和方法,而这个变量对象被添加到作用域链的前端,buildUrl()函数定义了qs,当在with对象中引用变量href
时引用的是location.href,可以在当前的执行环境中找到;
函数表达式:
匿名函数的name属性是空字符串;
函数表达式:创建函数再赋值给变量,如果能创建函数再赋值给变量也就能把函数作为其他函数的返回值,返回的函数可能会被赋值给一个变量,或者以其他方式被调用,在把函数当作值来使用的情况下,都可以使用匿名函数;
递归:
递归函数是在一个函数通过名字调用自身的情况下构成的,如下:
<script>
function factorial(num){
if(num<=1){
return 1;
}else{
return num*factorial(num-1);
}
}
alert(factorial(4));//24
</script>
这是一个经典的递归阶乘函数,但是下面的代码可能会导致它出错:
<script>
function factorial(num){
if(num<=1){
return 1;
}else{
return num*factorial(num-1);
}
}
var anotherFactorial=factorial;
factorial=null;
alert(anotherFactorial(4));// factorial is not a function
</script>
因为在当调用anotherFactorial的时候必须执行factorial ,而此时它已经不是函数;
arguments.callee;是一个指向正在执行的函数的指针;可以用它来实现递归;使用于IE8以上;
<script>
function factorial(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}
alert(factorial(5));//120
</script>
在严格模式下不能通过脚本访问arguments.callee,访问这个属性会出错,不过可以使用命名函数表达式来达成同样的结果;
<script>
var factorial=(function f(num){
if(num<=1){
return 1;
}else{
return num*f(num-1);
}
});
alert(factorial(4));//24
</script>
以上代码创建一个名为f()的命名函数表达式,然后将它赋值给变量factorial,即便把函数赋值给了另外一个变量,函数的名字仍然有效,所以递归调用照样能完成;