JavaScript 三种声明函数的方法 1. function 命令 function print(s) { console.log(s); } 2. 函数表达式,这种写法将一个匿名函数赋值给变量。 var print = function(s) { console.log(s); } 3. Function 构造函数,几乎无人使用 var add = new Function( 'x', 'y', 'return x + y' ); 圆括号运算符,return 语句和递归 function fib(num) { if (num === 0) return 0; if (num === 1) return 1; return fib(num - 2) + fib(num - 1); } 第一等公民 function add(x, y) { return x + y; } // 将函数赋值给一个变量 var operator = add; // 将函数作为参数和返回值 function a(op){ return op; } a(add)(1, 1) // 2 函数名的提升 JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。 函数的属性和方法 function fib(num) { if (num === 0) return 0; if (num === 1) return 1; return fib(num - 2) + fib(num - 1); } undefined fib.name //"fib" fib.length //1 函数作用域 var v = 1; //全局变量 // function f() { var v = 2; //局部变量,函数内部变量会覆盖全局变量 } //注意,对于var命令来说,局部变量只能在函数内部声明,在其他区块中声明,一律都是全局变量。 if (true) { var x = 5; // 这里的 x 是全局变量,因为它不在函数中 } 函数内部的变量提升 //与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。 function foo(x) { if (x > 100) { var tmp = x - 100; } } // 等同于 function foo(x) { var tmp; if (x > 100) { tmp = x - 100; }; } 函数本身的作用域 //函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。 var a = 1; var x = function () { console.log(a); }; function f() { var a = 2; x(); //函数x是在函数f的外部声明的,所以它的作用域绑定外层,内部变量a不会到函数f体内取值,所以输出1,而不是2。 } f() // 1 function foo() { var x = 1; function bar() { console.log(x); } return bar; } var x = 2; var f = foo(); f() // 1 参数 //函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。 var p = 2; function f(p) { p = 3; } f(p); p // 2 //但是,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。 var obj = { p: 1 }; function f(o) { o.p = 2; } f(obj); obj.p // 2 //注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。 var obj = [1, 2, 3]; function f(o) { o = [2, 3, 4]; } f(obj); obj // [1, 2, 3] 同名参数 如果有同名的参数,则取最后出现的那个值。 function f(a, a) { console.log(a); } f(1, 2) // 2 //调用函数f()的时候,没有提供第二个参数,a的取值就变成了undefined。这时,如果要获得第一个a的值,可以使用arguments对象。 function f(a, a) { console.log(a); } f(1) // undefined function f(a, a) { console.log(arguments[0]); } f(1) // 1 arguments 对象 //由于 JavaScript 允许函数有不定数目的参数,所以需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。 //arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。 var f = function (one) { console.log(arguments[0]); console.log(arguments[1]); console.log(arguments[2]); } f(1, 2, 3) // 1 // 2 // 3 var f = function(a, b) { arguments[0] = 3; arguments[1] = 2; return a + b; } f(1, 1) // 5 //上面代码中,函数f()调用时传入的参数,在函数内部被修改成3和2。 //严格模式下,arguments对象与函数参数不具有联动关系。也就是说,修改arguments对象不会影响到实际的函数参数。 var f = function(a, b) { 'use strict'; // 开启严格模式 arguments[0] = 3; arguments[1] = 2; return a + b; } f(1, 1) // 2 上面代码中,函数体内是严格模式,这时修改arguments对象,不会影响到真实参数a和b。 通过arguments对象的length属性,可以判断函数调用时到底带几个参数。 function f() { return arguments.length; } f(1, 2, 3) // 3 f(1) // 1 f() // 0 函数的其他知识点 //闭包 //闭包(closure)是 JavaScript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 //理解闭包,首先必须理解变量作用域。前面提到,JavaScript 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。 function f1() { var n = 999; function f2() { console.log(n); } return f2; } var result = f1(); result(); // 999 //闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。 //闭包的最大用处有两个,一个是可以读取外层函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。请看下面的例子,闭包使得内部变量记住上一次调用时的运算结果。 function createIncrementor(start) { return function () { return start++; }; } var inc = createIncrementor(5); // 将 createIncrementor 内部方法赋值给变量 inc inc() // 5 inc() // 6 inc() // 7 闭包的另一个用处,是封装对象的私有属性和私有方法 function Person(name) { var _age; function setAge(n) { _age = n; } function getAge() { return _age; } return { // return 通过 对象这个数据类型返回 函数内部的方法 name: name, getAge: getAge, setAge: setAge }; } var p1 = Person('张三'); p1.setAge(25); p1.getAge() // 25 立即调用的函数表达式(IIFE) var f = function f(){ return 1}(); f // 1 //函数定义后立即调用的解决方法,就是不要让function出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。 (function(){ /* code */ }()); // 或者 (function(){ /* code */ })(); eval 命令 //eval命令接受一个字符串作为参数,并将这个字符串当作语句执行。 eval('var a = 1;'); a // 1 //eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。 var a = 1; eval('a = 2'); a // 2 (function f() { 'use strict'; eval('var foo = 123'); console.log(foo); // ReferenceError: foo is not defined })() eval 的别名调用,不推荐使用