今天学习了阮一峰老师的es6函数的扩展部分,故在此做下笔记;
第一点:扩展是函数参数可以设置默认值
在ES5中,不能直接为函数的参数指定默认值,那么直接指定默认值解决了ES5什么问题呢?以下是es5为函数的参数指定默认值;
function log(x,y){
y = y || 'World';
console.log(x,y);
}
log('Hello')
// 打印出Hello World,这种情况是没问题的,只是代码写多些而已
log('Hello', 'China')
// 打印出Hello China,这种情况也是没问题的,只是代码写多些而已
log('Hello', '');
// 打印出Hello World,这种情况是有问题的
上面最后一次打印出Hello World,这种情况是有问题的,因为我们传值了给参数y,只是空字符串而已,结果被改为默认值,所以为了避免这个问题,还要再先判断一下参数y是否被赋值,如果没有,再令其等于默认值;
if (typeof y === 'undefined'){
y = 'World'
}
可以看出来,为了实现给参数设默认值要写这么多代码,而在ES6中,就非常简单
function (x,y='World'){
console.log(x,y);
}
log('Hello')
// 打印出Hello World,没问题
log('Hello', 'China')
// 打印出Hello World,没问题
log('Hello', '');
// 打印出Hello,没问题
可以看出ES6的写法比ES5简洁很多,ES6写法还有两个好处:1:阅读代码的人可以立刻意识到哪些参数是可以省络的,不用查看函数体或者文档;
2:有利于将来代码优化,即使未来的版本彻底拿掉这个参数,也不会导致以前的代码无法运行;
需要注意的是,参数默认值不是传值,而是每次都重新计算默认值表达式的值,也就是说,参数默认值是惰性求值的;
let x = 99;
function p(y=x+1){
console.log(y);
}
p()
// 打印出100
第二点是在ES6中参数默认值可以与解构赋值的默认值结合起来使用
至于什么是解构赋值,请看https://www.jianshu.com/p/c6c57cd0f275
如果只使用解构赋值的默认值,不使用参数默认值,是会有些问题出现,如下面的例子:
function foo({x, y=5}){
console.log(x,y)
}
foo({})
// 打印出undefined,5
foo();
// 报错,
上面最后一个运行会报错,Uncaught TypeError: Cannot destructure property 'x' of 'undefined' as it is undefined;
上面的例子,只有当函数foo的参数是一个对象时,参数x和y才会通过解构赋值而生成。我们可以对上面的例子进行改造,增加函数参数默认值
function foo({x, y=5} = {}){
console.log(x,y)
}
foo({})
// 打印出undefined,5
foo();
// 打印出undefined,5
下面讲解一个较为复杂的,但是原理是一样的例子
function fetch(url,{body = '',method = 'GET', headers = {}} = {}){
console.log(method);
}
fetch('http://www.baidu.com')
// 打印出GET
第三点:参数默认值应该是在函数的尾部,这样比较容易看出到底省络了哪些参数。
第四点:函数的length属性
函数的length属性返回没有指定默认值的参数个数。
(function(a,b){}).length // 2
(function(a,b=2){}).length //1
(function(a=3,b){}).length //0
length属性的含义是该函数预期传入的参数个数,某个参数指定默认值以后,预期传入的参数就不包括这个参数了,如果设置了默认值的参数不是尾部参数,那么length属性也不再计入后面的参数
第五点: 作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为在不设置参数默认值时是不会出现的。
let x = 1
function (x, y=x) {
console.log(y)
}
f(2) // 返回2
上述的代码中,参数y的默认值等于参数x,调用函数y时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数,而不是全局变量x,所以输出是2.
let x =1;
fucntion f(y = x){
let x =2;
console.log(y)
}
f() // 1
上面代码中,函数f调用时,参数y=x形成一个单独的作用域。在这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内的局部变量x影响不到默认值变量。
let x =1;
function foo(x, y = function(){x=2}){
var x = 3;
y();
console.log(x);
}
foo() // 3
x // 1
上面代码,函数foo的参数形成一个单独作用域,这个作用域中首先声明了变量x,然后声明了变量y.y的默认值是一个匿名函数,这个匿名函数内部的变量x指向同一个作用域的第一个参数。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量与外部变量x的值都没变。
第六点:具体应用
利用参数默认值可以指定某一个参数不得省络,如果省络就抛出一个错误。
function throwIfMissing(){
throw new Errow('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing) {
return mustBeProvided;
}
foo() // Missing parameter
如果调用的时候没有参数,以上代码中foo函数就会调用默认值throwIfMissing函数的运行结果,这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中函数就不会执行。
另外可将参数默认值设为undefined,表明这个参数是可以省络的。
function foo(optional = undefined){
}
第七点:严格模式
从ES5开始,函数内部就可以设定为严格模式。
function toDo(a, b) { 'use strict'; // code }
ES2016做了一点修改,规定只要函数参数使用了默认值、解构赋值或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。这样规定的原因是,函数内部的严格模式同时适用于韩素体和函数参数。但是函数执行时,先执行函数参数,然后执行函数体。这样就有一个不合理的地方:只有从函数体之中才能知道参数是否应该以严格模式执行,但是参数却应该先于函数体执行。虽然可以先解析函数体代码,再执行参数代码,但是这样无疑增加了复杂性。因此,标准索性禁止了这种用法,只要参数使用了默认值、解构赋值、扩展运算符,就不能显式指定严格模式。
有两种方法可以规避这种限制。第一种是设定全局性的严格模式,这是合法的。
'use strict'
function todo(a, b) {
// code
}
第二种是把函数包在一个无参数的立即执行函数里面;
const todo = (function(){
'use strict';
return function(value = 42){
return value;
}
}())
第八点: name属性
函数的name属性返回该函数的函数名。这个属性早就被浏览器广泛支持,但是直到ES6才写入标准。
需要注意的是,es6对这个属性的行为做了一些修改。如果将一个匿名函数赋值给一个变量,es5的name属性会返回空字符串,而ES6的name属性会返回实际的函数名。
let f = function(){};
// es5, f.name 返回''
// es6, f.name返回f
如果将一个具名函数赋值给一个变量,则ES5和ES6的name属性都返回这个具名函数原本的名字。
构造函数返回的函数实例,name属性值为anonymouse;
bind返回的函数,name属性会加上bound前缀;