函数默认参数及作用域详解
ES6引入了函数默认参数,先来看下例中对比
//es5
function fn(x,y){
if(typeof y === 'undefined'){
y = 'world';
}
console.log(x,y);
}
fn('hello');//hello world
//------------------------------------
//es6
function fn(x,y='world'){
console.log(x,y);
}
fn('hello');//hello world
通过上例可以看出 默认参数这一功能大大的让代码变得简洁而自然,一目了然。下面回顾一下es5中函数参数相关内容
ES5中函数参数及覆盖问题
es5中函数内部作用域是包括: 函数参数变量 函数体内声明的变量 this
arguments
下面回顾一下函数参数被覆盖问题的一个例子
function test(a,b){
console.log(a);
function a(){}
var b = 10;
console.log(b);
}
test(22,33);
函数内重复声明变量为函数模式(上例的function a(){}
)时会覆盖掉函数调用时传递过来的实际参数 a
,这就说明实际参数赋值给形式参数 是在函数内部的函数声明提升之前,最终导致 函数声明提升 覆盖掉了 a=22
的实参向形参赋值的操作
函数内重复声明变量为var
形式声明是 变量提升 相当于在开头只声明变量并不赋值var b
; 这种单纯的声明变量不赋值并不会影响变量原来的初始化值 此时变量b
的值还是 33
;等到执行 b=10
的时候才会改变变量b
的值
ES6中函数默认参数注意点
使用参数默认值是和不使用参数默认值的处理逻辑有很大区别 * 参数不能重复
function bar(x=10,x,y){
console.log(x,y);
}
//Uncaught SyntaxError: Duplicate parameter name not allowed in this context
- 参数变量默认会声明,类似使用
let
(作者猜测,哈哈) 函数体内不能再用let
或者const
再次声明
function bar(x=10,y){
let x=2;//error
}
//Uncaught SyntaxError: Identifier 'x' has already been declared
- 函数默认参数的值是在使用时进行求值,而不是声明时求值,也就是说,参数默认值是惰性求值
//摘自阮一峰ES6入门教程
let x = 99;
function foo(p = x + 1) {
console.log(p);
}
foo() // 100
x = 100;
foo() // 101
函数默认参数作用域
我们都知道ES5函数参数只用Local Scope
作用域;而ES6中函数默认值参数会形成单独的作用域,这个作用域占用了之前的Local Scope
而函数体内声明的变量形成了一个块级作用域Block Scope
图中断点停留在了 var name = 'zhangsan'
处,右边的作用域(Scope
)中显示了三个作用域环境 分别是Block
,Local
,Global
。块级作用域Block
中是函数体内声明的变量作用域,Local
作用域中显示的是函数参数所在的作用域环境。Global
表示全局作用域。
块作用域Block
中存在声明提升所以显示的值都为undefined
,只有当执行到变量赋值时才会被赋予真是的值。这跟之前声明提升(变量声明和函数声明)的逻辑是一致的。
如果声明函数呢?请看下图(chrome 浏览器)
火狐浏览器
通过两图对比可以发现 chrome浏览器中函数的声明不会显示到Block Scope
中 而 火狐浏览器会正常显示,那样我们猜测 只声明变量 不初始化赋值 会不会也有这种情况
chrome 浏览器
火狐浏览器
从图中对比发现 确实是chrome 浏览器变量只声明不赋值和函数声明确实不出现在块级Block
作用域中,而火狐浏览器表现正常
这难道是在chrome 浏览器中声明不生效吗?那我们打印一下这些变量看看到底有没有。
chrome浏览器
火狐浏览器
打印变量都会显示到块级Block
作用域中。以下是作者个人猜测:
chrome浏览器,只有声明的变量和函数在该作用域内被调用或者引用的时候才会显示(可能这是chrome浏览器的一种优化方案,用到的时候才会显示,不用就不会显示),然而在 火狐浏览器中只要声明就会一直显示
通过上面的介绍我们都知道函数参数和函数体内会形成两个独立的作用域 函数参数作用域对应于Local Scope
函数体内作用域对应于Block Scope
如果在函数体内重复声明参数对应的变量浏览器会怎么处理(这里只能使用var
声明,不能使用 let
或 const
否则会报错)
函数默认参数重复声明
我们先看看截图上的信息 * 被覆盖的参数声明为变量(var
形式)时
火狐浏览器与chrome浏览器表现行为一致就不贴火狐浏览器的截图了 可以看到只要函数体内声明的变量跟函数参数重复时 就会把参数作用域(Local Scope
)内对应的变量的值直接赋值给了函数体内对应的变量。这看起来就好比 从函数参数作用域中拷贝了一份变量 到 函数体内,嗯 我们可以这么理解(至少作者是这么理解的,不具有权威性)之后再函数体内对重复的变量进行操作 都只是函数体内的Block Scope
作用域内的变量改变,不会影响到函数参数作用域相同变量名的改变,如果想改变可以通过函数默认参数设置为一个函数。
function foo(m='local',n=function(){
console.log(m);//-----2
m='change';
console.log(m);//-----3
}){
var m = 'block';
console.log(m); //-----1
n();
console.log(m);//-----4
}
foo();// block local change block;
函数执行之前作用域变量分布
执行 var m='block'
的时候是对块级作用域 Block Scope
内的 m
进行重新赋值,并不影响 函数参数作用域 Local Scope
中变量 m
的值,之后又执行 n()
,n函数内的 m
由于没有使用 var
声明所以直接找到的是 Local Scope
中的 m
后面的赋值是针对 函数参数作用域内的 m
进行赋值 之后又打印了 这个 m
(参数作用域内的m) 函数 n()
执行完毕之后 回到 函数体内 接着打印 m
这里的 m
表示的还是函数体作用域( Block Scope
)中的 m
函数 n()
的执行并不会影响函数体内的 m
变化;
- 被覆盖的参数声明为函数时
先看一个不重复声明的情况
这样 function n(){}
直接在块级作用域Block Scope
内部提升到最前方,打印结果为一个函数
如果存在var
重复声明呢?
从图上可以看出 这里的function n(){}
重复声明没有起到作用,感觉好想是被覆盖掉了。
我们看看火狐浏览器的截图
火狐浏览器能够显示出来 这就存在兼容性问题了
chrome 浏览器和 火狐浏览器的 处理顺序不一致,以下内容为作用推测没有实质性深入验证: chrome 浏览器 重复变量声明时会赋值一份函数参数作用域内的变量及初始值,这个复制的时机在该函数体内函数声明提升之后,就会造成 函数声明会被初始化为函数参数作用域内的变量值 覆盖掉。
而火狐浏览器是 恰好相反,函数声明提升在 变量被赋值为函数参数作用域相同变量的值之后,这样就能得到上图的允许结果。
总结
通过深入介绍我们大致明白了
- ES5函数内只有一个
Local Scope
作用域 - ES6带默认参数的函数作用域分为2个:函数参数作用域
Local Scope
和 函数体作用域Block Scope
这两个作用域是相互独立的 - 具有默认值参数的函数参数不能重复声明
- 具有默认参数的函数体内不能再次使用
let
const
等声明参数变量 - 函数参数默认值是惰性求值的
- 函数体内重复声明的变量的初始值会是函数参数作用域内具有相同变量名的值,再从新声明成函数是 chrome 和 火狐浏览器处理变量逻辑不一样最终得到的结果也不一样(一般我们不这么玩,只是深入明白处理机制)