函数的扩展
1.函数参数的默认值;
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
1.1参数变量是默认声明的,所以不能用let或const再次声明。
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
1.2 参数名不能重复
1.3 默认参数的位置
一般为函数的尾参数;
如果非尾部的参数设置默认值,实际上这个参数是没法省略的。省略则会报错
2.函数的 length 属性;
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
3.作用域(暂时不能完全理解)
3.1 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
4. 应用
4.1 throwIfMissing 函数
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo() // Error: Missing parameter
4.2 rest 参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
函数的 length 属性,不包括 rest
5. 严格模式(不太理解)
从 ES5 开始,函数内部可以设定为严格模式。从 ES5 开始,函数内部可以设定为严格模式。
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
6. name 属性
函数的 name 属性,返回函数的函数名
6.1 对于匿名函数
var f = function () {};
// ES5 f.name // ""
// ES6 f.name // "f"
6.2 Function构造函数返回的函数实例,
name属性的值为anonymous
(new Function).name // "anonymous"
bind返回的函数,name属性值会加上bound前缀。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
7.箭头函数
7.1 基本用法
ES6 允许使用箭头( => )定义函数
var f = v => v;
上面函数等价于
var f = function(v) { return v; };
如果箭头函数不需要参数或者需要多个参数,就用圆括号()代表参数部分
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
//等同于
var sum = function(num1,num2){
return num1+num2;
}
如果代码块部分多于一条语句,就要用大括号将它们包裹起来,用 return 语句返回;
var sum = (sum1+sum2)=>{return num1+ num2 ;}
由于大括号被解释为一个代码块,所以如果箭头函数返回一个对象,必须用括号将对象括起来,否则会报错;
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
如果箭头函数只有一条语句,并且没有返回值,则可以使用以下语句
let fn=() => void doesNotReturn();
箭头函数可以结合变量解构使用
const foo = ({first,last})=>first+' '+last;
//等同于
function foo(person){
return person.first + ' ' person.last;
}
箭头函数使表达更加简洁
const a = n => n % 2 == 0 ;
const b = n => n * n ;
箭头函数的一个主要作用是简化回调函数
例1一个参数
//正常回调函数
[1,2,3].map( function(x ){
return x * })
//箭头函数
[1,2,3].map(x => x * x);
例2 多个参数
// 正常函数
const a = value.sort(function(x,y){
return x+ y;
});
// 箭头函数
const b = value.sort((x,y)=> x+y);
例3 rest 参数
const numbers = (...nums) => nums;
number(1,2,3,4,5);
// [1,2,3,4,5]
const a = (x,...y)=>[x,y];
a(1,2,3,4,5);
// [1,[2,3,4,5]]
7.2 注意事项
(1) 函数内的 this 对象,是定义时所在的对象,而不是使用时所在的对象;
(2)不可以当作构造函数,不能使用 new 对象,否则会报错;
(3)不可以使用 arguments 对象,若果要使用,则使用 rest参数 代替;
(4)不可以使用 yield 命令,所以箭头函数不可用作 Generator 函数;
this 对象的指向是可变的,但是在箭头函数中,它是固定的;
7.3 嵌套的箭头函数(难点,理解不了)
箭头函数中还可以嵌套箭头函数
例1 ES5 的嵌套函数
function insert(value) {
return {into: function (array) {
return {after: function (afterValue) { array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}
};
}
};
}
insert(2).into([1, 3]).after(1); //[1, 2, 3]
//等同于
let insert = (value) => ({into: (array) => ({after: (afterValue) => { array.splice(array.indexOf(afterValue) + 1, 0, value);
return array; }}
)}
);
insert(2).into([1, 3]).after(1); //[1, 2, 3]
下面是一个部署管道机制(pipeline)的例子,即前一个函数的输出是后一个函数的输入。(暂时不懂)
const pipeline = (...funcs) => val => funcs.reduce((a, b) => b(a), val);
const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);
addThenMult(5) // 12
8.双冒号运算符
箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用
函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数;该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
foo::bar;
// 等同于 bar.bind(foo);
foo::bar(...arguments);
// 等同于 bar.apply(foo, arguments);
const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) { return obj::hasOwnProperty(key); }
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
var method = obj::obj.foo;
// 等同于 var method = ::obj.foo;
let log = ::console.log;
// 等同于 var log = console.log.bind(console);
双冒号运算符的运算结果,还是一个函数,因此可以采用链式写法
// 例一
import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
// 例二
let { find, html } = jake;
document.querySelectorAll("div.myClass") ::find("p") ::html("hahaha");
9.尾调用优化
什么是尾调用?
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。