JavaScript——(function(){})()立即执行函数解析

 

要理解立即执行函数(function(){})(),先了解些函数的基本概念(函数声明、函数表达式、匿名函数)。

函数声明:使用function声明函数,并指定函数名。 

function setFn() {
    // coding   
}

函数表达式:使用function声明函数,但未指定函数名,将匿名函数赋予一个变量。

var setFn = function() {
    // coding
}

匿名函数:使用function关键字声明函数,但未指定函数名。匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。

function() {
    // coding
}

函数声明与函数表达式的不同在于:

1. 函数声明可在当前作用域下提前调用执行,函数表达式需等执行到该函数后,方可执行,不可提前调用。

setFn()
function setFn() {
    // coding  
}
// 正常,函数声明可提前调用

setFn()
var setFn = function() {
    // coding
} 
// 报错,setFn未保存对函数的引用,函数调用需放在函数表达式后面

 

2. 函数表达式可直接在函数后加括号调用。

var setFn = function() {
    console.log(2)
}()

// 2   解析至此,可直接执行调用

立即执行函数(function(){})()可以看出很像函数表达式的调用,但为什么要加括号呢?如果不加括号:

function(){
    console.log(1)
}()

// 报错,函数需要函数名

解析: 虽然匿名函数属于函数表达式,但未进行赋值,所以javascript解析时将开头的function当做函数声明,故报错提示需要函数名

 

立即执行函数里面的函数必须是函数表达式,所以由var setFn = function() {}()可以理解为在匿名函数前加了 = 运算符后,将函数声明转化为函数表达式,所以拿!,+,-,()...等运算符来测试下是否如此。

!function(){
    console.log(1)
}()
// 1
    
+function(){
    console.log(2)
}()
// 2
    
-function(){
    console.log(3)
}()
// 3
    
(function(){
    console.log(4)
})()
// 4

 

由此可见,加运算符确实可将函数声明转化为函数表达式,而之所以使用括号,是因为括号相对其他运算符会更安全,可以减少不必要的麻烦。

立即执行函数与正常函数传参形式是一致的。

(function(a, b){
    console.log(a + b);
})(1, 2)
// 3

(function(){}())这样写的好处是在内部定义的变量不会跟外部的变量有冲突,达到保护内部变量的作用。jquery等好多库里面就好多使用的这种方式,然后再return一个变量,详情可以查看另一篇博文https://blog.csdn.net/xcymorningsun/article/details/85234232

————————————————————————————————————————————————————

关于保护内部变量闭包问题,这个可以看下面的例子

 

循环中的闭包

一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号

    
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

上面的代码不会输出数字 0 到 9,而是会输出数字 10 十次。

当 console.log 被调用的时候, 匿名函数保持对外部变量 i 的引用,此时 for循环已经结束, i 的值被修改成了 10.

为了得到想要的结果,需要在每次循环中创建变量 i 的 拷贝

避免引用错误

为了正确的获得循环序号,最好使用 匿名包裹器( 译者注其实就是我们通常说的自执行匿名函数)。

    
for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。

当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是 不会被循环改变的。

有另一个方法完成同样的工作;那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。

    
for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

 

javascript function分析

06-09

上面介绍完javascript的数据类型和object,现在介绍下function的分析:rn[color=#FF0000] [b]函数调用过程与作用域链[/b] [/color]rn讲到作用域链,就要扯到函数的调用。当我们有一个函数 rnfunction fn(param) rn我们去调用它 rnfn(1); rn这个时候解析器为我们做了什么呢? rn有一定经验的javascript工程师也许会用过arguments、用过闭包、知道作用域,这一切的一切,都和execution context有关。 rn当我们进入一个函数调用的时候,解析器会为我们创建一个活动对象(Activation Object ),假设这里把这个活动对象叫做ac(为什么不叫ao呢,因为喜欢c)。然后做下面的事情: rn1. 初始化arguments对象,并将它添加到这个ac中。这个时候,对象ac就拥有了一个name为arguments的成员。这里arguments初始化过程就不具体说了,感兴趣的可以看ecma262的章节10.1.8。 rn2. 解析形参,并使用函数调用时传递的参数初始化。在上面的调用例子fn(1)中,这个时候,ac就拥有了一个name为param的成员,这个成员的值为1。 rn3. 对function declaration进行初始化,为所有FunctionBody中的function declaration,创建function Object,并添加到对象ac中作为ac的成员。在这一步,假设ac中已经包含了同名属性,会被覆盖掉。 rn4. 对var声明进行初始化,为所有var声明,在对象ac中创建同名成员,并初始化为undefined。在这一步,假设ac中已经包含了同名属性,不会被覆盖掉。 rn5. 初始化作用域链,并将这个作用域链与当前的执行上下文相关联。这个作用域链是一个链式列表,最前段是进入函数调用时初始化出来的活动对象ac,然后后面跟着的是该函数的[[scope]]的成员。[[scope]]是个什么东西呢,就是这个链。假如函数体中有创建function Object,叫做innerFn,那innerFn的[[scope]]成员,就是这个作用域链。当innerFn被调用时,会初始化新的活动对象,新的作用域链。新的作用域链就是初始化自这个新的活动对象和innerFn的[[scope]]。 rn那scope chain是什么作用呢?看下面的描述,来自10.1.4 rnDuring execution, the syntactic production PrimaryExpression : Identifier is evaluated using the following algorithm: rn1. Get the next object in the scope chain. If there isn't one, go to step 5. rn2. Call the [[HasProperty]] method of Result(1), passing the Identifier as the property name. rn3. If Result(2) is true, return a value of type Reference whose base object is Result(1) and whose property name is the Identifier. rn4. Go to step 1. rn5. Return a value of type Reference whose base object is null and whose property name is the Identifier. 可以看出,我们在访问一个变量的时候,其实是从和当前执行上下文相关的作用域链中查找成员。 rn在程序正常在全局下的函数,其[[scope]]成员的值是global object,所以无论任何调用,在作用域链的尾端,一定会是global object。在浏览器宿主环境下,就是window。 rn[color=#FF0000][b]函数调用过程中的this[/b] [/color]rn在函数的调用中,this是个什么东西,又是由什么决定的呢?在ecma262中,这是个比较绕的东西,其描述散落在世界各地。 rn首先,在10.2.3中告诉我们: The caller provides the this value. If the this value provided by the caller is not an object (note that null is not an object), then the this value is the global object. 我们可以知道,caller可以提供给我们this。如果没有提供,则this为global object。问题又来了,caller是怎么提供this的? rn在11.2.3中,找到如下关于Function calls的描述:The production CallExpression : MemberExpression Arguments is evaluated as follows: rn1. Evaluate MemberExpression. rn2. Evaluate Arguments, producing an internal list of argument values (see 11.2.4). rn3. Call GetValue(Result(1)). rn4. If Type(Result(3)) is not Object, throw a TypeError exception. rn5. If Result(3) does not implement the internal [[Call]] method, throw a TypeError exception. rn6. If Type(Result(1)) is Reference, Result(6) is GetBase(Result(1)). Otherwise, Result(6) is null. rn7. If Result(6) is an activation object, Result(7) is null. Otherwise, Result(7) is the same as Result(6). rn8. Call the [[Call]] method on Result(3), providing Result(7) as the this value and providing the list Result(2) as the argument values. rn9. Return Result(8). rn从步骤6、7中可以看出来,如果MemberExpression的结果是一个Reference的话,提供的this应该是GetBase(Reference),否则是空。步骤7中还有描述了6的结果是活动对象的情况,我们这里忽略。 又有疑问了,Reference?Reference是什么,GetBase又是什么? rn我们在8.7中,找到了Reference的答案。这里的描述比较长,我只摘了可以满足我们需要的一段: A Reference is a reference to a property of an object. A Reference consists of two components, the base object and the property name. rnThe following abstract operations are used in this specification to access the components of references: rnGetBase(V). Returns the base object component of the reference V. rnGetPropertyName(V). Returns the property name component of the reference V. rn已经很明显了,一个Reference必须引用一个对象的一个属性。所以我们通过obj.method()来调用的时候,obj.method这个表达式生成了一个中间态的Reference,这个Reference的base object就是obj,所以GetBase的结果就是obj,于是obj被caller提供作this rn我曾经看到很多文章,举了类似obj.method()这样的调用例子,认为obj就是caller,来解释这番话: rnThe caller provides the this value. If the this value provided by the caller is not an object (note that null is not an object), then the this value is the global object. rn这其实是说不通的。 rncaller绝不可能是obj,否则被attachEvent的函数或对象方法,他们运行时的this就解释不通了。 所以,通过我们自己代码调用的函数,caller由脚本引擎执行控制所决定;在浏览器宿主环境通过事件触发的,caller由浏览器控制的行为所决定。 rn[color=#FF0000][b]关于原型链的补充——原型链会不会是圆形链[/b] [/color]rn这个问题是telei同学提出的。答案是:不会 rn回头看看[[Construct]]的步骤,我们可以发现,创建一个对象obj时,obj.[[prototype]]成员被赋予其构造器的prototype成员。但是当构造器的prototype成员被指向为另外一个对象的引用时,obj.[[prototype]]依然是其构造器的前prototype对象。 rn描述代码如下:(注释里是说明) rnfunction A() rn this.testA = new Function(); rn rnfunction B() rn this.testB = new Function(); rn rn rnvar a = new A(); rn rnB.prototype = a; rn//a.[[prototype]] == ;(不是真的等,表示的是Function A初始的prototype object。下同) rn rnvar b = new B(); rn//b.[[prototype]] == a; rn//b.[[prototype]].[[prototype]] == a.[[prototype]] == ; rn rnA.prototype = b; rn rnvar a2 = new A(); rn//a2.[[prototype]] == b; rn//a2.[[prototype]].[[prototype]] == b.[[prototype]] == a; rn//a2.[[prototype]].[[prototype]].[[prototype]] == b.[[prototype]].[[prototype]] == a.[[prototype]] == ; rn rn//最后测试一下,很搞笑的 rnalert(a instanceof A); rn最后特殊的解释:好吧,上面代码的最后出现了很搞笑的事情,合乎语言的实现,但不合乎正常以及不正常地球人的逻辑。 我们知道,a对象是被A构造器创建出来的,所以a是A的实例。 但是,上面类型判断那里有讲,instanceof是通过构造器prototype成员与对象原型链的比较来判断的。所以当对象a被创建后,如果创建它的构造器的prototype发生了变化,a就和他妈(构造器)没任何关系了。 看到这里,你确定你还想要在实例化对象后,修改构造器的prototype成另外一个对象吗? rnrnrn rn

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试