函数定义
普通 函数声明 会被前置
function abs(x) { ... }
赋值 等价上一种 结尾加 ;
var abs = function (x) { ... };
立即调用函数表达式 IIFE
var area = (function() { var width = 10; var height = 10; return width * height; })();
Function 构造器
var func = new Function("a", "b", "console.log(a+b);"); // 调用func(1,2); //3 // 等价 var func = Function("a", "b", "console.log(a+b);");
参数
基本类型是作为值穿入,对象作为引用传入
传入的参数比定义的少也没有问题,形参接收到的值为 : undefined
js 没有重载
重载:为同一个函数名,编写不同的定义,根据参数决定调用的函数!
js 没有重载,因为 js 是不检查参数的。后面的同名函数直接覆盖掉前面的!
arguments 不定参数
arguments[0] === a
foo.name 函数名
foo.length 形参个数
arguments.length 实参个数
// 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变为默认值 } console.log(arguments.callee === foo); //true } foo(1, 2); console.log(foo.length); //3 console.log(foo.name); //foo
rest 参数 ES6
为了获取除了已定义参数 a、b 之外的参数,我们不得不用 arguments,并且循环要从索引 2 开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 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,所以,不再需要 arguments 我们就获取了全部参数。
如果传入的参数连正常定义的参数都没填满,也不要紧,rest 参数会接收一个空数组(注意不是 undefined)。
# 变量
## 全局变量
- JavaScript 默认有一个全局对象 window,全局作用域的变量实际上被绑定到 window 的一个属性
- JavaScript 实际上只有一个全局作用域。任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报 ReferenceError 错误。
"use strict";
var course = "Learn JavaScript";
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'
function foo() {
alert("foo");
}
foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用
- const 常量 ES6
"use strict";
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
## 命名空间
- 全局减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中
// 唯一的全局变量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
}
- let (ES6) 用于申明块级作用域变量
"use strict";
function foo() {
var sum = 0;
for (let i = 0; i < 100; i++) {
// i 是快级作用域
sum += i;
}
i += 1; // SyntaxError
}
## 变量提升
- JavaScript 的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量提升到函数顶部:
"use strict";
function foo() {
var x = "Hello, " + y;
alert(x);
var y = "Bob"; //提升
}
foo();
虽然是 strict 模式,但语句 var x = 'Hello, ' + y;并不报错,原因是变量 y 在稍后申明了。但是 alert 显示 Hello, undefined,说明变量 y 的值为 undefined。这正是因为 JavaScript 引擎自动提升了变量 y 的声明,但不会提升变量 y 的赋值。
- 在函数内部首先申明所有变量,用一个 var 申明函数内部用到的所有变量
function foo() {
var
x = 1, // x初始化为1
y = x + 1, // y初始化为2
z, i; // z和i为undefined
// 其他语句:
for (i=0; i<100; i++) {
...
}
}
## 函数嵌套
- 内部函数可以访问外部函数定义的变量,反过来则不行
"use strict";
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
- 函数内变量重名 函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量
"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();
}
# 高阶函数
- 将函数本身作为参数传入函数
function sum(num) {
return num + 10;
}
function box(a, b) {
return a(b);
}
console.log(box(sum, 10)); // 返回 20
## map
- 将函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9]上,我们调用 Array 的 map()方法,传入我们自己的函数,就得到了一个新的 Array 作为结果:
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
- map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的 f(x)=x2,还可以计算任意复杂的函数,比如,把 Array 的所有数字转为字符串:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
## reduce
- 再看 reduce 的用法。Array 的 reduce()把一个函数作用在这个 Array 的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4);
- 比方说对一个 Array 求和,就可以用 reduce 实现:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function(x, y) {
return x + y;
}); // 25
## filter
用于把 Array 的某些元素过滤掉,然后返回剩下的元素。
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]
- 把一个 Array 中的空字符串删掉,可以这么写:
var arr = ["A", "", "B", null, undefined, "C", " "];
var r = arr.filter(function(s) {
return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
});
r; // ['A', 'B', 'C']
## sort
- 可以接收一个比较函数来实现自定义的排序。
var arr = [10, 20, 1, 2];
arr.sort(function(x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
}); // [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;
}); // [20, 10, 2, 1]
- 默认情况下,对字符串排序,是按照 ASCII 的大小比较的,现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以:
`js var arr = ['Google', 'apple', 'Microsoft']; arr.sort(function (s1, s2) { x1 = s1.toUpperCase(); x2 = s2.toUpperCase(); if (x1 < x2) { return -1; } if (x1 > x2) { return 1; } return 0; }); // ['apple', 'Google', 'Microsoft']`
忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。
从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。
- 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 foo() {
return; // 自动添加了分号,相当于return undefined;
{
name: "foo";
} // 这行语句已经没法执行到了
}
// vs
function foo() {
return {
// 这里不会自动加分号,因为{表示语句尚未结束
name: "foo"
};
}
## 返回函数
- 如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数
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() 当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数
f(); // 15 调用函数f时,才真正计算求和的结果:
# 箭头函数 ES6
- 用于替代 匿名函数
x => x * x
//相当于
function (x) {
return x * x;
}
- 多行语句的
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
// 两个参数:
(x, y) => x x + y y
// 无参数:
() => 3.14
// 可变参数:
(x, y, ...rest) => {
var i, sum = x + y;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
- 返回对象
// SyntaxError:
x => {
foo: x;
};
// ok:
x => ({ foo: x });
## 箭头函数的 this 指向取决于上下文 也就是它所在的对象
- 匿名函数的 this
var obj = {
birth: 1990,
getAge: function() {
var b = this.birth; // 1990
var fn = function() {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};
- `=>`的 this
var obj = {
birth: 1990,
getAge: function() {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25
- 用 call()或者 apply()调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略
var obj = {
birth: 1990,
getAge: function(year) {
var b = this.birth; // 1990
var fn = y => y - this.birth; // this.birth仍是1990
return fn.call({ birth: 2000 }, year);
}
};
obj.getAge(2015); // 25
# Generator 多次返回函数
- 由 `function*` 定义(注意多出的 `*` 号),并且,除了 return 语句,还可以用 yield 返回多次
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
- 正常编写一个产生斐波那契数列的函数
function fib(max) {
var t,
a = 0,
b = 1,
arr = [0, 1];
while (arr.length < max) {
t = a + b;
a = b;
b = t;
arr.push(t);
}
return arr;
}
// 测试:
fib(5); // [0, 1, 1, 2, 3]
fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
- 使用 generator 改写
`js function* fib(max) { var t, a = 0, b = 1, n = 1; while (n < max) { yield a; t = a + b; a = b; b = t; n ++; } return a; } fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}`
`fib(5)`仅仅是创建了一个 generator 对象,还没有去执行它
- 调用 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: true}
`next()`方法会执行 generator 的代码,然后,每次遇到`yield x;`就返回一个对象`{value: x, done: true/false}`,然后“暂停”。
返回的 value 就是 yield 的返回值,done 表示这个 generator 是否已经执行结束了。如果 done 为 true ,则 value 就是 return 的返回值。
当执行到 done 为 true 时,这个 generator 对象就已经全部执行完毕,不要再继续调用`next()`了。
- 使用 `for of` 访问
for (var x of fib(5)) {
console.log(x); // 依次输出0, 1, 1, 2, 3
}
- 改写`ajax`
ajax('http://url-1', data1, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-2', data2, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-3', data3, function (err, result) {
if (err) {
return handle(err);
}
return success(result);
});
});
});
// 改为
try {
r1 = yield ajax('http://url-1', data1);
r2 = yield ajax('http://url-2', data2);
r3 = yield ajax('http://url-3', data3);
success(r3);
}
catch (err) {
handle(err);
}
看上去是同步的代码,实际执行是异步的。