为什么以下两个例子执行结果不同?

Rnorth
  • 498
var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1

而去掉var以后:

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

foo() // 2
x // 1

为什么实例一的y执行完成没有改变任何x的值?
而去掉var输出结果就会改变?

回复
阅读 2.6k
8 个回答
kikong
  • 19.1k
✓ 已被采纳

函数声明时设置的默认参数值是在函数调用时计算赋值的,而不是在函数声明时赋值

我们可以看下下面的例子

function foo2(a, b = (function() { console.log(c); return function(){} })()) {
    b();
}
foo2();

调用 foo2();
控制台将输出:
Uncaught ReferenceError: c is not defined

at b (<anonymous>:1:47)
at foo2 (<anonymous>:2:5)
at <anonymous>:1:1

而不掉用 foo2();
控制台将不报错

以上例子说明了函数参数的默认值是在调用是赋值的,而不是在声明时。

对于问题的代码,还有注意一点,y默认值函数声明中的x是绑定为函数声明中的参数x变量而不是foo外层作用域中的变量x

function foo(x, y = function() { x = 2; }) {
  console.log("x1:"+x);
  y();
  console.log("x2:"+x);
}
foo();

输出
x1:undefined
x2:2

那么下面的代码输出2就好理解了

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

foo(); // 2
Rnorth
  • 498

看到了很多答案,在大家的提示下 我去查了很多资料现在已经理解了这个结果。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1

以上的代码 作用域链如下:

clipboard.png

以上的图涉及一个问题,那就是为什么foo的参数居然是自成一个作用域链?
这是因为es6规定:
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
也就是说y中的x=2,因为y中是没有x变量的,所以向上一级查找,他的上一级就是传入的参数x,因此y函数的作用改变的是参数x
而当foo内部var x=3时,这时内部的x已经覆盖掉了参数x,
console.log查找的是它的上一层foo的内部变量作用域x,这个x是等于3的,因此foo执行为3,一下贴出作用域链的一个概念,觉得解释的非常好
作用域链:作用域链是一个对象列表或者链表,这组对象定义了这段代码’作用关于中’的变量。当javascript需要查找变量x的值得时候(变量解析),他会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,javascript会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象含有属性x,那么久认为这段代码的作用域链上不存在x,并最终爆出一个引用错误异常。(犀牛书P59)

而第二段代码:

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

foo() // 2
x // 1

作用域链如下:

clipboard.png

foo中的console.log(x)中的x会查到他的上一级传入的参数x,因为传入的参数被y函数改为2,所以输出2
而第一个例子中的x上一级作用域链直接是新定义的x=3所以输出3

厦冰
  • 8.5k

clipboard.png

clipboard.png

你可以这么理解,function的参数也会形成一个变量作用域,也就是函数声明的时候,先生成一个变量作用域,这个作用域中包含参数的声明,然后这个作用域里面又会生成一个变量作用域,这个作用域中是函数内部声明的变量。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

生成类似下面的作用域

clipboard.png

调用foo的时候,会调用yy函数变量作用域中没有变量x,所以会一直向上层作用域查找,终于在foo参数变量作用域中找到x,然后将该作用域的x变为2;然后执行console.log(x)的时候,这个语句是在foo函数内变量作用域中的,这个作用域中正好有变量xx的值是3,所以输出3。全局作用域中的x依然是1,所以输出1

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

生成类似下面的作用域:

clipboard.png

和上图对比你会发现不同的地方在foo函数内变量作用域没有声明一个新的变量x
执行x = 3的时候因为foo函数内变量作用域内没有x变量,所以会到foo参数变量作用域中查找,发现有x变量,然后将该作用域中的变量
x赋值为3;然后执行y的时候,因为y函数变量作用域中也没有x变量,所以会向上查找,直到在foo参数变量作用域找到变量x,然后将x赋值为2;执行console.log(x)的时候,因为foo函数内变量作用域没有x变量,所以打印的是foo参数变量作用域内的x变量,此时x2;然后输出全局的x1

xianshenglu
  • 4.4k
var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1

上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量yy的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。

如果将var x = 3var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

foo() // 2
x // 1

上述代码和解释来源:ES6 标准入门

温柔的码农
  • 380

有意思, 试了一下, 无语凝噎:

0060lm7Tly1fouuh0pmyej31c00i040z.jpg

0060lm7Tly1fouuh0qo00j31ay0h2gny.jpg

总结: 注意区分函数调用与函数声明, 不要给参数重新赋值 ?

函数形参是有自己的作用域的 所以y函数里修改的x只会是在他之前定义的x而不是外部的x 所以外部的x不会变
而在函数内重新var的x是函数作用域的x 不是形参作用域的x 函数运行在定义时 所以y修改的是形参x的只 log时的是函数作用域的x 没var的x会向上找x 找到的是形参的x 所以修改的是形参的x 打印的x也是形参的x

hfhan
  • 23.1k

function foo(x, y = function() { x = 2; }) 参数中 foo参数y函数里的 x 指向foo参数x,这形成了一个闭包

先说第二个函数,当执行 x=3 的时候,参数x的值变成了3,在执行y,因为引用的都是同一块内存空间,所以x又变成了2,最终打印2,这个x始终是foo的参数,所以不存在改变全局x的情况

第一个函数,当foo内部重新var x的时候,又重新开辟了一个内存空间,这个空间未被y引用,所以执行y的时候,改变的还是参数x,而打印的是重新开辟的x。

如果y在全局声明,或者在foo函数内部声明,那么又是不同的情况

宣传栏