函数
借助函数,我们才能不关心底层的计算问题,而是在更高的层次上思考问题。
写计算机程序也是一样,函数就是最基本的而一种代码抽象方式。
一、函数定义和调用
函数定义的两种方式:
function abs(x){
...
}
var abs = function(x){
...
}; //匿名函数赋值给了变量abs
函数内部的语句在执行时,一旦执行到return语句时,函数就执行完毕,并将结果返回。
- arguments
JS有一个关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的函数。arguments类型一个Array,但不是一个Array。
利用arguments可以获得调用者传入的所有参数。
arguments最常用于判断传入参数的个数:
// foo(a[, b], c)
// 接收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引入了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参数写后面,前面用...
标识,传入的参数先绑定a、b,多余的参数以数组的形式传给变量rest。如果传入参数不够,rest参数会接受一个空数组(而不是undefined)
二、变量作用域和解构赋值
- 局部作用域
在JS中,用var申明的变量实际上是有作用域的。
如果一个变量在函数内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量。
function foo() {
var x = 1;
x = x + 1;
}
x = x + 2; // ReferenceError! 无法在函数体外引用变量x
不同函数内部的同名变量相互独立,互不影响。
由于JS的函数可以嵌套,此时,内部的函数可以访问外部函数定义的变量,反过来不行。
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
- 全局作用域
不在任何函数内定义的变量就具有全局作用域。
JS有一个默认对象windows,全局作用域的变量实际上被绑定到windows的一个属性。
- let
由于JS的变量作用域实际上是函数内部,在for循环等语句块中是无法定义具有局部作用域的变量。
为了解决块级作用域,ES6引入了新的关键字let。
let可以替代var申明一个块级作用域的变量。
- const
let和const都具有块级作用域。
const用来申明常量。
- 解构赋值
ES6引入了解构赋值。
对数组元素进行解构赋值:
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
x; // 'hello'
y; // 'JavaScript'
z; // 'ES6'
从一个对象中取出若干属性,也可以使用解构赋值。
使用解构赋值对象属性时,如果对应的属性不存在,变量将被赋值为undefined
解构赋值可以使用默认值,就避免了不存在的属性返回undifined问题:
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true
方法
在一个对象中绑定函数,称为这个函数的方法。
在一个方法内部,this是一个特殊变量,它始终指向当前对象。
高阶函数
高阶函数:一个函数接受另一个函数为参数
- map/reduce
map()方法定义在JS的Array中,传入参数,就返回得到了一个新的Array
arr.map(function (){
…
})
Array的reduce函数必须接受两个参数,**reduce()**函数把结果和序列的下一个元素做累计计算。
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
- filter
filter用于把Array的某些元素过滤掉,然后返回剩下的元素。
Array的filter()也接受一个函数,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
例如:在一个Array中,删掉偶数保留奇数
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
arr;// [1, 2, 4, 5, 6, 9, 10, 15]
filter()函数接受的回调函数,通常只使用一个参数,表示Array的某个元素。回调函数一共可以接受三个参数,表示元素的位置和数组本身。
var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
console.log(element); // 依次打印'A', 'B', 'C'
console.log(index); // 依次打印0, 1, 2
console.log(self); // self就是变量arr
return true;
});
-sort.排序算法
通常规定,对于两个元素x和y,如果x<y
,则返回-1;如果x=y
,返回0;如果x>y
,返回+1。
JS的Array的**sort()**方法用于排序。
// 看上去正常的结果:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];
// apple排在了最后:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']
// 无法理解的结果:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]
第二个排序把apple排在最后是因为a的ASCLL码在大写字母之后
第三个只因为Array的SORT()方法默认把所有元素先转换成String再排序,10排在2前面,是因为1字符比2字符ASCLL小。
使用**sort()**方法比较数字需要谨慎,**sort()**函数是个高阶函数,可以接受一个比较函数来实现自定义排序。
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
});
console.log(arr); // [1, 2, 10, 20]
如果要倒序:
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return 1;
}
if (x > y) {
return -1;
}
return 0;
});
console.log(arr); // [20, 10, 2, 1]
注意:sort()方法会直接对Array进行修改,它返回的结果仍是当前Array
var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; // ['A', 'B', 'C']
a2; // ['A', 'B', 'C']
a1 === a2; // true, a1和a2是同一对象
闭包
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果返回。
一个简单的求和闭包函数:
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
f(); // 15
当调用lazy_sum()函数时,返回的并不是结果,而是求和函数。
注意到返回的函数在其定义内部引用了局部变量arr,当返回了一个函数后,其内部的局部变量还被新函数引用。
另一个值得注意的是,返回的函数并没有立即执行,而是知道调用了f()才执行。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
箭头函数
ES6新增了一个新的函数:箭头函数(Arrow Function)
箭头函数相当于匿名定义,并且简化了函数定义。
x => x*x
省略了{}和return。
如果有多条语句,不能省略
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
如果参数不止一个,需要用()包括起来。
如果返回一个对象:
x => ({
foo : x
})
需要加上(),因为和函数体的{...}
有语法冲突。
箭头函数修复了this作用域的问题,箭头函数内部的this是词法作用域,有上下文确定。
generator
generator(生成器)是ES6引入的新的数据类型,一个generator看上去像一个函数,但可以返回很多次。
generator和函数不同的是,genenrator由function*
定义,并且除了return语句,还可以用yield返回多次。
举一个例子,著名的斐波那契数列为例:0 1 1 2 3 5 8 13 ...
使用generator,可以一次返回一个数,不断返回多次:
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
直接调用:
fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
直接调用一个generator和直接调用一个函数不同,fib(5)
仅仅是创建了一个generator对象,还没有去执行它。
调用generator对象有两个方法:
- 不断调用对象的**next()**方法:
next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。
当执行到done为true时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
- 第二个方法是直接食用for…of循环迭代对象,这种方式不需要判断done、
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
for (var x of fib(10)) {
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}