1.函数的默认值
- 以前给函数的参数设置默认值
function fn(x,y){
y=y||'world'
}
fn('Hello') // Hello World
fn('Hello', 'China') // Hello China
fn('Hello', '') // Hello World
- Es6的写法
function fn(x=0,y=10){
return x+y
}
fn() // 0
fn(10) // 20
fn(10,20) // 30
- 不可以使用let或者const重新声明参数
function fn(x){
let x=10;
}
//error
上面代码中,参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
- 使用函数参数默认值不可以有相同的参数
function fn(x,x,y){
}
上边的代码不会报错,因为并没有设置默认值,但是第二个的x的值会覆盖第一个x
function fn(x=5,x,y=10){
//error
}
-
函数参数默认值与解构赋值结合使用
- 参数默认值可以与解构赋值的默认值,结合起来使用。
function fn({a,y:5}){ console.log(a,y) } fn() //error fn({}) // undefined,5 fn({x:1}) // 1,5 fn({x:1,y:5}) // 1,6
为啦防止上边的第一种错误的情况,可以这么写:
function fn({x=1,y}={}){ console.log(x,y) } fn() //x:1,y:undefined
仔细比较这个代码和上边的代码有什么不同,这里的fn执行的时候没有传入参数,而上边的也没有传入参数,为什么这里的能正常执行呢,是应为,上边的代码没有在fn()执行的时候添加和参数默认值解构的对象,而这里的是在参数里边已经做啦解构赋值,只不过是一个空对象而已.
-
函数的length
- 函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真
function fn(x){ } console.log(fn.length) // 1 function fn1(x=1){ } console.log(fn.length) //0
- 函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真
-
作用域
- 一旦参数设置啦默认值,函数在声明初始化时会形成一个单独的域,等到初始化结束,域消失,参数没有设置默认值时,是不会形成这个域的
let x= 10;
function fn (x,y=x){
console.log(y)
}
fn(1) // 1
上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。
let x = 1;
function fn(y = x) {
let x = 2;
console.log(y);
}
fn() // 1
在上边的代码中,虽然函数里有变量x,但是这个作用域是在函数声明初始化的时候形成的,所以里边里边的x指向全局的x.
function f(y = x) {
let x = 2;
console.log(y);
}
f() // ReferenceError: x is not defined
报错是因为在全局中没有找到全局变量x
var x = 1;
function foo(x = x) {
// ...
}
foo() // ReferenceError: x is not defined
上面代码中,参数x = x形成一个单独作用域。实际执行的是let x = x,由于暂时性死区的原因,这行代码会报错”x 未定义“。
2.rest参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for(var i = 0;i < values.length;i++){
num+=values[i]
}
return sum;
}
add(2, 5, 3) // 10
- rest必须是最后一个参数,否则会报错
function fn(x,...values,y){
}
//error
- 函数的length属性,不包括 rest 参数。
function fn(x,y=2,...values){
}
console.log(fn.length)
3.严格模式
从 ES5 开始,函数内部可以设定为严格模式 , ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
下边是报错的一些例子:
function a(a, b = a) {
'use strict';
// code
}
// 使用啦参数设置默认值报错
const a= function ({a, b}) {
'use strict';
// code
};
// 使用啦解构赋值报错
const a= (...a) => {
'use strict';
// code
};
// 报错
function doSomething(value = 070) {
'use strict';
return value;
}
上面代码中,参数value的默认值是八进制数070,但是严格模式下不能用前缀0表示八进制,所以应该报错。但是实际上,JavaScript 引擎会先成功执行value = 070,然后进入函数体内部,发现需要用严格模式执行,这时才会报错。
解决办法
1.设置全局的严格模式
'use strict';
function a(a, b = a) {
// code
}
2.套在一个无参数的自执行函数里
let a =(function(){
'use strict';
retuen function (x=2,y){
console.log(x,y)
}
}())
4.name 属性
- 函数的name属性,返回该函数的函数名。
function foo() {}
foo.name // "foo"
- ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"
- 如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
var a = function fn(){
};
//Es5
console.log(a.name) // fn
//Es6
console.log(a.name) // fn
- Function构造函数返回的函数实例,name属性的值为anonymous。
(new Function).name // "anonymous"
5.Es6允许使用“箭头”(=>)定义函数。
- 基本用法
var f = v => 1;
f() //1
// v 函数的形参, 1 函数的实参和返回值
- 如果有多个参数,使用一个圆括号代表参数部分
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
- 如果要返回一个对象必须在对象外边加圆括号
//报错
let obj= id => { id: id, name: "Temp" };
//不报错
let obj= id => ({ id: id, name: "Temp" });
- 与解构赋值的结合使用
var f = ({name,age}) => name+' '+age
f({name:'suo',age:28}) //"suo 20"
箭头函数使用注意事项
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
上面代码中,setTimeout的参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。
箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。
6.双冒号运算符
- 函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即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);
7尾调用
-
什么是尾调用
尾调用是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
调用之后再操作的都不算尾调用
尾调用不一定出现在函数尾部,只要是最后一步操作就好
// 情况一 function f(x){ let y = g(x); return y; } // 情况二 function f(x){ return g(x) + 1; } // 情况三 function f(x){ g(x); }
情况一 : 调用之后有操作;
情况二 : 调用之后有操作;
情况三 : function(){g(x) return undefined;};