阅读(4819) (16)

JavaScript学习笔记整理(6):函数

2017-06-19 12:00:31 更新
       函数就是一段可以反复调用的代码块。

函数使用function关键字来定义,还包括一个称为形参(parameter)的标识符列表,这些参数在函数体内像局部变量一样工作。

函数调用会为形参提供实参的值。函数使用它们实参的值来计算返回值,称为该函数调用表达式的值。

除了实参之外,每次调用还会拥有另一个值---本次调用的上下文---这就是this关键字的值。

如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法

JavaScript的函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量,这就是JavaScript的闭包1、函数定义(声明)

JavaScript有三种方法,可以定义一个函数。

(1)function命令

function name() {}

name是函数名称标识符。函数名称是函数声明语句必需的部分。不过对于函数表达式来说,名称是可选的:如果存在,该名字只存在于函数体内,并指向该函数对象本身。 圆括号:圆括号内可放置0个或多个用逗号隔开的标识符组成的列表,这些标识符就是函数的参数名称。 花括号:可包含0条或多条JavaScript语句。这些语句构成了函数体。一旦调用函数,就会执行这些语句。 (2)函数表达式

var f = function(x){   

  console.log(x);  

}

采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
(3)Function()
函数定义还可以通过Function()构造函数来定义

var f=new Function('x','y','return x+y');

等价于

var f=function(x,y){

  return x+y;

}

除了最后一个参数是函数体外,前面的其他参数都是函数的形参。如果函数不包含任何参数,只须给构造函数简单的传入一个字符串---函数体---即可。 不过,Function()构造函数在实际编程中很少会用到。

注意点:
如果同一个函数被多次定义(声明),后面的定义(声明)就会覆盖前面的定义(声明)

function f(){

 console.log(1);

}

f()  //1


function f(){

 console.log(2);

}

f()  //2

函数可以调用自身,这就是递归(recursion)

function f(x){

  if(x>2){

    console.log(x);

    return f(x-1);

  }else{

    return 1;

  }

}

f(4); 

// 4

//3

不能再条件语句中声明函数 2、函数命名
任何合法的JavaScript标识符都可以用做一个函数的名称。函数名称通常是动词或以动词为前缀的词组。 通常函数名的第一个字符为小写。当函数名包含多个单词时,可采取下划线法,比如:like_this();也可以采取驼峰法,也就是除了第一个单词之外的单词首字母使用大写字母,比如:likeThis();
3、被提前
就像变量的“被提前”一样,函数声明语句也会“被提前”到外部脚本或外部函数作用域的顶部,所以以这种方式声明的函数,可以被在它定义之前出现的代码所调用。

f()


function f(){}

上面的代码不会报错。
注意:以表达式定义的函数并没有“被提前”。

f();  

var f = function (){};  

// TypeError: f is not a function

变量其实是分为声明,赋值两部分的,上面的代码等同于下面的形式

var f;

f();

f = function() {};

调用f的时候,f只是被声明了,还没有被赋值,等于undefined,所以会报错。

4、嵌套函数

在JavaScript中,函数可以嵌套在其他函数里。

function go(){   

  function play(){}   

  return play();  

}

5、函数调用

构成函数主体的JavaScript代码在定义时并不会执行,只有调用该函数,它们才会执行。有4种方式调用JavaScript函数:
  • 作为函数
  • 作为方法
  • 作为构造函数
  • 通过它们的call()和apply()方法间接调用

5.1函数调用

f();

5.2方法调用

o.f=funciton(){} o.f();

5.3构造函数调用
如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。 凡是没有形参的构造函数调用都可以省略圆括号。

var o=new Object(); var o=new Object;

5.4间接调用
6、函数的实参和形参
可选形参 当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。 为了保持好的适应性,一般应当给参数赋予一个合理的默认值。

function go(x,y){   

  x = x || 1;   

  y = y || 2;  

}

注意:当用这种可选实参来实现函数时,需要将可选实参放在实参列表的最后。那些调用你的函数的程序员是没法省略第一个参数并传入第二个实参的。
可变长的实参列表:实参对象

当调用函数时,传入的实参个数超过函数定义时的形参个数时,是没有办法直接获得未命名值的引用。

这时,标识符arguments出现了,其指向实参对象的引用,实参对象是一个类数组对象,可以通过数字下标来访问传入函数的实参值,而不用非要通过名字来得到实参。

function go(x){   

  console.log(arguments[0]);   

  console.log(arguments[1]);  

}  

go(1,2);  

//1

//2

arguments有一个length属性,用以标识其所包含元素的个数。

function f(x){

  console.log(arguments.length);

}

f(1,2)  // 2

注意:arguments并不是真正的数组,它是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及length属性。

通过实参名字来修改实参值的话,通过arguments[]数组也可以获取到更改后的值。

function f(x){   

  console.log(x);    // 1

  arguments[0]=null;   

  console.log(x);    // null

}


f(1);

在上面的例子中,arguments[0]和x指代同一个值,修改其中一个的值会影响到另一个。 注意:如果有同名的参数,则取最后出现的那个值。

function f(x,x){

 console.log(x);

}

f(1,2)  // 2

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

7、将对象属性用做实参

当一个函数包含超过三个形参时,要记住调用函数中实参的正确顺序是件让人头疼的事。不过,我们可以通过名/值对的形式传入参数,这样就无法管参数的顺序了。
function f(params){ console.log(params.name); } f({name:'a'}) 8、作为值的函数
在JavaScript中,我们可以将函数赋值给变量。 function f(){} var a=f;
9、函数作用域
作用域(scope)指的是变量存在的范围。Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。 在函数外部声明的变量就是全局变量(global variable),它可以在函数内部读取。

var a=1;

function f(){

 console.log(a)

}

f()  //1

上面的代码中,函数f内部可以读取全局变量a。

在函数内部定义的变量,外部无法读取,称为“局部变量”(local variable)。

function f(){

  var a=1;

}

v  //ReferenceError: v is not defined

上面代码中,变量v在函数内部定义,所以是一个局部变量,函数之外就无法读取。

函数内部定义的变量,会在该作用域内覆盖同名全局变量。

var a=1;

function f(){

  var a=2;

  console.log(a);

}

f()  //2

a  //1

注意:对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。
函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
10、函数内部的变量提升
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

function f(x){

  if(x>10){

    var a = x -1;

  }

}


//等同于


function f(x){

  var a;

  if(x>10){

    a = x - 1;

  }

}

11、函数属性、方法和构造函数
name属性
name属性返回紧跟在function关键字之后的那个函数名。

function f(){}

f.name   //f

length属性
函数的length属性是只读属性,代表函数形参的数量,也就是在函数定义时给出的形参个数。

function f(x,y){}

f.length  //2

prototype属性 每一个函数都包含一个prototype属性,这个属性指向一个对象的引用,这个对象称做“原型对象”(prototype object)。
call()方法和apply()方法

call()

语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明: call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

apply()

语法:apply([thisObj[,argArray]]) 定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明: 如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。 如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。 bind()方法
bind()方法是在ECMAScript 5中新增的方法。 toString()方法

函数的toString方法返回函数的源码。

function f(){

  return 1;

}

f.toString()  

//function f(){

//  return 1;

//}

12、闭包
JavaScript的函数可以嵌套在其他函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量,这就是JavaScript的闭包。 闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。

function f(a){   

  return function(){   

    return a++;   

  };   

}   

var c=f(1);   

console.log(c());    //1

console.log(c());   //2

console.log(c());  //3

闭包的另一个用处,是封装对象的私有属性和私有方法。

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

在Javascript中,一对圆括号()是一种运算符,跟在函数名之后,表示调用该函数。

(function(){  

  statement

}())

上面的函数会立即调用。

注意:上面代码的圆括号的用法,function之前的左圆括号是必需的,因为如果不写这个左圆括号,JavaScript解释器会试图将关键字function解析为函数声明语句。而使用圆括号,JavaScript解释器才会正确地将其解析为函数定义表达式。

当然,下面的方法也会以表达式来处理函数定义的方法。

!function(){}();

~function(){}();

-function(){}();

+function(){}();


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

eval('var a=1');

a  //1

eval没有自己的作用域,都在当前作用域内执行

JavaScript规定,如果使用严格模式,eval内部声明的变量,不会影响到外部作用域。

(function(){

  'use strict';

  eval('var a=1');

  console.log(a);  //ReferenceError: a is not defined

})();