function create(x,y){
y = y || 'andy';
console.log(x,y);
}
create('Hello');//Hello andy
create('Hello','China');//Hello China
create('Hello','');//Hello andy
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function create(x,y='andy'){
console.log(x,y);
}
create('Hello');//Hello andy
create('Hello','China');//Hello China
create('Hello','');//Hello
function createNums(x=0,y=0){
this.x = x;
this.y = y;
}
var nums = new createNums();
console.log(nums);//{x:0,y:0}
参数变量是默认声明的,所以不能用
let
或
const
再次声明。
function createNums(x=0){
let x = 5;
const x= 8;
}
上面代码中,参数变量
x
是默认声明的,在函数体中,不能用
let
或
const
再次声明,否则会报错。
let x = 9;
function foo(p = x+1){
console.log(p);
}
foo();//10
x = 10;
foo();//11
//如果参数默认值是变量,那么参数就不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的
function createNums({x,y=2}){
console.log(x,y);
}
createNums({});// undefined, 2
createNums({x:1});//1 2
createNums({x:1,y:5});//1 5
//createNums();//Cannot read property 'x' of undefined
//上面代码使用了对象的解构赋值默认值,而没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值而生成。
//如果函数foo调用时参数不是对象,变量x和y就不会生成,从而报错。如果参数对象没有y属性,y的默认值2才会生效。
//通常情况下,定义了默认值的参数,应该是函数的尾参数。
//因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
function foo(x=1,y){
console.log([x,y]);
}
foo();//[1, undefined]
foo(1);//[1, undefined]
foo(3);//[3, undefined]
//foo(,2);//报错
foo(undefined,2);//[1, 2]
//如果传入undefined,将触发该参数等于默认值,null则没有这个效果。
foo(null,5);//[null, 5]
指定了默认值以后,函数的length
属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length
属性将失真。
console.log((function(a){}).length);//1
console.log((function(x=1){}).length);//0
console.log((function(x,y=1){}).length);//1
//这是因为length属性的含义是,该函数预期传入的参数个数。
//某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也不会计入length属性。
console.log((function(...args){}).length);//0
//如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
console.log((function(x=0,b,c){}).length);//0
console.log((function(x,y=1,z,g){}).length);//1
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。
等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1;
function foo(x,y=x){
console.log(x,y);
}
foo(3);//3 3
//上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。
//在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是3。
let y = 1;
function foo1(z=y){
let y = 2;
console.log(z);
}
foo1();//1
//上面代码中,函数f调用时,参数z = y形成一个单独的作用域。
//这个作用域里面,变量y本身没有定义,所以指向外层的全局变量y。函数调用时,函数体内部的局部变量y影响不到默认值变量y。
//如果此时,全局变量x不存在,就会报错
function foo2(y=x){
let x = 2;
console.log(y);
}
foo2();
//下面这种写法也会报错
var x =2;
function f(x=x){
//...to do
}
f();
//上面代码中,参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错”x 未定义“。
ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments
对象了。
rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values){
let sum = 0;
for(var val of values){
sum += val;
}
return sum;
}
console.log(add(1,2,3));//6
//上面代码的add函数是一个求和函数,利用 rest 参数,可以向该函数传入任意数目的参数。
//函数的length属性,不包括 rest 参数。
console.log((function(a){}).length);//1
console.log((function(a,...b){}).length);//1
console.log((function(...b){}).length);//0
//console.log((function(a,...b,c){}).length);//注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
扩展运算符(spread)是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列
console.log(...[1,2,3]);//1 2 3
console.log(1,...[2,3,4],5);//1 2 3 4 5
console.log([...document.getElementsByTagName('b')]);//[b, b, b, b]
//该运算符主要用于函数调用
function push(array,...items){
array.push(...items);
}
function add(x,y){
return x+y;
}
var nums = [1,2];
console.log(add(...nums));//3
//上面代码中,array.push(...items)和add(...numbers)这两行,都是函数的调用,
//它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列
替换数组的apply方法
//由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了
//es5
function f(x,y,z){
//
console.log();
}
var args = [1,2,3];
f.apply(null,args);
//es6
function f1(x,y,z){
//...
}
var arg = [1,2,3];
f(...arg);
//下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法
// es5
console.log(Math.max.apply(null,[1,2,3]));//3
//es6
console.log(Math.max(...[1,2,3,4]));//4
//等同于
console.log(Math.max(1,2,3,4));//4
//另一个例子是通过push函数,将一个数组添加到另一个数组的尾部
//es5
var arr1 = [1,2,3];
var arr2 = [4,5,6];
Array.prototype.push.apply(arr1,arr2);
console.log(arr1);//[1, 2, 3, 4, 5, 6]
//es6
var arr3 = [7,8,9];
arr2.push(...arr3);
console.log(arr2);//[4, 5, 6, 7, 8, 9]
//上面代码的ES5写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。
//有了扩展运算符,就可以直接将数组传入push方法
合并数组
var arr1 = [1,2];
var arr2 = [3,4];
var arr3 = [5,6];
//es5
var arr4 = arr1.concat(arr2,arr3);
console.log(arr4);//[1, 2, 3, 4, 5, 6]
//es6
console.log([...arr1,...arr2,...arr3]);//[1, 2, 3, 4, 5, 6]
与解构赋值相结合
扩展运算符可以与解构赋值结合起来,用于生成数组。
const [n1,...rest] = [1,2,3];
console.log(n1);//1
console.log(rest);//[2,3]
const [n2,...values] = [];
console.log(n2);//undefined
console.log(values);//[]
const [n3,...v] = ['foo'];
console.log(n3);//foo
console.log(v);//[]
//果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [...n4,n5] = [1,2,3,4,5];//报错
const [n6,...n7,n8] = [1,2,3];//报错
字符串
扩展运算符还可以将字符串转为真正的数组
console.log([...'andy']);//["a", "n", "d", "y"]
//上面的写法,有一个重要的好处,那就是能够正确识别32位的Unicode字符
console.log('x\uD83D\uDE80y'.length);//4
console.log([...'x\uD83D\uDE80y'].length);//3
实现了Iterator接口的对象
任何Iterator接口的对象,都可以用扩展运算符转为真正的数组
var nodelist = document.getElementsByTagName('b');
var array = [...nodelist];
console.log(array);//[b, b, b, b]
//上面代码中,getElementsByTagName方法返回的是一个nodeList对象。
//它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了Iterator接口
//对于那些没有部署Iterator接口的类似数组的对象,扩展运算符就无法将其转为真正的数组
let arraylike = {
'0':'a',
'1':'b',
length:2
}
let arr = [...arraylike];
//上面代码中,arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。
//这时,可以改为使用Array.from方法将arrayLike转为真正的数组
扩展运算符内部调用的是数据结构的Iterator接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构
let map = new Map([
[1,'a'],
[2,'b'],
[3,'c']
]);
let arr = [...map.keys()];
console.log(arr);//[1, 2, 3]
从ES5开始,函数内部可以设定为严格模式
function doSomething(a, b) {
'use strict';
// code
}
《ECMAScript 2016标准》做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,
那么函数内部就不能显式设定为严格模式,否则会报错
这样规定的原因是,函数内部的严格模式,同时适用于函数体代码和函数参数代码。
但是,函数执行的时候,先执行函数参数代码,然后再执行函数体代码。
这样就有一个不合理的地方,只有从函数体代码之中,才能知道参数代码是否应该以严格模式执行,
但是参数代码却应该先于函数体代码执行
// 报错
function doSomething(a, b = a) {
'use strict';
// code
}
两种方法可以规避这种限制。第一种是设定全局性的严格模式,这是合法的。
'use strict';
function doSomething(a, b = a) {
// code
}
第二种是把函数包在一个无参数的立即执行函数里面。
const doSomething = (function () {
'use strict';
return function(value = 42) {
return value;
};
}());
function foo(){
}
console.log(foo.name);//foo
//需要注意的是,ES6 对这个属性的行为做出了一些修改。
//如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
var f = function(){
}
//es5
console.log(f.name);//''
//es6
console.log(f.name);//f
//如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字
const n1 = function bar(){
}
console.log(n1.name);//bar
//Function构造函数返回的函数实例,name属性的值为anonymous。
console.log((new Function).name);//anonymous
//bind返回的函数,name属性值会加上bound前缀。
function a1(){}
console.log(a1.bind({}).name);//bound a1
ES6允许使用“箭头”(=>
)定义函数
var f = v =>v;
//等同于
var f = function(v){
return v;
};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分
var f1 = ()=>5;
//等同于
var f1 = function(){
return 5;
};
var sum = (n1,n2) => n1+n2;
//等同于
var sum = function(n1,n2){
return n1+n2;
}
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回
var sum = (n1,n2) => {return n1+n2};
//由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
var getid = id => ({id:id,name:'andy'});
console.log(getid(1));//{id: 1, name: "andy"}
//箭头函数可以与变量解构结合使用
const full = ({n1,n2}) => n1 +' '+ n2;
//等同于
function full(person){
return person.n1 +' '+person.n2;
}
//箭头函数更加简洁
const square = n => n*n;
console.log(square(5));//25
箭头函数的一个用处是简化回调函数
//正常函数写法
var n = [1,2,3,4].map(function(x){
return x*x;
});
console.log(n);//[1, 4, 9, 16]
//箭头函数写法
var n1 = [1,2,3].map(x => x*x);
console.log(n1);//[1, 4, 9]
//下面是rest参数与箭头函数结合的例子
const num = (...nums) => nums;
console.log(num(1,2,3,4,5));//[1, 2, 3, 4, 5]
const arr = (n1,...n) => [n1,n];
console.log(arr(1,2,3,4));//[1,[2,3,4]]
箭头函数有几个使用注意点。
(1)函数体内的this
对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new
命令,否则会抛出一个错误。
(3)不可以使用arguments
对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield
命令,因此箭头函数不能用作Generator函数。
上面四点中,第一点尤其值得注意。this
对象的指向是可变的,但是在箭头函数中,它是固定的。
//箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域
function f(){
setTimeout(() => {
console.log('id:',this.id);
},100)
}
var id = 2;
f.call({id:3});
//箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面
var handler = {
id : '1',
init:function(){
document.addEventListener('click',
event => this.doSomething(event.type),false);
},
doSomething:function(type){
console.log('Handing'+type+'for'+this.id);
}
}
//上面代码的init方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。
//另外,由于箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向