函数定义和调用
arguments
关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array。
利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值。
function foo(x) {
alert(x); // 10
for (var i=0; i<arguments.length; i++) {
alert(arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
rest参数
ES6标准引入了rest参数,rest参数只能写在最后,前面用...标识,从运行结果可知,传入的参数先绑定a、b,多余的参数以数组形式交给变量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 []
JavaScript自动添加分号带来的陷阱
function foo() {
return
{ name: 'foo' };
}
自动添加分号之后变成了。冏rz
function foo() {
return;
{ name: 'foo' };
}
下面两种写法都没有问题:
function foo() {
return { name: 'foo' };
}
function foo() {
return { // 这里不会自动加分号,因为{表示语句尚未结束
name: 'foo'
};
}
变量作用域
- 如果一个变量在函数体内部申明,则该变量的作用域为整个函数体。
- 由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。
'use strict';
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
变量提升
- JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部。
- 提升的只是声明,赋值还是在原来的位置进行赋值。
function foo() {
var x = 'Hello, ' + y;
alert(x);
var y = 'Bob';
}
相当于:
function foo() {
var y; // 提升变量y的申明
var x = 'Hello, ' + y;
alert(x);
y = 'Bob';
}
所以,我们最好在开头声明所有的变量。
function foo() {
var
x = 1, // x初始化为1
y = x + 1, // y初始化为2
z, i; // z和i为undefined
}
全局作用域
不在任何函数内定义的变量具有全局作用域。
全局作用域的变量实际上被绑定到window的一个属性。
var course = 'Learn JavaScript';
alert(course);
alert(window.course);
名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () { return 'foo';};
把自己的代码全部放入唯一的名字空间MYAPP
中,会大大减少全局变量冲突的可能。
许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。
局部作用域
由于JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的:
'use strict';
function foo() {
for (var i=0; i<100; i++) {
//
}
i += 100; // 仍然可以引用变量i
}
为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:
'use strict';
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
i += 1; // SyntaxError
}
常亮
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:
var PI = 3.14;
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:
'use strict';
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
方法
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
this是一个特殊的变量,始终指向当前对象,也就是xiaoming这个对象。
如果方法是一个对象的一个方法,那么这个方法里面的this指向的是这个对象。但如果这个方法是一个独立的方法,那么这个方法里面的this,在strict模式指向undefined,在非strict模式指向window。为了解决这个问题,我们可以使用apply。
apply
要指定函数的this指向哪个对象,可以使用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。
//apply的使用
function getAge(){
var y = new Date().getFullYear();
return y-this.birth;
}
var xiaoming = {
name:'小明',
birth:1991,
age:getAge
};
console.log(xiaoming.age());//这里直接使用this是有值的
var age = getAge.apply(xiaoming, []);//使用外部的方法就需要用apply了。
console.log(age);
call
call和apply类似,不同之处在于apply是把参数打包成Array传入,而call把参数按照顺序传入。
Math.max(2,3,4);
Math.max.call(null,2,3,4);
利用apply实现装饰器
利用apply(),我们还可以动态改变函数的行为。
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt():
//装饰器
var count = 0;
var oldParseInt = parseInt;
window.parseInt = function(){
count +=1;
return oldParseInt.apply(null,arguments);
// return oldParseInt(arguments);//为什么用这个就不行呢?
}
parseInt('10');
parseInt('10');
parseInt('10');
console.log(parseInt('10'));
console.log(count);
高阶函数
高阶函数就是一个能接收一个函数当做变量的函数。
map/reduce
map
把一个函数作用在Array的每一个元素上。
注意:map对原数组是没有改变的,map返回一个新的数组。
//高阶函数map
function pow(x){
return x*x;
}
var arr = [1,2,3,4,5];
var arr2 = arr.map(pow);
console.log(arr);
console.log(arr2);
//把数组的所有数字转成字符串
//只需要一行代码,是不是太强大了
arr2 = arr.map(String);
console.log(arr2);
reduce
Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
不使用parseInt()把字符串转成int
console.log('不使用parseInt()把字符串转成int');
function string2int(s){
var arr = [];
for(var i of s){
arr.push(i);
}
if(s.length==1){
return s[0]*1;
}
var res = arr.reduce(function(x,y){
return x*10+y*1;
});
return res;
}
// 测试:
if (string2int('0') === 0 && string2int('12345') === 12345 && string2int('12300') === 12300) {
if (string2int.toString().indexOf('parseInt') !== -1) {
alert('请勿使用parseInt()!');
} else if (string2int.toString().indexOf('Number') !== -1) {
alert('请勿使用Number()!');
} else {
alert('测试通过!');
}
}
else {
alert('测试失败!');
}
请把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']。
console.log("请把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']。");
function normalize(arr) {
return arr.map(function(x){
return x.substring(0,1).toUpperCase()+x.substring(1).toLowerCase();
});
}
// 测试:
if (normalize(['adam', 'LISA', 'barT']).toString() === ['Adam', 'Lisa', 'Bart'].toString()) {
alert('测试通过!');
}
else {
alert('测试失败!');
}
一个存在问题的例子:
小明希望利用map()把字符串变成整数,他写的代码很简洁。
'use strict';
var arr = ['1', '2', '3'];
var r;
r = arr.map(parseInt);
alert('[' + r[0] + ', ' + r[1] + ', ' + r[2] + ']');
结果竟然是[1, NaN, NaN],小明百思不得其解,请帮他找到原因并修正代码。
提示:参考Array.prototype.map()的文档。
由于map()接收的回调函数可以有3个参数:callback(currentValue, index, array),通常我们仅需要第一个参数,而忽略了传入的后面两个参数。不幸的是,parseInt(string, radix)没有忽略第二个参数,导致实际执行的函数分别是:
parseInt('0', 0); // 0, 按十进制转换
parseInt('1', 1); // NaN, 没有一进制
parseInt('2', 2); // NaN, 按二进制转换不允许出现2
可以改为r = arr.map(Number);,因为Number(value)函数仅接收一个参数。
filter
filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
//高阶函数filter
var arr = [1,2,3,4,5,6,7,8,9.0];
var res = arr.filter(function(x){
return x%2==0;
});
console.log(res);
sort(sort直接修改原数组)
Array的排序默认把数字转成字符串然后进行排序(这就导致了10比2小),而且排序是按照Ascii码排序(这导致a比B大)。
//高阶函数-sort
var arr = [10, 20, 1, 2];
arr.sort(function(x,y){
/*
if(x<y){
return -1;
}else if(x>y){
return 1;
}else{
return 0;
}
*/
return y-x;//这样写岂不是更方便
});
console.log(arr);