一、定义
function abs(x){}
如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefinedvar abs = function (x) {};
注意第二种方式按照完整语法需要在函数体末尾加一个" ; ",表示赋值语句结束
由于JavaScript的函数也是一个对象
上述定义的abs()函数实际上是一个函数对象,而函数名abs可以视为指向该函数的变量 (1,2完全等价)
调用:JavaScript允许传入任意个参数而不影响调用
二、 关键字arguments
关键字arguments只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
arguments类似Array但它不是一个Array
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
arguments最常用于判断传入参数的个数
三、rest参数(ES6新增)
function foo(a, b, ...rest){}
传入的参数先绑定a、b,多余的参数以数组形式交给变量rest。
所以,不再需要arguments我们就获取了全部参数。
四、变量提升
函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部
【 JavaScript引擎自动提升变量的声明,但不会提升变量的赋值 】
(由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则)
五、作用域
-
全局作用域
JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性 (顶层函数的定义也被视为一个全局变量)任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报ReferenceError错误
名字空间:
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。
许多著名的JavaScript库都是这么干的:jQuery,YUI,underscore等等。
-
局部作用域
为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量引入了新的关键字const定义常量,const与let都具有块级作用域
六、解构赋值 (ES6新增)
-
同时对一组变量进行赋值
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
// 忽略前两个元素,只对z赋值第三个元素
let [, , z] = ['hello', 'JavaScript', 'ES6'];
-
//从对象中取出若干属性 (分别被赋值为person中对应变量名的属性):
var {name, age, passport} = person;
//嵌套取值
var {name, address: {city, zipcode}} = person;
//要使变量名和属性名不一致
// 把passport属性赋值给变量id:
let {name, passport:id} = person;
(如果对应的属性不存在,变量将被赋值为undefined)
-
//使用默认值
// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
-
语法错误:
// 声明变量: var x, y; // 解构赋值: {x, y} = { name: '小明', x: 100, y: 200}; // 语法错误: Uncaught SyntaxError: Unexpected token =
这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:
({x, y} = { name: '小明', x: 100, y: 200});
七、this
在一个方法内部,this始终指向当前对象
=> 要保证this指向正确,必须用obj.xxx()的形式调用。单独调用函数,this指向全局对象。
=> ECMA决定,在strict模式下让函数的this指向undefined (在非strict模式下,指向全局对象window)
1️⃣ 解决方法:
用 var that = this;
首先捕获this,就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。
2️⃣ 控制this指向:
- apply
要指定函数的this指向哪个对象,可以用函数本身的apply方法。
它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
- call
另一个与apply()类似的方法是call(),唯一区别是:
apply()把参数打包成Array再传入;
call()把参数按顺序传入。
对普通函数调用,我们通常把this绑定为null
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
3️⃣ 装饰器:
利用apply(),还可以动态改变函数的行为
// 修改默认函数:
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
八、闭包
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
1️⃣ 结构体:
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
在函数lazy_sum中又定义了函数sum:
内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中。
- 每次调用都会返回一个新的函数,调用结果互不影响
- 当函数返回了一个函数后,其内部的局部变量(arr) 还被新函数引用
2️⃣ 返回的函数并不会立刻执行,而是直到调用了f()才执行
//返回函数引用循环变量:返回函数结果都相同-变量相同
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
- 一定要引用循环变量:
再创建一个函数,用该函数的参数绑定循环变量当前的值
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
3️⃣ 这里用了一个“创建一个匿名函数并立刻执行”的语法
(function (x) {
return x * x;
})(3); // 9
- 由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来
(function (x) { return x * x }) (3);
4️⃣ 功能
- 闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来
应用:封装一个私有变量
//用JavaScript创建一个计数器
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
- 把多参数的函数变成单参数的函数
//利用闭包创建新的函数pow2和pow3
function make_pow(n) {
return function (x) {
return Math.pow(x, n);
}
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);
console.log(pow2(5)); // 25
console.log(pow3(7)); // 343
九、箭头函数(ES6新增)
1️⃣ 箭头函数相当于匿名函数,并且简化了函数定义。
function (x) {
return x * x;
}
简化为
x => x * x
- 多条语句,就不能省略{ … }和return
- 参数不是一个,就需要用括号()括起来
// 两个参数:
(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;
}
- 返回一个对象
x => { foo: x } // SyntaxError:
x => ({ foo: x }) // ok: 函数体的{ ... }有语法冲突
2️⃣ 箭头函数和匿名函数有个明显的区别:
箭头函数内部的this是词法作用域,由上下文确定。
=> 如果使用箭头函数 var that = this;
就不再需要了
- 由于this在箭头函数中已经按照词法作用域绑定了,所以,
用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略
十、generator(生成器)(ES6新增)
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
generator由function*定义,除了return语句,还可以用yield返回多次。
用generator改写斐波那契数列函数
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
1️⃣ 调用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}
next()方法会执行generator的代码,然后,每次遇到yield x;
就返回一个对象{value: x, done: true/false}
,然后“暂停”。
返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。
当执行到done为true时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。
- 直接用for … of循环迭代generator对象
for (var x of fib(10)) {
console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}
2️⃣ 应用
- 保存状态
因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数。
利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。 - 把异步回调代码变成“同步”代码
3️⃣ 练习
生成一个自增的ID,用generator保存状态
function* next_id() {
for(let n=1;;n++){
yield n;
}
}
十一、立即调用函数表达(IIFE)
函数表达式末尾的两个括号()会让它被立即执行或调用。
常用于将相关功能分组到单个对象或者是 module 中:
module 模式的优点是: 所有的运动相关的行为都可以打包成一个对象,然后由代码的其他部分使用。
let motionModule = (function () {
return {
glideMixin: function(obj) {
obj.glide = function() {
console.log("Gliding on the water");
};
},
flyMixin: function(obj) {
obj.fly = function() {
console.log("Flying, wooosh!");
};
}
}
})();
//use
motionModule.glideMixin(duck);
duck.glide();
十二、函数柯里化
函数柯里化(Currying)意思是把接受多个参数的函数变换成接受单一参数的函数。
即:重构函数让它接收一个参数,然后返回接收下一个参数的函数,依此类推。
构建:
function unCurried(x, y) {
return x + y;
}
function curried(x) {
return function(y) {
return x + y;
}
}
const curried = x => y => x + y
curried(1)(2)
即:
function add(x) {
return function(y){
return function(z){
return x+y+z;
}
}
}
add(10)(20)(30);
柯里化在不能一次为函数提供所有参数情况下很有用。
因为它可以将每个函数的调用保存到一个变量中,该变量将保存返回的函数引用,该引用在下一个参数可用时接受该参数。
使用:
const funcForY = curried(1);
console.log(funcForY(2)); // 3
局部调用( partial application):
一次对一个函数应用几个参数,然后返回另一个应用更多参数的函数。
function impartial(x, y, z) {
return x + y + z;
}
const partialFn = impartial.bind(this, 1, 2);
partialFn(10); // 13