js读书笔记(Function类型)
JavaScript高级程序设计(第3版)P110
Function 类型
1.函数定义的三种方式:
1.函数声明定义函数:(具有声明提升的作用,建议使用)
1 function sum (num1, num2) { 2 return num1 + num2; 3 }
2.函数表达式定义:
1 var sum = function(num1, num2){ 2 return num1 + num2; 3 };//这里有分号,就跟一般变量声明一样 var a = "abv";
3.使用函数的构造函数定义函数:(不推荐使用,但是对于理解函数也是对象很好理解)
1 var sum = new function("num1", "num2", "return num1 + num2"); //构造函数的最后一个参数被看做是函数体,前面的都是参数
2.js中没有函数重载:
出现重名函数的话,后者覆盖前者.为什么js中函数没有重载,因为js中函数的参数可以不指定个数,参数是一个数组对象arguments,利用这个arguments数组就可以间接的实现重载,比如:
1 !function sum(){ 2 if(arguments.length==2){ 3 console.log(arguments[0] + arguments[1]); 4 } 5 if(arguments.length==3){ 6 console.log(arguments[0] + arguments[1] + arguments[2]); 7 } 8 }(1,2); 9 //上面这个函数就间接的实现了函数的重载,可以运算两个数的加法,也可以运算三个数的加法,当然也可以计算任意个数的加法 10 !function sum2(){ 11 var result = 0; 12 for(var i = 0; i < arguments.length; i++){ 13 result += arguments[i]; 14 } 15 alert(result); 16 }(1,1,1,1,11,1);
3.函数作为值:
在js中,函数也是对象,函数名就是变量名,这个变量名指向的是函数,也是指向函数的指针,但是有一点跟普通引用变量不同,普通引用变量访问对象本身的属性使用的是点操作符'.'或者方括号'[]',而函数名访问函数本身使用的是圆括号'()'(我不知道这里讲的对不对,这里讲的访问函数本身而不是函数的属性,其实我是有点糊涂的)
1 function sum(num1, num2){ 2 return num1 + num2; 3 }; 4 !function GetResult(sum, x, y){ 5 console.log(sum(x, y)); 6 }(sum, 1, 3); 7 //学过C#的委托应该会觉得有点眼熟,C#中的委托是将函数名声明作为一种类型,然后就可以作为参数传递了,就可以在也就是将函数名作为了参数. 8 9 当然也可以从一个函数中返回另一个函数,而且这也是一种极为有用的技术, 10 11 eg:function createCompareFunction(propertyName){ 12 return function(obj1, obj2){//这里拿到的是两个对象 13 var val1 = obj1[propertyName];//获取对象对应的属性 14 var val2 = obj2[propertyName]; 15 if(val1 < val2){ 16 return -1; 17 }else if(val1 > val2){ 18 return 1; 19 }else{ 20 return 0; 21 } 22 }; 23 } 24 25 var person1 = { 26 name : 'ZhangSan', 27 age : 23 28 }; 29 var person2 = { 30 name :'LiSi', 31 age : 24 32 }; 33 var person3 = { 34 name : 'WangWu', 35 age : 25 36 }; 37 var persons = [person1, person2, person3]; 38 persons.sort(createCompareFunction('name'));//将name属性作为排序的标准 39 alert(persons[0].name);//弹出LiSi,说明已经按照name排序了 40 //上面再sort里面传递的是createCompareFunction('name'),是一个具体的函数, 41 //之前在讲数组的时候传递的是一个函数名,是不带有括号的,而这里带括号是因为 42 //在createCompareFunction()函数里面返回的是一个函数,也就是相当于还是传递了一个函数进去了, 43 44 我们将上面的比较函数改的复杂一点,主要根据名字排序,次要根据年龄排序(都升序) 45 function createCompareFunction(properTyName1, properTyName2){ 46 return function (obj1, obj2){ 47 if(obj11 ) 48 var main1 = obj1[properTyName1]; 49 var minor1 = obj1[properTyName2]; 50 var main2 = obj2[properTyName1]; 51 var minor2 = obj2[properTyName2]; 52 if(main1 < main2){ 53 return -1; 54 }else if(main1 > main2){ 55 return 1; 56 }else if(minor1 < minor2){ 57 return -1; 58 }else if (minor1 > minor2) { 59 return 1; 60 }else{ 61 return 0; 62 } 63 }; 64 } 65 66 var person1 = { 67 name : 'Z', 68 age : 23 69 }; 70 var person2 = { 71 name :'Z', 72 age : 21 73 }; 74 var person3 = { 75 name : 'A', 76 age : 23 77 }; 78 var persons = [person1, person2, person3]; 79 showArray(persons); 80 function showArray(array){ 81 for (var i = 0; i < array.length; i++) { 82 console.log('Name: ' + array[i].name + '; Age: ' + array[i].age) 83 }; 84 } 85 persons.sort(createCompareFunction('name','age'));//将name属性作为排序的标准 86 showArray(persons); 87 //但是有一点没考虑到,js中的数组是可以存储任意类型的,我们无法保证persons数组里面全是person,如果存在非person类型,那么排序就得不到想要的结果.
4.函数内部属性:
函数内部有两个特殊的对象:arguments和this.
arguments是参数数组,this指代的是当前函数运行的环境对象,也就是作用域.arguments这个对象有一个 callee属性,该属性是一个指针,它指向拥有这个arguments对象的函数,这个参数就跟函数名是等价的,函数名也是一个指针,也是指向函数本身,只不过这个指针在函数内部
看下面这个经典的求阶乘的递归:
1 function factorial(num){ 2 if(num <= 1){ 3 return 1; 4 }else { 5 return num * factorial(num - 1); 6 } 7 } 8 var result = factorial(5); 9 console.log(result);//打印120 10 11 //上面的递归调用使得求阶乘这个功能和这个函数名绑定在一起了, 12 //我们可以利用callee这个内部指针,进行解耦,将功能和函数名解耦 13 function factorial(num){ 14 if(num <= 1){ 15 return 1; 16 }else { 17 return num * arguments.callee(num - 1); 18 } 19 } 20 var otherFun = factorial; 21 factorial = function(){//这里重新定义了factorial函数 22 return 0; 23 } 24 var result = otherFun(5); 25 var result2 = factorial(5); 26 console.log(result);//打印120 27 console.log(result2);//打印0 28 关于this,this引用的是函数据以执行的环境对象,或者也可说是this的值 29 (当在全局环境下使用this时,this对象的引用就是windows对象) 30 下面例子: 31 window.color = 'red'; 32 var obj = { 33 color : 'blue' 34 }; 35 function sayColor(){ 36 console.log(this.color); 37 } 38 sayColor(); 39 obj.sayColor = sayColor;//标记 40 obj.sayColor(); 41 42 上面的代码标记的地方,是将函数对象赋值给了o对象的sayColor属性 43 上面代码等价于下面: 44 window.color = 'red'; 45 function sayColor(){ 46 console.log(this.color); 47 } 48 var obj = { 49 color : 'blue', 50 objSayColor : sayColor//将函数名赋值给对象o的一个属性,这里是为了避免重名,才改成了objSayColor 51 }; 52 sayColor(); 53 obj.objSayColor();//调用obj的的属性objSayColor指向的函数 54 55 我为什么不像下面这样做呢? 56 window.color = 'red'; 57 var obj = { 58 color : 'blue', 59 sayColor : function /*sayColor*/(){//这里的sayColor可以有也可以没有 60 console.log(this.color); 61 } 62 }; 63 function sayColor(){ 64 console.log(this.color); 65 } 66 sayColor(); 67 obj.sayColor(); 68 69 那是因为 obj.sayColor = sayColor;这句话是将sayColor变量指向的函数赋值给了obj的sayColor属性 70 最终sayColor(); 71 obj.sayColor();这两行代码调用的是同一个函数,而不是两个相同的函数,也就是说,内存中只存在一个sayColor()函数, 72 而向上面这样做的话,就制造出两个功能相同的sayColor()函数了,这样不是等价的.所以我没有这样做. 73 //我的这句话也是对115页作者强调标记的理解. 74 函数还有一个特殊的对象属性:caller,这个属性保存着调用当前函数的函数的引用,也就是主调函数的引用,可以说caller和主调函数的函数名指向的是同一个函数对象,既然是函数的引用,那么在全局作用域中调用该函数时,没有主调函数,那么caller的值就是null了. 75 eg: 76 function outer(){ 77 inner(); 78 } 79 function inner(){ 80 alert(inner.caller); 81 } 82 outer(); 83 84 当函数在严格模式下运行时,访问arguments.callee会导致错误, 85 严格模式下还有一个限制:不能为caller属性赋值,否则会导致错误,也就是说,caller属性是只读的,不可写.
5.函数的属性和方法:
之前说过函数也是对象,既然是对象,那么就有属性和方法【其中方法当然有继承下来的 toString(),valueOf()方法】
每个函数都包含两个属性:length和prototype.其中length属性表示函数希望接收的命名参数的个数,
1 function sum(num1, num2){ 2 return sum1 + num2; 3 } 4 alert(sum.length);//弹出2 5 sum.length = 5; 6 alert(sum.length);//弹出2,说明length属性不可写,可读.
prototype属性是一个比较重要的属性了,对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在, 换句话说,诸如toString()和valueOf()等方法实际上都是保存在prototype名下,只不过是通过各自对象实例访问罢了,在创建自定义引用类型以及实现继承时,prototype属性是极其重要的(在第六章将详细介绍,下一篇博客我就写第六章的内容), 在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in都无法发现。
每个函数都包含两个非继承而来的方法:apply()和call()方法,这两个方法的用途都是在特定的作用域调用该函数, 实际上等于设置函数体内的this对象的值.
apply()函数接受两个参数:第一个是在其中运行函数的作用域,第二个参数是数组(或者是arguments对象),
call()函数接收的参数:第一个同apply一样,是该函数运行的作用域,其余的参数都是都是直接传递给函数(就是将参数数组的各个项都列举出来)
这两个函数的作用一样,调用时就看参数是否确定,也就是说如果传递给函数的是一个数组,那么apply()更适合,反之call()可能更合适,当然,你也可以将确定个数的参数放在数组里,然后使用apply()函数. 事实 上,传递参数并非apply()和call()真正的用武之地;他们真正强大的地方是它们能够扩大函数赖以运行的作用域, 也就是说, 我传什么作用域进去, 函数就在那个作用域运行.
1 eg: 2 window.color = 'red'; 3 var o = { 4 color : 'blue' 5 }; 6 function sayColor(){ 7 alert(this.color); 8 } 9 sayColor();//弹出red,函数内部this指向的是全局作用域 10 11 sayColor.call(this);//弹出red,函数的执行环境是全局作用域 12 sayColor.call(window);//弹出red,函数的执行环境是全局作用域 13 sayColor.call(o);//弹出blue,函数内部this指向的是引用类型o 14 15 使用call()和apply()来扩充作用域最大的好处,就是对象不需要域方法有任何耦合关系. 16 上面的例子是先将sayColor()函数放到了对象o中作为了一个方法,然后通过o来调用这个方法, 17 而ECMAScript还定义了一个方法,bind(),这个方法会创建一个函数的实例,并作为返回值返回,这个实例的中this的值 18 会被绑定到传给bind()方法的值,这个实例也是一个函数,只不过里面的this值指定的是传递的那个参数(这个参数会是一个对象) 19 eg: 20 window.color = 'red'; 21 var o = { 22 color : 'blue' 23 }; 24 function sayColor(){ 25 alert(this.color); 26 } 27 var objectSayColor = sayColor.bind(o);//得到一个函数的实例,这个实例也是一个函数,只不过这个函数体内的this指向的是对象o 28 objectSayColor();//弹出blue 29 //如果我们直接敲入objectSayColor会打印出sayColor函数的定义语句,和敲入objectSayColor的结果一样,但是前者打印结果中多了一个感叹号,
其内容是Function was resolved from bound function,表明这个是来源于函数,但是与函数不同。