js学习笔记:引用类型——Function

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

定义函数

函数声明

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

函数表达式

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

函数声明与函数表达式的区别

  • 解析器会率先读取函数声明,并使其在执行任何代码之前可用(函数声明提升
  • 而对于函数表达式,则必须等到解析器执行到他所在的代码行才会真正被执行解释。
sum(10,10); //完全可以正常运行
function sum(num1,num2){
    return num1+num2;
}

上段代码之所以可以运行,是因为在代码开始执行之前,解析器就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。所以即使声明函数的代码在调用它的代码后面,js引擎也能把函数声明提升到顶部。

sum(10,10); //产生错误
var sum = function (num1,num2){
    return num1+num2;
}

上段代码之所以会产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明,在执行到函数所在语句之前,变量sum中不会保存有对函数的引用。

使用Function构造函数

构造函数可以接收任意数量的参数,但最后一个参数始终被看成是函数体,而前面的参数则枚举出了新函数的参数。

var sum = new Function("num1","num2","return num1+num2");

不推荐这种方法定义函数,因为这种方法回到直接洗两次代码(第一次解析常规ECMAScript代码,第二次是解析传入构造函数中的字符串),从而影响性能

由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没什么不同。

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

//anotherSum和sum指向同一函数
var anotherSum = sum;
anotherSum(10,10); //20

sum = null;
anotherSum(10,10); //20

使用不带圆括号的函数名是访问指针,而非调用函数。

返回

当一个函数被调用时,它从第一个语句开始执行,并在遇到关闭函数体的}时结束,然后函数把控制权交还给调用该函数的程序。

return语句可用来使函数提前返回。当return被执行时,函数立即返回而不再执行余下的语句。

一个函数总会返回一个值,如果没有指定返回值,则返回undefined。

如果函数调用时在前面加上了new前缀,且返回值不是一个对象,则返回该新对象(this)。

没有重载

之前曾经有个例子:

function addSomeNumber(num){
    return num+=100;
}
function addSomeNumber(num){
    return num+=200;
}

var result = addSomeNumber(100);  //300

结果是后面的函数覆盖了前面的函数。以上代码其实和以下代码没什么区别:

var addSomeNumber = function(num){
    return num+=100;
}
addSomeNumber= function(num){
    return num+=200;
}

var result = addSomeNumber(100);  //300

观察重写之后的代码,很容易看清楚在创建第二个函数时,实际上覆盖了引用第一个函数的变量。


作为值的函数

因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。

  • 可以像传递参数一样把一个函数传递给另一个函数
  • 可以将一个函数作为另一个函数的结果返回。
function callSomeFunction(someFunction,someArgument){
    return someFunction(someArgument);
}

fucntion add10(num){
    return num+10;
}
var result = callSomeFunction(add10,10); //20

要访问函数的指针而不执行函数的话,必须去掉函数名后面的圆括号。因此上面例子中传递给callSomeFunction的是add10而不是执行它们之后的结果。

函数内部属性

arguments

是一个类数组对象,包含着传入函数中的所有参数。

  • callee属性:是一个指针,指向拥有这个arguments对象的函数。
//经典的阶乘函数
function factorial(num){
    if(num<=1){
        return 1;
    }else{
        return num*factorial(num-1);
    }
}

这个函数的执行与函数名紧紧耦合在了一起,为了消除这种耦合现象,可以使用arguments.callee:

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

this

是函数据以执行的环境对象。

window.color = "red";
var o = {color"blue"};

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

sayColor(); //red

o.sayColor = sayColor;
o.sayColor(); //blue

函数的名字仅仅是一个包含指针的变量而已,因此,即使在不同的环境中执行,全局的sayColor和o.sayColor 指向的仍然是同一个函数

caller

保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。

function outer(){
    inner();
}

function inner(){
    alert(inner.caller);
}

outer();

alert警告框中最后会显示outer函数的源代码。因为outer调用了inner,所以inner.caller就指向outer()。
为了实现更松散的耦合,也可以通过arguments.callee.caller来访问相同的信息。

function outer(){
    inner();
}

function inner(){
    alert(arguments.callee.caller);
}

outer();

当在严格模式下运行时,访问arguments.callee会导致错误。
并且严格模式下,不能为函数的caller属性赋值。


函数的属性和方法

length属性

表示函数希望接受的命名参数的个数。

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

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

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

sayName.length; //1
sum.length; //2
sayHi.length; //0

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

上面的代码中,callSum1在执行sum函数时传入了this(因为是在全局作用域中调用的,所以传入的就是window对象)。

  • call方法和apply方法的作用相同,区别仅在于接收参数的方式不同。
    call方法将参数直接传递给函数而不是以数组的形式,换句话说,call方法传递给函数的参数必须逐个列举出来。
function sum(num1,num2){
    return num1+num2;
}

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

call方法和apply方法的结果并没有什么不同,决定使用哪个完全取决于采取哪种给函数传递参数的方式最方便。如果打算直接传入arguments对象或者包含函数中先接收到的也是一个数组,那么使用apply方法肯定更方便,否则可能就会选择call方法。

call和apply最大的用武之地是能够扩充函数赖以运行的作用域

window.color = "red";
var o = {color"blue"};

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

sayColor(); //red

sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue

在调用sayColor.call(o)时函数的执行环境改变了,函数内部的this对象指向了o。

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

bind( )

这个方法会创建一个函数的实例,其this值会被绑定到传给bind函数的值:

window.color = "red";
var o = {color"blue"};

fucntion sayColor(){
    alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue

这里,sayColor调用bind并传入o,创建了objectSayColor 函数,objectSayColor 函数的this值等于o,因此即使在全局作用域中调用这个函数,也会看到blue

兼容到ie9,polyfill:

Function.prototype.bind = function(context){
    var args = [].slice.apply(arguments,1);
    var func = this;
    return function(){
        return func.apply(context,args.concat([].slice.apply(arguments)));
    }
}

继承的方法

toLocaleString、toString和valueOf都返回函数的代码。

你可能感兴趣的:(javascript)