1、函数参数的默认值
function log( x, y = 'hello' ){
console.log( x, y );
};
log( 'hi' ); // hi hellp
log( 'hi', 'world' ); // hi world
log( 'hi', '' ); // hi
2、函数参数变量是默认声明的,不能用 const 或 let 再次声明
function foo( x = 5 ){
const x = 1; // error
let x = 2; // error
}
foo();
3、函数参数使用默认值时,函数不能有同名参数
function foo( x, x = 1, y ){
// ...
}
foo(); // SyntaxError: Duplicate parameter name not allowed in this context
4、参数默认值不是传值的,而是每次都重新计算默认值表达式的值; 就是说,参数默认值时惰性求值的
let x = 10;
function foo( p = x + 1 ){
console.log( p );
}
foo(); // 11
x = 99;
foo(); // 100
参数p的默认值是 x + 1。这时,每次调用函数 foo 都会重新计算 x + 1, 而不是默认 p = 11。
1、解构赋值默认值与函数参数默认值结合使用
例1
function foo( { x, y = 5 } ){
console.log( x, y );
};
foo({}); // undefined 5
foo({x:1}); // 1 5
foo({x:1,y:2}); // 1 2
foo(); // TypeError: Cannot read propety 'x' of undefined
例2
function fetch( url, { body = '', method = 'GET', headers = {} } ){
console.log( method );
};
fetch( 'http://example.com', {} ); // GET
fetch( 'http://example.com'); // 报错
例3
fetch( 'http://example.com') 不能省略第二个参数,如果结合函数参数的默认值就可以省略
function fetch( url, { method = 'GET'} = {} ){
console.log( method );
};
fetch( 'http://example.com' ); // GET
2、解构赋值默认值与函数参数默认值的两种写法区别
例1
function foo( { x = 0, y = 0 } = {} ){
return [ x, y ];
};
函数参数的默认值是空对象,但设置了解构赋值的默认值
例2
function bar( { x, y } = { x:0, y:0 }){
return [ x, y ];
};
/函数参数的默认值是一个有具体属性的函数,但是没有设置对象解构赋值的默认值
1、参数默认值的位置
定义默认值的参数应该是函数的尾参数,这样比较容易看出省略了那些参数;
如果非尾部的参数设置默认值,这个参数无法省略。
例1
function foo( x = 1, y ){
return [ x, y ];
};
foo(); // [ 1, undefined ]
foo(2); // [ 2, undefined ]
foo( ,1); // 报错
foo( undefined, 1 ); // [ 1, 1 ]
例2
function bar( x, y = 5, z ){
return [ x, y, z ];
};
bar(); // [undefined,5,undefined]
bar(1); // [1,5,undefined]
bar(1, ,2); // 报错
bar(1,,undefined,2); // [1,5,2]
2、如果传入的值是 undefined, 将触发该参数等于默认值, null 没有这个效果
function foo( x = 5, y = 6 ){
console.log( x, y );
};
foo( undefined, null ); // 5 null
1、函数的 length 属性
指定了默认值以后,函数的 length 属性将返回没有指定默认值的参数个数。也就是说,
指定了默认值以后, length 属性将失真。
例
( function (a){}).length // 1
( function (a = 5){}).length // 0
( function (a, b, c = 5){}).length // 2
2、如果设置了默认值的参数不是尾参数,那么 length 属性也不再计入后面的参数
( function ( a = 0, b, c ){}).length // 0
( function ( a, b = 0, c ){}).length // 1
1、函数参数的作用域
一旦设置了参数的默认值,函数进行生命初始化时,参数会形成一个单独的作用域(context)。
等到初始化结束,这个作用域就会消失。这种语法行为在不设置参数默认值时是不会出现的。
例1
let x = 1;
function foo( x, y = x ){ // foo(2) => 单独的作用域
console.log( y );
};
foo(2); // 2
参数 y 的默认值等于变量 x 。调用函数 foo 时,参数会形成一个单独的作用域。
在这个作用域里面,默认值变量指向第一个参数 x ,而不是全局变量 x, 所以输出2
例2
let x = 1;
function bar( y = x ){ // { let y = x; }
let x = 2;
console.log( y );
};
bar(); // 1
函数 bar 调用时,参数 y = x 形成一个单独的作用域。在这个作用域里面,变量 x 本身没有定义,
这时候会通过 RHS 查询到顶层作用域变量 x 的值。
例3
function foo( y = x ){
let x = 2;
console.log( y );
};
foo(); // ReferenceError: x is not fined
如果全局变量 x 不存在时,就会报错。RHS 查询不到变量 x 的值
例4
function bar( x = x ){
console.log( x );
};
bar(); // ReferenceError: x is not fined
参数 x = x 形成一个单独作用域, 实际上执行的是 let x = x; 由于暂时性死区,
执行这行代码会产生 "定义" 错误。这时候走不到 RHS 查询,就会报错。
例5
var x = 1;
function foo( x, y = function(){ x = 2 } ){
var x = 3;
y();
console.log( x );
};
foo(); // 3
( x, y = function(){ x = 2 } ) 函数参数会形成一个单独的作用域。参数 y 默认值是
匿名函数。这个匿名函数内部的变量 x 指向的值这个作用域内的变量 x。
而foo函数内部 { var x = 3; y(); console.log(x) } 又是另外一个作用域(它与函数参
数的作用域是各自独立并且互相不能访问), y() 函数中的变量 x 指向的是函数参数的
作用域,所以, console.log( x ); 输出 3;
例6
var x = 1;
function foo( x, y = function(){ x = 2 } ){
x = 3;
y();
console.log( x );
};
foo(); // 2
将 var 去除以后, 变量 x 指向的就是一个参数 x ,与匿名函数内部的 x 是一直的,所以
输出 2。
1、rest参数
ES6引入了rest参数(形式为"...变量名"),用于获取函数的多余参数,这样就不需要 arguments
对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入其中。
例1
function add( ...values ){
let sum = 0;
for( let val of values ){
sum += val;
}
return sum;
};
add( 2, 5, 10 ); // 17
例2
let numbers = [ 2,5,1,4,3];
const sortNumbers = ( ...numbers ) => numers.sort();
2、rest 参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量
var arr = [];
function push( array, ...items ){
items.forEach( function( item ){
array.push( item );
} )
return array;
};
push( arr, 1, 2, 3 );
3、rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错
function bar( a, ...b, c ){ // 报错
// ...
};
4、函数的 length 属性不包括 rest 参数
( function(a){}).length // 1
( function(...a){}).length // 0
( function(a, ...b){}).length // 1
1、箭头函数
let foo = v => v;
// 等价于一下代码
var foo = function( v ){
return v;
};
2、如果箭头函数不需要参数或需要多个参数,就使用圆括号代表参数部分
let bar = () => 5;
//等同于
let bar = function() {
return 5;
};
let sum = ( num1, num2 ) => num1 + num2;
//等同于
let sum = function( num1, num2 ){
return num1+num2;
};
3、如果箭头函数的代码块多余一条语句,就要用大括号将其括起来,并使用 return 语句返回
let sum = ( num1, num2 ) => { return num1+num2 };
console.log( sum(1,1) ); // 2
4、如果箭头函数直接返回一个对象,必须在对象外面加上圆括号
var obj = id => ( { id: id, name: 'Temp' } );
5、箭头函数可以与变量解构结合使用
let obj = { first: 'hello', last: 'world' };
const full = ( { first, last } ) => first + '' + last;
console.log( full( obj ) );
6、箭头函数–注意事项
1)、函数体内的 this 对象就是定义时所在的对象,而不是使用时所在的对象
2)、不可以当做构造函数。也就是说,不可以使用 new 命令,否则会抛出错误
3)、不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数
4)、不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替
this 对象的指向是可变的,但在箭头函数中它是固定的,this 指向的固定化并不是因为函数内部
有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this 就是外层代码
块的 this。正是因为没有 this, 所以不能用作构造函数。由于箭头函数没有自己的 this, 当然也
就不能用 call()、apply()、bind()这些方法来改变 this 的指向。
let id = 20;
function foo(){
setTimeout( () => {
console.log( 'id:', this.id );
}, 100 );
};
foo.call( { id: 42 } ); // 21
箭头函数的 this 总是指向函数定义生效时所在的对象(本例是{id:42})
箭头函数中的 this 绑定定义时所在的作用域,而不是指向运行时所在的作用域
function Timer(){
this.s1 = 0;
this.s2 = 0;
setInterval( () => this.s1++, 1000 );
setInterval( function(){ this.s2++; }, 1000 );
};
var timer = new Timer();
setTimeout( () => console.log( 's1:', timer.s1 ), 3000 ); // 3
setTimeout( () => console.log( 's2:', timer.s2 ), 3000 ); // 2
Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的 this 绑定定义
时所在的作用域(即Timer函数),后者的 this 指向运行时所在的作用域( 即全局对象 )
箭头函数可以让 this 指向固定化,有利于封装回调函数
var handler = {
id: '12',
init: function(){
document.addEventListener('click', event => this.doSomething( event.type ),false);
},
doSomething:function( type ){
console.log( 'Handling ' + type + ' for ' + this.id );
}
};
init 方法中使用了箭头函数,导致箭头函数里面的 this 总是指向 handler 对象。否则,
运行回调函数时,this.doSomething 一行报错, 因此 this 指向 document 对象