函数

1. 函数声明语句:
用关键字 function 来声明一个函数, function 后接函数名连着一对圆括号 ( ), 再接一个花括号{ }. ( ) 中传的叫参数, 可以不传任何参数;{ } 里面的是函数体, 是一些数学运算或执行语句. 函数被定义一次, 能被调用多次, 如下代码中的 add( ) 函数和 Three.js 中的 v() 函数. function output(){ alert('Hello!'); } function add(n1, n2){ return n1 + n2; } add(1,2); // 3 add(3,7); // 10 function v( x, y, z ) { placeholder.push( x ); placeholder.push( y ); placeholder.push( z ); } v( vert.x, vert.y, depth + z ); v( position2.x, position2.y, position2.z );

2. 函数的参数:
形参、实参. 在函数声明语句中的是形参, 而函数执行调用时传入的是实参. 如下代码, n1, n2 都是形参, 不需要做类型检查. add(1, 2) 中的 1 和 2 是实参, 进行求和运算, 返回运算结果 3. function add(n1, n2){ return n1 + n2; } add(1, 2); // 3 (1) 实参 < 形参: 把可选参数放在最后, 这些参数在执行时为 undefined 值. 要为这些可以多出来的形参设一个默认值. 如下代码, 前两个位置 origin 和 direction 是必须要填的参数;而后两个参数 near 和 far, 在函数调用时可以不传, 传入时会按照实际传入值进行计算, 没传入时则 按照函数体内的 0 和 Infinity 来进行计算. function Raycaster( origin, direction, near, far ) { this.ray = new Ray( origin, direction ); this.near = near || 0; this.far = far || Infinity; } (2) 实参 > 形参: 这时超出的实参都不会应用到. 为了解决传入不定个数的实参仍然可以运行, 在函数体内采用 arguments 来指向实参对象的引用. 它是一个类数组对象, 可通过数字索引来访问实参, 但并非是真正的数组. function add(){ var sum = 0; for(var i = 0; i < arguments.length; i++){ sum += arguments[i]; } return sum; } add(1, 25, 7, -4, 9, -2); // 36 remove: function ( object ) { if ( arguments.length > 1 ) { for ( var i = 0; i < arguments.length; i ++ ) { this.remove( arguments[ i ] ); } return this; } ... ... } (3) 可通过参数对已知变量的属性进行操作. var a = new Vector3(); var b = new Vector3(); var c = new Vector3(); for ( var i = 0; i < indices.length; i += 3 ) { getVertexByIndex( indices[ i + 0 ], a ); getVertexByIndex( indices[ i + 1 ], b ); getVertexByIndex( indices[ i + 2 ], c ); } function getVertexByIndex( index, vertex ) { var stride = index * 3; vertex.x = vertices[ stride + 0 ]; vertex.y = vertices[ stride + 1 ]; vertex.z = vertices[ stride + 2 ]; }

3. 函数返回值:
函数体内可通过关键字 return 来返回运算结果, 若没有 return, 函数的返回值是 undefined. 通过 return 来得出求和运算的结果. 通常, return 跟函数的参数关联使用, 根据不同的参数返回不同的结果. add(1, 2) 返回运算结果 3, 这是一个作为值的函数. function add(n1, n2){ return n1 + n2; } add(1, 2); // 3 function area( p, q, r ) { return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); }

4. 函数定义表达式:
函数是一种值, 可以赋给一个变量, 因此函数定义表达式可称为"函数直接量". 它可以不带函数名. 调用时使用变量名 +( ), 可传入所需的参数. var square = function(x){ return x * x; } square(5); // 25 square(25); // 625 var searchNodeSubtree = function ( children ) { for ( var i = 0; i < children.length; i ++ ) { var childNode = children[ i ]; if ( childNode.name === nodeName || childNode.uuid === nodeName ) { return childNode; } var result = searchNodeSubtree( childNode.children ); if ( result ) return result; } return null; }; var result = searchNodeSubtree( childNode.children ); var subTreeNode = searchNodeSubtree( root.children ); 函数声明语句和函数定义表达式都创建了一个新的函数对象, 但函数声明语句 实际新建了一个变量, 把函数对象赋值给变量. 但函数定义表达式并没有新建一个变量. 函数定义表达式可以立即调用. var square = ( function(x){return x * x;}(10) ); console.log( square ); // 100

5. 方法: 函数除了可以赋值给变量, 还也可以赋值给对象的属性. 当函数和对象合写在一起时, 函数就成了方法 method. var obj = { square: function(x){ return x * x }; } obj.square(10); // 100 var _Math = { clamp: function ( value, min, max ) { return Math.max( min, Math.min( max, value ) ); }, } Math.acos( _Math.clamp( theta, -1, 1 ) ); 以下是函数作为值和作为对象方法的比较. function add(x, y){ return x + y; } function sub(x, y){ return x - y; } function mul(x, y){ return x * y; } function divide(x, y){ return x / y; } function operate(operator, n1, n2){ return operator(n1, n2); } var operators = { add: function(x, y){ return x + y; }, sub: function (x, y){ return x - y; }, mul: function (x, y){ return x * y; }, divide: function (x, y){ return x / y; } } function operator2(operation, n1, n2){ if(typeof operators[operation] === "function") return operators[operation](n1, n2); }

6. 构造函数:
函数用来初始化 (new) 一个新对象, 我们称之为构造函数 constructor. function BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) { this.parameters = { width: width, height: height, depth: depth, widthSegments: widthSegments, heightSegments: heightSegments, depthSegments: depthSegments }; ... ... } var geometry = new BoxGeometry(1, 1, 1, 3, 3, 3);

7. 匿名函数:
你写了一段 JavaScript 代码给别人调用, 你不知别人是否也会声明了相同名字的全局变量, 那可把全局变量 改为局部变量放在函数内. 还有一种做法就是写个匿名函数直接调用, 格式为把整个函数体 +( ) 再用圆括号 ( ) 括起来, function 后面不接函数名. 下面代码中, 上面的写法可以打印出来;下面的写法编程软件会提示错误, 运行后控制点打印: SyntaxError: function statement requires a name. 因为 function 左边不写 “(”, JavaScript解析器会视图将关键字 function 解析为函数声明语句. (function(){ console.log( 1 ); }()) function(){ console.log( 2 ); }()

8. 函数作用域:
在函数内声明的所有变量在函数体内始终可见, 这称声明提前, 但也只有在执行到 var 时, 才会被赋值. function test(o){ var i = 0; if(typeof o == 'object'){ var j = 0; } console.log( j ); } test( new Object() ); // 0 test( [] ); // 0 test( 1 ); // undefined 每一段代码都有一个与之关联的作用域链 scope chain, 它是一个对象列表或者链表, 这组对象定义了这段代码作用域中的变量.
(1) 在最顶层代码中(不包含任何函数定义内的代码), 作用域链有一个全局对象组成;
(2) 在不包含嵌套的函数体内, 作用域链上有两个对象, 第一个是定义函数和局部变量的对象, 第二个是全局对象;
(3) 在一个嵌套的函数体内, 作用域链上至少有三个对象.

声明一个函数语句会创建了一个新的函数对象, 调用这个函数时, 又创建一个新的对象来储存它的局部变量, 并将这个对象添加至保存的那个作用域链 上, 同时创建一个新的更惨的边数函数调用作用域的链. 调用嵌套函数时, 内部函数又会重新定义一遍, 因为每次调用外部函数的时候, 作用域链都是不同的.

9. 递归:
函数体内部再次调用本身, 但参与运算的中间值会有所变化. 如下面计算阶乘的函数, 是根据公式 !n = n * (n-1) * (n-2) ... * 2 * 1. 函数体内最后一句 return 中的 factorial(n-1) 就是调用自身继续进行运算, 直到 n == 1. function factorial(n){ if(n <= 1) return 1; return n * factorial(n-1); } console.log( factorial(5) ); // 120 如果把这函数写对象的属性内, 可以如下进行: var calculator = { factorial: function(n){ if(n == 1) return 1; else return n * this.factorial(n - 1); } } var c = new Calculator(); console.log( calculator.factorial(10) ) // 3628800 或者去掉this, 加上函数名. var calculator = { factorial: function f(n){ if(n == 1) return 1; else return n * f(n - 1); } }