首页 > 代码库 > JavaScript的一等公民 — 函数

JavaScript的一等公民 — 函数

https://software.intel.com/zh-cn/articles/javascript-first-class-citizen-function/

 

此文是对博客的笔记和扩充,参考了JavaScript高级程序设计。

 

1、简单的JavaScript函数

1)常见的函数声明方式有以下两种:

function myfunc(args) { ... }

var myfunc = function(args) { ... }

两种函数有一些区别:第一种声明方式,可以把函数声明放到调用它的语句后面。但第二种,则不可以。原因是第二种声明方式严格意义上并不是函数的声明而是一个函数表达式。

 

myfunc1(); // 能够正常调用,因为myfunc1采用直接声明的方式

function myfunc1() {

}

 

myfunc2(); // 出错 TypeError: undefined is not a function

var myfunc2 = function() {

};

 

2)JavaScript函数也直接或间接地支持递归,比如斐波那契函数:

function fib(n) {

  if (n == 1 || n == 2) {

    return 1;

  } else {

    return fib(n - 2) + fib(n - 1);

  }

}

 

3)在JavaScript的函数可以处理变长参数,在函数内部都拥有一个名为arguments的局部变量,它是一个类数组(array-liked)的对象,里面包含了所有调用时传入的参数,有length属性表示参数的个数。例如:

function test() {

  alert(arguments.length);

}

 

test(1); // 1

test(1, ‘a‘); // 2

test(true, [], {}); // 3

 

2、函数进阶

1)匿名函数和嵌套函数

没有名称的函数成为匿名函数(Anonymouse Function)。也叫λ函数,匿名函数的name属性是空字符串。

函数内部声明函数,成为嵌套函数(Nested Function),潜逃函数作用域为整个父函数。

 

var myfunc = function(args) { ... }这种声明方式也算是一种匿名函数。

 

匿名函数常被用来防止全局环境污染。

 

例:

(function() { // 匿名函数

 

function log(msg) {

    console.log(msg);

}

 

// 其他代码

 

}()); // 立即执行

 

如果需要暴露一些接口的话有如下几种方法:

var mylib = (function(global) {

 

function log(msg) {

  console.log(msg);

}

 

log1 = log;  // 法一:利用没有var的变量声明的默认行为,在log1成为全局变量(不推荐)

 

global.log2 = log;  // 法二:直接在全局对象上添加log2属性,赋值为log函数(推荐)

 

return {  // 法三:通过匿名函数返回值得到一系列接口函数集合对象,赋值给全局变量mylib(推荐)

   log: log

};

 

}(window));

 

2)高阶函数

a.函数作为参数:

function negative(n) {

  return -n; // 取n的相反值

}

 

function square(n) {

  return n*n; // n的平方

}

 

function process(nums, callback) {

  var result = [];

 

  for(var i = 0, length = nums.length; i < length; i++) {

    result[i] = callback(nums[i]); // 对数组nums中的所有元素传递给callback进行处理,将返回值作为结果保存

  }

 

  return result;

}

 

var nums = [-3, -2, -1, 0, 1, 2, 3, 4];

var n_neg = process(nums, negative);

// n_neg = [3, 2, 1, 0, -1, -2, -3, -4];

var n_square = process(nums, square);

// n_square = [9, 4, 1, 0, 1, 4, 9, 16];

 

在process中,起先并不知道callback是什么,直到传递进去参数后,才直到到底要进行何种操作。

 

b.函数作为返回值

function generator() {

  var i = 0;

  return function() {

    return i++;

  };

}

 

var gen1 = generator(); // 得到一个自然数生成器

var gen2 = generator(); // 得到另一个自然数生成器

var r1 = gen1(); // r1 = 0

var r2 = gen1(); // r2 = 1

var r3 = gen2(); // r3 = 0

var r4 = gen2(); // r4 = 1

以上其实是一个闭包。

 

3)一个常用的类比来解释闭包和类(Class)的关系:类是带函数的数据,闭包是带数据的函数。

闭包中使用的变量有一个特性,就是它们不在父函数返回时释放,而是随着闭包生命周期的结束而结束。比如像上一节中generator的例子,gen1和gen2分别使用了相互独立的变量i(在gen1的i自增1的时候,gen2的i并不受影响,反之亦然),只要gen1或gen2这两个变量没有被JavaScript引擎垃圾回收,他们各自的变量i就不会被释放。

 

在JavaScript编程中,不知不觉就会使用到闭包,闭包的这个特性在带来易用的同时,也容易带来类似内存泄露的问题。例如:

var elem = document.getElementById(‘test‘);

elem.addEventListener(‘click‘, function() {

  alert(‘You clicked ‘ + elem.tagName);

});

这段代码的作用是点击一个结点时显示它的标签名称,它把一个匿名函数注册为一个DOM结点的click事件处理函数,函数内引用了一个DOM对象elem,就形成了闭包。这就会产生一个循环引用,即:DOM->闭包->DOM->闭包...DOM对象在闭包释放之前不会被释放;而闭包作为DOM对象的事件处理函数存在,所以在DOM对象释放前闭包不会释放,即使DOM对象在DOM tree中删除,由于这个循环引用的存在,DOM对象和闭包都不会被释放。可以用下面的方法可以避免这种内存泄露:

var elem = document.getElementById(‘test‘);

elem.addEventListener(‘click‘, function() {

  alert(‘You clicked ‘ + this.tagName); // 不再直接引用elem变量

});

上面这段代码中用this代替elem(在DOM事件处理函数中this指针指向DOM元素本身),让JS运行时不再认为这个函数中使用了父类的变量,因此不再形成闭包。

 

4)类构造函数

参考:http://zjzhome.sinaapp.com/article-content.php?id=29

 

3、JavaScript的妖怪

1)Function类

在JavaScript运行时中有一个内建的类叫做Function,用function关键字声明一个函数其实是创建Function类对象的一种简写形式,所有的函数都拥有Function类所有的方法,例如call、apply、bind等等,可以通过instanceof关键字来验证这个说法。

 

既然Function是一个类,那么它的构造函数就是Function(它本身也是Function类的对象),应该可以通过new关键字来生成一个函数对象。第一个妖怪来了,那就是如何用Function类构造一个函数。Function的语法如下:

new Function ([arg1[, arg2[, ... argN]],] functionBody)

 

其中arg1, arg2, ... argN是字符串,代表参数名称,functionBody也是字符串,表示函数体,前面的参数名称是可多可少的,Function的构造函数会把最后一个参数当做函数体,前面的都当做参数处理。

 

var func1 = new Function(‘name‘, ‘return "Hello, " + name + "!";‘);

func1(‘Ghostheaven‘); // Hello, Ghostheaven!

以上方法就通过Function构造了一个函数,这个函数跟其他用function关键字声明的函数一模一样。

 

看到这儿,很多人可能会问为什么需要这样一个妖怪呢?“存在的即是合理的”,Function类有它独特的用途,你可以利用它动态地生成各种函数逻辑,或者代替eval函数的功能,而且能保持当前环境不会被污染。

 

2)自更新函数(Self-update Function)

在很多语言中,函数一旦声明过就不能再次声明同名函数,否则会产生语法错误,而在JavaScript中的函数不仅可以重复声明,而且还可以自己更新自己。自己吃自己的妖怪来了!

function selfUpdate() {

  window.selfUpdate = function() {

    alert(‘second run!‘);

  };

 

  alert(‘first run!‘);

}

selfUpdate(); // first run!

selfUpdate(); // second run!

 

这种函数可以用于只运行一次的逻辑,在第一次运行之后就整个替换成一段新的逻辑。