默认参数
ES5中要为参数指定默认值,只能如下所示:
function makeRequst(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function () { }
}
但是这样有一个问题如果timeout传进来的值为0,则也会赋值变成2000,
所以更加完全的做法是检测参数类型
function makeRequst(url, timeout, callback) {
timeout = (typeof timeout !== ‘undefined‘) ? timeout : 2000;
callback = (typeof callback !== ‘undefined‘) ? callback : function () { }
}
ES6中直接
function makeRequst(url, timeout = 2000, callback = function () { }) {
}
可以为任意参数指定默认值,在已指定默认值的参数后可以继续声明无默认值参数
function makeRequst(url, timeout = 2000, callback) {
}
是否使用函数默认值主要依赖于调用函数是实参是否全等于undefined
ES5非严格模式下,函数命名参数的变化会体现在arguments对象中
function mixArgs(first, second) {
console.log(first === arguments[0])//true
console.log(second === arguments[1])//true
first = ‘c‘
second= ‘d‘
console.log(first === arguments[0])//true
console.log(second === arguments[1])//true
}
mixArgs(‘a‘, ‘b‘)
而在严格模式下,无论参数如何变化,arguments对象不再随之改变
function mixArgs(first, second) {
‘use strict‘
console.log(first === arguments[0])//true
console.log(second === arguments[1])//true
first = ‘c‘
second = ‘d‘
console.log(first === arguments[0])//false
console.log(second === arguments[1])//false
}
mixArgs(‘a‘, ‘b‘)
如果使用了函数默认值,则arguments对象的行为将与ES5在严格模式下保持一致:arguments对象保持与命名参数分离(备注:其实这种分离的特性可以将参数恢复为初始值)
function mixArgs(first, second=‘b‘) {
console.log(first === arguments[0])//true
console.log(second === arguments[1])//false
first = ‘c‘
second = ‘d‘
console.log(first === arguments[0])//false
console.log(second === arguments[1])//false
}
mixArgs(‘a‘)
函数默认值除了可以给原始值,还可以指定函数,只不过只有未传入参数需要默认值时才能去调用些此函数,也就是说默认参数是在函数调用时才求值
指定默认值为函数不要忘记小括号,如果忘记小括号则传入的是对函数的引用而不是函数调用的结果
function getValue(value) {
return value + 5
}
function add(first, second = getValue(first)) {
return first + second
}
console.log(add(1))//7
函数参数有自己的作用域和临时死区,其与函数体的作用域是各自独立的,也就是说参数的默认值是不可访问函数体内声明的变量
处理无命名参数
在函数命名参数前添加...三个点表明这是一个不定参数
function pick(obj, ...keys) {
let result = Object.create(null)
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]]
}
return result
}
函数的length属性统计的是函数命名参数的数量,不定参数的加入不会影响length属性的值
每个函数最多只能声明一个不定参数,而且一定要放在所有参数的末尾
不定参数不能用于对象字面量setter之中,因为对象字面量setter的参数有且只能有一个
let obj = {
//Uncaught SyntaxError: Setter function argument must not be a rest parameter
set name(...value) {
console.log(value)
}
}
看一个有趣的例子:无论是否使用不定参数,arguments对象总是包含所有传入函数的参数
默认参数和不定参数的特性同样适用于Function构造函数
var add = new Function("first", "second=first", "return first+second")
console.log(add(1))//2
var pickFirst=new Function("...args","return args[0]")
console.log(pickFirst(1,2))//1
展开运算符
举例来说,Math.max可接受任意数量的参数并返回最大的那个,但如果传入的是一个数组,则只能使用apply
console.log(Math.max(11, 2, 3, 12, 43, 904, 3543, 43))
let values = [11, 2, 3, 12, 43, 904, 3543, 43]
console.log(Math.max.apply(Math, values))
使用展开运算符就变得特别简单了
let values = [11, 2, 3, 12, 43, 904, 3543, 43]
console.log(Math.max(...values))
如果你想限定Math.max返回的最小值为0,还可以如下使用
let values = [-11, -2, -3, -12]
console.log(Math.max(...values, 0))
函数的name属性
函数的name属性的值不一定引用同名变量,它只是协助调用用的额外信息,所以不能使用name属性的值来获取函数的引用
function doSomething() { }
console.log(doSomething.name)//doSomething
var doSome = function doSomeElse() { }
var person = {
get firstName() { return ‘angela‘ },
sayName: function () { }
}
console.log(doSome.name)//doSomeElse
console.log(person.sayName.name)//sayName
console.log(person.firstName.name)//undefined
var doThing = function () { }
console.log(doThing.bind().name)//bound doThing
console.log((new Function()).name)//anonymous
函数的多重用途
JS函数中有两个不同的内部方法:[[Call]]和[[Construct]]
当通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上
如果不是通过new关键字调用函数,则执行[[Call]]函数,从而直接执行代码中的函数体
不是所有函数都有[[Construct]]方法,因此不是所有方法都可以通过new来调用,具有[[Construct]]方法的函数被统称为构造函数
ES5中想确定一个函数是否通过new关键字被调用
function Person(name) {
if (this instanceof Person) {
this.name = name
} else {
throw new Error(‘必须通过new关键字来调用‘)
}
}
但是上述方法不是绝对完全可靠的,比方说如下调用就失效了
var person = new Person(‘angela‘)
var notAPerson = Person.call(person, ‘Shing‘)//这样对于函数本身是无法区分是通过Person.Call、Apply还是new调用得到的实例
ES6中可以如下绝对安全的判定
function Person(name) {
//或者typeof new.target===Person
if (typeof new.target !== ‘undefined‘) {
this.name = name
} else {
throw new Error(‘必须通过new关键字来调用‘)
}
}
var person = new Person(‘angela‘)
var notAPerson = Person.call(person, ‘Shing‘)//抛出错误
当调用函数的[[Construct]]方法时,new.target被赋值为new操作符的目标,如果调用的是[[Call]]方法,则new.target的值为undefined
在函数外使用new.target是一个语法错误
块级函数
ES5严格模式下,在代码块内部声明函数程序会报错
在ES6严格模式下,可以在代码块中声明函数,块级函数声明会被提升至此代码块顶部,超出此块级作用域,则函数将不再存在
‘use strict‘
if (true) {
console.log(typeof doSomeThing) //function
doSomeThing()//----------
function doSomeThing() {
console.log(‘----------‘)
}
}
console.log(typeof doSomeThing) //undefined
在ES6非严格模式下,这些函数不再提升至代码块的顶部而是提升至外围函数或全局作用域的顶部
if (true) {
console.log(typeof doSomeThing) //function
doSomeThing()//----------
function doSomeThing() {
console.log(‘----------‘)
}
}
console.log(typeof doSomeThing) //function
箭头函数
箭头函数与传统函数有如下几点不同
- 没有this、super、arguments和new.target绑定--也就是说箭头函数中的this、super、arguments和new.target这些值由外围最近一层非箭头函数决定
- 不能通过new关键字调用--因为箭头函数没有[[Construct]]
- 没有原型--箭头函数不存在prototype属性
- 不可以改变this的绑定--函数内的this值不可被改变
- 不支持arguments对象--只能通过命名参数和不定参数来访问函数中的参数
- 不支持重复的命名参数
当箭头函数只有一个参数时,可以直接写参数名,箭头紧随其后,箭头右侧的表达式被求值后便立即返回,即使没有显式的返回语句,
如果要传入两个或两个以上参数则需要在参数两侧添加一对小括号
如果函数没有参数,也要在声明时写一组没有内容的小括号
let sum = (num1, num2) => num1 + num2
//相当于
let sum = function (num1, num2) {
return num1 + num2
}
尾调用优化
尾调用指的是函数作为另一个函数的最后一条语句被调用,其实也就是满足以下三个条件
- 尾调用不访问当前栈帧的变量--函数不是一个闭包
- 在函数内部,尾调用是最后一条语句
- 尾调用的结果作为函数值返回
ES6严格模式下,JS引擎才会进行尾调用自动优化
function f(x){
return g(x);
}
function factorial(n) {
if (n <= 1) {
return 1
} else {
//无法优化,必须在返回后执行乘法操作
return n * factorial(n - 1)
}
}
function factorial(n, p = 1) {
if (n <= 1)
return 1 * p
else {
let result = n * p;
//优化后
return factorial(n - 1, result)
}
}