这是该系列的第19篇笔记!
让学习“上瘾”,成为更好的自己!!!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>函数参数的默认值</title>
</head>
<body>
<script>
// 1, 基本用法
// (1)
// ES6之前:不能直接为函数的参数指定默认值
// function log(x, y) {
// // y = y || 'world'; // 缺点:参数y被赋值了,但是对应的布尔值时false,则该赋值不起作用
// if (typeof y === 'undefined') {
// y = 'world';
// }
// console.log(x, y);
// }
// log('hello');
// log('hello', 'china');
// log('hello', '');
// ES6: 能直接为函数的参数指定默认值
// function log(x, y = 'world') {
// console.log(x, y);
// }
// log('hello');
// log('hello', 'china');
// log('hello', '');
// function Point(x = 0, y = 0) {
// this.x = x;
// this.y = y;
// }
// var p = new Point();
// console.log(p);
// ES6写法的好处:a, 简洁明了
// b, 阅读代码的人可以立即意识到哪些参数是可以省略的,不用查看函数体或文档
// c, 有利于将来代码优化,即使未来的版本彻底拿掉了这个参数,也不会导致以前的代码无法运行
// (2) 参数变量时默认声明的,不用let or const再次声明
// function foo(x = 5){
// let x = 23; // error
// const x = 232; // error
// var x = 12; // normal
// console.log(x);
// }
// (3) 使用参数默认值时,不能有同名参数
// function foo(x, x, y = 1){ // error
// // ...
// }
// (4) 参数默认值不是传值的,而是每次都重新计算默认值表达式的值,即参数默认值是“惰性求值”的
// let x = 12;
// function foo(p = x + 1){
// console.log(p);
// }
// foo(); // 13
// x = 100;
// foo();
// 【解释】参数p的默认值是"x + 1",每次调用函数foo都会重新计算"x + 1",而不是默认p为13
// 2, 与解构赋值默认值结合使用
// (1) 参数默认值可以与解构赋值的默认值结合起来使用
// function foo({
// x,
// y = 5
// }) { // 使用了对象的解构赋值默认值,而不是函数参数的默认值
// console.log(x, y);
// }
// foo({});
// foo({x: 1}); // 1 5
// foo({x:12, y: 23}); // 12 23
// foo(); // error --> 如果函数foo调用时参数不是对象,则变量x and y不会生成,从而报错
// (2)
// function fetch(url, {body = '', method = 'GET', headers = {}}){ // 这种写法不能省略第二个参数,在传值时
// console.log(method, headers);
// }
// fetch('http://example.com' ,{}); // GET
// fetch('http://example.com'); // 报错
// 上面的这种写法不能省略第二个参数,如果结合函数参数的默认值,则可以省略第二个参数,这时,会出现“双重默认值”
function fetch(url, {
method = 'GET'
} = {}) { // 这种写法可以省略第二个参数,在传值时
console.log(method);
}
fetch('http://example.com'); // 不报错
// --> 函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method取得默认值GET
// (3) 比较下面的写法
// way 1: 函数参数的默认值是空对象,但是设置了对象解构赋值的默认值
function m1({
x = 0,
y = 0
} = {}) {
return [x, y];
}
// way 2: 函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值
function m2({
x,
y
} = {
x: 0,
y: 0
}) {
return [x, y];
}
// 函数没有参数的情况
// console.log(m1()); // [0, 0]
// console.log(m2()); // [0, 0]
// // x and y have the value
// console.log(m1({x: 3, y: 8})); // [3, 8]
// console.log(m2({x: 3, y: 8})); // [3, 8]
// x has the value but y doesn't have
// console.log(m1({x: 3})); // [3, 0]
// console.log(m2({x: 3})); // [3, undefined]
// // x and y都无值的情况
// console.log(m1({})); // [0, 0]
// console.log(m2({})); // [undefined, undefined]
// 3, 参数默认值的位置
// 通常情况下,定义了默认值的参数应该是函数的尾参数
// --> 因为这样比较容易看出到底省略哪些参数
// 如果非尾部参数设置了默认值,实际上这个参数是无法省略的
// (1)
// example 1
// function f(x = 1, y){
// return [x, y];
// }
// console.log(f()); // [1, undefined]
// console.log(f(2)); // [2, undefined]
// console.log(f(undefined, 1)); //[1, 1]
// example 2
function f(x, y = 5, z){
// 非尾部参数设置了默认值,这时,无法只省略该参数而不省略其后的参数,除非显式输入undefined
return [x, y, z];
}
// console.log(f()); // [undefined, 5, undefined]
// console.log(f(1)); // [1, 5, undefined]
// console.log(f(1,undefined,2)); // [1, 5, 2]
// (2) 如果传入undefined,将触发该参数等于默认值,null则没有这个效果
// function foo(x = 5, y = 12){
// console.log(x, y);
// }
// foo(undefined, null); // 5 null
// 4, 函数的length属性
// 指定了默认值后,函数length属性将返回“没有指定默认值”的参数的个数
// (1) 指定了默认值后,length属性将失真
// console.log((function(a){}).length); // 1
// console.log((function(a = 5){}).length); // 0
// console.log((function(a, b, c = 23){}).length); // 2
// 指定了默认值后,length属性将失真?
// --> 因为length属性的函数是该函数预期传入的参数的个数,某个参数指定默认值后,预期传入的参数个数就不包括这个参数了
// 同理,rest参数也不计入length属性
// (2)
// console.log((function(a, b, ...args){}).length); // 2
// (3) 如果设置了默认值的参数不是“尾参数”,那么length属性也不再计入后面的参数
// console.log((function(a = 0, b, c){}).length); // 0
// console.log((function(a, b = 1, c){}).length); // 1
// 5, 作用域
// 一旦设置了参数默认值,函数进行声明初始化时,参数会形成一个单独的作用域,等到初始化结束,这个作用域消失
// 这种语法行为在不设置参数默认值时是可不会出现的
// (1)
// example 1
// var x = 1;
// function f(x, y = x){
// console.log(y);
// // console.log(y);
// }
// f(); // undefined --> 这里,参数y的默认值等于变量x,调用函数f时,参数形成了一个单独的作用域
// 在这个作用域中,默认值变量x指向第一个参数x,而不是全局变量x,所以输出2
// f(2);
// example 2:
// let x = 1;
// function f(y = x){
// let x = 2;
// console.log(y);
// }
// f(); // 1 --> 函数f调用时,参数"y = x"形成一个单独的作用域,在这个作用域中,变量x本身没有定义,所以指向外层的全局变量x
// 函数调用时,函数体内部的局部变量x影响不到默认变量x
// 如果此时全局变量不存在,就会报错
// function f(y = x){
// let x = 2;
// console.log(y);
// }
// f(); // 报错
// 下边写法也会报错
// var x = 1;
// function foo(x = x){
// // ...
// }
// foo(); // 报错 --> "x = x"形成了一个单独的作用域,实际执行的是"let x = x",由于“暂时性死区”,执行这行代码会产生“定义”错误
// (2) 如果参数的默认值是一个函数,该函数的作用域也遵守这个规则, like "example 2"
// let foo = 'outer';
// function bar(func = x => foo){
// // let foo = 'inner';
// console.log(func());
// }
// bar(); // outer
// (3) a more complicated example
var x = 1;
function foo(x, y = function(){x = 2;}){
var x = 3;
y();
console.log(x);
}
foo(); // 3
console.log(x); // 1
//【解释】
// 函数foo的参数形成了一个单独的作用域,这个作用域中首先声明了变量x,然后声明变量y
// y的默认值是一个匿名函数,这个匿名函数内部的变量x指向同一个作用域的第一个参数x
// 函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以,在执行完y后,内部变量x和外部全局变量x的值“不会变”
// 【改变一下】
var x = 1;
function foo(x, y = function(){x = 2;}){
x = 3; // var去掉,此时,函数foo内部变量x就指向第一个参数x,与匿名函数内部的x是一致的
y();
console.log(x);
}
foo(); // 2
console.log(x); // 1
// 6, 应用
// (1)利用参数默认值可以指定一个参数不得省略,如果省略就抛出一个错误
// function throwIfMissing(){
// throw new Error('Missing parameter');
// }
// function foo(mustBeProvided = throwIfMissing()){ // 参数的默认值不是在定义时执行,而是在运行是执行
// return mustBeProvided;
// }
// console.log(foo(12)); // Missing parameter
// var err = new Error('i was wrong!');
// console.log(err);
// (2)将参数的默认值设为undefined,表明这个参数是可以省略的
// function foo(optional = undefined){
// // ......
// }
</script>
</body>
</html>