JavaScript进阶之理解篇
一、函数声明与函数表达式
1、函数声明之后,可以在声明之前调用,也可以在声明之后调用,因为所有的函数声明(包括var声明的变量)都会在代码执行之前就加载到作用域中,
函数名其实是一个Function类型的对象的引用,声明函数时函数名其实也就被赋值了;
2、而函数表达式则不同,函数表达式是将函数赋值给一个变量,只有当代码执行到那一行的时候,函数才真正的有定义,因此这个变量只有在表达式之后才能使用,
否则这个变量为undefined,如果这个变量不是通过var关键字声明,那么它就没有任何值。
例1:
fn1(); //fn1 fn2(); //fn2 is not a function console.log(typeof fn2); //undefined function fn1(){ console.log("fn1"); } var fn2 = function(){ console.log("fn2"); } fn2(); //fn2
例2:
var fn = 10; function fn(){ console.log("test"); } console.log(fn); //10 fn(); //fn is not a function
在例2的代码中,变量声明会在代码执行之前就加载到作用域中,函数声明时,函数名就是一个Function类型的对象引用,此时它已经被赋值了,
同一个变量被多次声明时,只会忽略后面的声明,但会执行后面的变量初始化;此时变量fn的值已经被覆盖了,值为10。
例2中的代码可以理解为下面的代码:
var fn; fn = function() { console.log("test"); } fn = 10; console.log(fn); //10 fn(); //fn is not a function
二、函数执行环境、变量对象与作用域链
1、后台中每个函数的执行环境都有一个变量对象,全局环境中的变量对象(浏览器中的window对象)始终存在,当函数被调用时,其活动对象等同于变量对象,
函数中的局部执行环境中的活动对象只存在函数执行过程中,函数执行完后,其活动对象就会被撤销;
2、当一个函数第一次被执行时,会创建一个执行环境(execution context)和相应的作用域链(包含了所有外部函数的活动对象及全局变量对象),
并把作用域链赋值给函数的一个特殊的内部[[Scope]]属性中,然后用this,arguments和其他参数初始化函数的活动对象(activation object),函数在执行时,
会通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链,注意函数本身也有一个作用域,作为执行环境作用域链的前端。
3、理解函数执行环境、变量对象与作用域链,是理解闭包的关键所在。
4、关于this,arguments对象需要注意的地方:
当一个函数第一次被执行时,就会用this,arguments对象等其他参数初始化函数的活动对象,但是如果是闭包(即内部函数)在搜索这两个对象时,
只会搜索到其活动对象为止,而不会搜索其他函数作用域中的活动对象,也就是说内部函数无法访问其包含函数的this,arguments对象,
但是可以通过变量的形式来访问外部函数中的this,arguments对象,由于闭包的执行环境具有全局性,因此其this指向window对象。
三、闭包及其所存在的问题
闭包:是指有权访问外部函数作用域中的变量的函数,创建一个闭包的方式就是在一个函数中创建另一个函数
例3:
function fn(name){ return function(){ return name } } var nm = fn("yjh"); console.log(nm()); //yjh
在例3中,fn中有一个匿名函数,即闭包,在上面代码的执行中,结果为yjh,因为闭包可以访问外部函数作用域中的变量,
内部函数中的变量是从作用域链的前端开始搜索的,如果没有找到,直至搜索到作用域链的末端(全局执行环境)。
闭包所存在的问题:
就拿例3中的例子来说,在函数fn执行完毕后,其变量name(包括其活动对象)并没有被销毁,因为内部函数(闭包)的作用域链包含了fn中的作用域,
内部函数仍然在引用fn中的活动对象,当fn执行完后,其作用域链会被销毁,但是它的活动对象会继续保存在内存中,直到匿名函数被销毁,它的活动对象才会被销毁;
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过多的使用闭包会导致内存占用过多。
四、块级作用域
JavaScript中没有块级作用域,不像其他编程语言中拥有块级作用域的概念,但是我们可以在js中模拟块级作用域的概念,我们都知道,
一般而言,函数执行完毕后,执行环境中的活动对象就会被撤毁,因此我们可以像下面这样:
(function(){ })()
声明一个匿名函数,然后立即调用它,这样在这个匿名函数中声明的变量在函数执行完毕就会被撤毁,外部函数也不能访问其中定义的变量
函数声明和调用的几种写法:
function(){} //缺少(或意外)标识符(ie9以下的ie浏览器不会报错) function fn(){} //常规函数声明 var fn = function(){} //赋值表达式 (fn = function(){})() //函数被立即调用 var fn = function(){}() //函数被立即调用 (function(){})() //函数被立即调用
总结:
1、变量,函数声明都会提前加载到作用域中,函数赋值表达式只有当代码执行到那一行时才会有定义
2、每一个函数都有一个执行环境(作用域),变量对象(活动对象),以及相应的作用域链(各个活动对象的指针列表),当函数执行时,作用域链会赋值给函数
的内部特殊属性[[Scope]]以构建起函数执行环境的作用域链
3、闭包就是指有权访问外部作用域的变量的函数,创建闭包的方式就是在一个函数中创建另一个函数,闭包会包含外部函数的作用域,因此占用的内存会比其他函数多,
过多的使用闭包会使内存占用过多,还会导致外部函数的活动对象不会被释放,只有闭包的引用被撤销,外部函数作用域中的活动对象才会被释放
4、闭包(内部函数)在搜索this,arguments对象时,只会在自身的活动对象中搜索,永远也不能访问外部作用域中的this,arguments对象,但可以通过变量赋值的
形式来访问外部函数作用域中的this,arguments对象,由于闭包的执行环境具有全局性,因此其this对象指向window对象,全局环境中没有arguments对象的
5、在js中还可以模拟其他语言中的块级作用域,即声明一个匿名函数,然后立即调用它