深入理解JavaScript系列(15) 函数(Functions)

介绍
本章节我们要着重介绍的是一个非常常见的ECMAScript对象——函数(function),我们将详细讲解一下各种类型的函数是如何影响上下文的变量对象以及每个函数的作用域链都包含什么,以及回答诸如像下面这样的问题:下面声明的函数有什么区别么?(如果有,区别是什么)。
原文:http://dmitrysoshnikov.com/ecmascript/chapter-5-functions/


代码如下:

var foo = function () {
...
};

平时的惯用方式:


代码如下:

function foo() {
...
}

或者,下面的函数为什么要用括号括住?


代码如下:

(function () {
...
})();

关于具体的介绍,早前面的12章变量对象和14章作用域链都有介绍,如果需要详细了解这些内容,请查询上述2个章节的详细内容。

但我们依然要一个一个分别看看,首先从函数的类型讲起:

函数类型
在ECMAScript 中有三种函数类型:函数声明,函数表达式和函数构造器创建的函数。每一种都有自己的特点。
函数声明
函数声明(缩写为FD)是这样一种函数:
有一个特定的名称
在源码中的位置:要么处于程序级(Program level),要么处于其它函数的主体(FunctionBody)中
在进入上下文阶段创建
影响变量对象
以下面的方式声明


代码如下:

function exampleFunc() {
...
}

这种函数类型的主要特点在于它们仅仅影响变量对象(即存储在上下文的VO中的变量对象)。该特点也解释了第二个重要点(它是变量对象特性的结果)——在代码执行阶段它们已经可用(因为FD在进入上下文阶段已经存在于VO中——代码执行之前)。
例如(函数在其声明之前被调用)


代码如下:

foo();
function foo() {
alert('foo');
}

另外一个重点知识点是上述定义中的第二点——函数声明在源码中的位置:


代码如下:

// 函数可以在如下地方声明:
// 1) 直接在全局上下文中
function globalFD() {
// 2) 或者在一个函数的函数体内
function innerFD() {}
}

只有这2个位置可以声明函数,也就是说:不可能在表达式位置或一个代码块中定义它。
另外一种可以取代函数声明的方式是函数表达式,解释如下:
函数表达式
函数表达式(缩写为FE)是这样一种函数:
在源码中须出现在表达式的位置
有可选的名称
不会影响变量对象
在代码执行阶段创建
这种函数类型的主要特点在于它在源码中总是处在表达式的位置。最简单的一个例子就是一个赋值声明:


代码如下:

var foo = function () {
...
};

该例演示是让一个匿名函数表达式赋值给变量foo,然后该函数可以用foo这个名称进行访问——foo()。
同时和定义里描述的一样,函数表达式也可以拥有可选的名称:


代码如下:

var foo = function _foo() {
...
};

需要注意的是,在外部FE通过变量“foo”来访问——foo(),而在函数内部(如递归调用),有可能使用名称“_foo”。
如果FE有一个名称,就很难与FD区分。但是,如果你明白定义,区分起来就简单明了:FE总是处在表达式的位置。在下面的例子中我们可以看到各种ECMAScript 表达式:
// 圆括号(分组操作符)内只能是表达式
(function foo() {});
// 在数组初始化器内只能是表达式
[function bar() {}];
// 逗号也只能操作表达式
1, function baz() {};
表达式定义里说明:FE只能在代码执行阶段创建而且不存在于变量对象中,让我们来看一个示例行为:


代码如下:

// FE在定义阶段之前不可用(因为它是在代码执行阶段创建)
alert(foo); // "foo" 未定义
(function foo() {});
// 定义阶段之后也不可用,因为他不在变量对象VO中
alert(foo); // "foo" 未定义
相当一部分问题出现了,我们为什么需要函数表达式?答案很明显——在表达式中使用它们,”不会污染”变量对象。最简单的例子是将一个函数作为参数传递给其它函数。
function foo(callback) {
callback();
}
foo(function bar() {
alert('foo.bar');
});
foo(function baz() {
alert('foo.baz');
});

在上述例子里,FE赋值给了一个变量(也就是参数),函数将该表达式保存在内存中,并通过变量名来访问(因为变量影响变量对象),如下:


代码如下:

var foo = function () {
alert('foo');
};
foo();

另外一个例子是创建封装的闭包从外部上下文中隐藏辅助性数据(在下面的例子中我们使用FE,它在创建后立即调用):


代码如下:

var foo = {};
(function initialize() {
var x = 10;
foo.bar = function () {
alert(x);
};
})();
foo.bar(); // 10;
alert(x); // "x" 未定义

我们看到函数foo.bar(通过[[Scope]]属性)访问到函数initialize的内部变量“x”。同时,“x”在外部不能直接访问。在许多库中,这种策略常用来创建”私有”数据和隐藏辅助实体。在这种模式中,初始化的FE的名称通常被忽略:


代码如下:

(function () {
// 初始化作用域
})();

还有一个例子是:在代码执行阶段通过条件语句进行创建FE,不会污染变量对象VO。


代码如下:

var foo = 10;
var bar = (foo % 2 == 0
? function () { alert(0); }
: function () { alert(1); }
);
bar(); // 0

关于圆括号的问题
让我们回头并回答在文章开头提到的问题——”为何在函数创建后的立即调用中必须用圆括号来包围它?”,答案就是:表达式句子的限制就是这样的。
按照标准,表达式语句不能以一个大括号{开始是因为他很难与代码块区分,同样,他也不能以函数关键字开始,因为很难与函数声明进行区分。即,所以,如果我们定义一个立即执行的函数,在其创建后立即按以下方式调用:


代码如下:

function () {
...
}();
// 即便有名称
function foo() {
...
}();

我们使用了函数声明,上述2个定义,解释器在解释的时候都会报错,但是可能有多种原因。
如果在全局代码里定义(也就是程序级别),解释器会将它看做是函数声明,因为他是以function关键字开头,第一个例子,我们会得到SyntaxError错误,是因为函数声明没有名字(我们前面提到了函数声明必须有名字)。
第二个例子,我们有一个名称为foo的一个函数声明正常创建,但是我们依然得到了一个语法错误——没有任何表达式的分组操作符错误。在函数声明后面他确实是一个分组操作符,而不是一个函数调用所使用的圆括号。所以如果我们声明如下代码:


代码如下:

// "foo" 是一个函数声明,在进入上下文的时候创建
alert(foo); // 函数
function foo(x) {
alert(x);
}(1); // 这只是一个分组操作符,不是函数调用!
foo(10); // 这才是一个真正的函数调用,结果是10

上述代码是没有问题的,因为声明的时候产生了2个对象:一个函数声明,一个带有1的分组操作,上面的例子可以理解为如下代码:


代码如下:

// 函数声明
function foo(x) {
alert(x);
}
// 一个分组操作符,包含一个表达式1

(1);


代码如下:

// 另外一个操作符,包含一个function表达式
(function () {});
// 这个操作符里,包含的也是一个表达式"foo"
("foo");
// 等等

如果我们定义一个如下代码(定义里包含一个语句),我们可能会说,定义歧义,会得到报错:
if (true) function foo() {alert(1)}
根据规范,上述代码是错误的(一个表达式语句不能以function关键字开头),但下面的例子就没有报错,想想为什么?
我们如果来告诉解释器:我就像在函数声明之后立即调用,答案是很明确的,你得声明函数表达式function expression,而不是函数声明function declaration,并且创建表达式最简单的方式就是用分组操作符括号,里边放入的永远是表达式,所以解释器在解释的时候就不会出现歧义。在代码执行阶段这个的function就会被创建,并且立即执行,然后自动销毁(如果没有引用的话)。


代码如下:

(function foo(x) {
alert(x);
})(1); // 这才是调用,不是分组操作符

上述代码就是我们所说的在用括号括住一个表达式,然后通过(1)去调用。
注意,下面一个立即执行的函数,周围的括号不是必须的,因为函数已经处在表达式的位置,解析器知道它处理的是在函数执行阶段应该被创建的FE,这样在函数创建后立即调用了函数。


代码如下:

var foo = {
bar: function (x) {
return x % 2 != 0 ? 'yes' : 'no';
}(1)
};
alert(foo.bar); // 'yes'

就像我们看到的,foo.bar是一个字符串而不是一个函数,这里的函数仅仅用来根据条件参数初始化这个属性——它创建后并立即调用。
因此,”关于圆括号”问题完整的答案如下:当函数不在表达式的位置的时候,分组操作符圆括号是必须的——也就是手工将函数转化成FE。
如果解析器知道它处理的是FE,就没必要用圆括号。
除了大括号以外,如下形式也可以将函数转化为FE类型,例如:


代码如下:

// 注意是1,后面的声明
1, function () {
alert('anonymous function is called');
}();
// 或者这个
!function () {
alert('ECMAScript');
}();
// 其它手工转化的形式
...

但是,在这个例子中,圆括号是最简洁的方式。
顺便提一句,组表达式包围函数描述可以没有调用圆括号,也可包含调用圆括号,即,下面的两个表达式都是正确的FE。
实现扩展:函数语句
下面的代码,根据贵方任何一个function声明都不应该被执行:


代码如下:

if (true) {
function foo() {
alert(0);
}
} else {
function foo() {
alert(1);
}
}
foo(); // 1 or 0 ?实际在上不同环境下测试得出个结果不一样

这里有必要说明的是,按照标准,这种句法结构通常是不正确的,因为我们还记得,一个函数声明(FD)不能出现在代码块中(这里if和else包含代码块)。我们曾经讲过,FD仅出现在两个位置:程序级(Program level)或直接位于其它函数体中。
因为代码块仅包含语句,所以这是不正确的。可以出现在块中的函数的唯一位置是这些语句中的一个——上面已经讨论过的表达式语句。但是,按照定义它不能以大括号开始(既然它有别于代码块)或以一个函数关键字开始(既然它有别于FD)。
但是,在标准的错误处理章节中,它允许程序语法的扩展执行。这样的扩展之一就是我们见到的出现在代码块中的函数。在这个例子中,现今的所有存在的执行都不会抛出异常,都会处理它。但是它们都有自己的方式。
if-else分支语句的出现意味着一个动态的选择。即,从逻辑上来说,它应该是在代码执行阶段动态创建的函数表达式(FE)。但是,大多数执行在进入上下文阶段时简单的创建函数声明(FD),并使用最后声明的函数。即,函数foo将显示”1″,事实上else分支将永远不会执行。
但是,SpiderMonkey (和TraceMonkey)以两种方式对待这种情况:一方面它不会将函数作为声明处理(即,函数在代码执行阶段根据条件创建),但另一方面,既然没有括号包围(再次出现解析错误——”与FD有别”),他们不能被调用,所以也不是真正的函数表达式,它储存在变量对象中。
我个人认为这个例子中SpiderMonkey 的行为是正确的,拆分了它自身的函数中间类型——(FE+FD)。这些函数在合适的时间创建,根据条件,也不像FE,倒像一个可以从外部调用的FD,SpiderMonkey将这种语法扩展 称之为函数语句(缩写为FS);该语法在MDC中提及过。
命名函数表达式的特性
当函数表达式FE有一个名称(称为命名函数表达式,缩写为NFE)时,将会出现一个重要的特点。从定义(正如我们从上面示例中看到的那样)中我们知道函数表达式不会影响一个上下文的变量对象(那样意味着既不可能通过名称在函数声明之前调用它,也不可能在声明之后调用它)。但是,FE在递归调用中可以通过名称调用自身。


代码如下:

(function foo(bar) {
if (bar) {
return;
}
foo(true); // "foo" 是可用的
})();
// 在外部,是不可用的
foo(); // "foo" 未定义

“foo”储存在什么地方?在foo的活动对象中?不是,因为在foo中没有定义任何”foo”。在上下文的父变量对象中创建foo?也不是,因为按照定义——FE不会影响VO(变量对象)——从外部调用foo我们可以实实在在的看到。那么在哪里呢?
以下是关键点。当解释器在代码执行阶段遇到命名的FE时,在FE创建之前,它创建了辅助的特定对象,并添加到当前作用域链的最前端。然后它创建了FE,此时(正如我们在第四章 作用域链知道的那样)函数获取了[[Scope]] 属性——创建这个函数上下文的作用域链)。此后,FE的名称添加到特定对象上作为唯一的属性;这个属性的值是引用到FE上。最后一步是从父作用域链中移除那个特定的对象。让我们在伪码中看看这个算法:


代码如下:

specialObject = {};
Scope = specialObject + Scope;
foo = new FunctionExpression;
foo.[[Scope]] = Scope;
specialObject.foo = foo; // {DontDelete}, {ReadOnly}
delete Scope[0]; // 从作用域链中删除定义的特殊对象specialObject

因此,在函数外部这个名称不可用的(因为它不在父作用域链中),但是,特定对象已经存储在函数的[[scope]]中,在那里名称是可用的。
但是需要注意的是一些实现(如Rhino)不是在特定对象中而是在FE的激活对象中存储这个可选的名称。Microsoft 中的执行完全打破了FE规则,它在父变量对象中保持了这个名称,这样函数在外部变得可以访问。
NFE 与SpiderMonkey
我们来看看NFE和SpiderMonkey的区别,SpiderMonkey 的一些版本有一个与特定对象相关的属性,它可以作为bug来对待(虽然按照标准所有的都那样实现了,但更像一个ECMAScript标准上的bug)。它与标识符的解析机制相关:作用域链的分析是二维的,在标识符的解析中,同样考虑到作用域链中每个对象的原型链。
如果我们在Object.prototype中定义一个属性,并引用一个”不存在(nonexistent)”的变量。我们就能看到这种执行机制。这样,在下面示例的”x”解析中,我们将到达全局对象,但是没发现”x”。但是,在SpiderMonkey 中全局对象继承了Object.prototype中的属性,相应地,”x”也能被解析。


代码如下:

Object.prototype.x = 10;
(function () {
alert(x); // 10
})();

活动对象没有原型。按照同样的起始条件,在上面的例子中,不可能看到内部函数的这种行为。如果定义一个局部变量”x”,并定义内部函数(FD或匿名的FE),然后再内部函数中引用”x”。那么这个变量将在父函数上下文(即,应该在哪里被解析)中而不是在Object.prototype中被解析。


代码如下:

Object.prototype.x = 10;
function foo() {
var x = 20;
// 函数声明
function bar() {
alert(x);
}
bar(); // 20, 从foo的变量对象AO中查询
// 匿名函数表达式也是一样
(function () {
alert(x); // 20, 也是从foo的变量对象AO中查询
})();
}
foo();

尽管如此,一些执行会出现例外,它给活动对象设置了一个原型。因此,在Blackberry 的执行中,上面例子中的”x”被解析为”10″。也就是说,既然在Object.prototype中已经找到了foo的值,那么它就不会到达foo的活动对象。
AO(bar FD or anonymous FE) -> no ->
AO(bar FD or anonymous FE).[[Prototype]] -> yes - 10
在SpiderMonkey 中,同样的情形我们完全可以在命名FE的特定对象中看到。这个特定的对象(按照标准)是普通对象——”就像表达式new Object()“,相应地,它应该从Object.prototype 继承属性,这恰恰是我们在SpiderMonkey (1.7以上的版本)看到的执行。其余的执行(包括新的TraceMonkey)不会为特定的对象设置一个原型。


代码如下:

function foo() {
var x = 10;
(function bar() {
alert(x); // 20, 不上10,不是从foo的活动对象上得到的
// "x"从链上查找:
// AO(bar) - no -> __specialObject(bar) -> no
// __specialObject(bar).[[Prototype]] - yes: 20
})();
}
Object.prototype.x = 20;
foo();

NFE与Jscript
当前IE浏览器(直到JScript 5.8 — IE8)中内置的JScript 执行有很多与函数表达式(NFE)相关的bug。所有的这些bug都完全与ECMA-262-3标准矛盾;有些可能会导致严重的错误。
首先,这个例子中JScript 破坏了FE的主要规则,它不应该通过函数名存储在变量对象中。可选的FE名称应该存储在特定的对象中,并只能在函数自身(而不是别的地方)中访问。但IE直接将它存储在父变量对象中。此外,命名的FE在JScript 中作为函数声明(FD)对待。即创建于进入上下文的阶段,在源代码中的定义之前可以访问。


代码如下:

// FE 在变量对象里可见
testNFE();
(function testNFE() {
alert('testNFE');
});
// FE 在定义结束以后也可见
// 就像函数声明一样
testNFE();

正如我们所见,它完全违背了规则。
其次,在声明中将命名FE赋给一个变量时,JScript 创建了两个不同的函数对象。逻辑上(特别注意的是在NFE的外部它的名称根本不应该被访问)很难命名这种行为。


代码如下:

var foo = function bar() {
alert('foo');
};
alert(typeof bar); // "function",
// 有趣的是
alert(foo === bar); // false!
foo.x = 10;
alert(bar.x); // 未定义
// 但执行的时候结果一样
foo(); // "foo"
bar(); // "foo"

再次看到,已经乱成一片了。
但是,需要注意的是,如果与变量赋值分开,单独描述NFE(如通过组运算符),然后将它赋给一个变量,并检查其相等性,结果为true,就好像是一个对象。


代码如下:

(function bar() {});
var foo = bar;
alert(foo === bar); // true
foo.x = 10;
alert(bar.x); // 10

此时是可以解释的。实际上,再次创建两个对象,但那样做事实上仍保持一个。如果我们再次认为这里的NFE被作为FD对待,然后在进入上下文阶段创建FD bar。此后,在代码执行阶段第二个对象——函数表达式(FE)bar 被创建,它不会被存储。相应地,没有FE bar的任何引用,它被移除了。这样就只有一个对象——FD bar,对它的引用赋给了变量foo。
第三,就通过arguments.callee间接引用一个函数而言,它引用的是被激活的那个对象的名称(确切的说——再这里有两个函数对象。


代码如下:

var foo = function bar() {
alert([
arguments.callee === foo,
arguments.callee === bar
]);
};
foo(); // [true, false]
bar(); // [false, true]

第四,JScript 像对待普通的FD一样对待NFE,他不服从条件表达式规则。即,就像一个FD,NFE在进入上下文时创建,在代码中最后的定义被使用。


代码如下:

var foo = function bar() {
alert(1);
};
if (false) {
foo = function bar() {
alert(2);
};
}
bar(); // 2
foo(); // 1

这种行为从”逻辑上”也可以解释。在进入上下文阶段,最后遇到的FD bar被创建,即包含alert(2)的函数。此后,在代码执行阶段,新的函数——FE bar创建,对它的引用赋给了变量foo。这样foo激活产生alert(1)。逻辑很清楚,但考虑到IE的bug,既然执行明显被破坏,并依赖于JScript 的bug,我给单词”逻辑上(logically)”加上了引号。
JScript 的第五个bug与全局对象的属性创建相关,全局对象由赋值给一个未限定的标识符(即,没有var关键字)来生成。既然NFE在这被作为FD对待,相应地,它存储在变量对象中,赋给一个未限定的标识符(即不是赋给变量而是全局对象的普通属性),万一函数的名称与未限定的标识符相同,这样该属性就不是全局的了。


代码如下:

(function () {
// 不用var的话,就不是当前上下文的一个变量了
// 而是全局对象的一个属性
foo = function foo() {};
})();
// 但,在匿名函数的外部,foo这个名字是不可用的
alert(typeof foo); // 未定义

“逻辑”已经很清楚了:在进入上下文阶段,函数声明foo取得了匿名函数局部上下文的活动对象。在代码执行阶段,名称foo在AO中已经存在,即,它被作为局部变量。相应地,在赋值操作中,只是简单的更新已存在于AO中的属性foo,而不是按照ECMA-262-3的逻辑创建全局对象的新属性。
通过函数构造器创建的函数
既然这种函数对象也有自己的特色,我们将它与FD和FE区分开来。其主要特点在于这种函数的[[Scope]]属性仅包含全局对象:


代码如下:

var x = 10;
function foo() {
var x = 20;
var y = 30;
var bar = new Function('alert(x); alert(y);');
bar(); // 10, "y" 未定义
}

我们看到,函数bar的[[Scope]]属性不包含foo上下文的Ao——变量”y”不能访问,变量”x”从全局对象中取得。顺便提醒一句,Function构造器既可使用new 关键字,也可以没有,这样说来,这些变体是等价的。

这些函数的其他特点与Equated Grammar ProductionsJoined Objects相关。作为优化建议(但是,实现上可以不使用优化),规范提供了这些机制。如,如果我们有一个100个元素的数组,在函数的一个循环中,执行可能使用Joined Objects 机制。结果是数组中的所有元素仅一个函数对象可以使用。

代码如下:

var a = [];

for (var k = 0; k < 100; k++) {
a[k] = function () {}; // 可能使用了joined objects
}

但是通过函数构造器创建的函数不会被连接。


代码如下:

var a = [];

for (var k = 0; k < 100; k++) {
a[k] = Function(''); // 一直是100个不同的函数
}

另外一个与联合对象(joined objects)相关的例子:


代码如下:

function foo() {

function bar(z) {
return z * z;
}

return bar;
}

var x = foo();
var y = foo();

这里的实现,也有权利连接对象x和对象y(使用同一个对象),因为函数(包括它们的内部[[Scope]] 属性)在根本上是没有区别的。因此,通过函数构造器创建的函数总是需要更多的内存资源。
创建函数的算法
下面的伪码描述了函数创建的算法(与联合对象相关的步骤除外)。这些描述有助于你理解ECMAScript中函数对象的更多细节。这种算法适合所有的函数类型。


代码如下:

F = new NativeObject();

// 属性[[Class]]是"Function"
F.[[Class]] = "Function"

// 函数对象的原型是Function的原型
F.[[Prototype]] = Function.prototype

// 医用到函数自身
// 调用表达式F的时候激活[[Call]]
// 并且创建新的执行上下文
F.[[Call]] = <reference to function>

// 在对象的普通构造器里编译
// [[Construct]] 通过new关键字激活
// 并且给新对象分配内存
// 然后调用F.[[Call]]初始化作为this传递的新创建的对象
F.[[Construct]] = internalConstructor

// 当前执行上下文的作用域链
// 例如,创建F的上下文
F.[[Scope]] = activeContext.Scope
// 如果函数通过new Function(...)来创建,
// 那么
F.[[Scope]] = globalContext.Scope

// 传入参数的个数
F.length = countParameters

// F对象创建的原型
__objectPrototype = new Object();
__objectPrototype.constructor = F // {DontEnum}, 在循环里不可枚举x
F.prototype = __objectPrototype

return F

注意,F.[[Prototype]]是函数(构造器)的一个原型,F.prototype是通过这个函数创建的对象的原型(因为术语常常混乱,一些文章中F.prototype被称之为“构造器的原型”,这是不正确的)。

结论
这篇文章有些长。但是,当我们在接下来关于对象和原型章节中将继续讨论函数,同样,我很乐意在评论中回答您的任何问题。

其它参考

(0)

相关推荐

  • javascript学习笔记(四)function函数部分

    函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块. Jscript 支持两种函数:一类是语言内部的函数(如eval() ),另一类是自己创建的. 在 JavaScript 函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它.(该变量的作用域是局部的). 您可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量. 函数的调用方式 1.普通调用:functionName(实际参数...) 2.通过指向函数的变量去调用: var  myVar

  • Javascript自执行匿名函数(function() { })()的原理浅析

    函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数指没有指定函数名或指针的函数,自执行匿名函数只是其中一种,下文中称这种函数为:自执行函数 下面是一个最常见的自执行函数: // 传统匿名函数 (function() { alert('hello'); })(); 这段代码的执行效果就是在页面再载入时弹出:"hello" 是什么促使它自动执行的?,来看下面的代码 // 在传统写法上去掉小括号,并在前面加上运算符 ~,!,+,- ~function(){

  • js function定义函数的几种不错方法

    js function定义函数的4种方法 1.最基本的作为一个本本分分的函数声明使用. 复制代码代码如下: 复制代码 代码如下: function func(){} 或 var func=function(){}; 2.作为一个类构造器使用: 复制代码代码如下: 复制代码 代码如下: function class(){} class.prototype={}; var item=new class(); 3.作为闭包使用: 复制代码代码如下: 复制代码 代码如下: (function(){ //

  • JS特殊函数(Function()构造函数、函数直接量)区别介绍

    函数定义 函数是由这样的方式进行声明的:关键字 function.函数名.一组参数,以及置于括号中的待执行代码. 函数的构造语法有这三种: Js代码 复制代码 代码如下: 1.function functionName(arg0, arg1, ... argN) { statements }//function语句 2.var function_name = new Function(arg1, arg2, ..., argN, function_body);//Function()构造函数 3

  • js 在定义的时候立即执行的函数表达式(function)写法

    1.前言 函数需要先定义,后使用. 这基本上所有编程语言的一条铁的定律. 一般状况下, 我们需要调用一个JavaScript 函数, 基本的状况都是先定义, 然后再调用. 看一个例子 复制代码 代码如下: <!--by oscar999 2013-1-16--> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dt

  • 深入理解javascript中的立即执行函数(function(){…})()

    javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解. ( function(){-} )()和( function (){-} () )是两种javascript立即执行函数的常见写法,最初我以为是一个括号包裹匿名函数,再在后面加个括号调用函数,最后达到函数定义后立即执行的目的,后来发现加括号的原因并非如此.要理解立即执行函数,需要先理解一些函数的基本概念.

  • 浅析JS中对函数function的理解(基础篇)

    正文:我们知道,在js中,函数实际上是一个对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法.因此,函数名实际上是指向函数对象的指针,不与某个函数绑定.在常见的两种定义方式(见下文)之外,还有一种定义的方式能更直观的体现出这个概念: var sum = new Function("num1", "num2", "return num1 + num2"); //不推荐 Function的构造函数可以接收任意数量的参

  • 深入理解JavaScript系列(4) 立即调用的函数表达式

    前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下"自执行"这个叫法,本文对这个功能的叫法也不一定完全对,主要是看个人如何理解,因为有的人说立即调用,有的人说自动执行,所以你完全可以按照你自己的理解来取一个名字,不过我听很多人都叫它为"自执行",但作者后面说了很多,来说服大家称呼为"立即调用的函数表达式". 本文英文原文地址:http://benalman

  • 深入理解JavaScript系列(2) 揭秘命名函数表达式

    前言 网上还没用发现有人对命名函数表达式进去重复深入的讨论,正因为如此,网上出现了各种各样的误解,本文将从原理和实践两个方面来探讨JavaScript关于命名函数表达式的优缺点. 简单的说,命名函数表达式只有一个用户,那就是在Debug或者Profiler分析的时候来描述函数的名称,也可以使用函数名实现递归,但很快你就会发现其实是不切实际的.当然,如果你不关注调试,那就没什么可担心的了,否则,如果你想了解兼容性方面的东西的话,你还是应该继续往下看看. 我们先开始看看,什么叫函数表达式,然后再说一

  • 深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点

    具体一点就是编写高质量JavaScript的一些要素,例如避免全局变量,使用单变量声明,在循环中预缓存length(长度),遵循代码阅读,以及更多. 此摘要也包括一些与代码不太相关的习惯,但对整体代码的创建息息相关,包括撰写API文档.执行同行评审以及运行JSLint.这些习惯和最佳做法可以帮助你写出更好的,更易于理解和维护的代码,这些代码在几个月或是几年之后再回过头看看也是会觉得很自豪的. 书写可维护的代码(Writing Maintainable Code ) 软件bug的修复是昂贵的,并且

  • 深入理解JavaScript系列(3) 全面解析Module模式

    简介 Module模式是JavaScript编程中一个非常通用的模式,一般情况下,大家都知道基本用法,本文尝试着给大家更多该模式的高级使用方式. 首先我们来看看Module模式的基本特征: 模块化,可重用 封装了变量和function,和全局的namaspace不接触,松耦合 只暴露可用public的方法,其它私有方法全部隐藏 关于Module模式,最早是由YUI的成员Eric Miraglia在4年前提出了这个概念,我们将从一个简单的例子来解释一下基本的用法(如果你已经非常熟悉了,请忽略这一节

  • 深入理解JavaScript系列(47):对象创建模式(上篇)

    介绍 本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码. 模式1:命名空间(namespace) 命名空间可以减少全局命名所需的数量,避免命名冲突或过度.一般我们在进行对象层级定义的时候,经常是这样的: 复制代码 代码如下: var app = app || {}; app.moduleA = app.moduleA || {}; app.moduleA.subModule = app.moduleA.subModule || {}; app.mod

  • 深入理解JavaScript内置函数

    javascript函数一共可分为五类: · 常规函数 · 数组函数 · 日期函数 · 数学函数 · 字符串函数 1.常规函数 javascript常规函数包括以下9个函数: (1)alert函数:显示一个警告对话框,包括一个OK按钮. (2)confirm函数:显示一个确认对话框,包括OK.Cancel按钮. (3)escape函数:将字符转换成Unicode码. (4)eval函数:计算表达式的结果. (5)isNaN函数:测试是(true)否(false)不是一个数字. (6)parseF

  • 深入理解JavaScript系列(12) 变量对象(Variable Object)

    JavaScript编程的时候总避免不了声明函数和变量,以成功构建我们的系统,但是解释器是如何并且在什么地方去查找这些函数和变量呢?我们引用这些对象的时候究竟发生了什么? 原始发布:Dmitry A. Soshnikov 发布时间:2009-06-27 俄文地址:http://dmitrysoshnikov.com/ecmascript/ru-chapter-2-variable-object/ 英文翻译:Dmitry A. Soshnikov 发布时间:2010-03-15 英文地址:http

  • 深入理解JavaScript系列(16) 闭包(Closures)

    介绍 本章我们将介绍在JavaScript里大家经常来讨论的话题 -- 闭包(closure).闭包其实大家都已经谈烂了.尽管如此,这里还是要试着从理论角度来讨论下闭包,看看ECMAScript中的闭包内部究竟是如何工作的. 正如在前面的文章中提到的,这些文章都是系列文章,相互之间都是有关联的.因此,为了更好的理解本文要介绍的内容,建议先去阅读第14章作用域链和第12章变量对象. 英文原文:http://dmitrysoshnikov.com/ecmascript/chapter-6-closu

  • 深入理解JavaScript系列(13) This? Yes,this!

    介绍 在这篇文章里,我们将讨论跟执行上下文直接相关的更多细节.讨论的主题就是this关键字.实践证明,这个主题很难,在不同执行上下文中this的确定经常会发生问题. 许多程序员习惯的认为,在程序语言中,this关键字与面向对象程序开发紧密相关,其完全指向由构造器新创建的对象.在ECMAScript规范中也是这样实现的,但正如我们将看到那样,在ECMAScript中,this并不限于只用来指向新创建的对象. 英文翻译: Dmitry A. Soshnikov在Stoyan Stefanov的帮助下

  • 深入理解JavaScript系列(10) JavaScript核心(晋级高手必读篇)

    适合的读者:有经验的开发员,专业前端人员. 原作者: Dmitry A. Soshnikov 发布时间: 2010-09-02 原文:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ 参考1:http://ued.ctrip.com/blog/?p=2795 参考2:http://www.cnblogs.com/ifishing/archive/2010/12/08/1900594.html 主要是综合了上面2位高手的中文翻译,

随机推荐