函数

1、函数定义

总共有三种函数定义的方式:函数声明语句、函数表达式、内置构造函数。

  1. 函数声明语句
function functionName(parameters) {
    //执行的代码
}

函数声明后不会立即执行,会在我们需要的时候调用到。

小练习:定义一个求阶乘的函数。

function fn2(n) {
            var s = n;
            var result = 1;
            for (var n; n >= 1; n--) {
                result *= n;
            }
            console.log(s + '的阶乘等于' + result);
        }
        fn2(12);

 

2.函数表达式

var functionName  = function (parameters) {
    //执行的代码
};
//函数以分号结尾,因为它实际上是一个执行语句

以上函数实际上是一个 匿名函数 (函数没有名称),函数存储于变量中,故通常不加函数名。

当写递归函数时,也可加上函数名。

定义一个表达式函数:

var square = function factorial(h) {
    if(h===1) {return 1;}
    else     {return h*factorial(h-1);}
}
console.log(square(3));

3.Function内置构造函数

在以上实例中,我们了解到函数通过关键字 function 定义。

函数同样可以通过内置的 JavaScript 函数构造器(Function())定义。

var functionName  = new Function("parameters","执行的代码")
//注意引号不可省

这种方式不推荐,无法写递归。

用Function()构造函数创建一个函数时并不遵循典型的作用域,它一直把它当作是顶级函数来执行。所以,在 JavaScript 中,很多时候,你需要避免使用 new 关键字。

var y = "global";
function constructFunction() {
  var y = "local";
  function test(){};
  return new Function("return y"); // 无法获取局部变量,作用域始终是全局作用域 
}
alert(  constructFunction()()   );

补充:函数可以嵌套在其他函数里面,也就是在函数里面可以定义函数

function distance (x1, y1, x2, y2) {
    var n = 10;
    function square(h) { console.log(n);return h*h;}
    return Math.sqrt(square(x2-x1) + square(y2-y1)).toFixed(2);
}

被嵌套的函数可以访问嵌套他们的函数的变量或参数。

2、 函数调用

javascript一共有4种调用模式:函数调用模式、方法调用模式、构造器调用模式和间接调用模式。

每种方式的不同在于 this 的初始化。

  • 函数调用模式
var myfunc = function(a,b){
    console.log(this);
    return a+b;
}

alert( myfunc(3,4) );

函数调用模式中:

a, this是指向Window的 

b, 返回值是由return语句决定的,如果没有return则表示没有返回值

  • 方法调用模式
    先定义一个对象,然后在对象的属性中定义方法,通过myobject.property来执行方法。
var name = "james";
var obj = {
  name : "wade",
  fn1 : function () {
    console.log(this.name);
  }  
};
obj.fn1(); //wade

方法调用模式中:

a, this 是指向调用该方法的对象

b, 返回值还是由return语句决定,如果没有return表示没有返回值

  • 构造器调用模式
    如果函数或者方法调用之前带有关键字new,它就当成构造函数调用。
function Fn () {
    this.name = "james";
    this.age = 32;
    console.log(this);   
    
};
var fn1 = new Fn(); //Fn整个对象 
console.log(fn1);    //Fn整个对象

通过上面的代码结果分析,会得到以下结论(构造函数调用模式中):

a, this是指向构造函数的实例

b, 如果没有添加返回值的话,默认的返回值是this

但是如果手动添加返回值之后呢?

function Fn1 () {
   this.name = "james";
   return "wade" ;          
};
var fn1 = new Fn1();
console.log(fn1);           
console.log(fn1.name);  

function Fn2 () {
   this.name = "james";
   return [1,2,3];          
};
var fn2 = new Fn2();    
console.log(fn2);           
console.log(fn2.name);

而通过上面的代码结果分析,优化上面的结论:

a, this是指向构造函数的实例

b, 如果没有添加返回值的话,默认的返回值是this

c, 如果有返回值,且返回值是简单数据类型(Number,String,Boolean··)的话,最后仍回返回this

d, 如果有返回值,且返回值是复杂数据类型(对象)的话,最终返回该对象,所以上面的fn2是指向数组,所以fn2.name为undefined

  • 间接调用模式
    也称之为“apply、call调用模式” 或 “上下文调用模式”。
var myobject={};
var sum = function(a,b){
    console.log(this);
  return a+b;
};
var sum2 = sum.call(myobject,10,30); 

alert(sum2);

由之前所学,this指向由传入的第一个参数决定。

再看看,下面函数调用中this指向如何呢?

function f1(){
   console.log(this);
}
f1.call(null);          
f1.call(undefined);     
f1.call(123);          
f1.call("abc");        
f1.call(true);         
f1.call([1,2,3]);

通过上面的代码结果分析,得出以下结论(上下文调用模式中):

a, 传递的参数不同,this的指向不同,this会指向传入参数的数据类型

b, 返回值是由return决定,如果没有return表示没有返回值。

3、函数参数

函数可以传进来任意多个参数,无论传进来的参数是什么类型,甚至可以不传参。

function add(x){
    return x+1;
}
console.log(add());//NaN

特殊情况1:同名形参

非严格模式下,函数中可以出现同名形参,且只能访问最后出现的该名称的形参。

function add(x,x,x){
    return x;
}
console.log( add(1,2,3) );//3

严格模式下,出现同名形参会抛出语法错误

特殊情况2:参数个数与传入的实参个数不相等时

当实参比函数声明指定的形参个数要少,剩下的形参都将设置为undefined值。当实参多于形参,则只使用有效的实参,多出部分没影响。

function add(x,y){
    console.log(x,y);
}
add(1);//1  undefined

4、参数分类

总的来说,函数参数分为两类:函数显式参数(Parameters)与隐式参数(Arguments)

显式参数(Parameters)

function functionName(parameter1, parameter2, parameter3) {
  // 要执行的代码……
}

函数显式参数在函数定义时列出(即形参)。

函数调用未传参时,参数会默认设置为: undefined。有时这是可以接受的,但是建议最好为参数设置一个默认值

function myFunction(x, y) {
  if (y === undefined) {
    y = 0;
  } 
  return x+y;
}
console.log(myFunction(1)); //打印1  因为设置了当之传入一个参数,也就是y为undefined的时候默认值为0,所以返回1+0=1

或者,更简单的方式:

function myFunction(x, y) {
  y = y || 0;//  ||当y存在值时就为那个值,否则默认为0,这个方法在后面将大量用到
}

隐式参数(Arguments)

JavaScript 函数有个内置的对象 arguments 对象。

argument 对象包含了函数调用的参数数组(实参数组)。

通过这种方式你可以很方便的找到最大的一个参数的值:

x = findMax(1, 123, 500, 115, 44, 88);

function findMax() {
    var  max = arguments[0];
    
    if(arguments.length < 2) return max;
 
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] > max) {
            max = arguments[i];
        }
        // max=arguments[i]>max?arguments[i]:max
    }
    return max;
}
 console.log(x);
//返回传入参数的最大值500

同理我们也可以的到传入参数的累计和:

x = findMax(1, 123, 500, 115, 44, 88);

        function findMax() {
            var sum = 0;

            for (var i = 0; i < arguments.length; i++) {
                sum += arguments[i];
            }
            return sum;
        }
        console.log(x);//所有参数的值相加为871
  • arguments对象与传入参数的映射规则:
function sum(a,b){

  arguments[1]=4;  //把4赋值给第二个参数也就是b
  
  console.log(arguments[1]);
  console.log(b);
}
sum(1);
sum(1,2);
//依次打印 4 undefined 4 4

arguments对象与形参是相互独立的,但又存在映射规则:

  1. 当传入参数与形参个数相等时,arguments对象与形参才是一一对应的;
  2. 当传入参数与形参个数不等时,arguments对象与有传入参数的形参才存在映射规则。
     

练习一道阿里巴巴2013年的一道笔试题:

下面代码中console.log的结果是[1,2,3,4,5]的选项是()AC
//A
function foo(x){
    console.log(arguments)
    return x;
}

foo(1,2,3,4,5)

//B
function foo(x){
    console.log(arguments)
    return x;
}(1,2,3,4,5)  //无输出

//C
(function foo(x){
    console.log(arguments)
    return x;
})(1,2,3,4,5)

//D
function foo(){
    bar.apply(null,arguments);
}
function bar(x){
    console.log(arguments);
}
foo(1,2,3,4,5)