以下大部分为学习《JavaScript 高级程序设计》》(第 3 版) 所做笔记。
函数是Function类型的实例,具有属性和方法。函数是对象,函数名是一个指向函数对象的指针。
目录:1、定义函数的几个方法
2、立即执行函数
4、没有重载
6、作为值的函数
7、函数内部属性
8、函数属性和方法
定义函数的方式有 2 种:一种是函数声明,另一种是函数表达式。
1 <script> 2 //1、函数声明 3 function functionName(arg0, arg1, arg2){ 4 //函数体 5 }; 6 //Firefox、Safari、Chrome 和 Opera 给函数定义一个非标准的 name 属性,通过这个属性可以访问到函数指定的名字 7 console.log( functionName.name ); //输出:functionName 8 9 //2、函数表达式 10 //创建一个函数并将它赋值给 functionName2 ,这种形式创建的函数叫做匿名函数(或者叫拉姆达函数),因为 function 关键字后面没有标识符。 11 var functionName2 = function(arg0, arg1, arg2){ 12 //函数体 13 }; 14 15 //3、使用function构造函数 16 //不推荐使用,最后一个参数被看作是函数体 17 var sum = new Function("num1", "num2", "return num1 + num2"); 18 console.log(sum(3, 4)); //输出:7 19 </script>
匿名函数属于函数表达式。匿名函数的形式是 function( 形参列表 ){ 函数体 } 。立即执行函数的形式是 ( function( 形参列表 ){ 函数体 }( 实参列表 ) ); 或者 ( function( 形参列表 ){ 函数体 } )( 实参列表 ); 。对括号的理解可以理解为 JS 里括号() 有求值的作用,括号里是函数就是起到执行函数的作用,记住就行。
1 <script> 2 //几种正确的使用方式: 3 //( function(){...}() ); 4 ( function(num1, num2){ 5 console.log( num1 + num2 ); 6 }(1, 2) ); //输出:3 7 8 //( function(){...} )(); 9 ( function(num3, num4){ 10 console.log( num3 + num4 ) 11 })(1, 2); //输出:3 12 13 //var functionName = ( function(){...}() ); 14 var fn1 = ( function(num5, num6){ 15 console.log( num5 + num6 ); 16 }(1, 2) ); //输出:3 17 18 //var functionName = ( function(){...} )(); 19 var fn2 = ( function(num7, num8){ 20 console.log( num7 + num8 ); 21 }(1, 2) ); //输出:3 22 23 //缺少括号是错误的使用方式,比如:function(){...}(); 24 //缺少括号但是匿名函数前有+/-/!也可以实现立即执行函数 25 //+ 26 + function(num1, num2){ 27 console.log( num1 + num2 ); 28 }(1, 2); //输出:3 29 //- 30 - function(num3, num4){ 31 console.log( num3 + num4 ); 32 }(1, 2); //输出:3 33 //! 34 ! function(num7, num8){ 35 console.log( num7 + num8 ); 36 }(1, 2); //输出:3 37 </script>
1 <script> 2 function sum(num1, num2){ 3 return num1 + num2; 4 } 5 console.log(sum(2, 2)); //输出: 4 6 7 var another = sum; //此时 another 跟 sum 都指向了同一个函数 8 console.log(another(2, 2)); //输出:4 9 10 sum = function(){ 11 return 0; 12 }; 13 console.log(sum(2, 2)); //输出:0 14 console.log(another(2, 2)); //输出:4 15 </script>
如果声明了两个函数同名,则后声明的函数将覆盖前声明的函数。
1 <script> 2 //使用函数声明语法定义函数时 3 function num(num1){ 4 return num1 + 10; 5 } 6 function num(num1){ 7 return num1 + 20; 8 } 9 console.log(num(1)); //输出:21 10 11 //使用函数表达式定义函数时 12 var num = function(num1){ 13 return num1 + 30; 14 } 15 var num = function(num1){ 16 return num1 + 40; 17 } 18 console.log(num(1)); //输出:41 19 </script>
在代码执行之前,解析器有一个函数声明提升的过程。函数提升的过程即读取并且将函数声明添加到执行环境中的过程。
使用函数声明语法定义函数,可以先调用函数再定义函数。
1 console.log(num(1)); //输出:11 2 function num(num1){ 3 return num1 + 10; 4 }
使用函数表达式定义函数,不可以先调用函数再定义函数。
1 console.log(num(1)); 2 var num = function(num1){ 3 return num1 + 10; 4 } 5 //报错:Uncaught TypeError: num is not a function
函数也可以作为值。去掉函数名后面的圆括号,则只访问函数的指针不执行函数。
1 <script> 2 //可以像传递参数一样将函数传递给另一个函数 3 //这里像传递参数一样将函数someFunction传递给另一个函数 functionOne 4 function functionOne(someFunction, someArgument){ 5 //可以将一个函数作为另一个函数的结果返回 6 //这里将函数 someFunction 作为另一个函数 functionOne 的结果返回 7 //这里 functionOne 函数接受2个参数,第一个参数是一个函数即 someFunction, 第二个参数 someArgument 是传递给函数 someFunction 的一个值 8 return someFunction(someArgument); 9 } 10 function functionTwo(num){ 11 return num + 10; 12 } 13 var result = functionOne(functionTwo, 100); 14 console.log(result); //输出:110 15 </script>
可以从一个函数中返回另一个函数。createComparisonFunction() 返回一个匿名函数。可以根据某个对象属性对数组进行排序:
1 <script> 2 //从一个函数中返回另一个函数 3 //定义函数createComparisonFn接收属性名propertyName 4 function createComparisonFn(propertyName){ 5 //比较函数接收2个参数 6 return function(a, b){ 7 //根据属性名propertyName创建一个比较函数 8 var value1 = a[propertyName]; 9 var value2 = b[propertyName]; 10 if(value1 < value2){ 11 //如果value1 应该位于 value2 之前则返回一个负数 12 return -1; 13 }else if(value1 > value2){ 14 //如果value1 应该位于 value2 之后则返回一个正数 15 return 1; 16 }else{ 17 //如果value1 = value2 则返回0 18 return 0; 19 } 20 }; 21 } 22 var data = [{name: "xiaoxu", age:20 }, {name: "zhangsan", age: 10}]; 23 //将比较函数createComparisonFn传递给数组sort()方法 24 //指明按照对象的 name 属性来比较 25 data.sort(createComparisonFn("name")); 26 console.log(data[0].name); //输出:xiaoxu 27 //指明按照对象的 age 属性来比较 28 data.sort(createComparisonFn("age")); 29 console.log(data[0].age); //输出:10 30 </script>
callee
arguments 和 this 是函数内部的两个特殊对象。arguments 是一个包含传入函数中的所有参数的一个类数组对象。arguments 对象有一个 callee 属性。callee 属性是一个指向拥有 arguments 对象的函数的指针。在严格模式下访问 arguments.callee 会报错。
1 <script> 2 function factorial(num){ 3 if( num<=1 ){ 4 return 1; 5 }else{ 6 console.log(arguments.callee); 7 } 8 } 9 factorial(); 10 </script>
输出:
可以看到执行语句“ console.log(arguments.callee); ”输出的是拥有arguments对象的函数factorial, 说明 callee 属性是一个指向拥有 arguments 对象的函数的指针。
定义一个阶乘函数:
1 <script> 2 function factorial(num){ 3 if( num<=1 ){ 4 return 1; 5 }else{ 6 return num * factorial(num-1); 7 } 8 } 9 </script>
上面这样函数的执行与函数名 factorial 紧密耦合。使用 arguments.callee 可以消除这种紧密耦合的情况。
1 <script> 2 function factorial(num){ 3 if( num<=1 ){ 4 return 1; 5 }else{ 6 //callee 属性是一个指向拥有 arguments 对象的函数的指针 7 return num * arguments.callee(num-1) 8 } 9 } 10 //使用arguments.callee消除耦合之后,无论用什么函数名调用该函数都不影响执行。 11 var anotherName = factorial; 12 factorial = function(num){ 13 return 0; 14 } 15 console.log(factorial(4)); //输出:0 16 console.log(anotherName(4)); //输出:24 17 </script>
this
this引用的是函数据以执行的环境对象。当在全局作用域中调用函数时 this 对象引用的是 window。
1 <script> 2 window.num = 1; 3 var i = { 4 num : 2 5 } 6 function sayNum(){ 7 console.log(this.num); 8 } 9 //在全局作用域中调用 sayNum 中时,this 引用的是全局对象 window,此时 this.color 等同于 window.color 10 sayNum(); //输出:1 11 //把函数赋给对象 i 并调用 i.sayNum() 12 i.sayNum = sayNum; 13 //this引用的是对象 i,此时 this.color 等同于 i.color 14 i.sayNum(); //输出:2 15 16 //函数名仅仅时一个包含指针的变量,即使函数在不同的环境中执行,window.sayNum()函数跟i.sayNum()指向的仍是同一函数。 17 //但是对window.sayNum()或i.sayNum()进行更改,均不影响另一环境中的sayNum()函数。 18 //如下,对全局的sayNum()进行了更改,不影响i.sayNum() 19 sayNum = function(){ 20 console.log("3"); 21 }; 22 sayNum(); //输出:3 23 i.sayNum(); //输出: 2 24 25 </script>
caller
caller 是一个函数属性,保存着调用当前函数的函数的引用。
1 <script> 2 function outer(){ 3 //调用 inner 函数 4 inner(); 5 } 6 function inner(){ 7 console.log(inner.caller); 8 } 9 outer(); //输出: ƒ outer(){ 10 // //调用 inner 函数 11 // inner(); 12 // } 13 14 //如果在全局作用域中调用,返回 null 15 inner(); //输出:null 16 </script>
如果想要解除紧密耦合,可以用 arguments.callee 代替函数名。
1 <script> 2 function outer(){ 3 //调用 inner 函数 4 inner(); 5 } 6 function inner(){ 7 console.log(arguments.callee.caller); 8 } 9 outer(); //输出: ƒ outer(){ 10 // //调用 inner 函数 11 // inner(); 12 // } 13 14 //如果在全局作用域中调用,返回 null 15 inner(); //输出:null 16 </script>
严格模式下不支持 arguments.caller ,在非严格模式下定义caller属性是 undefined。不支持向函数的caller属性赋值。
每个函数都有 length 跟 prototype 这两个属性。每个函数都有两个非继承而来的方法:apply() 和 call()。apply() 跟 call() 作用相同,区别在于接收参数的方式。bind() 方法会创建一个函数的实例,第一个参数 this 值会被绑定传给 bind() 函数的值。
length
length 表示函数希望接收命名参数的个数。
1 <script> 2 function a(){} 3 function b( num ){} 4 function c( num1, num2){} 5 console.log( a.length ); //输出:0 6 console.log( b.length ); //输出:1 7 console.log( c.length ); //输出:2 8 </script>
prototype
prototype 保存引用类型的所有方法的真正所在。例如 toString() 跟 valueOf() 方法实际上都是保存在 prototype 之下。prototype 方法是不可枚举的。
apply()
apply() 接收2个参数。第一个参数是在其中运行的作用域,第二个参数可以是参数数组、Array的实例或者arguments对象。
1 <script> 2 function sum(num1, num2){ 3 return num1 + num2; 4 } 5 function applySum(num1, num2){ 6 //在执行sum()函数时传入this作为this值,因为是在全局作用域中调用的,所以传入的this就是window对象 7 return sum.apply(this, arguments); 8 } 9 function applySum2(num1, num2){ 10 return sum.apply(this, [num1, num2]) 11 } 12 console.log(applySum(1, 2)); //输出:3 13 console.log(applySum2(1, 2)); //输出:3 14 </script>
call()
call() 的第一个参数是在其中运行的作用域,其余传给函数的参数必须逐个列举出来。
1 <script> 2 function sum(num1, num2){ 3 return num1 + num2; 4 } 5 function applySum(num1, num2){ 6 return sum.call(this, num1, num2) 7 } 8 console.log(applySum(1, 2)); //输出:3 9 </script>
apply() 跟 call() 的强大之处是他们可以扩充函数赖以生存的作用域。
1 <script> 2 window.num = 1; 3 var i = { 4 num : 2 5 }; 6 function sayNum(){ 7 console.log( this.num ); 8 } 9 //call()的第一个参数为运行函数的作用域 10 //在全局作用域中调用,this值等同于window 11 sayNum.call(this); //输出:1 12 sayNum.call(window); //输出:1 13 //在全局作用域中调用i,那么运行函数的作用域为对象 i。即先将sayNum()函数放到对象 i 中,然后再通过对象 i 来调用它 14 sayNum.call(i); //输出:2 15 </script>
bind()
1 <script> 2 window.num = 1; 3 var i = { 4 num : 2 5 }; 6 function sayNum(){ 7 console.log( this.num ); 8 } 9 //创建一个函数实例 a 。sayNum() 调用 bind() 并传入对象 i, 创建 a() 函数。a函数的 this值等于 i。 10 var a = sayNum.bind( i ); 11 //即使在全局作用域中调用 a(), 它的作用域仍为 i。 12 a(); //输出:2 13 </script>