javascript理解

来源:互联网 发布:男生不主动追女生知乎 编辑:程序博客网 时间:2024/05/20 18:54

转自:http://www.cnblogs.com/yangjunhua/archive/2012/05/22/2513340.html



一、函数声明与函数表达式

1、函数声明之后,可以在声明之前调用,也可以在声明之后调用,因为所有的函数声明(包括var声明的变量)都会在代码执行之前就加载到作用域中,函数名其实是一个Function类型的对象的引用,声明函数时函数名其实也就被赋值了;2、而函数表达式则不同,函数表达式是将函数赋值给一个变量,只有当代码执行到那一行的时候,函数才真正的有定义,因此这个变量只有在表达式之后才能使用,否则这个变量为undefined,如果这个变量不是通过var关键字声明,那么它就没有任何值。例1:

fn1(); //fn1
fn2(); //fn2 is not a function
console.log(typeoffn2); //undefined
functionfn1(){
    console.log("fn1");
}
varfn2 = function(){
    console.log("fn2");
}
fn2(); //fn2
 例2:

varfn = 10;
functionfn(){
    console.log("test");
}
console.log(fn);//10
fn(); //fn is not a function
    在例2的代码中,变量声明会在代码执行之前就加载到作用域中,函数声明时,函数名就是一个Function类型的对象引用,此时它已经被赋值了,同一个变量被多次声明时,只会忽略后面的声明,但会执行后面的变量初始化;此时变量fn的值已经被覆盖了,值为10。
例2中的代码可以理解为下面的代码:

varfn;
functionfn(){
    console.log("test");
}
//上一句函数声明其实等价于执行赋值表达式fn = new 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:

functionfn(name){
    returnfunction(){
        returnname
    }
}
varnm = fn("yjh");
console.log(nm());//yjh
    在例3中,fn中有一个匿名函数,即闭包,在上面代码的执行中,结果为yjh,因为闭包可以访问外部函数作用域中的变量,内部函数中的变量是从作用域链的前端开始搜索的,如果没有找到,直至搜索到作用域链的末端(全局执行环境)。
闭包所存在的问题    就拿例3中的例子来说,在函数fn执行完毕后,其变量name(包括其活动对象)并没有被销毁,因为内部函数(闭包)的作用域链包含了fn中的作用域,内部函数仍然在引用fn中的活动对象,当fn执行完后,其作用域链会被销毁,但是它的活动对象会继续保存在内存中,直到匿名函数被销毁,它的活动对象才会被销毁;由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过多的使用闭包会导致内存占用过多。

四、块级作用域

    JavaScript中没有块级作用域,不像其他编程语言中拥有块级作用域的概念,但是我们可以在js中模拟块级作用域的概念,我们都知道,一般而言,函数执行完毕后,执行环境中的活动对象就会被撤毁,因此我们可以像下面这样:

(function(){
 
})()
    声明一个匿名函数,然后立即调用它,这样在这个匿名函数中声明的变量在函数执行完毕就会被撤毁,外部函数也不能访问其中定义的变量    函数声明和调用的几种写法:

function(){}//缺少(或意外)标识符(ie9以下的ie浏览器不会报错)
functionfn(){} //常规函数声明
varfn = function(){}//赋值表达式
(fn = function(){})()//函数被立即调用
varfn = function(){}()//函数被立即调用
(function(){})()//函数被立即调用

五、解析js中常见的一个问题

例4

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

varli = document.getElementsByTagName("li");
for(vari=0;i<li.length;i++){
    li[i].onclick =function(){
        console.log(i);
    }
}
例4中的代码相信很多人都遇到过,执行结果为什么不是预期的所想要的解读一下    首先变量li获取了所有的li元素节点,然后通过for循环为每个li元素的click事件绑定一个事件处理函数,注意click事件绑定函数是通过赋值表达式来绑定的,而当代码执行到那一行的时候,并不会执行(这并不是函数赋值表达式引起的),因为click事件是晚绑定的,即只有当某个li元素的click事件被触发时,才会绑定函数,而在click事件被触发之前,事件处理函数并没有定义(因为赋值表达式语句并没有执行),此时全局环境中的i变量通过for循环已经变为5了,事件处理函数作为一个闭包,访问的全局环境中的i变量都是4,因此每次输出的结果也都是4了。解决办法可以通过匿名函数传参的形式来解决,因为函数传参,是传值,而不是传址,即复制另一个变量值的副本

方法1
varli = document.getElementsByTagName("li");
for(vari=0;i<li.length;i++){
    (function(j){
        li[i].onclick =function(){
            console.log(j);
        }
    })(i)
}
 
方法2
varli = document.getElementsByTagName("li");
for(vari=0;i<li.length;i++){
    (function(){
        varj = i;
        li[i].onclick =function(){
            console.log(j);
        }
    })(i)
}
两种方法大同小异,都是通过函数传参的形式来保存全局变量i值的一个副本,然后使事件处理函数中的变量来访问这个副本来达到预期的结果。不好意思,之前这里的i变量初始化为1,那么第一个li就没有绑定事件处理函数,忽略小细节了,之前只注重输出结果了,感谢园友笨瓜一号,xujif的提醒。

六、总结:

1、变量,函数声明都会提前加载到作用域中,函数赋值表达式只有当代码执行到那一行时才会有定义2、每一个函数都有一个执行环境(作用域),变量对象(活动对象),以及相应的作用域链(各个活动对象的指针列表),当函数执行时,作用域链会赋值给函数的内部特殊属性[[Scope]]以构建起函数执行环境的作用域链3、闭包就是指有权访问外部作用域的变量的函数,创建闭包的方式就是在一个函数中创建另一个函数,闭包会包含外部函数的作用域,因此占用的内存会比其他函数多,过多的使用闭包会使内存占用过多,还会导致外部函数的活动对象不会被释放,只有闭包的引用被撤销,外部函数作用域中的活动对象才会被释放4、闭包(内部函数)在搜索this,arguments对象时,只会在自身的活动对象中搜索,永远也不能访问外部作用域中的this,arguments对象,但可以通过变量赋值的形式来访问外部函数作用域中的this,arguments对象,由于闭包的执行环境具有全局性,因此其this对象指向window对象,全局环境中没有arguments对象的5、在js中还可以模拟其他语言中的块级作用域,即声明一个匿名函数,然后立即调用它