函数参数可以拥有默认值。调用函数时,如果没有进行相应的实参传递,参数就会使用默认值。怎么给参数提供默认值呢?很简单,声明函数时候,给形参赋一个值就可以了,这个值就是参数的默认值。
// num2拥有默认参数值5,如果没有给num2形参传值,它的取值将会是5 function sum (num1, num2 = 5) { return num1 + num2; } console.log(sum(1)) // 6 调用sum函数时, 只传递了一个参数1,所以函数中num1 =1, num2就会使用默认参数值5, 1+5 =6; console.log(sum(1,2)) // 3 函数调用时,我们传递了两个参数,所以默认参数值不起作用, 函数使用我们传递过去的参数 1+2 =3
当参数拥有默认值以后,它影响了argumets 对象。我们都知道,每一个函数内部都有一个arguments 对象,保存函数调用时传递过去的参数,第一个参数对应的就是arguments[0], 第二个参数对应的就是arguments[1]. 像上面的sum 函数, num1 == argument[0]; 但有了默认参数值,这种对于关系打破了. sum(1) 调用sum 函数的时候,我们只传递了一个值1,也就意味着arguments[1] 的值是undefined, 但是它对应的num2 形参,num2 参数由于默认值的存在,这里取5. arguments[1] 就不等于num2 了。还有一点就是,arguments 只是保存了传递过去的值,如果在函数内部 参数的值有更改,那么arguments 也不会实时反应这种变化,还是上面的sum(1) 调用,arguments[0] 永远等于1。 初始的时候,num1 == arguments[0]; 但如果在函数体中 num1 重新赋值为2, arguments[0] 就不等于num1 了。
function sum (num1, num2 = 5) { console.log(arguments.length); // 1, 只传递了一个参数 console.log(num1 === arguments[0]); // true 初始时相等 console.log(num2 === arguments[1]); // false 只传一个参数,arguments[1] 是undefined, num2 取默认值5 num1 = 2; console.log(num1 === arguments[0]); // false arguments只保存调用时的初值。 return num1 + num2; } console.log(sum(1));
记住一点就可以了, arguments 对象只保存调用函数时传递过去的参数的初始值。不太理解也没有关系,arguments 对象几乎用不到了,因为ES6 提供了更好的参数保存方式(剩余参数rest),下面会介绍。继续默认参数的学习。默认参数值,不仅可以像上面一样使用原始值,还可以使用js表达式,比如调用一个函数。
function getValue() { return 5; } //参数second的默认值,是函数表达式 function add(first, second = getValue()) { return first + second; } console.log(add(1, 1)); // 2 console.log(add(1)); // 6
默认参数为函数,这个函数的调用是惰性的,如add(1,1)传递了两个参数,函数就用不到的,不会调用。只有传递一个参数的时候,如add(1), 这个默认函数才会调用。其实getValue函数还可以接受参数, 最主要的是调用getValue函数的时候可以把first 传递给它。
function getValue(value) { return value + 5; } // 函数参数是第一个参数的值。 function add(first, second = getValue(first)) { return first + second; } console.log(add(1, 1)); // 2 console.log(add(1)); // 7
当参数像上面这样取默认值的时候,一定要注意参数的顺序,如果second 和first 顺序调换了,就出错了。因为参数也形成了自己的作用域。add 函数的参数的声明就像下面一样
let first;
let second = getValue(first);·
如果写反了,就变成了
let second = getValue(first);
let first;
first 变量还没有声明,就使用了,造成了暂存死区。
剩余参数 (rest)
当我们调用函数的时候,我们可以传递任意数量的实参给函数,如果函数形参的数量少于实参的数量,我们就只能通过函数内部的arguments 获取多余的实参。ES6 提供了一个更简单的方法来获取这些多余的参数,就是剩余参数。我们在声明函数的时候,在一个参数的前面加上..., 这个参数就变成了一个数组,它会把多余的参数收集到它里面,变成它的元素。
let sum = (obj, ...rest) => { console.log(rest) // [2,3,4,5] } sum({a:1},2,3,4,5)
上面代码中的rest就是一个剩余参数,它把2,3,4,5 收集起来,变成了它的元素,它本身是一个数组。
注意:一个函数中只能有一个剩余参数,且它必须放到所有参数最后,这很好理解,因为,它把所有参数都收集到一起了,一个就足够了,如果它后面还有参数,这些参数也获取不到数据了,所以也就没有必要设置参数了。
扩展操作符(...)
扩展操作符,把一个可迭代对象(如数组)扩展给一个一个的单体。
let array = [1,2,3,4,5]; console.log(...array) // 1 2 3 4 5
js 中的函数有两个内部的方法, [[Call]] 和[[Construct]] , 当我们调用函数的时候,没有使用new, 那[[Call]] 方法就会被调用,执行函数体。当调用函数的时候前面加了一个new, 那[[Construct]] 方法就会被调用,生成一个对象,调用函数,给对象赋值,并返回对象。当然并不是每一个函数都有[[Construct]] 方法,比如箭头函数就没有,所以箭头函数就不能使用new 进行调用。那怎么决定函数是用那种方法呢?
ES6 增加了一个new.target, 如果一个函数通过new 调用,它内部会获取一个new.target 的元属性,它指向的就是我们的构造函数。 当然,如果这个函错误地通过一般函数调用,new.target 就是undefined. 这样我们就可以轻松地判断一个函数是不是通过new进行调用,从而避免了构造函数用普通方式进行调用产生的错误。
function Person(name) { if (typeof new.target !== "undefined") { this.name = name; } else { console.error("You must use new with Person.") } } var person = new Person("Nicholas"); var notAPerson = Person.call(person, "Michael"); // You must use new with Person.
箭头函数
语法: 参数列表 => 函数体;一个简单的箭头函数如下
(a,b) => a+b;
可以看到它只是一个匿名函数表达式,只有把它赋值给一个变量引用,才能对它进行调用,
let sum = (a,b) => a+b; sum(1, 2)
箭头函数的参数列表,可以有一个参数,可以有多个参数,也可以没有参数。多个参数呢, 就像上面一样,把所有参数一一列出来,并用括号括起来,没有参数简单,直接一个() 就可以了,有一个参数呢,也可以使用()把这个参数包括起来,但通常会省略括号
let hello = () => console.log('hello'); // 箭头函数没有参数,直接用一个括号表示 let add10 = num => num +10; // 箭头函数只有一个参数num,通常直接写这个参数,不用括号括起来。
箭头函数右侧的函数体,可以只有一句表达式,如上面的a +b, 这时,除了可以直接写这一句表达式外,它默认是会返回表达式的值,这也就是我们没有写return a +b 的原因,这里要注意一点,如果返回一个对象,这个对象要用() 括起来。
let obj = name =>({name:name}) // 如果不写外面的括号,{} 就会被当做块级作用域
那如果函数体是一段可以执行的语名块呢?那就需要用{}把语名块包起来,如果语句块执行完毕,还要返回值,那就要在语句块的末尾显示调用return
let amount = n => { let sum =0; for(let i=0; i<=n; i++){ sum = sum + i; } return sum; }
可以看到箭头函数大大简化了写法,尤其是回调函数的函数体只有一句表达式的时候,比如,sort 函数
var arr =[3, 7, 9, 5]; arr.sort(function(value1, value2){ return value1 – value2 }) // 箭头函数 arr.sort((value1, value2) => value1 –value2);
箭头函数如果用到高阶函数上,也简化了高阶函数的书写。所谓的高阶函数,他或者接受一个或多个函数作为参数,它或者返回一个函数作为运行结果,只要两者满足其一,就是高阶函数。在高级程序设计书中,根据对象的属性对对象进行排序,就用到了高阶函数。
function compare(property) { return function(obj1, obj2) { var value1 = obj1[property]; var value2 = obj2[property]; if(value1 < value2) { return -1; }else if(value1 > value2) { return 1; }else { return 0 } } }
用箭头函数进行简化, 箭头首先是一个函数表达式,也就是我们要把箭头函数赋值给一个变量,否则无法调用。let compare = 箭头函数; 箭头函数表示方法是()=》表达式,接受多个参数,返回一个值。let compare =(property) => 返回值,但这里返回值又是一个函数, 函数有又可以用箭头函数表示 (obj1, obj2) => {},
let compare = property => (obj1, obj2) = > { var value1 = obj1[property]; var value2 = obj2[property]; if(value1 < value2) { return -1; }else if(value1 > value2) { return 1; }else { return 0 } }
但当我看到这个箭头函数,怎样转化成一般的函数,我们怎么读懂连缀的箭头函数?
箭头函数永远是 ()=》表达式, 所以右侧的连缀箭头可以一个一个分开。从左向右依次分开, property => 返回值。 箭头前面永远是函数调用需要的参数,后面是返回的值,后面有多个箭头,我们可以看作一个整体(返回值)。 let compare = property => 返回值。 那么调用时, compare(name) ======》(obj1,obj2)=> 返回值,他又是函数,接着调用 compare(name)(obj1,obj2),函数执行了。像这种箭头函数,它返回的永远都是函数,所以是闭包,可以获得前面参数的值。
箭头函数除了简化书写外,还有一个更重要的内容,就是this 指向。
箭头函数内部没有自己的this,但是我们可以在它里面使用this,这时this 的指向继承自外围作用域,就是this 会顺着函数的作用域链向上进行查找,直到找到离它最近的一个this,然后使用它。我们都知道this只存在函数中,所以箭头函数内部的this 就指向它的父函数或祖先函数中this,这样我们内部的this值和外部的this 值就统一了,再也不用 let that = this , 用that 去代替this了。
var object = { f1: function(){ console.log(this); var f2 = () => console.log(this); f2(); setTimeout(f2, 1000); } } object.f1(); // f1 函数内部的this 全都指向 object
f2函数体中有this, 但它是箭头函数,所以只能向上找,使用包含它的函数中的this, 找到了f1函数, f2 中的this 和f1 中的this 保持一致。这里我们再改一下,把f1 也改成箭头函数,
var object = { f1: () => { console.log(this); var f2 = () => console.log(this); f2(); setTimeout(f2, 1000); } } object.f1(); // f1 函数内部的this 全都指向 object
这时你会发现this 指向了window, 按照 object.f1() 的调用方式,f1 函数中的this 应该指向object.其实不是,箭头函数的this 是在它定义的时候,就已经确定了,就像使用了bind方法,而不是动态绑定了, 当箭头函数调用的时候,真正要确定的是它在定义的时候,它所能向上寻找到的包含它的最外围的函数中this. 我们再来分析一下,f2 向上找f1, f1 也是箭头函数,它还要向上找,但你发现包含f1的函数没有了,只有全局对象window了,this 指向了window, 箭头函数在调用的时候,它真正确定的是包含f1函数的函数中this 的指向, 如果没有包含函数,就是window全局对象了。
箭头函数始终保持它里面的this和它外围的包含它的函数中this 一致。再看一个例子
var object = { f1: function(){ console.log(this); var f2 = () => console.log(this); f2(); setTimeout(f2, 1000); } } var anoter = object.f1; anoter() // this 指向了window
anoter 函数在全局作用域中调用, f1 函数中的this 指向了window, f2 是箭头函数,和它外围的f1 保持一致,里面的this 指向window.