1、 Function类型是js中引用类型之一,每个函数实际上都是Function类型的实例对象,具有自己的属性和方法。正因为函数式对象,所以函数名实际上也是一个指向函数对象的指针。
2. 常用的函数定义方式
1. 函数声明:
1
2
3
|
function
sum(a , b ){
return
a+b;
}
|
2. 表达式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var
sum =
functiot
(){
return
a+b;
};
//注意分号
//两种方式的区别:
//解释器会率先读取函数声明,并使其在执行之前可以访问,
而使用表达式则必须等到解析器执行到它所在的代码行,才会真正被解释执行
(变量声明提前,而值留在原地).
alert (sum (10 ,10));
function
sum(a ,b){
return
a+b;
}
//↑上面的代码会正常执行,因为在代码执行前,解析器通过函数声明提升,读取并将函数声明添加到执行环境中,放到代码树的顶部
alert (
typeof
sum);
alert(sum(10 , 10));
var
sum =
function
(a ,b){
return
a+b;
}
//↑报错,原因在于函数位于一个初始化语句中,不是一个函数声明,不会被提前,而只会把var sum提前,用typeof操作符显示sum是undefined,所以报错
|
3. new 构造函数,虽然这种用法也是函数表达式,但该用法不推荐。因为这种语法会导致解析两次代码(第一次是解析常规的ECMAScript代码,第二次是解析传入构造函数中的字符串),影响性能。
使用 Function 构造函数,构造函数可以接受任意数量的参数,但最后一个参数始终都被看成是函数体,前面的参数则枚举出了新函数的参数。
var sum=new Function('num1','num2','return num1+num2;'); sum;// function anonymous(num1,num2 /**/) { return num1+num2; }
当使用不带圆括号的函数名是访问函数指针,而非调用函数。
3. 函数名仅仅保存指向函数对象的指针,因此函数名与包含对象指针的其他变量没什么不同,也就是说,一个函数对象可以有多个名字:
1
2
3
4
5
6
7
8
|
function
sum(a , b ){
return
a+b;
}
console.log(sum(2 ,3));
//5
var
anotherSum = sum;
//变量anotherSum也指向了同一个函数对象
console.log(anotherSum(4 , 5));
//9
sum =
null
;
//sum变量不再保存函数对象的指针了
console.log(anotherSum(1 , 3));
//anotherSum这个变量仍能调用
4. JS为何没有重载这个概念。
|
4. JS为何没有重载这个概念。
1
2
3
4
5
6
7
8
|
function
add(a){
return
a+3 ;
}
function
add(a){
return
a+5;
}
var
result = add(3);
//8
//两个函数同名了,结果只能是后一个函数覆盖前一个,所以不能重载
|
不能实现重载的原因:
function reload(){ if(arguments.length==0){ console.log('没传参'); }else if(arguments.legth==1){ console.log('传了一个参数'); } }
function add(){ return 100; } function add(num){ return num+200; } //实际上和下面代码没什么区别 function add(){ return 100; } add=function(num){ return num+200; }
5. 函数的内部属性:函数内部,有两个特殊的对象,arguments和this
1. arguments:
arguments是个类数组对象,包含着传入函数的所有参数,这个对象有一个叫callee的属性,属性值是一个指针,指向拥有这个arguments对象的函数本身
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function
foo (){
var
a =arguments.callee;
return
a.toString();
}
foo();
/*
返回结果:
"function sum(){
var a =arguments.callee;
return a.toString();
}"
也就是说,一个函数内部,arguments.callee指的就是这个函数本身。这个函数在递归调用时有点用,有许多缺陷,在ES5严格模式被移除
*/
|
arguments :类数组对象,包含传入函数中所有参数。是每个函数自身的属性,之所以可以直接访问 arguments ,是因为命名空间??以下变化是为了加强JavaScript语言的安全性,这样第三方代码就不能在相同的环境下窥视其他代码了。
//一般阶乘函数 function factorial(num){ if(num<=1){ return 1;} else { return num*factorial(num-1); } }
定义阶乘函数用到递归算法,这样定义是没问题。
缺点:这个函数的执行与函数名 factorial 紧紧耦合在一起。万一出现改变函数指向的这种情况就不太好了,
factorial=function(){} factorial(3);// undefiend
为了消除这种现象。
function factorial(num){ if(num<=1){ return 1; } else{ return num*arguments.callee(num-1); } }
这样无论引用函数使用的是什么名字都可以保证完成递归。
2. this:简单来说,this指的就是函数执行的环境对象,在哪个对象中执行,this就指哪个对象。
3. caller :不止是ECMAScript5中新增函数对象上的属性,还是 arguments 上的属性。保存着调用当前函数的函数的引用。如果是在全局作用域中调用当前函数,它的值为 null 。
Object.getOwnPropertyNames(Function);// ["length", "name", "arguments", "caller", "prototype"]
function outer(){ inner(); } function inner(){ console.log(inner.caller); //为了实现更松散的耦合,arguments.callee.caller } outer();// function outer(){ inner()}
严格模式下不能为函数的 caller 属性赋值,否则会导致出错。
4. length属性:表示函数希望接受的参数个数
1
2
3
4
|
function
add(a ,b ,c){
return
a+b+c;
}
add.length;
//3
|
5. 著名的prototype属性,简单来说,是一个对象,是通过调用构造函数而创建的一个对象,包含可以由特定类型的所有实例共享的属性和方法。
因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。
function callSomeFunction(someFunction,someArgument){ return someFunction(someArgument); } function concated(str){ return "Hi "+str; } callSomeFunction(concated,'xx');// 'Hi xx'
从一个函数中返回另一个函数的应用:假设有一个对象数组,想要根据某个对象属性对数组进行排序,但传给 sort() 方法的比较函数要接收两个参数,即要比较的值。我们需要一种方式来指明按照哪个属性来排序。我们可以定义一个函数它接收一个属性名,然后根据这个属性名来创建一个比较函数。默认情况下, sort 函数会调用每个对象的 toString() 方法以确定它们的次序。
function createCompare(property){ return function(obj1,obj2){ var value1=obj1[property], value2=obj2[property]; if(value1<value2) return -1; else if(value1>value2) return 1; else return 0; } }
var data=[{name:'aa',age:20},{name:'bb',age:12},{name:'cc',age:30}]; data.sort(createCompare("age"));// [{name:'bb',age:12},{name:'aa',age:20},{name:'bb',age:30}]
7.函数的属性和方法
function sayName(name){ // } function sum(num1,num2){ // } function sayHi(){ // } sayName.length;// 1 sum.length;// 2 sayHi.length;// 0
function sum(num1,num2){ return num1+num2; } function callSum1(num1,num2){ return sum.apply(this,arguments);//sum.apply(this,[num1,num2]) } callSum1(10,30);// 40
严格模式下,未指定环境对象而调用函数, this 值不会转型为 window 。除非明确把函数添加到某个对象或者调用 apply 或 call ,否则 this 值将是 undefined
function callSum1(num1,num2){ retrun sum.call(this,num1,num2); } callSum1(10,30);// 40
call 和 apply 真正强大的地方是能够扩充函数赖以运行的作用域,改变函数的执行环境。
传递参数并调用函数并非call()和apply()的用武之地,二者真正强大的地方是扩充函数运行的作用域
1
2
3
4
5
6
7
8
9
10
11
|
var
color =
'red'
;
var
obj = {
color :
'blue'
}
function
foo(){
console.log(
this
.color);
}
foo();
//'red'
foo.call(
this
);
//'red'
foo.call(obj);
//'blue'
//最后一次调用foo()函数的执行环境变了,其中的this指向了obj对象,
所以是'blue'
|
使用call()和apply()扩充作用域的最大好处,就是使对象与方法之间解耦
window.color="red"; var o={color:'blue'}; function sayColor(){ console.log(this.color); } var newobj=sayColor.bind(o); newobj;// function sayColor(){ console.log(this.color); } newobj==sayColor;// false newobj();// blue
深入理解:可以将函数绑定到指定环境的函数。接收一个函数和一个环境,返回在给定环境中调用给定函数的函数。
function bind(func,context){ return function(){ func.apply(context,arguments);//这里创建了一个闭包,arguments使用的返回的函数的,而不是bind的 } }
当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。
function bind(func,context,args){ return function(){ func.call(context,args); }; }