码迷,mamicode.com
首页 > 其他好文 > 详细

函数和闭包

时间:2015-04-12 14:38:46      阅读:199      评论:0      收藏:0      [点我收藏+]

标签:

一 概述

1.1 函数声明

(1)function命令

  函数就是使用function命令命名的代码区块,便于反复调用。这种声明方式叫做函数的声明(Function Declaration)。

 

function print(){
  // ...
}

 

 

 

(2)函数表达式

 

  除了用function命令声明函数,还可以采用变量赋值的写法。

var print = function (){
  // ...
};

 

  这种写法将一个匿名函数赋值给变量。这时,这个匿名函数又称函数表达式(Function Expression),因为赋值语句的等号右侧只能放表达式。采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名将成为函数内部的一个局部变量,只在函数体内部有效,在函数体外部无效。

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

x // ReferenceError: x is not defined

print() // function

 

(3)Function构造函数

  还有第三种声明函数的方式:通过Function构造函数声明。如果只有一个参数,该参数就是函数体。

var add = new Function("x","y","return (x+y)");
// 相当于定义了如下函数
// function add(x, y) {
//   return (x+y);
// }

 

 

1.2 return语句

  JavaScript引擎遇到return语句,就直接返回return后面的那个表达式的值,后面即使还有语句,也不会得到执行。也就是说,return语句所带的那个表达式,就是函数的返回值。return语句不是必需的,如果没有的话,该函数就不返回任何值,或者说返回undefined。

1.3 第一等公民

  JavaScript的函数与其他数据类型处于同等地位,可以使用其他数据类型的地方就能使用函数。比如,可以把函数赋值给变量和对象的属性,也可以当作参数传入其他函数,或者作为函数的结果返回。这表示函数与其他数据类型的地方是平等,所以又称函数为第一等公民。

 

function add(x,y){
  return x+y;
}

// 将函数赋值给一个变量
var operator = add;

// 将函数作为参数和返回值
function a(op){
  return op;
}
a(add)(1,1) // 2

 

 

 

 

1.4 函数名的提升

  JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会被提升到代码头部。所以,下面的代码不会报错:

f();
function f(){}

  表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错:

f();
var f = function (){}; // TypeError: undefined is not a function

 上面的代码等同于:

var f;
f();
f = function (){};

 

  当调用f的时候,f只是被声明,还没有被赋值,等于undefined,所以会报错。因此,如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。

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

function f() {
  console.log(‘2‘);
}

f() // 1

 

1.5 不能声明函数的情况

  函数声明语句并非真正的语句,ECMAScript规范只是允许他们作为顶级语句。它们可以出现在全局代码里,或者内嵌在其他函数里,但它们不能出现在循环、条件判断、或者try/catch/finally以及with语句中。

1.6 name属性

  大多数JavaScript引擎支持非标准的name属性。该属性返回紧跟在function关键字之后的那个函数名。

function f1() {}
f1.name // ‘f1‘

var f2 = function () {};
f2.name // ‘‘

var f3 = function myName() {};
f3.name // ‘myName‘

 

  上面代码中,函数的name属性总是返回紧跟在function关键字之后的那个函数名。对于f2来说,返回空字符串,对于f3来说,返回函数表达式的名字(真正的函数名还是f3,myName这个名字只在函数体内部可用)。

2. 函数的实参和形参

2.1 length属性

  所有函数都有一个length属性,返回函数定义中参数的个数。length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的”方法重载“(overload)。

function f(a,b) {}

f.length // 2

 

  上面代码定义了空函数f,它的length属性就是定义时参数的个数。不管调用时输入了多少个参数,length属性始终等于2。

2.2 参数的省略

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

function f(a,b){
    return a;
}

f(1,2,3) // 1
f(1) // 1
f() // undefined

f.length // 2

 

  上面代码的函数f定义了两个参数,但是运行时无论提供多少个参数(或者不提供参数),JavaScript都不会报错。被省略的参数的值就变为undefined。需要注意的是,函数的length属性与实际传入的参数个数无关,只反映定义时的参数个数。

  但是,没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。

2.3 传参方式

  JavaScript的函数参数传递方式是传值传递(passes by value),这意味着,在函数体内修改参数值,不会影响到函数外部。

// 修改原始类型的参数值
var p = 2; 

function f(p){
    p = 3;
}

f(p);
p // 2

// 修改复合类型的参数值
var o = [1,2,3];

function f(o){
    o = [2,3,4];
}

f(o);
o // [1, 2, 3]

 

  上面代码分成两段,分别修改原始类型的参数值和复合类型的参数值。两种情况下,函数内部修改参数值,都不会影响到函数外部。

  需要十分注意的是,虽然参数本身是传值传递,但是对于复合类型的变量来说,属性值是传址传递(pass by reference),也就是说,属性值是通过地址读取的。所以在函数体内修改复合类型变量的属性值,会影响到函数外部。

// 修改对象的属性值
var o = { p:1 };

function f(obj){
    obj.p = 2;
}

f(o);
o.p // 2

// 修改数组的属性值
var a = [1,2,3];

function f(a){
    a[0]=4;
}

f(a);
a // [4,2,3]

 

  上面代码在函数体内,分别修改对象和数组的属性值,结果都影响到了函数外部,这证明复合类型变量的属性值是传址传递。

  某些情况下,如果需要对某个变量达到传址传递的效果,可以将它写成全局对象的属性。

var a = 1;

function f(p){
    window[p]=2;
}

f(‘a‘); //变量a本来是传值传递,但是写成window对象的属性,就达到了传址传递的效果。

a // 2

 

2.4 可变长的实参列表arguments对象

  ECMAScript函数不介意传递进来多少个参数,也不在乎传进来的参数是什么类型的,原因是在ECMAScript中的参数是用一个数组表示的,这个数字就是arguments。arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,依次类推。这个对象只有在函数体内部,才可以使用。

技术分享
var f = function(one) {
  console.log(arguments[0]);
  console.log(arguments[1]);
  console.log(arguments[2]);
}

f(1, 2, 3)
// 1
// 2
// 3
View Code

 

  arguments对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)。可以通过arguments对象的length属性,判断函数调用时到底带几个参数。

(1)与数组的关系

  需要注意的是,虽然arguments很像数组,但它是一个对象。某些用于数组的方法(比如slice和forEach方法),不能在arguments对象上使用。但是,有时arguments可以像数组一样,用在某些只用于数组的方法。比如,用在apply方法中,或使用concat方法完成数组合并。

// 用于apply方法
myfunction.apply(obj, arguments).

// 使用与另一个数组合并
Array.prototype.concat.apply([1,2,3], arguments)

 

(2)caller和callee

  arguments对象带有一个callee属性,返回它所对应的原函数。

函数的内部属性arguments和this

 

函数和闭包

标签:

原文地址:http://www.cnblogs.com/borage/p/4419454.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!