2. 函数
2.1 函数定义和调用
定义函数
有两种方法,一种是:
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
另一种是:
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};//末尾添加 ; 构成完整语法
两种定义方法完全等价。
函数调用
按顺序传入参数即可。
js允许传入任意个参数,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数。
传入的参数少也不会出错。
arguments
js有一个关键字 arguments
,它只在函数内部起作用,利用 arguments
可以获得调用者传入的所有参数。
rest
略
2.2 变量作用域与解构赋值
如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响。
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行。
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
变量提升
JavaScript的函数定义会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部。由于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的一个属性。
由于函数定义有两种方式,以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象。
'use strict';
function foo() {
alert('foo');
}
foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用
JavaScript实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误。
名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
局部作用域
由于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;
}
// SyntaxError:
i += 1;
}
常量
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”。ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域。
解构赋值
ES6引入了解构赋值,可以对多个变量同时赋值。
如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致。
解构赋值还可以忽略某些元素。
如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性。
解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题。
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
city: 'Beijing',
street: 'No.1 Road',
zipcode: '100001'
}
};
var {name, age, passport} = person;// name, age, passport分别被赋值为对应属性
var {name, address: {city, zip}} = person;//city:"Beijing", zip是undefined,因为属性名是zipcode不是zip
let {name, passport:id} = person;//id:'G-12345678'
var {name, single=true} = person;// 如果person对象没有single属性,默认赋值为true
2.3 方法
给 xiaoming
绑定一个函数,就可以做更多的事情。比如,写个 age()
方法,返回 xiaoming
的年龄。
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了
在一个方法内部, this
是一个特殊变量,它始终指向当前对象,也就是 xiaoming
这个变量。
JavaScript 的函数内部如果调用了 this
,如果以对象的方式调用,比如 xiaoming.age()
,该函数的 this
指向被调用的对象,也就是 xiaoming
;如果单独调用函数,此时该函数的 this
指向全局对象,即 window
。
apply
要指定函数的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(),区别在于aplly()把参数打包成Array传入,call()把参数按顺序传入。对普通函数调用,通常把this绑定为null,即:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
2.4 高阶函数
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
map/reduce
map
由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果。
'use strict';
function pow(x){
return x*x;
};
var arr = [1,2,3,4,5,6,7,8,9,10];
var results = arr.map(pow);//map传入的参数是pow,即函数对象本身
console.log(results);//1,4,9,16,25,36,49,64,81,100
reduce
Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算。
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25
fliter
filter用于把Array的某些元素过滤掉,然后返回剩下的元素。和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
var
r,
arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
r = arr.filter(function (element, index, self) {
return self.indexOf(element) === index;//true的话过滤
});//indexOf返回第一个元素的位置,后续重复元素位置与indexOf返回的位置不相等,所以被fliter过滤掉了
sort
Array的sort()方法默认把所有元素先转换为String再排序,幸运的是,sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。sort()方法会直接对Array进行修改,它返回的结果仍是当前Array。
//倒序排序
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return 1;
}
if (x > y) {
return -1;
}
return 0;
}); // [20, 10, 2, 1]
every
every()方法可以判断数组的所有元素是否满足测试条件。
find
find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined。
findIndex
findIndex()和find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1。
forEach
forEach()和map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()常用于遍历数组,因此,传入的函数不需要返回值。
2.5 闭包
函数作为返回值
此处参考廖雪峰老师的讲解,不再赘述 https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016
2.6 generator
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。generator和函数不同的是,generator由function定义(注意多出的号),并且,除了return语句,还可以用yield返回多次。
调用generator对象有两个方法,一是不断地调用generator对象的next()方法,next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。
当执行到done为true时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。
第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done。