[关闭]
@FunC 2016-10-30T12:10:51.000000Z 字数 2887 阅读 2389

理解JavaScript中的函数

JavaScript


本笔记参考的是阮一峰的《JavaScript 标准参考教程(alpha)》中的函数部分,非教程向。
(另外真的要感叹一句,阮一峰老师真的太适合去编写初高中教辅了2333,能把这些知识点这样来回折腾)

*鉴于JavaScript弱类型语言以及函数为一等公民的特性,JavaScript中的函数与其他语言中的函数有着不少差异,以致于很容易一不小心就会误用函数的一些特性,特此写下这篇笔记以便日后翻阅。
*以下内容基于ES2015,其中大多的坑据说日后ES6普及了就会消失。

概述

函数的声明

共三种方法

(1) function声明

  1. function print(s){
  2. console.log(s);
  3. }

此处的function实质为function命令,声明后通过print()的形式调用(即加上括号操作符);

(2) 函数表达式
因为JavaScript中函数是一等公民,所以可以通过声明匿名函数再赋值给变量的方式声明函数。

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

注意此处function的花括号}后面还有一个分号;
为什么呢?还记得赋值号=右边只能放什么吗?没错,就是表达式。所以这里赋值号的右边其实是一个函数表达式,所以要在最后加上分号结束语句(之前一直搞不懂为什么有的时候要加分号有的时候不用,看到这里终于懂了)
这种方法与(1)差别不大,但由于声明函数时是匿名声明,所以其实函数没有函数名(当然也可以加上函数名,但此时函数名只在函数内部有效
另外一个比较大的差别在于变量提升,将在后文谈到。

(3) Function构造函数(注意首字母为大写F)
这个太不直观,几乎没人用,也没什么特殊特性,故在此不谈。

函数的重复声明

后声明的会覆盖先声明的,所以也没有函数重载(没有重载的另一个原因在于JavaScript里面函数中的arguments对象,后文会谈到)

关于return

函数内部的return表示返回,return后不跟表达式则返回undefined(即没有返回)。另外JavaScript中函数不一定有返回值,但建议统一有return,便于调试。

关于函数名的提升

var一样,用function命令声明函数时,函数被提升到代码头部。而用函数表达式声明函数,因为先声明的是变量,后赋值,所以函数表达式声明的函数不会被提升。

不能/不应该在非函数代码块中声明函数

例如:if语句,for循环中

如果你有使用JSLinter,当你尝试在条件语句中声明函数时,JSLinter将会发出一个警告建议你不要这样做。
如果你想要在特定条件下才声明一个函数,因为函数名提升的原因,采用function命令声明将达不到想要的效果(此时声明的函数将在if所在作用域内均可被调用)。若想达到本来的目的,可以采用函数表达式声明(这样声明的就是局部变量了)

此外若在for循环中声明函数,会造成函数的重复声明,有可能会导致严重的效果。(此时也可以采用函数表达式声明、或者在循环体外声明,再循环体内循环调用)

函数的属性和方法

name属性

返回function关键字后的那个函数名。

length属性

返回函数定义时设置的参数个数。(可用于实现JavaScript中的函数重载)

toString()方法

返回函数的源码(包括注释)

关于变量作用域

Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。

再次强调,局部变量只能在函数内部声明,而不是代码块。(如条件判断区块中声明的变量是全局变量)

函数本身的作用域

函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不能引用函数A的内部变量。

具体看下面这个例子比较容易懂

  1. var x = function () {
  2. console.log(a);
  3. };
  4. function y(f) {
  5. var a = 2;
  6. f();
  7. }
  8. y(x)
  9. // ReferenceError: a is not defined

参数

函数参数不是必需的,Javascript允许省略参数。

个人疑问:采用addEventListener时若只有一个参数会报错,是因为V8对内部定义函数作了处理吗?

一般省略靠后参数,若想省略靠前参数,可通过显示传入undefined实现

默认值

通过下面方法可以为函数的参数设置默认值:

  1. function f(a){
  2. a = a || 1;
  3. return a;
  4. }
  5. f('') // 1
  6. f(0) // 1
  7. //这种方式依赖于或运算符的工作方式,也就是说传入的参数不能是undefined、0、空字符串和null

更精确的写法如下:

  1. function f(a) {
  2. (a !== undefined && a !== null) ? a = a : a = 1;
  3. return a;
  4. }
  5. f() // 1
  6. f('') // ""
  7. f(0) // 0

传递方式

本来传递方式应该可以分为按值传递和按引用传递两种,红宝书上说参数只会按值传递,阮一峰说复合类型的值按引用传递(个人觉得只是没定义清楚到底什么是按引用传递,反正这里复合类型的值传递实质是把参数的指针赋值给了函数的形式参数,所以对形参重新赋值将切断原来的引用)

某些情况下,如果需要对某个原始类型的变量,获取传址传递的效果,可以将它写成全局对象的属性。

个人疑问:为什么不直接把它返回给原变量?
个人猜想:return会结束语句,如果要修改多个全局变量也许要用?

同名参数

保留后者,第一个同名的参数相当于不存在。若想获得可以使用arguments对象。

aruments对象

arguments对象是函数内部自动建立的一个对象(故在函数外部不能使用),它包含了传入函数的所有参数。

既然叫做“对象”,就不是数组了,所以数组专有的方法不能直接在arguments对象上面直接用。
若要使用数组的方法:
1. apply方法(伪)
2. slice方法(会阻碍V8引擎的优化)
3. 逐一填入新数组

  1. var args = [];
  2. for (var i = 0; i < arguments.length; i++) {
  3. args.push(arguments[i]);
  4. }

闭包

谈到JavaScript似乎总绕不开闭包,初学者见到闭包总觉得是一种很高深的东西。然而其实闭包并不是一个特殊的机制、它只是利用JavaScript函数的特点实现的一个效果的概括罢了。

*滥用闭包导致的性能问题

立即调用的函数表达式(IIFE)

IIFE有很多种实现方式,主要思想是避开function命令放在句首时的引擎对它的定义操作,要让引擎将其理解成一个表达式。
下面给出其中一种实现方案

  1. !function(){ /* code */ }();
  2. //function后的()内是形参,后面的()是实际传入的参数。

作用:

通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是IIFE内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

eval命令

作用:将字符串当作语句执行。(不建议)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注