- JavaScript的函数不但是“头等公民”,而且可以像变量一样使用,具有非常强大的抽象能力
- 函数体内部的语句在执行时,一旦执行到
return
时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。如果没有
return
语句,函数执行完毕后也会返回结果,只是结果为undefined
。 - 定义(2)
var abs = function (x) { if (typeof x !== ‘number‘) { throw ‘Not a number‘; } if (x >= 0) { return x; } else { return -x; } }; //可以通过变量abs就可以调用该函数。 abs(10); // 返回10 abs(-9); // 返回9
-
arguments
它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments
类似Array
但它不是一个Array。即使函数不定义任何参数,还是可以拿到参数的值:
function abs() { if (arguments.length === 0) { return 0; } var x = arguments[0]; return x >= 0 ? x : -x; } abs(); // 0 abs(10); // 10 abs(-9); // 9 //通常用于判断参数个数 // 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null: function foo(a, b, c) { if (arguments.length === 2) { // 实际拿到的参数是a和b,c为undefined c = b; // 把b赋给c b = null; // b变为默认值 } // ... }
-
rest参数(ES6)
ES6标准引入了rest参数,用于接收额外的rest
参数
function foo(a, b, ...rest) { console.log(‘a = ‘ + a); console.log(‘b = ‘ + b); console.log(rest); } foo(1, 2, 3, 4, 5); // 结果: // a = 1 // b = 2 // Array [ 3, 4, 5 ] foo(1); // 结果: // a = 1 // b = undefined // Array [] 如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组(注意不是undefined)。
- 变量作用域
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量
如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
‘use strict‘; function foo() { var x = 1; function bar() { var x = ‘A‘; alert(‘x in bar() = ‘ + x); // ‘A‘ } alert(‘x in foo() = ‘ + x); // 1 bar(); } //这说明JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找
-
变量提升
JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:
function foo() { var x = ‘Hello, ‘ + y; alert(x); var y = ‘Bob‘; } //JavaScript引擎看到的代码相当于: function foo() { var y; // 提升变量y的申明 var x = ‘Hello, ‘ + y; alert(x); y = ‘Bob‘; } //由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量: function foo() { var x = 1, // x初始化为1 y = x + 1, // y初始化为2 z, i; // z和i为undefined // 其他语句: for (i=0; i<100; i++) { ... } }
-
全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window
,全局作用域的变量实际上被绑定到window
的一个属性。因此,直接访问全局变量course
和访问window.course
是完全一样的。实际上alert等函数也是window的一个变量。
-
名字空间
全局变量会绑定到window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP: var MYAPP = {}; // 其他变量: MYAPP.name = ‘myapp‘; MYAPP.version = 1.0; // 其他函数: MYAPP.foo = function () { return ‘foo‘; };
-
局部作用域let(ES6)
用let
替代var
可以申明一个块级作用域的变量:
‘use strict‘; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } i += 1; // SyntaxError }
-
常量const(ES6)
ES6标准引入了新的关键字const
来定义常量,const
与let
都具有块级作用域:
‘use strict‘; const PI = 3.14; PI = 3; // 某些浏览器不报错,但是无效果! PI; // 3.14
- this
JavaScript的函数内部如果调用了this,那么这个
this
到底指向谁?答案是,视情况而定!
由于这是一个巨大的设计错误,要想纠正可没那么简单。ECMA决定,在strict模式下让函数的this
指向undefined;这个决定只是让错误及时暴露出来,并没有解决
this
应该指向的正确位置。
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: ‘小明‘, birth: 1990, age: getAge }; xiaoming.age; // function xiaoming.age() xiaoming.age(); // 25, 正常结果 getAge(); // NaN 如果单独调用函数,比如getAge(),此时,该函数的this指向全局对象,也就是window。 如果这么写也是不行,要保证this指向正确,必须用obj.xxx()的形式调用!: var fn = xiaoming.age; // 先拿到xiaoming的age函数 fn(); // NaN
修复的办法也不是没有,我们用一个that
变量首先捕获this
:
‘use strict‘; var xiaoming = { name: ‘小明‘, birth: 1990, age: function () { var that = this; // 在方法内部一开始就捕获this function getAgeFromBirth() { var y = new Date().getFullYear(); return y - that.birth; // 用that而不是this } return getAgeFromBirth(); } }; xiaoming.age(); // 25
-
apply与call
要指定函数的this
指向哪个对象,可以用函数本身的apply
方法,它接收两个参数,第一个参数就是需要绑定的this
变量,第二个参数是Array
,表示函数本身的参数。
用apply
修复getAge()
调用:
function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: ‘小明‘, birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
另一个与apply()
类似的方法是call()
,唯一区别是:
apply()
把参数打包成Array
再传入;call()
把参数按顺序传入。
Math.max.apply(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5 对普通函数调用,我们通常把this
绑定为null
。
-
装饰器
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数,利用apply()
,我们还可以动态改变函数的行为。
现在假定我们想统计一下代码一共调用了多少次parseInt()
,可以把所有的调用都找出来,然后手动加上count += 1
,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt()
:
var count = 0; var oldParseInt = parseInt; // 保存原函数 window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 调用原函数 }; // 测试: parseInt(‘10‘); parseInt(‘20‘); parseInt(‘30‘); count; // 3