本人学习ES6看的是阮一峰老师的书,写在博客里是因为,只看不动手总结,很多东西理解的不透彻,也容易忽略掉一些知识点,我也只是把我自己理解并且认为重要的总结在一起,如果想要对这一节有准确而深入的理解,请点击http://es6.ruanyifeng.com/#docs/function嗯、阮老师真的很厉害
函数参数的默认值
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
function fn(x="donna"){
console.log("hello:"+x);
}
fn();//hello:donna
注意:参数变量是默认声明的,所以不能用let或const再次声明(可以使用var声明)
function fn(x="donna"){
let x = "leo";//SyntaxError: Identifier 'x' has already been declared
console.log("hello:"+x);
}
fn();
function fn(x="donna"){
var x = "leo";
console.log("hello:"+x);
}
fn();//hello:leo
使用参数默认值时,函数不能有同名参数。
function fn(x="donna",x){//SyntaxError: Duplicate parameter name not allowed in this context
console.log("hello:"+x);
}
fn();
但是,如果不设置默认值,是可以有同名参数的
原因是因为一旦参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,等到初始化结束,这个作用域就会消失,这种语法行为,在不设置参数默认值的时候,是不会出现的。
另外,参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也即是说,参数默认值时惰性求值的。 let x = 100;
let z = 0;
function fn(y = x+1){
z+= 3;
console.log(y);
console.log(z);
}
fn();//101 3
fn();//101 6
与解构赋值默认值结合使用
function fn({a,b="donna"}={}){
console.log(a,b);
}
fn();//undefined "donna"
fn({a:1,b:"leo"});//1 "leo"
嗯、再来看个例子:
function fn1({a=0,b=0}={}){
return console.log([a,b]);
}//设置了对象结构赋值的默认值,同时将函数参数的默认值设为空对象
function fn2({a,b}={a:0,b:0}){
return console.log([a,b]);
}//没有对象结构赋值的默认值,但是将函数参数的默认值设为一个具有具体属性的对象
fn1();//[0,0]
fn2();//[0,0]
fn1({});//[0,0]
fn2({});//[undefined,undefined]
fn1({a:1});//[1,0]
fn2({a:1});//[1,undefined]
fn1({a:1,b:2});//[1,2]
fn2({a:1,b:2});//[1,2]
fn1({z:3});//[0,0]
fn2({z:3});//[undefined,undefined]
参数默认值的位置:通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数,如果非尾部的参数设置为默认值,实际上这个参数是没法省略的。
还有,如果传入undefined,将触发该参数等于默认值,null则没有这个效果
function fn(a="leo",b="donna"){
console.log(a,b);
}
fn(undefined,null);//leo,null
函数的length属性(length属性的含义是:该函数预期传入的参数个数)
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数,也就是说,制定了默认值后,length属性将失真。
因为指定了默认值后,预期传入的参数个数就不包括这个参数了(同理,后面的rest参数也不会计入length属性)
rest参数
ES6引入rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了,rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中
arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call()方法将其转为数组。rest参数就是一个数组,数组特有的方法它都可以使用。
注意:rest参数之后不能再有其他参数,否则会报错,并且函数的length属性不包括rest参数。
严格模式:从ES5开始,函数内部可以设定为严格模式。
function fn(a, b) {
'use strict';
}
ES6做了一些修改,规定只要函数参数使用了默认值,解构赋值,或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
这样规定的原因是,函数内部的严格模式,同时适用于函数体和函数参数,但是,函数执行的时候,先执行函数参数,然后执行函数体。这样就有一个不合理的地方,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数却因该先于函数体执行。
name属性:函数的name属性,返回该函数的函数名
如果将一个匿名函数赋值给一个变量,ES5的name属性,会返回空字符串,而ES6的name属性会返回实际的函数名
如果将一个具名函数赋值给一个变量,ES5和S6的name属性都会返回这具名函数原本的名字
Function构造函数返回的函数实例,name属性的值为anonymous
bind返回的函数,name属性值会加上bound前缀
let fn1 = function fn(){
}
let fn2 = function(){}
let fn3 = function(){}
let fn4 = new Fun();
function Fun(){}
console.log(fn1.name);//fn
console.log(fn2.name);//fn2
console.log(fn3.bind().name);//bound fn3
console.log(fn4.name);//undefined
console.log((new Function).name);//anonymous
箭头函数:
ES6允许使用箭头函数(=>)
//ES5函数写法
var f = function fn(a){
return a;
}
//ES6函数写法
var f = a => a;
箭头函数的正确使用方式
1、如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
2、如果箭头函数的代码块部分多于一条语句,就要使用大括号将他们括起来,并且使用return语句返回
3、如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错(因为大括号被解释为代码块)
4、如果箭头函数只有一行语句,且不需要返回值,可以使用下面的方法(前面加void),就不用写大括号了。
document.onclick = ()=>void alert(0);
document.onclick = (a,b)=>{
alert(Number(null));//0
alert(Number(b));//undefined
};
使用箭头函数需要注意的点:
1、函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
2、不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
3、不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以使用rest参数代替
4、不可以使用yield命令,因此箭头函数不能用作Generator函数
特别注意:this对象的指向是可变的,但是在箭头函数中,它是固定的。
document.onclick =function(){
setTimeout(()=>{
alert(this);
},100);
}//[object HTMLDocument]
var id = 1;
var obj = {
id:3
}
var obj2 = {
id:4
}
function foo(){
return ()=>{
console.log(this.id);
}
}
var fn = foo();
foo()();//1(此时箭头函数定义时所在的对象是window)
foo.call(obj)();//3(此时箭头函数定义时所在的对象是obj)
fn.call(obj2);//1(此时箭头函数定义时所在的对象是window,注意箭头函数没有自己的this)
研究了好久才搞懂,嗯、注意,箭头函数的this指向是定义时所在的对象(切记是对象,本人刚开始没注意到,所以就总是感觉阮老师搞错了,最后才发现是自己没看仔细)
箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this即是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
除了this,arguments、super、new.target三个变量在箭头函数中也是不存在的,指向外层函数的对应变量
箭头函数可以嵌套箭头函数
双冒号运算符:(::)“函数绑定运算符”
函数绑定运算符是并排的两个冒号,双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
let log = ::console.log;
// 等同于
var log = console.log.bind(console);
如果双冒号运算符的运算结果,还是一个对象,就可以采用链式写法
尾调用优化:
尾调用:是函数式编程的一个重要概念,指某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
一下三种情况都不属于尾调用
// 情况一
function f(x){
let y = g(x);//调用g函数之后还有赋值操作
return y;
}
// 情况二
function f(x){
return g(x) + 1;//调用g函数之后还有操作
}
// 情况三
function f(x){
g(x);
}//相当于在g(x)后面还有一个return undefined
尾调用不一定出现在函数的尾部,只要是最后一步即可