函数只定义一次,但可能被执行或调用任意次。JS函数是参数化的,函数的定义会包括一个称为形参的标识符列表,这些参数在函数体中像局部变量一样工作。函数调用会为形参提供实参的值。函数使用它们实参的值来计算返回值,成为该函数调用表达式的值。除了实参之外,每次调用还会拥有另一个值,本次调用的上下文——this关键字的值。
如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文,也就是该函数的this的值。用于初始化一个新创建的对象的函数称为构造函数。
在JS里,函数即对象,JS可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数是对象,所以可以给它们设置属性,甚至调用它们的方法。
JS的函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。这意味着JS函数构成了一个闭包。
函数定义
函数使用function关键字来定义,它可以用在函数定义表达式或函数声明语句里。在这两种形式中,函数定义都从function关键字开始,其后跟随这些组成部分:
函数名称标识符。新定义的函数对象会赋值给这个变量。对函数定义表达式来说,这个名字是可选的:如果存在,该名字只存在于函数体中,并指代该函数对象本身。
一对圆括号。其中包含0个或多个用逗号隔开的标识符组成的列表。这些标识符是函数的参数名称,它们就像函数体中的局部变量一样。
一对花括号。其中包含0或多条JS语句。这些语句构成了函数体,一旦调用函数就会执行这些语句。
//输出o的每个属性的名称和值,返回undefined
function printprops(o){
for(var p in o){
console.log(p+":"+o[p]+"\n")
}
}
//这个函数表达式定义了一个函数用来求传入参数的平方
var square=function(x){return xx;}
//函数表达式可以包含名称,这在递归时很有用
var f=function fact(x){
if(x<=1){return 1;}else{ return xfact(x-1);}
};
//函数表达式也可以作为参数传给其它参数
data.sort(function(a,b){return a-b;});
//函数表达式有时定义后立即调用
var tansquared=(function(x){return x*x;}(10))
以表达式方式定义的函数,函数的名称是可选的。一条函数声明语句实际上声明了一个变量,并把一个函数对象赋值给它。定义函数表达式时并没有声明一个变量。如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量。
函数声明语句被提前到外部脚本或外部函数作用域的顶部,所以以这种方式声明的函数,可以被在它定义之前出现的代码调用。以表达式方式定义的函数在定义之前无法调用,因为变量的声明虽然提前了,但给变量赋值是不会提前的。
return语句导致函数停止执行,并返回它的表达式的值给调用者。如果return语句没有一个与之相关的表达式,则它返回undefined值。如果一个函数不包含return语句,那它就只执行函数体中的每条语句,并返回undefined值给调用者。
嵌套函数
函数可以嵌套在其他函数里。
function hypotenuse(a,b){
function square(x){return x*x;}
return Math.sqrt()(square(a)+square(b));
}
嵌套函数的变量作用域规则:它们可以访问嵌套它们的函数的参数和变量。
函数声明语句并非真正的语句,ECMAScript规范只是允许它们作为顶级语句。它们可以出现在全局代码里,或者内嵌在其他函数中,但它们不能出现在循环语句、条件判断语句、或try/catch/finally和with语句中。函数定义表达式可以出现在JS代码的任何地方。
函数调用
构成函数主体的JS代码在定义之时并不会执行,只有调用该函数时,它们才会执行。有4种方式来调用函数:
作为函数
作为方法
作为构造函数
通过它们的call()和apply()方法间接调用
函数调用
使用调用表达式可以进行普通的函数调用也可进行方法调用。一个调用表达式由多个函数表达式组成,每个函数表达式都是由一个函数对象和左圆括号、参数列表和右圆括号组成,参数列表是由逗号分隔的0个或多个参数表达式组成。如果函数表达式是一个属性访问表达式,即该函数是一个对象的属性或数组中的一个元素,那么它就是一个方法调用表达式。
printprops({x:1});
var total=distance(0,0,2,1)+distance(2,1,3,5);
var probability=factorial(5)/factorial(13);
在一次调用中,每个参数表达式都会计算出一个值,计算的结果作为参数传递给另外一个函数。这些值作为实参传递给声明函数时定义的形参。在函数体中存在一个形参的引用,指向当前传入的实参列表,通过它可以获得参数的值。
对于普通的函数调用,函数的返回值成为调用表达式的值。如果该函数返回是因为解释器到达结尾,返回值就是undefined。如果函数返回是因为解释器执行到一条return语句,返回值就是return之后的表达式的值,如果return语句没有值,则返回undefined。
以函数形式调用的函数通常不适用this关键字。不过this可以用来判断当前是否是严格模式。根据ES3和非严格的ES5对函数调用的规定,调用上下文(this的值)是全局对象。在严格模式下调用上下文则是undefined。
//定义并调用一个函数来确定当前脚本运行时是否为严格模式
var strict=(function(){return !this;}());
方法调用
一个方法就是保存在一个对象的属性里的函数。如果有一个函数f和一个对象o,则给o定义一个名为m()的方法可用下面的方法:
o.m=f;
o.m(); //调用m()方法
对方法调用的参数和返回值的处理,和普通函数调用一致。方法调用和函数调用有一个重要区别,即调用上下文。属性访问表达式由两部分组成:一个对象(o)和属性名称(m)。在像这样的方法调用表达式里,对象o成为调用上下文,函数体可以使用this引用该对象。
var calculator={
operand1:1,
operand2:1,
add:function(){
this.result=this.operand1+this.operand2;
}
};
calculator.add(); //calculator.result=>2
大多数方法调用使用点符号来访问属性,使用方括号也可以进行属性访问操作。
o"m"; //o.m(x,y)的另一种写法
a0; //假设a[0]是一个函数
方法调用可能包括更复杂的属性访问表达式:
customer.surname.toUpperCase(); //调用customer.surname的方法
f().m(); //在f()调用结束后继续调用返回值中的方法m()
任何函数只要作为方法调用实际上都会传入一个隐式的实参。这个实参是一个对象,方法调用的母体就是这个对象。通常来讲,基于那个对象的方法可以执行多种操作,方法调用的语法表明了函数将基于一个对象进行操作。
rect.setSize(width,height);
setRectSize(width,height);
第一行的方法调用表明这个函数执行的载体是rect对象,函数中的所有操作都将基于这个对象。
this是一个关键字,不是变量也不是属性名。JS的语法不允许给this赋值。
和变量不同,this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下)。如果想访问外部函数的this值,需要将this的值保存在一个变量里,这个变量和内部函数都同在一个作用域内。
var o={
m:function(){
var self=this;
console.log(this===o); //true
f();
function f(){ //定义一个嵌套函数f()
console.log(this===o); //false,this的值是全局对象或undefined
console.log(self===o); //true
}
}
};
o.m();
构造函数调用
如果函数或方法调用之前带有关键字new,它就构成构造函数调用。如果构造函数调用在圆括号内包含一组实参列表,先计算这些实参表达式,然后传入函数内,这和函数调用和方法调用是一致的。但如果构造函数没有形参,构造函数调用的语法允许省略实参列表和圆括号。凡是没有形参的构造函数调用都可以省略圆括号。
var o=new Object();
var o=new Object;
构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数试图初始化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用这个新创建的对象。尽管构造函数看起来像一个方法调用,它依然会使用这个新对象作为调用上下文。也就是说,在表达式new o.m()中,调用上下文并不是o。
构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值。如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用return语句但没有指定返回值,或返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果。
间接调用
函数也是对象,函数对象也可以包含方法。其中的两个方法call()和apply()可以间接地调用函数。两个方法都允许显式指定调用所需的this值,也就是说任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。两个方法都可以指定调用的实参。call()方法使用它自有的实参列表作为函数的实参。apply()方法则要求以数组的形式传入参数。
函数的实参和形参
函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型的检查。函数调用甚至不检查传入形参的个数。
可选形参
当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。因此在调用函数时形参是否可选以及是否可以省略应当保持较好的适应性。为做到这一点,应当给省略的参数赋一个合理的默认值。
//将对象o中可枚举的属性名追加至数组a中,并返回这个数组a
//如果省略a,则创建一个新数组并返回这个新数组
function getPropertyNames(o,/optional/a){
if(a===undefined){a=[];} //如果未定义则使用新数组或使用a=a||[];
for(var property in o){a.push(property);}
return a;
}
//这个函数调用可传入1个或2个实参
var a=getPropertyNames(o); //将o的属性存储到一个新数组中
getPropertyNames(p,a); //将p的属性追加至数组a中
当用这种可选实参来实现函数时,需要将可选实参放在实参列表的最后。调用函数是没办法省略第一个实参并传入第二个实参的,它必须将undefined作为第一个实参显式传入。在函数定义中使用/optional/来强调形参是可选的。
可变长的实参列表:实参对象
当调用函数的时候传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值的引用。参数对象解决了这个问题。在函数体中,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象,这样可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。
假设定义了函数f,它的实参只有一个x。如果调用这个函数时传入两个实参,第一个实参可以通过参数名x来获得,也可以通过arguments[0]来得到。第二个实参只能通过arguments[1]来得到。arguments也包含一个length属性。,用以标识其所包含元素的个数。
function f(x,y,z){
//首先验证传入实参的个数是否正确
if(arguments.length!=3){
throw new Error("function f called with"+arguments.length+"arguments")
}
}
省略的实参都将是undefined,多出的参数会自动省略。
实参对象有一个重要的用处,就是让函数可以操作任意数量的实参。
function max(/.../){
var max=Number.NEGATIVE_INFINITY;
//遍历实参,查找并记住最大值
for(var i=0;i<arguments.length;i++){
if(arguments[i]>max) {max=arguments[i];}
return max; //返回最大值
}
}
var largest=max(1,10,100,2,3,10000); 10000
类似这种函数可以接收任意个数的参数,这种函数也称为不定实参函数。不定实参函数的实参个数不能为0,arguments[]对象最适合在这样一类函数中,这类函数包含固定个数的命名和必须参数,以及随后个数不定的可选实参。
arguments并不是真正的数组,它是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及length属性。
数组对象包含一个特性。在非严格模式下,当一个函数包含若干形参,实参对象的数组元素是函数形参所对应实参的别名,实参对象中以数字索引,并且形参名称可以认为是相同变量的不同命名。通过实参名字来修改实参值的话,通过arguments[]数组也可以获取到更改后的值。
function f(x){
console.log(x); //输出实参的初始值
arguments[0]=null; //修改实参数组的元素同样会修改x的值
console.log(x); //输出null
}
在ES5中移除了实参对象的这个特性。在非严格模式中,函数里的arguments仅仅是一个标识符,在严格模式中,它变成了一个保留字。严格模式中的函数无法使用arguments作为形参名或局部变量名,也不能给arguments赋值。
callee和caller属性
除了数组元素,实参对象还定义了callee和caller属性。在ES5严格模式中,对这两个属性的读写操作都会产生一个类型错误。在非严格模式下callee属性指代当前正在执行的函数。caller是非标准的,它指代调用当前正在执行的函数的函数。通过caller属性可以访问调用栈。callee属性在某些时候非常有用,比如在匿名函数中通过callee来递归的调用自身。
var factorial=function(x){
if(x<=1) {return 1;}
return x*arguments.callee(x-1);
};
将对象属性用做实参
当一个函数包含超过三个形参时,最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。为了实现这种调用,定义函数的时候,传入的实参都写入一个对象中,在调用的时候传入一个对象,对象中的名/值对是真正需要的实参数据。
//将原始数组的length元素复制至目标数组
//开始复制原始数组的from_start元素
//并且将其复制至目标数组的to_start中
function arraycopy(/array/from,/index/from_start,/array/to,/index/to_start,/integer/length){
//逻辑代码
}
function easycopy(args){
arraycopy(args.from,args.from_start||0,args.to,args.to_start||0,args.length);
}
var a=[1,2,3,4],b=[];
easycopy({from:a,to:b,length:4});
实参类型
JS函数的形参并未声明类型,在形参传入函数体之前也未做任何类型检查。当一个函数可以接收任意数量的实参时,可以使用省略号。
function max(/number.../){}
如果函数期望接收一个字符串实参,而调用函数时传入其他类型值,所传入的值会在函数体内将其用做字符串的地方转换为字符串类型。所有的原始类型都可以转换为字符串,所有的对象都包含toString()方法(尽管不一定有),所以不会报错。
JS是一种灵活的弱类型语言,有时适合编写实参类型和实参个数不确定的函数。
//可以接收任意数量的实参,并可以递归地处理实参是数组的情况
//这个方法尽可能的在抛出异常之前将非数字转换为数字
function flexisum(a){
var total=0;
for(var i=0;i<arguments.length;i++){
var element=arguments[i],n;
if(element==null){continue;} //忽略null和undefined实参
if(isArray(element)){n=flexisum.apply(this,element);} //如果是数组递归计算累加和
else if(typeof element=="function"){ //如果是函数
n=Number(element()); //调用它并做类型转换
}
else {n=Number(element);} //否则直接做类型转换
if(isNaN(n)){throw Error("flexisum():can't convert "+element+"to number")};
total+=n;
}
return total;
}
作为值的函数
可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另外一个函数。
fucntion square(x){return x*x;}
这个定义创建一个新的函数对象,并将其赋值给变量square。函数的名字实际上是看不见的,它(square)仅仅是变量的名字,这个变量指代函数对象。函数还可以赋值给其他变量或赋值给对象的属性。当函数作为对象的属性调用时,函数就称为方法。
fucntion square(x){return xx;}
var s=square; //square(4)=s(4)
var o={square:function(){return xx;}};
var y=o.square(16); // y=256
var a=[function(x){return x*x;},20];
a0; //400
自定义函数属性
函数并不是原始值,而是一种特殊的对象,也就是说,函数可以拥有属性。当函数需要一个“静态”变量来在调用时保持某个值不变,最方便的方法就是给函数定义属性。
//由于函数声明被提前了,因此可以在函数声明之前给它的成员赋值
uniqueInteger.counter=0;
//每次调用这个函数都会返回一个不同的整数
function uniqueInteger(){
return uniqueInteger.counter++; //先返回计数器的值,然后计数器自增1
}
//第二个例子,计算阶乘并将结果缓存至函数的属性中
function factorial(n){
if(isFinite(n)&&n>0&&n==Math.round(n)){ //有限的正整数
if(!(n in factorial)){ //如果没有缓存结果
factorial[n]=n*factorial(n-1); //计算结果并缓存
}
return factorial[n]; //返回缓存结果
}else{return NaN;} //如果输入有误
}
factorial[1]=1; //初始化缓存以保存这种基本情况
作为命名空间的函数
函数作用域的概念:在函数中声明的变量在整个函数体内都是可见的,在函数的外部是不可见的。不在任何函数内声明的变量是全局变量。在JS中是无法声明只在一个代码块内可见的变量的,基于这个原因,我们常常简单的定义一个函数用做临时的命名空间,在这个命名空间内定义的变量不会污染全局环境。
闭包
函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。函数对象可以通过作用域链相互关联起来,函数体内的变量都可以保存在函数作用域内,这种特性称为闭包。
所有的函数都是闭包:它们都是对象,它们都关联到作用域链。定义大多数函数时的作用域链在调用函数时依然有效,但这并不影响闭包。
var scope="global scope";
function checkscope(){
var scope="local scope";
function f(){return scope;}
return f();
}
checkscope(); //local scope
//改变一下代码
function checkscope(){
var scope="local scope";
function f(){return scope;}
return f;
}
checkscope()(); //local scope
改动代码后checkscope()现在仅仅返回函数内嵌套的一个函数对象,而不是直接返回结果。函数的执行用到了作用域链,这个作用域链是函数定义时创建的。嵌套的函数f()定义在这个作用域链里,其中的变量scope一定是局部变量,不管在何时何地执行f(),这种绑定在执行f()时依然有效。因此返回local scope。
var uniqueInteger=(function(){
var counter=0;
return function(){return counter++;}
}());
这段代码定义了一个立即调用函数,因此是这个函数的返回值赋值给变量uniqueInteger。这个函数返回另外一个函数,这是一个嵌套的函数,我们将它赋值给变量uniqueInteger,嵌套的函数是可以访问作用域内的变量的,而且可以访问外部函数中定义的counter变量。当外部函数返回之后,其他任何代码都无法访问counter变量,只有内部的函数可以访问到它。
像counter一样的私有变量不是只能用在一个单独的闭包内,在同一个外部函数内定义的多个嵌套函数也可以访问它,这多个嵌套函数都共享一个作用域链。
function counter(){
var n=0;
return {
count:function(){return n++;}
reset:function(){n=0;}
};
}
var c=counter(),d=counter();
c.count(); //0
d.count(); //0
c.reset(); //reset()和count()方法共享状态
c.count(); //0
d.count(); //1
reset()方法和count()方法都可以访问私有变量n。每次调用counter()都会创建一个新的作用域链和一个新的私有变量。因此如果调用counter()两次,则会得到两个计数器对象,而且彼此包含不同的私有变量,调用其中一个计数器对象的count()或reset()不会影响到另外一个对象。
函数属性、方法和构造函数
length属性
在函数体里,arguments.length表示传入函数的实参的个数。函数的length属性是只读属性,它代表函数实参的数量,这里的参数指的是“形参”而非“实参”,也就是在函数定义时给出的实参个数,通常也是在函数调用时期望传入函数的实参个数。
//这个函数使用arguments.callee,因此它不能在严格模式下工作
function check(args){
var actual=args.length; //实参的真实个数
var expected=args.callee.length; //期望的实参个数
if(actual!==expected){
throw Error("Expected"+expected+"args;got"+actual);
}
}
function f(x,y,z){
check(arguments);
return x+y+z;
}
prototype属性
每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。
call()方法和apply()方法
我们可以将call()和apply()看做是某个对象的方法,通过调用方法的形式来间接调用函数。call()和apply()的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this来获得对它的引用。要想以对象o的方法来调用函数f(),可以像下面这样:
f.call(o);
f.apply(o);
//每行代码和下面的代码功能类似
//假设对象o中预先不存在名为m的属性
o.m=f; //将f存储为o的临时方法
o.m();
delete o.m;
在ES5的严格模式中,call()和apply()的第一个实参都会变为this的值,哪怕传入的实参是原始值甚至是null或undefined。在ES3和非严格模式中,传入的null和undefined都会被全局对象替代,而其他原始值则会被相应的包装对象所替代。
对于call()来说,第一个调用上下文实参之后的所有实参就是要传入待调用函数的值。
//以对象o
的方法的形式调用函数f
,并传入两个参数
f.call(o,1,2);
apply()方法传入的实参都放入一个数组中。
f.apply(o,[1,2])
如果一个函数的实参可以是任意数量,给apply()传入的参数数组可以是任意长度的。
var biggest=Math.max.apply(Math,array_of_numbers);
传入apply()的参数数组可以是类数组对象也可以是真实数组。可以将当前函数的arguments数组直接传入(另一个函数的)apply()来调用另一个函数。
//将对象o中名为m()的方法替换为另一个方法
//可以在调用原始的方法之前和之后记录日志消息
function trace(o,m){
var original=o[m]; //在闭包中保存原始方法
o[m]=function(){
console.log(new Date(),"entering:",m); //输出日志消息
var result=original.apply(this,arguments); //调用原始函数
console.log(new Date(),"exiting:",m);
return result;
}
}
bind()方法
bind()是ES5中新增的方法。这个方法主要作用是将函数绑定至某个对象。当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。(以函数调用的方式)调用新的函数将会把原始的函数f()当做o的方法来调用。传入新函数的任何实参都将传入原始函数。
function f(y){return this.x+y;} // 待绑定的函数
var o={x:1}; // 将要绑定的对象
var g=f.bind(o); // 通过调用g(x)来调用o.f(x)
//g(2) =>3
可以通过如下代码实现这种绑定。
//返回一个函数,通过调用它来调用o
中的方法f()
,传递它所有的实参
function bind(f,o){
if(f.bind) {return f.bind(o);} //如果bind()方法存在,使用bind()方法
else return function(){
return f.apply(o,arguments);
}
}
ES5中的bind()方法不仅仅是将函数绑定至一个对象,它还附带一些其它应用:除了第一个实参之外,传入bind()的实参也会绑定至this,这个附带的应用是一种常见的函数式编程技术,有时也被称为“柯里化”。
var sum=function(x,y){return x+y;}
//创建一个类似sum的新函数,但this的值绑定到null
//并且第一个参数绑定到1,这个新的函数期望只传入一个实参
var succ=sum.bind(null,1);
succ(2); //3,x绑定到1,并传入2作为实参y
function f(y,z){return this.x+y+z;};
var g=f.bind({x:1,2}); //g(3)=6,this.x绑定到1,y绑定到2,z绑定到3
bind()方法返回一个函数对象,这个函数对象的length属性是绑定函数的形参个数减去绑定实参的个数。bind()方法可以顺带用作构造函数,如果bind()返回的函数用作构造函数,将忽略传入bind()的this,原始函数就会以构造函数的形式调用,其实参也已经绑定。由bind()方法所返回的函数并不包含prototype属性,并且将这些绑定的函数用作构造函数时所创建的对象从原始的未绑定的构造函数中继承prototype。同样,在使用instanceof运算符时,绑定构造函数和未绑定构造函数并无两样。
toString()方法
函数的toString()方法返回一个字符串,这个字符串和函数声明语句的语法有关。实际上,大多数的toString()方法的实现都返回函数的完整源码。内置函数往往返回一个类似[native code]的字符串作为函数体。
Function()构造函数
除了函数声明和函数表达式,函数还可以通过Function()构造函数来定义。
var f=new Function("x","y","return x*y;");
Function()构造函数可以传入任意数量的字符串实参,最后一个实参所表示的文本就是函数体,它可以包含任意的JS语句,每两条语句之间用分号分隔。传入构造函数的其他所有的实参字符串是指定函数的形参名字的字符串。如果定义的函数不包含任何参数,只需给构造函数传入一个字符串——函数体即可。
Function()构造函数并不需要通过传入实参以指定函数名。就像函数字面量一样,Function()构造函数创建一个匿名函数。
关于Function()构造函数有几点需要注意:
Function()构造函数允许JS在运行时动态的创建并编译函数。
每次调用Function()构造函数都会解析函数体,并创建新的函数对象。如果是在一个循环或者多次调用的函数中执行这个构造函数,执行效率会受影响。相比之下,循环中的嵌套函数和函数定义表达式则不会每次执行时都重新编译。
Function()构造函数创建的函数并不是使用词法作用域,相反,函数体代码的编译总是会在顶层函数执行。
var socpe="global";
function constructFunction(){
var scope="local";
return new Function("return scope"); //无法捕获局部作用域
}
constructFunction()(); //global
网友评论