js中函数的函数

函数是支撑一门编程语言的重要内容,在JavaScript(下面简称js)中,函数有多种声明和调用方式,而且函数的位置还和作用域息息相关。同时在ES2015中新增了箭头函数的用法,他们之间有很多易于混淆并且平时很难注意到的小的知识点,这篇文章希望能够全面总结关于函数声明的那些问题。

词法作用域

在js中,尤其是ES2015之前,只存在两种词法作用域,一种是全局作用域,一种是函数作用域。变量在不同的作用域具有屏蔽的效应。

```javascript
var info = 'global info';
function fun() {
    var info = 'local info';
    
    console.log(info);  //local info;
}
console.log(info);  //glocal info;
```

在函数作用域内会屏蔽在全局作用域上的相同名称的变量。

声明方式

ES2015之前,js中函数有两种声明方式。一种是通过function关键字进行函数声明,一种是将变量命名为函数。

```javascript
var fun1 = function () {
    console.log('this is fun1');
}

function fun2 () {
    console.log('this is fun2');
}
```

上面就是两种声明函数的方式,两种方式都能进行函数的声明,之后再进行函数的调用。

```javascript
...
fun1(); //this is fun1
fun2(); //this is fun2
```

那么这两种方式都有哪些区别呢?

1. 匿名与非匿名问题

使用变量赋值函数的声明方式,后面的函数默认是个匿名函数,它是将一个匿名函数赋值给一个变量。从而能通过变量引用函数。

使用函数关键字定义的声明方式,函数的名称就是定义时的名称。

平日使用时,可能这两种方式区别不是很明显,当使用console.log时,他们区别就得以体现:

```javascript
...
console.log(fun1)
/*
 * function () {
 *      console.log('this is fun1');
 * }
 */

console.log(fun2)
/*
 * function fun2 () {
 *      console.log('this is fun2');
 * }
 */
```

所以,我们在通过变量定义函数时,可以同时加上函数名称,这样能够在报错或者使用console的函数输出时,能够得到最全的信息。

```javascript
var fun1 = function fun1 () {
    ...
}
```

上面的这种写法,就能让console输出时同时打印出函数的名称,方便程序调试。

2. 定义位置的问题

想上面屏蔽变量一样,函数名称在全局环境中也是一个变量,它们是否接受这样的设定呢?在不同的作用域中有屏蔽的现象?

答案是肯定的,而且这种屏蔽的机制或许更复杂。

在这里,我们所需要具备的js基础知识不仅有上面作用域的问题,还有变量提升,变量改变问题。

相同作用域下的变量改变:

ES2015之前,我们只能通过var关键字定义变量。var a = 1;即在stack内存定义了一块区域存放1的值。如果在接下来的相同作用域环境内重新出现了一个名称为a的变量,那么这个区域的值就会改变。

```javascript
var a = 1;
console.log(a); //1
var a = 2;
console.log(a); //2
```

实际上,上面的代码在引擎解析成为抽象语法树时与接下来的代码是相同的:

```javascript
var a = 1;
console.log(a); //1
a = 2;
console.log(a); //2
```

变量提升:

同样的,使用var关键字还会遇到变量提升的问题。引擎在解析时,会首先将所有使用var定义的关键字放到前面。

```javascript
console.log(a); //undefined
var a = 1;
```

上面的代码可能会让初学者震惊,为什么变量在之后定义,但是却没有抛出Refference Error(引用错误),而是输出undefined。

这就是var关键字的神奇之处,它能够将本作用域中所有使用var关键字的变量提升到作用域最前方。

简单来说,上面的代码和下面的代码是等效的:

```javascript
var a;
console.log(a);
a = 1;
```

同样的,无论多少个变量,都会提升到作用域的开头。

有了上面基础知识的铺垫,接下来关于函数声明的部分就变得容易很多。因为函数变量声明函数就用到了var关键字。

所以在使用多个var关键字进行函数声明时,语句中的函数名称总是指向最近的函数,这句话并不好理解,但是代码却很好理解:

```javascript
var fun = function fun() {console.log(1);}
fun();  //1

fun = function fun() {console.log(2);}
fun();  //2
```

但是,这里有一个常错的地方,就是在函数的提前调用。

```javascript
fun();      //Type Error
var fun = function fun() {console.log(1);}
```

上面这种情况下就会报错,如果改变一下代码结构就会清晰很多:

```javascript
var fun;    //变量提升
fun();      //函数调用
fun = function fun() {console.log(1);}  //函数声明  
```

因为var关键字有变量提升,所以没有抛出一个引用错误,而在调用时,fun的值因为变量提升为undefined,此时却将它作为函数调用的方法来进行使用,所以抛出类型错误。

这也就告诉我们,使用变量名称进行函数声明时,必须在声明结束后才能使用函数。

但是另一方面,使用function关键字进行函数声明就不会遇到这样的问题,而且function也会进行变量提升。

```javascript
fun();      //1
function fun () {console.log(1)}
```

多次定义时也不尽相同,总是以最后一次为准。

```javascript
function fun () {console.log(1)}
fun();      //2
function fun () {console.log(2)}
fun();      //2
```

所以,在没有ES2015标准之前,这种写法就是错误的:

```javascript
if (something) {
    function fun () {
        console.log(1);
    } 
} else {
    function fun () {
        console.log(2);
    }
}
```

因为函数会同时被提升到当前词法作用域最顶层,有的浏览器会报错,有的浏览器只会使用fun的第二个定义。

以上就是使用关键字function和关键字var定义函数的区别。

那么,接下来的代码输出什么呢?

```javascript
print();

var print = function () {
    console.log(1);
};

print();

function print () {
    console.log(2);
}

print();

function print () {
    console.log(3);
}

print = function () {
    console.log(4);
}
```

上面一共有三次调用print的地方。上面的结果是 3 1 1 ,这就说明function关键字的提升是先于var的提升的。

箭头函数

ES2016中新定义了一种叫做箭头函数的函数,不同于上面两种声明方式,箭头函数只能通过定义变量的方式进行声明,且必须是匿名函数。

```javascript
var arrow = () => {
    console.log('this is arrow function');
}
```

在仅有一行结果时,可以省略return关键字和大括号

```javascript
var arrow = () => console.log(1);
arrow();        //1
```

同时,箭头函数,内部的this指针永远指向调用该函数的词法作用域。

自执行函数

自执行函数全称是Immediately-invoked Function Expression,中文翻译是立即执行函数表达式。它将匿名函数放在一条语句中,在里面声明一个匿名函数,从而能够封装出一个函数作用域,然后在定义时执行这个匿名函数。

能这么做的原因就是因为在js中,所有的程序语句都是表达式。所以下面两个语句快是等价的:

```javascript
//第一种
var fun1 = function () {console.log(1)}
fun1();

//第二种
( function fun2 () {console.log(2)} )()
```

上面两种写法都能执行定义的函数,第二种写法将函数定义在由括号封装的语句中,再加上使用()结尾进行调用函数,从而使函数执行。同样的道理,我们可以仿造第二种执行函数的方式,制造出一种只执行一次的自执行函数出来。

```javascript
( function autoRun() {
    console.log('auto run here!');
} )()
```

这样,当程序执行到这里时函数就会自执行,同样的,我们可以不给函数命名,声明一个匿名函数,从而不会污染全局变量。

```javascript
( function () {
    console.log('also auto run!');
} )()
```

还可以将执行的括号放在里面,不至于变的混乱,增强程序可读性。

```javascript
( function () {
    console.log('auto run here!');
} ())
```

但是,使用ES2015箭头函数定义的函数不能像上面这样将执行的括号放在表达式里面,只能通过放在表达式外面进行函数自执行。

```javascript
( () => console.log('auto run') )()
```

这样做的好处也很多,在ES2015标准之前,仅存在全局作用域和函数作用域,没有传统编程语言上面的块级作用域,这个时候IIFE就派上用场了,他能创建出一个函数作用域出来,但是这个函数仅仅会在运行到这一步执行一次,实际上就等同于一个简易的块级作用域出来。从而解决很多问题,但是在ES2015的块级作用域出现之后,IIFE现在很少能派上用场了,不过有些场景下还是会使用到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容