通读ES6--函数的扩展
栏目: JavaScript · 发布时间: 6年前
内容简介:ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值,==即被赋的值对应的布尔值不能为false==为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function log(x, y) { y = y || 'World'; //如果y不为undefined,就是被赋了值,取被赋的值,如果为undefined,没有被赋值,取word默认值 console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello World 复制代码
这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值,==即被赋的值对应的布尔值不能为false==
为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。
if (typeof y === 'undefined') { y = 'World'; } 复制代码
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello 复制代码
可以看到,ES6 的写法比 ES5 简洁许多,而且非常自然。下面是另一个例子。
function Point(x = 0, y = 0) { this.x = x; this.y = y; } const p = new Point(); p // { x: 0, y: 0 } 复制代码
参数变量是默认声明的,所以不能用let或const再次声明。
function foo(x = 5) { let x = 1; // error const x = 2; // error } 复制代码
上面代码中,参数变量x是默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
另外,一个容易忽略的地方是,参数默认值不是传值的,而是每次都重新计算==默认值表达式的值==。也就是说,参数默认值是惰性求值的,即和复合值一样,保存的是一个指针,一个表达式,而不是一个具体的值。
let x = 99; function foo(p = x + 1) { console.log(p); } foo() // 100 x = 100; foo() // 101 复制代码
与解构赋值默认值结合使用
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 property 'x' of undefined 复制代码
上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。
function foo({x, y = 5} = {}) {//默认值为一个空对象 console.log(x, y); } foo() // undefined 5 复制代码
下面是另一个解构赋值默认值的例子。
function fetch(url, { body = '', method = 'GET', headers = {} }) { console.log(method); } fetch('http://example.com', {}) // "GET" fetch('http://example.com') // 报错 复制代码
上面代码中,如果函数fetch的第二个参数是一个对象,就可以为它的三个属性设置默认值。这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) { console.log(method); } fetch('http://example.com') // "GET" 复制代码
上面代码中,函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method才会取到默认值GET
作为练习,请问下面两种写法有什么差别?
// 写法一 function m1({x = 0, y = 0} = {}) { return [x, y]; } // 写法二 function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; } 复制代码
上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
// 函数没有参数的情况 m1() // [0, 0] m2() // [0, 0] // x 和 y 都有值的情况 m1({x: 3, y: 8}) // [3, 8] m2({x: 3, y: 8}) // [3, 8] // x 有值,y 无值的情况 m1({x: 3}) // [3, 0] 参数默认值是有值的,不过为空,这里赋值,y没有赋值,就是undefined,那么会取对象解构赋值的默认值,所以y为0 m2({x: 3}) // [3, undefined], 这里,没有对象解构赋值的默认值,就直接中undefined // x 和 y 都无值的情况 m1({}) // [0, 0]; 参数值为空,取对象解构赋值的默认值 m2({}) // [undefined, undefined] 参数x,y都没有赋值,也没有象解构赋值的默认值,所以undefined m1({z: 3}) // [0, 0] 参数x,y都没有赋值,取对象解构赋值的默认值 m2({z: 3}) // [undefined, undefined] 参数x,y都没有赋值,也没有象解构赋值的默认值,所以undefined 复制代码
参数默认值的位置
通常情况下,定义了默认值的参数,如果不赋值,会报错
function f(x, y = 5, z) { return [x, y, z]; } f() // [undefined, 5, undefined] f(1) // [1, 5, undefined] f(1, ,2) // 报错 f(1, undefined, 2) // [1, 5, 2] 复制代码
如果传入undefined,将触发该参数等于默认值,null则没有这个效果。
function foo(x = 5, y = 6) { console.log(x, y); } foo(undefined, null) // 5 null 复制代码
函数的 length 属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
(function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2 复制代码
即,从指定默认值的参数那里断开,length不再计入后面的参数
(function (a = 0, b, c) {}).length // 0 (function (a, b = 1, c) {}).length // 1 复制代码
参数的作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
var x = 1; function f(x, y = x) { console.log(y); } f(2) // 2 复制代码
上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。
再看下面的例子。 let x = 1; function f(y = x) { let x = 2; console.log(y); } f() // 1 复制代码
上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。
如果此时,全局变量x不存在,就会报错。
function f(y = x) { let x = 2; console.log(y); } f() // ReferenceError: x is not defined 复制代码
如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。请看下面的例子。
let foo = 'outer'; function bar(func = () => foo) { let foo = 'inner'; console.log(func()); } bar(); // outer 复制代码
上面代码中,函数bar的参数func的默认值是一个匿名函数,返回值为变量foo。函数参数形成的单独作用域里面,并没有定义变量foo,所以foo指向外层的全局变量foo,因此输出outer。
var 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,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。
自我理解可以理解为参数形成的作用域,在函数外层,在全局内层,如果内层作用域中,如果变量没有声明,就在向上查找,直到找到它的声明的作用域,它的取值就在这个作用域内,如果找到最外层,还是没有声明,那么当我们给它赋值时,它的作用域是全局。
如果将var x = 3的var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。
自我理解没有var声明,就向上查找,在参数作用域中找到x的,在这个作用域内,x默认在声明,所以函数内的x与参数x是同一变量
var x = 1; function foo(x, y = function() { x = 2; }) { x = 3; y(); console.log(x); } foo() // 2 x // 1 复制代码
应用
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter 复制代码
函数调用时,如果没有赋值,参数取默认值,调用throwIfMissing()函数,从而抛出错误
rest 参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3) // 10 复制代码
严格模式
从 ES5 开始,函数内部可以设定为严格模式。
function doSomething(a, b) { 'use strict'; // code } 复制代码
ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
// 报错 function doSomething(a, b = a) { 'use strict'; // code } // 报错 const doSomething = function ({a, b}) { 'use strict'; // code }; // 报错 const doSomething = (...a) => { 'use strict'; // code }; const obj = { // 报错 doSomething({a, b}) { 'use strict'; // code } }; 复制代码
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属性都返回这个具名函数原本的名字。
const bar = function baz() {}; // ES5 bar.name // "baz" // ES6 bar.name // "baz" 复制代码
箭头函数
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; }; 复制代码
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => { return num1 + num2; } 复制代码
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
// 报错 let getTempItem = id => { id: id, name: "Temp" }; // 不报错 let getTempItem = id => ({ id: id, name: "Temp" }); 复制代码
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。
let fn = () => void doesNotReturn(); 复制代码
以上所述就是小编给大家介绍的《通读ES6--函数的扩展》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 通读审计之AACMS
- 通读审计之DOYOCMS
- 5分钟通读vue源码架构
- Python 拓展之特殊函数(lambda 函数,map 函数,filter 函数,reduce 函数)
- Python 函数调用&定义函数&函数参数
- python基础教程:函数,函数,函数,重要的事说三遍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Code Reading
Diomidis Spinellis / Addison-Wesley Professional / 2003-06-06 / USD 64.99
This book is a unique and essential reference that focuses upon the reading and comprehension of existing software code. While code reading is an important task faced by the vast majority of students,......一起来看看 《Code Reading》 这本书的介绍吧!