js基础--函数属性、方法和构造函数

 

我们看到在JavaScript程序中,函数是值。对函数执行typeof运算会返回字符串"function",但是函数是JavaScript中特殊的对象。因为函数也是对象,它们也可以拥有属性和方法,就像普通的对象可以拥有属性和方法一样。甚至可以用Function()构造函数来创建新的函数对象。

  • length属性

在函数体里,arguments.length表示传入函数的实参的个数。而函数本身的length属性则有着不同含义。函数的length属性是只读属性,它代表函数实参的数量,这里的参数指的是“形参”而非“实参”,也就是在函数定义时给出的实参个数,通常也是在函数调用时期望传入函数的实参个数。

下面的代码定义一个名叫check()的函数,从另外一个函数给它传入arguments数组,它比较arguments.length(实际传入的实参个数)和arguments.callee.length(期望传入的实参个数)来判断所传入的实参个数是否正确。如果个数不正确,则抛出异常。check()函数之后定义一个测试函数f(),用来展示check()的用法:

//这个函数使用arguments.callee,因此它不能在严格模式下工作
function check(args){
    var actual=args.length;//实参的真实个数
    var expected=args.callee.length;//期望的实参个数
    if(actual!==expected)//如果不同则抛出异常
        throw Error("Expected"+expected+"args;got"+actual);
}
function f(x,y,z){
    check(arguments);//检查实参个数和期望的实参个数是否一致
    return x+y+z;//再执行函数的后续逻辑
}
  • prototype属性

每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”(prototype object)。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。

  • call()方法和apply()方法

我们可以将call()和apply()看做是某个对象的方法,通过调用方法的形式来间接调用(见8.2.4节)函数(比如在例6-4我们使用了call()方法来调用一个对象的Objec t.prototype.toString方法,用以输出对象的类)。call()和apply()的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this来获得对它的引用。要想以对象o的方法来调用函数f(),可以这样使用call()和apply():
f.call(o);
f.apply(o);
每行代码和下面代码的功能类似(假设对象o中预先不存在名为m的属性)。
o.m=f;//将f存储为o的临时方法
o.m();//调用它,不传入参数
delete o.m;//将临时方法删除
在ECMAScript 5的严格模式中,call()和apply()的第一个实参都会变为this的值,哪怕传入的实参是原始值甚至是null或undefined。在ECMAScript 3和非严格模式中,传入的null和undefined都会被全局对象代替,而其他原始值则会被相应的包装对象(wrapper object)所替代。

对于call()来说,第一个调用上下文实参之后的所有实参就是要传入待调用函数的值。比如,以对象o的方法的形式调用函数f(),并传入两个参数,可以使用这样的代码:

f.call(o,1,2);

apply()方法和call()类似,但传入实参的形式和call()有所不同,它的实参都放入一个数组当中:

f.apply(o,[1,2]);

如果一个函数的实参可以是任意数量,给apply()传入的参数数组可以是任意长度的。比如,为了找出数组中最大的数值元素,调用Math.max()方法的时候可以给apply()传入一个包含任意个元素的数组:
var biggest=Math.max.apply(Math,array_of_numbers);
需要注意的是,传入apply()的参数数组可以是类数组对象也可以是真实数组。实际上,可以将当前函数的arguments数组直接传入(另一个函数的)apply()来调用另一个函数,参照如下代码:


//将对象o中名为m()的方法替换为另一个方法
//可以在调用原始的方法之前和之后记录日志消息
function trace(o,m){
    var original=o[m];//在闭包中保存原始方法
    o[m]=function(){//定义新的方法
        console.log(new Date(),"Entering:",m);//输出日志消息
        var result=original.apply(this,arguments);//调用原始函数
        console.log(new Date(),"Exiting:",m);//输出日志消息
        return result;//返回结果
    };
}
trace()函数接收两个参数,一个对象和一个方法名,它将指定的方法替换为一个新方法,这个新方法是“包裹”原始方法的另一个泛函数[14]。这种动态修改已有方法的做法有时称做"monkey-patching"。
  • bind()方法

bind()是在ECMAScript 5中新增的方法,但在ECMAScript 3中可以轻易模拟bind()。从名字就可以看出,这个方法的主要作用就是将函数绑定至某个对象。当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。(以函数调用的方式)调用新的函数将会把原始的函数f()当做o的方法来调用。传入新函数的任何实参都将传入原始函数,比如:


function f(y){return this.x+y;}//这个是待绑定的函数
var o={x:1};//将要绑定的对象
var g=f.bind(o);//通过调用g(x)来调用o.f(x)
g(2)//=>3
可以通过如下代码轻易地实现这种绑定:


//返回一个函数,通过调用它来调用o中的方法f(),传递它所有的实参
function bind(f,o){
    if(f.bind)return f.bind(o);//如果bind()方法存在的话,使用bind()方法
    else return function(){//否则,这样绑定
        return f.apply(o,arguments);
    };
}
ECMAScript 5中的bind()方法不仅仅是将函数绑定至一个对象,它还附带一些其他应用:除了第一个实参之外,传入bind()的实参也会绑定至this,这个附带的应用是一种常见的函数式编程技术,有时也被称为“柯里化”(currying)。参照下面这个例子中的bind()方法的实现:


var sum=function(x,y){return x+y};//返回两个实参的和值
//创建一个类似sum的新函数,但this的值绑定到null
//并且第一个参数绑定到1,这个新的函数期望只传入一个实参
var succ=sum.bind(null,1);
succ(2)//=>3:x绑定到1,并传入2作为实参y
function f(y,z){return this.x+y+z};//另外一个做累加计算的函数
var g=f.bind({x:1},2);//绑定this和y
g(3)//=>6:this.x绑定到1,y绑定到2,z绑定到3
我们可以绑定this的值并在ECMAScript 3中实现这个附带的应用。例8-5中的示例代码就模拟实现了标准的bind()方法。

注意,我们将这个方法另存为Function.prototype.bind,以便所有的函数对象都继承它,这种技术在9.4节中有详细介绍:

例8-5:ECMAScript 3版本的Function.bind()方法


if(!Function.prototype.bind){
    Function.prototype.bind=function(o/*,args*/){//将this和arguments的值保存至变量中
//以便在后面嵌套的函数中可以使用它们
        var self=this,boundArgs=arguments;//bind()方法的返回值是一个函数
        return function(){//创建一个实参列表,将传入bind()的第二个及后续的实参都传入这个函数
            var args=[],i;
            for(i=1;i<boundArgs.length;i++)args.push(boundArgs[i]);
            for(i=0;i<arguments.length;i++)args.push(arguments[i]);//现在将self作为o的方法来调用,传入这些实参
            return self.apply(o,args);
        };
    };
}
我们注意到,bind()方法返回的函数是一个闭包,在这个闭包的外部函数中声明了self和boundArgs变量,这两个变量在闭包里用到。尽管定义闭包的内部函数已经从外部函数中返回,而且调用这个闭包逻辑的时刻要在外部函数返回之后(在闭包中照样可以正确访问这两个变量)。

ECMAScript 5定义的bind()方法也有一些特性是上述ECMAScript 3代码无法模拟的。首先,真正的bind()方法返回一个函数对象,这个函数对象的length属性是绑定函数的形参个数减去绑定实参的个数(length的值不能小于零)。再者,ECMAScript 5的bind()方法可以顺带用做构造函数。如果bind()返回的函数用做构造函数,将忽略传入bind()的t his,原始函数就会以构造函数的形式调用,其实参也已经绑定[15]。由bind()方法所返回的函数并不包含prototype属性(普通函数固有的prototype属性是不能删除的),并且将这些绑定的函数用做构造函数时所创建的对象从原始的未绑定的构造函数中继承prototype。同样,在使用instanceof运算符时,绑定构造函数和未绑定构造函数并无两样。
  • toString()方法

和所有的JavaScript对象一样,函数也有toString()方法,ECMAScript规范规定这个方法返回一个字符串,这个字符串和函数声明语句的语法相关。实际上,大多数(非全部)的toString()方法的实现都返回函数的完整源码。内置函数往往返回一个类似"[native code]"的字符串作为函数体。

  • Function()构造函数

不管是通过函数定义语句还是函数直接量表达式,函数的定义都要使用function关键字。但函数还可以通过Function()构造函数来定义,比如:

var f=new Function("x","y","return x*y;");
这一行代码创建一个新的函数,这个函数和通过下面代码定义的函数几乎等价:

var f=function(x,y){return x*y;}
Function()构造函数可以传入任意数量的字符串实参,最后一个实参所表示的文本就是函数体;它可以包含任意的JavaScript语句,每两条语句之间用分号分隔。传入构造函数的其他所有的实参字符串是指定函数的形参名字的字符串。如果定义的函数不包含任何参数,只须给构造函数简单地传入一个字符串——函数体——即可。

注意,Function()构造函数并不需要通过传入实参以指定函数名。就像函数直接量一样,Function()构造函数创建一个匿名函数。

关于Function()构造函数有几点需要特别注意:

·Function()构造函数允许JavaScript在运行时动态地创建并编译函数。

·每次调用Function()构造函数都会解析函数体,并创建新的函数对象。如果是在一个循环或者多次调用的函数中执行这个构造函数,执行效率会受影响。相比之下,循环中的嵌套函数和函数定义表达式则不会每次执行时都重新编译。

·最后一点,也是关于Function()构造函数非常重要的一点,就是它所创建的函数并不是使用词法作用域,相反,函数体代码的编译总是会在顶层函数[16]执行,正如下面代码所示:


var scope="global";
function constructFunction(){
    var scope="local";
    return new Function("return scope");//无法捕获局部作用域
}
//这一行代码返回global,因为通过Function()构造函数
//所返回的函数使用的不是局部作用域
constructFunction()();//=>"global"
我们可以将Function()构造函数认为是在全局作用域中执行的eval()(参照4.12.2节),eval()可以在自己的私有作用域内定义新变量和函数,Function()构造函数在实际编程过程中很少会用到

猜你喜欢

转载自blog.csdn.net/wuyufa1994/article/details/85314811
今日推荐