构造函数通常不使用return关键字,它们初始化新对象,当构造函数的函数体执行完毕时,它会显示返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值。然而如果构造函数显示地使用return语句返回一个对象,或者返回一个原始值,那么这是将忽略返回值,同时使用这个新对象作为调用结果。
callee和caller:实参对象(arguments)的两个属性,在ECMAScript5严格模式中,对这两个属性的读写操作都会产生一个类型错误。前者指代当前正在执行的函数。caller是非标准的,但大多说浏览器实现了这个属性,它指代调用当前正在执行的函数的函数。callee经常用在匿名函数的递归调用上。eg:
varfactorial=function(x){
if(x<=1) return 1;
return x*arguments.callee(x-1);
}
console.log(factorial(3));
3.函数声明会在代码执行前被加载到作用域(全局作用域)中——有声明提升
函数表达式是在代码执行到那一行的时候才会定义——没有函数声明提升
function functionName(arg1,arg2){//这种方式叫做函数声明
//函数体
}
varfunctionName = function(arg1,arg2){//这种方式叫做函数表达式
//函数体
}
4.函数值传递:在js中,函数名师指向函数对象的指针,其本身就是变量,而函数也可以作为返回值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另外一个函数,而且可以将一个函数作为另外一个函数的结果返回。
但是js中的传递方式只有一种:即所有参数都是按值传递。也就是说数字、字符串等按值传递,数组、对象等也按值传递。(虽然数组、对象等的按值传递与数字、字符串还是有所不同的。)
var v1 =[1,2,3];
var v2 ={};
var v3 ={a:123};
functionfn1(v1, v2, v3){
v1 = [1];
v2 = [2];
v3 = {a:3};
}
fn1(v1,v2, v3);
alert(v1); // [1,2,3]
alert(v2); // [object Object]
alert(v3.a); // 123
观察上面的代码可以看出:v1、v2、v3都没有被改变、v1仍然是值为[1,2,3]的数组,v2是空白的对象,v3是具有值为123的属性a的对象。因为数组、对象等按值传递,是指变量地址的值。在函数内部我们为v1,v2,v3赋了值,也就是说我们把v1,v2,v3的指针的指向改变了,指向了新的数组和对象。这样内部的v1、v2、v3和外部的v1、v2、v3的联系完全断了。图解为:
由以上图解可知如果,在子函数内不改变局部变量的引用,而直接操作这些数组或对象的话,那么全局变量的值也是会改变的。eg:
var v1 =[1,2,3];
var v2 ={};
var v3 ={a:123};
functionfoo(v1, v2, v3)
{
v1.push(1);
v2.a = 2;
v3.a = 33;
}
foo(v1,v2, v3);
alert(v1); // [1,2,3,1]
alert(v2.a); // 2
alert(v3.a); // 33
由此可知,虽然函数的参数是按值传递的,但数组、对象等引用类型的按值传递和数字、字符串等基本类型的按值传递还是不同的,主要表现在:
1) 数字、字符串是把值直接复制进去来实现的;
2) 数组、对象是把变量地址复制进去来实现的。
5.我们将作用域描述为一个对象列表,不是绑定的栈。每次调用Javascript函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉。如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收。但如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这是就会有一个外部引用指向这个嵌套的函数。他就不会被当做垃圾回收,并且他所指向的变量绑定对象也不会被当做垃圾回收。
6.在函数体里,arguments.length表示传入函数的实参的个数。而函数本身的length属性则有着不同的含义。函数的length属性是只读属性,它代表函数形参的个数,可以通过arguments.length获取实参个数,通过argume.callee.length获取形参的个数
7.在ECMAScript5的严格模式中,call()和apply()的第一个实参都会变为this的值,哪怕传入的实参是原始值,甚至是null或undefined。在ECMAScript3和非严格模式中,传入的null和undefined都会被全局对象替代。而其他原始值则会被相应的包装对象所替代。
8.bind()方法:bind()是在ECMAScript5中新增的方法,但在ECMAScript3中可以轻易模拟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
在ECMAScri5中bind()方法不仅仅是将函数绑定至一个对象,它还附带一些其他应用:除了第一个实参之外,传入bind()的实参也会绑定至this。eg
varsum=function(x,y){
console.log(x+y);
}
//创建一个类似sum的新函数,但this的值绑定到null
//并且第一个参数绑定到1,这个新的函数期望只传入一个实参
varsucc=sum.bind(null,1);
succ(2);//=>3
在ECMAScript3中模拟实现标准的bind()方法
if(!Function.prototype.bind){
Function.prototype.bind=function(o){
var self=this,boundArgs=arguments;
return function(){
var args=[],i;
for(var i=1;i<boundArgs.length;i++){
args.push(boundArgs[i]);
}
for (var i = 0; i <arguments.length; i++) {
args.push(arguments[i]);
};
return self.apply(o,args);
};
};
};
我们注意到,bind()方法返回的函数是一个闭包。在这个闭包的外部函数中声明了self和boundArgs变量,而且调用这个变量在闭包里用到。尽管定义闭包的内部函数已经从外部函数中返回,而且调用这个闭包逻辑的时刻要在外部函数返回之后(在闭包中照样可以正确访问这两个变量)。
ECMAScript5定义的bind()方法也有一些特性是上述ECMASCript3代码无法模拟的。首先,真正的bind()方法返回一个函数对象,这个函数对象的length属性是绑定函数的形参个数减去绑定实参的个数(length的值不能小于零)。再者ECMAScript5的bind()方法可以顺带做构造函数。如果bind()返回的函数用作构造函数,将忽略传入bind()方法所返回的函数并不包含prototype属性(普通的函数固有的prototype属性是不能删除的),并且将这些绑定的函数用作构造函数时所创建的对象从原始的未绑定的构造函数中继承prototype。同样,在使用instanceof运算符时,绑定构造函数和未绑定构造函数并无两样。