JS(5)——函数

1. Function 类型

每个函数都是Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。换句话说,一个函数可能会有多个名字,如下面的例子所示:

function sum(num1, num2){

    return num1 + num2;

}

alert(sum(10,10)); //20

var anotherSum = sum;

alert(anotherSum(10,10)); //20

sum = null;

alert(anotherSum(10,10)); //20

注意:因为函数名是指针,所以js没有函数重载的概念,如果声明了两个同名函数,后面的函数将会覆盖前面的函数。

因为ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。


2. 函数声明与函数表达式

1解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。例如:

alert(sum(10,10));

function sum(num1, num2){

    return num1 + num2;

}

以上代码完全可以正常运行。因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升(function declaration hoisting)的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以,即使声明函数的代码在调用它的代码后面,JavaScript 引擎也能把函数声明提升到顶部。如果像下面例子所示的,把上面的函数声明改为等价的函数表达式,就会在执行期间导致错误。

alert(sum(10,10));

var sum = function(num1, num2){

    return num1 + num2;

};

以上代码之所以会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。换句话说,在执行到函数所在的语句之前,变量sum 中不会保存有对函数的引用;而且,由于第一行代码就会导致“unexpected identifier”(意外标识符)错误,实际上也不会执行到下一行。

除了什么时候可以通过变量访问函数这一点区别之外,函数声明与函数表达式的语法其实是等价的。

2如何判断是函数声明还是函数表达式呢?ECMAScript是通过上下文来区分的,如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。例如:

function foo(){} // 声明,因为它是程序的一部分
var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分

new function bar(){}; // 表达式,因为它是new表达式
(function(){
   function bar(){} // 声明,因为它是函数体的一部分
})();

3. 命名函数表达式

可以像函数声明一样为函数表达式指定一个名字,但这并不会使函数表达式成为函数声明。命名函数表达式的名字不会进入名字空间,也不会被提升。例如:

f();//TypeError: f is not a function

foo();//ReferenceError: foo is not defined

var f = function foo(){console.log(typeof foo);};

f();//function

foo();//ReferenceError: foo is not defined

注意命名函数表达式的名字只在该函数的作用域内部有效。


4. 函数内部属性

4.1 递归与callee

在函数内部,有两个特殊的对象:arguments this。arguments 是一个类数组对象,包含着传入函数中的所有参数。虽然arguments 的主要用途是保存函数参数,但这个对象还有一个名叫callee 的属性, 该属性是一个指针,指向拥有这个arguments 对象的函数。请看下面这个非常经典的阶乘函数。

function factorial(num){
    if (num <=1) {
        return 1;
    } else {
        return num * factorial(num-1)
    }
}

定义阶乘函数一般都要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee

function factorial(num){

    if (num <=1) {
        return 1;
     } else {
        return num * arguments.callee(num-1)
    }
}

在这个重写后的factorial()函数的函数体内,没有再引用函数名factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。例如:

var trueFactorial = factorial;

factorial = function(){
 
    return 0;
};

alert(trueFactorial(5)); //120
aler t(factorial(5)); //0

当函数在严格模式下运行时,访问arguments.callee 会导致错误。不过,可以使用命名函数表达式来达成相同的结果。例如:

var factorial = (function f(num){
    if (num <= 1){
        return 1;
    } else {
        return num * f(num-1);
    }
});

注意:ECMAScript 5 还定义了arguments.caller 属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller 和函数的caller 属性


4.2 callercallee的区别 

1 、caller 返回一个调用当前函数的引用如果是由顶层调用的话则返回null。

例如:

  var callerTest = function() {

        console.log(callerTest.caller) ;  

     } ;

     function a() {
           callerTest() ;   
     }

     a() ;//输出function a() {callerTest();}

     callerTest() ;//输出null 

2 、callee返回一个正在被执行函数的引用,calleearguments对象的一个成员表示对函数对象本身的引用它有个length属性代表形参的长度

  var c = function(x,y) {

        console.log(arguments.length,arguments.callee.length,arguments.callee)

  } ;

  c(1,2,3) ;//输出3 2 function(x,y)


5. 函数属性和方法

5.1 函数属性(lengthprototype

ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:length prototype。其中,length 属性表示函数希望接收的命名参数的个数,例如:

function sayName(name){
    alert(name);
}

function sum(num1, num2){
    return num1 + num2;
}

function sayHi(){
    alert("hi");
}

alert(sayName.length); //1
alert(sum.length); //2
alert(sayHi.length); //0

对于ECMAScript 中的引用类型而言, prototype 是保存它们所有实例方法的真正所在。换句话说,诸如toString()valueOf()等方法实际上都保存在prototype 名下,只不过是通过各自对象的实例访问罢了。在ECMAScript 5 中,prototype 属性是不可枚举的,因此使用for-in 无法发现。


5.2 函数方法(apply()call()bind()

1apply()

每个函数都包含两个非继承而来的方法:apply()call()。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是arguments 对象。例如:

function sum(num1, num2){
    return num1 + num2;
}

function callSum1(num1, num2){
    return sum.apply(this, arguments); // 传入arguments 对象
}

function callSum2(num1, num2){
    return sum.apply(this, [num1, num2]); // 传入数组
}

alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20


2call()

call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,例如:

function sum(num1, num2){
    return num1 + num2;
}

function callSum(num1, num2){
    return sum.call(this, num1, num2);
}

alert(callSum(10,10)); //20

在使用call()方法的情况下,callSum()必须明确地传入每一个参数。结果与使用apply()没有什么不同。至于是使用apply()还是call(),完全取决于你采取哪种给函数传递参数的方式最方便。当你参数是明确知道数量时用call,而不确定的时候用 apply,然后把参数 push 进数组传递进去当参数数量不确定时,函数内部也可以通过arguments这个数组来遍历所有的参数。

使用call()(或apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。

3bind()

ECMAScript 5 还定义了一个方法:bind()。这个方法会创建一个函数的实例,其this 值会被绑定到传给bind()函数的值。例如:

window.color = "red";

var o = { color: "blue" };

function sayColor(){
    alert(this.color);
}

var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
sayColor.bind(o)();//blue

在这里,sayColor()调用bind()并传入对象o,创建了objectSayColor()函数。object-

SayColor()函数的this 值等于o,因此即使是在全局作用域中调用这个函数,也会看到"blue"

支持bind()方法的浏览器有IE9+Firefox 4+Safari 5.1+Opera 12+Chrome

4toLocaleString()、toString()、valueOf()

每个函数继承的toLocaleString()toString()方法始终都返回函数的代码。返回代码的格式则因浏览器而异——有的返回的代码与源代码中的函数代码一样,而有的则返回函数代码的内部表示, 即由解析器删除了注释并对某些代码作了改动后的代码。另外一个继承的valueOf()方法同样也只返回函数代码。


6.构造函数

构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new 操作符来调用,那它跟普通函数也不会有什么两样


7. 函数对象的三种变量及方法

7.1 私有变量、函数
function Obj(){
    var a=0; //私有变量
    var fn=function(){ //私有函数

      }
 }

var o=new Obj();
console.log(o.a); //undefined
console.log(o.fn); //undefined

在函数对象Obj外部无法访问变量a和函数fn,它们就变成私有的,只能在Obj内部使用,即使是函数Obj的实例仍然无法访问这些变量和函数


7.2 静态变量、函数
function Obj(){

 }
Obj.a=0; //静态变量
Obj.fn=function(){ //静态函数

}

console.log(Obj.a); //0
console.log(typeof Obj.fn); //function

var o=new Obj();
console.log(o.a); //undefined
console.log(typeof o.fn); //undefined

当定义一个函数后通过.”为其添加的属性和函数,通过对象本身仍然可以访问得到,但是其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数


7.3 实例变量、函数
function Obj(){
    this.a=[]; //实例变量
    this.fn=function(){ //实例方法
   }
}

console.log(typeof Obj.a); //undefined
console.log(typeof Obj.fn); //undefined

var o=new Obj();

console.log(typeof o.a); //object
console.log(typeof o.fn); //function

var o1=new Obj();
o1.a.push(1);
o1.fn={};
console.log(o1.a); //[1]
console.log(typeof o1.fn); //object
var o2=new Obj();
console.log(o2.a); //[]
console.log(typeof o2.fn); //function

o1中的属性和方法与o2中的属性与方法虽然同名但却不是一个引用,而是对Obj对象定义的属性和方法的一个复制。

猜你喜欢

转载自blog.csdn.net/u013789656/article/details/80941802