以下内容主要是 《JavaScript权威指南》第三版 -》第八章 函数=》8.7节的学习笔记,希望在学习的同时能给大家带来帮助。仅供参考。
函数是JavaScript中特殊的对象。函数也是对象,像其它对象一样也有属性和方法。甚至可以用Function()构造函数来创建新的函数对象。接下来的内容将介绍函数的属性、方法和Function()构造函数。
length属性
函数的length属性是只读的,它表示函数定义时的形参个数。例如:
function f(a,b,c){}结果为3,表示函数f()的三个形参a、b、c。
console.log(f.length);//=> 3
我们再来看一个稍微复杂点的例子。有这样的一个函数,它的功能是检查函数调用时出入实参的个数与函数定义时形参的个数是否相同,如果不同将会抛出异常。
//检查实参个数与形参个数是否一致
function check(args){
var actual = args.length; //实际的实参个数(函数调用出入的实参个数)
var expected = args.callee.length;//期望的实参个数(函数定义时的形参个数)
if(actual !== expected )
throw Error("传入的实参个数与形参个数不一致。");
}
//通过函数f()来验证函数check()
function f(a,b,c){
check(arguments);
console.log(a,b,c);
}
f(1,2,3); //将会正常输出出入的实参
f(1,2,3,4)//将会抛出异常,不会有输出
prototype属性
每个函数都包含一个prototype属性,这个属性指向一个对象,这个对象被称为"原型对象"。可以为每个对象指定的prototype属性指定不同的原型对象。当函数用作构造函数的时候(如:var d = new Date()),新创建的对象会继承原型对象上的属性和方法(d可以使用Date.prototype上所有属性和方法)。估计大家都应该对"原型对象"挺熟悉的,就不在墨迹了。
call()方法和apply()方法
这两个方法的用途都是在特定的作用域(也可以理解说成特定对象、特定的调用上下文)中调用函数。说白了就是能够扩充函数赖以运行的作用域,达到代码复用的目的。
但是他们的使用形式不同:
call()方法的第一个参数是特定的作用域,它就是调用上下文。后续其它参数都是要传递给调用函数的实参。
apply()方法的第一个参数和call()的第一个参数一样。第二个参数是一个数组,数组中的元素是要传递给调用函数的实参。
为了很好理解这两个方法,我们来看一个例子:
var firstName = "fly";//全局变量默认都是window的属性通过这个例子我们了解了call()方法和apply()方法。如果上述例子按照传统的方式实现,会是这样的:
var lastName = "zxy";
var myObj = {
firstName: "my",
lastName: "Obj",
showName: function(info){
console.log("hi,i'am is %s_%s 留言信息:",this.firstName,this.lastName,info);
}
};
myObj.showName("留你妹呀!!"); //=> hi,i'am is my_Obj
myObj.showName.call(window,"给调用函数传递的实参");//=> hi,i'am is fly_zxy //这里使用myObj.showName()方法输出了全局firstName和lastName,这时函数体内this指向的调用上下文就是window
myObj.showName.apply(window,["给调用函数传递的实参"]);//等价上一行代码。如果调用函数没有参数,只传递第一个参数就可以了。
var firstName = "fly";//全局变量默认都是window的属性显然使用call()方法和apply()方法的例子很好一些,更面向对象。
var lastName = "zxy";
var myObj = {
firstName: "my",
lastName: "Obj",
showName: function(info){
console.log("hi,i'am is %s_%s 留言信息:",this.firstName,this.lastName,info);
}
};
var showName = myObj.showName;
myObj.showName("留你妹呀!!"); //=> hi,i'am is my_Obj
showName("这回该流你妹夫了。。。");
顺便在说一下在ECMAScript3、ECMAScript5非严格模式和ECMAScript5严格模式中这两个方法第一个参数的区别(如果感觉脑袋已经看大了。。就不要看区别了):
在ECMAScript3和ECMAScript5的非严格模式中这两个方法的第一个参数如果出入的是undefined、null将会 转换成全局对象(window)。如果出入的是原始类型,会转换成对应的包装类。
在ECMAScript5的严格模式中出入什么值this就指向什么值,哪怕是undefined、null、原始类型。
示例代码如下:
var name = "window";
(function(){
"use strict";//在当前函数体内使用严格模式
var myObj = {
name:"myObj",
showThis: function(){
console.log("this:",this);
console.log("this.name:",this.name);
}
};
myObj.showThis.call(null);
}());
在谷歌浏览中运行以上代码。将以上代码中的 "use strict";//在当前函数体内使用严格模式 去掉在此运行。通过两次执行结果的对比,你就会发现上述的区别。
bind()方法
bind()方法是EMCAScript5新增的方法。这个方法的主要作用就是将一个方法绑定到某个对象上,并将绑定后的新函数的引用返回。例如:
function f(y){ return this.x+y; };//这是个待绑定的函数在ECMAScript3中也可以很轻易的实现这种绑定,下面我们来看看ECMASscript3怎么模拟ECMASCript5的bind():
var o = {x:10};//将要绑定的对象
var g = f.bind(o);//注意:bind()只返回绑定后新函数的引用,并不像call()和appley()方法直接调用函数。待绑定的函数体内的this会指向bind()的第一个实参(这个例子中就是o)
var r = g(12);//调用函数,并传入实参
console.log(r);//=>22
function bind(f,o){再来说一下bind()使用时传递的参数。bind()的第一个参数将要绑定的对象,第二个以及后续参数是要传递给待绑定函数的实参。例如:
if(f.bind) return f.bind(o);//bind()方法存在,就使用已经存在的bind()
else return function(){//注意:如果这里直接返回f.apply(o)会直接调用绑定后的新函数,所以返回一个函数引用,执行函数引用的时候将会真正的执行f.apply(o)这段代码
return f.apply(o,arguments);
};
}
//验证自定义的bind()
function f(y){ return this.x+y; };//这是个待绑定的函数
var o = {x:10};//将要绑定的对象
var g = bind(f,o);//注意:bind()只返回绑定后新函数的引用,并不像call()和appley()方法直接调用函数
var r = g(12);//调用函数,并传入实参
console.log(r);//=>22
function f(y){ return this.x+y; };//这是个待绑定的函数
var o = {x:10};//将要绑定的对象
var g = f.bind(o,100);//100就是传递給y的实参
var r = g();//调用函数
console.log(r);//=>110
如果使用bind()传递的第一个参数是一个undefined、null,那么bind()方法会将undefined、null转为为全局对象(window对象)。例如:
var x = 10;在ECMAScript3中也可以实现使用bind()是传递多个参数的情况,代码如下:
function f(y,z){ return this.x+y+z; };//这是个待绑定的函数
var g = f.bind(null,100);//100就是传递给y的实参
var r = g(10);//10就是传递给z的实参
console.log(r);//=>120
Function.prototype.bind = function(o){
var self = this,args1 = arguments;
return function(){
var bindArgs = [],i;//创建一个实参列表,将传入bind()的二个参数以及后续参数和执行bind()返回的函数引用时传递的参数,以数组的形式传递给待绑定的函数
for(i=1;i<args1.length;i++) bindArgs.push(args1[i]);
for(i=0;i<arguments.length;i++) bindArgs.push(arguments[i]);
return self.apply(o,bindArgs);
};
};
//验证自定义的bind()
function f(y,z){ return this.x+y+z; };
var o={x:1};
var g = f.bind(o,2);
g(3);//=>6
toString()方法
和大多数的对象一样,函数也有toString() 方法。函数的toString()方法会返回函数的源码(一般情况下都是这样的)。而内置函数的toString()返回一个类型"[native code]"的函数体。
Function()构造函数
不管是通过函数定义语句还是函数直接量表达式,函数都要使用function关键字定义。但函数的创建也可以通过Function()函数来定义。例如:
var f = new Function("x","y","return x*y;");以上代码等同于一下代码:
f(2,2);
function f(x,y){ return x*y; };
f(2,2);
Function()构造函数可以传入任意数量的字符串类型的实参。最后一个实参表示函数体内的代码,其它实参表示函数形参名字。如果定义的函数不包括任何参数,只传入一个字符处作为函数内代码即可。
关于Function()构造函数有几点需要注意:
1)Function()函数不需要通过传入实参来指定函数名字,Function()总是返回匿名函数。2)Function()构造函数也许JavaScript在运行时动态的创建并编译函数。
3)每次调用Function()构造函数都会解析函数体,并创建新的对象。如果是在一个循环中或多次调用的函数体中执行这个构造函数,执行效率会受到影响。
相比之下,循环体中的嵌套函数和函数定义表达式则不会每次执行时都编译。
4)最后一点,也是最重要的一点。就是使用Function()构造函数创建的函数并不是使用词法作用域,相反,函数体的编译总在顶层函数执行。什么意思呢?看下边代码你就懂了,如下:
var scope = "global";Function()构造函数在实际编程中很少用到,这里知道一下就可以了。
function constructorFunction(){
var scope = "local";
return new Function("return scope");//无法捕捉到函数体内的scope 局部变量
};
//这一行返回global,是因为通过Function()创建的函数使用的不是局部作用域
constructorFunction()();//=> global