JavaScript函数 1:函数定义与闭包的概念

一、定义函数

JavaScript定义函数有两种方法:函数声明和函数表达式。

1. 函数声明

function funcName(arg0, arg1, arg2) {
    //...
}
特性: 函数声明提升(function declaration hoisting)。在执行代码前会先读取函数声明,因此可以将函数声明放在调用语句之后:
test();
function test(a) {
    //...
}

2. 函数表达式

var funcName = function(arg0, arg1, arg2) {
    //...
};
这种形式相对于赋值语句,创建一个函数并赋值给变量funcName。此时创建的函数为 匿名函数(anonymous function),function关键字后并没有函数名字。函数表达式方式定义的函数不能进行函数声明提升,因为变量使用之前必须先赋值:
test(); //error,函数不存在

var test = function(a) {
    //...
};

两中定义方式的区别也正是函数提升机制的关键。例如以下错误代码:
if (condition) {
    function hello() {
        console.log("1");
    }
} else {
    function hello() {
        console.log("2");
    }
}
这在ECMAscript中是无效语法,不同浏览器对这种使用方式的处理方式不同,并不能按condition定义函数。使用函数表达式就没有问题:
if (condition) {
    var hello = function() {
        console.log("1");
    };
} else {
    var hello = function() {
        console.log("2");
    };
}

两种定义方法的区别还有立即执行函数的机制。

3. 立即执行函数

当函数只使用一次时,通常用 立即执行函数表达式 IIFE (Immediately Invokable Function Expressions)。
(function() {
    statements
})();

或者

(function() {
    statements
}());
为函数体加上括号是为了防止JavaScript解析歧义。IIFE中的函数必须是函数表达式而不能是函数声明,function关键字前的小括号的作用正是将匿名函数 转化为一个函数表达式

立即执行函数传参与普通函数一样:
(function(a, b){
    console.log(a + b);
})(1, 2)
// 3
IIFE一般用于构造私有变量,防止作用域污染。与C/C++等语言不同,JavaScript没有块级作用域。简单理解就是JavaScript环境中除了全局变量就是函数的私有变量。为了达到“封装”内部变量的效果,使用将变量定义在函数体内部就能实现隔离。

4. 箭头函数

ECMAScript 6新增了箭头函数(Arrow Function)。 箭头函数的语法比函数表达式更短,并且不会绑定自己的 this
基本语法:
(参数1, 参数2, …, 参数N) => {函数声明}
(参数1, 参数2, …, 参数N) => 表达式(单一)
//相当于:(参数1, 参数2, …, 参数N) =>{ return表达式}

// 当只有一个参数时,圆括号是可选的:
(单一参数) => {函数声明}
单一参数 => {函数声明}

// 没有参数的函数应该写成一对圆括号。
() => {函数声明}
JavaScript中函数是一等公民,可以作为值来在函数间传递,因此JavaScript也是一种函数式编程语言。

二、arguments

每个函数都内置了一个 arguments对象,可通过它来在函数中引用 函数的参数。例如:
function foo(arg0, arg1) {
    for (var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}

foo(1, 2, 3);
for循环会将参数1,2,3都输出,即arguments的元素个数与函数声明无关,只取决于调用时传入参数的个数。这种机制对于可以传递可变数量的参数的函数很有用。若想得到函数声明中参数数目可用Function.length属性。

arguments对象并不是一个Array,除了 length属性和索引元素之外没有任何Array属性。可以用以下几种方式将其转化为一个Array:
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES2015
const args = Array.from(arguments);

下面看一个最简单的的阶乘 递归函数:
function factorial(n) {
    if (n <= 1) {
        return 1;
    } else {
        return n * factorial(n-1);
    }
}
一般情况这个函数没有问题,但是使用如下方式会出错:
var func = factorial;
factorial = null;
func(5);    //error: factorial is not a function
上述代码将factorial()函数保存在变量func中,又将factorial置为null,导致指向原始阶乘函数的引用只剩下了func。但调用func(5)时,必须通过factorial()执行,而factorial()指向已经置空,导致错误。

为解决上述问题,可使用 arguments.callee解决。arguments.callee是一个指向当前函数的指针,可利用它来实现函数递归:
function factorial(n) {
    if (n <= 1) {
        return 1;
    } else {
        return n * arguments.callee(n-1);
    }
}
使用arguments.callee代替函数名就能避免上述问题。但是 严格模式下禁止访问arguments.callee。可以用命名函数表达式达成相同效果:
var factorial = (function f(n) {
    if (n <= 1) {
        return 1;
    } else {
        return n * f(n-1);
    }
});
这种方式创建了一个名为f()的命名函数表达式,并将其复制给变量factorial。即时将函数复制给另一变量,函数名字f仍然有效。具体原理见 Mozilla文档

三、闭包

首先来看 函数嵌套。当一个函数里面嵌套着另一个函数时,内部函数对外部函数是私有的,内部函数能“继承”外部函数的参数和变量,即:内部函数包含外部函数的作用域。

函数嵌套总结如下:
  • 内部函数只能通过外部函数访问
  • 内部函数可以访问外部函数的参数和变量,但外部函数不能访问内部函数的参数和变量
下例展示了一个简单的函数嵌套:
function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41
内部函数被外部函数所嵌套,它自身就形成了一个 闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数)。

不必纠结于闭包的定义,将上例改写为:
function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
fn_inside = outside(3); 
result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8
fn_inside = outside(3); 这句将参数3传给外部函数,也就是x=3。外部函数将内部函数return,赋值给了fn_insider,继续给fn_insider传递参数5,也就是y=5,就能调用return x + y;这句,得到最终结果。

这里外部函数的返回值并不是结果,而是它内部的一个函数。调用外部函数时并不能马上得到结果,而是将参数“保留”,“延迟”到调用内部函数时给出结果。

注意return inside时x是如何被保留的。一般来讲,某个函数调用完成后,它的内部的局部变量都会被垃圾回收机制销毁。而这种写法将outside()的变量x保留了下来。
一个闭包必须保存它可见作用域中所有参数和变量。

由于内部函数可以访问外部函数的作用域,因此当内部函数生存周期大于外部函数时,外部函数中定义的变量和函数将的生存周期比内部函数执行时间长。当内部函数以某一种方式被任何一个外部函数作用域访问时,一个闭包就产生了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值