函数提升会改变变量的值。

3

请问有人能解释一下为什么我得到了不同的输出结果吗?

代码1:

var a = 1;

function b() {
  a = 10;
  console.log(a); //output 10
}

b();
console.log(a); //output 10

代码 2:

var a = 1;

function b() {
  a = 10;
  console.log(a); //output 10
  function a() {}
}

b();
console.log(a); //output 1

调用函数“b”后,“a”变量的输出结果为什么不同?需要清晰的解释发生了什么?

1
我不确定你在这里没有理解什么。你已经知道了关于变量提升的概念,甚至包括函数提升。 - Sebastian Simon
@Nag,我并不是想无礼,只是想了解一下你的知识水平,这样我才能向你解释。提升是建立在几个基础块上的,如果你不了解它们,那么你永远无法理解提升。 - Learn on hard way
@Xufox,我的理解是函数“a”试图在函数“b”内重新声明变量a。这就是为什么函数b内部的var a值为10,而外部的值为1。当我不使用相同名称的函数即a时,输出仍为10!https://jsbin.com/rabovemaka/1/edit?html、js、console、output - Nag
@Nag,这里没有尝试重新声明任何内容。在函数b中只有一个声明:即函数a的声明。 - Sebastian Simon
@Nag 我已经检查过了。那又怎样?顺便说一下,你还没有回答我的问题。 - Learn on hard way
显示剩余8条评论
2个回答

5

我认为情况1是非常容易理解的,因为a的值被a=10覆盖了。

在情况2中,由于function a() {}hoisting,变量声明和函数定义被移动到最近的词法作用域,也就是function b()对于function a(),所以a的作用域是function b(),改变它的值不会影响全局a的值,而是覆盖了function a()

因此,console.log(a)function b()外部打印的是1,因为全局值没有改变。


1
@Nag 我相信你在写那条评论时是基本上错误的,而且你误解了变量提升的工作原理。代码不会上下移动,JS只有在声明东西时才有优先级。函数首先声明,然后是变量。 - Learn on hard way
@Nag 看看我的新回复。可能有帮助。 - Learn on hard way
@Learnonhardway,谢谢你的帮助,我正在学习并尝试理解。在此之前,我看到了这个链接 https://dev59.com/R1kR5IYBdhLWcg3w4hHN 我的问题是,在变量提升的时候,Javascript 编译器会根据函数名称在该作用域中创建同名的新变量吗?如果是的话,那么这就解决了我所有的疑惑! - Nag
@Nag 正确的术语是给该函数分配标识符,这意味着该标识符知道该函数在内存中的位置。为了使事情更容易,您可以始终通过var声明定义函数,并完全忘记所有函数声明...就像这样 - Learn on hard way
请使用@Nag var标识符来澄清其含义。 - Learn on hard way
显示剩余8条评论

0

我相信我们的OP已经尝到了网络学习的权衡。虽然可以访问大量信息,但缺少指导。不幸的是,我们不知道您知道多少,并且我们假设许多事情,并且这些是您所知道的一些先验知识,最终导致对可怕的“提升”是什么有所了解。这就是典型的磁铁如何工作,因此,我将提供一些关键词,应该作为JavaScript理解的指南。

创建阶段+ 激活(也称代码执行)阶段 = 执行上下文

好的,但这些都是什么?

作用域链 + 创建参数、函数、变量 + 'this'关键字的值 = 创建阶段

为函数分配值和引用,然后执行代码 = 激活(也称代码执行)阶段

  • 创建阶段

    • 设置作用域链
    • 创建包含变量的对象
      • 创建参数对象
      • 搜索函数声明,对于每个函数声明,在上述对象中创建具有该名称的属性,如果函数名存在,则引用指针值将被覆盖
      • 搜索变量声明,对于每个变量声明,在上述对象中创建具有该名称的属性,并将其值设置为undefined。如果名称已经存在,则不执行任何操作。
    • this关键字的值被解析。
  • 执行阶段,也称为运行代码阶段

    • 执行函数()并将值分配给this = value is assigned

代码1按照上述算法逐行开始执行。

阶段1:创建阶段

globalExeContext = {
  //no scope
  objForVariables = {
    //no arguments because its no function
    // function declarations
    b: `points to function`,
    // variable declarations
    a: `undefined`
  },
  this: //not important for this example
}

创建阶段已完成。我们按照上面定义的指令逐行进行了操作。现在我们进入执行阶段,其中包括赋值=和执行()。再次从第一行开始执行。

第二阶段:执行阶段

globalExeContext = {
  //no scope
  objForVariables = {
    //no arguments because its no function
    // function declarations
    b: `points to function`,
    // variable declarations
    a: 1 // because we did the `=` on line 1
  },
  this: //not important for this example
}

然后在第8行,我们发现了函数执行,这意味着在全局执行上下文的顶部创建新的执行上下文,这意味着我们针对该函数再次遵循上述算法。

阶段1:创建阶段

bExeContext = {
  // This is the scope object, and in this object now is placed the global exe context we have worked on before
  scope: {globalExeContext}
  objForVariables = {
    //the is arguments object for this one but its empty, because we have no arguments for this function
    args:{},
    // function declarations are none here
    // variable declarations are none here
  },
  this: //not important for this example
}

现在,我们已经进入执行阶段,在这个阶段我们会使用()=。在第一行中告诉我们a = 10,这意味着我们需要找到a并将值赋给它。但是我们在bExeContext对象中没有a怎么办?现在我们进入scope并尝试在其中找到它。果然,在我们的全局空间中有a,现在我们将10分配给它。我们已经覆盖了它。它将永远不会再是同样的,我将带回globalExeContext来向您展示。

阶段2:执行阶段

globalExeContext = {
  //no scope
  objForVariables = {
    //no arguments because its no function
    // function declarations
    b: `points to function`,
    // variable declarations
    a: 10 // look what you have done
  },
  this: //not important for this example
}

现在问题已经解决,我们回到执行我们b函数中的下一行,即console.log(a);。我们需要解决a是什么,再次搜索b没有找到,但我们在globalExeStack中有它。你记得,它是10。所以我们记录10。我们已经到达函数的末尾,它从堆栈中弹出。它不再存在。
现在我们恢复执行全局代码的最后一行:console.log(a);,很容易,在globalExeStack中有a。它是10代码2第1阶段:创建阶段。
globalExeContext = {
  //no scope
  objForVariables = {
    //no arguments because its no function
    // function declarations
    b: `points to function`,
    // variable declarations
    a: `undefined`
  },
  this: //not important for this example
}

第二阶段:执行阶段=()第一行与原文相同

globalExeContext = {
  //no scope
  objForVariables = {
    //no arguments because its no function
    // function declarations
    b: `points to function`,
    // variable declarations
    a: 1
  },
  this: //not important for this example
}

第8行指示执行b函数。

阶段1:创建阶段

bExeContext = {
  scope: {globalExeContext}
  objForVariables = {
    //the is arguments object for this one but its empty, because we have no arguments for this function
    args:{},
    // function declarations
    a: `points to function`
    // variable declarations are none here
  },
  this: //not important for this example
}

阶段2:执行阶段

第1行指示查找a并将10赋值给它。这次我们很幸运,因为a可以在我们的bExeContext中找到。

bExeContext = {
  scope: {globalExeContext}
  objForVariables = {
    //the is arguments object for this one but its empty, because we have no arguments for this function
    args:{},
    // function declarations
    a: 10 // no longer points to `points to function`
    // variable declarations are none here
  },
  this: //not important for this example
}

这意味着我们不必去全局空间找a。执行b函数的第2行告诉我们console.log(a);。我们需要解决a是什么,确保它正确。

bExeContext = {
  // blablbalblablbalblablblal     
    // blablbalblablbalblablblalb
    a: 10 // no longer points to `points to function`
    // blablbalblablbalblablblalb
  },
  // blablbalblablbalblablblalb
}

我们在bExeContext中找到了它。我们记录10。我们执行完b函数并将其从执行堆栈中弹出。它不再存在。

现在我们恢复评估全局代码。我们完成了第9行的b();,现在我们在第10行。console.log(a);为了解析a,我们必须先找到它。为了刷新我们的globalExeContext是什么样子,这是最后一张图片:

globalExeContext = {
  //no scope
  objForVariables = {
    //no arguments because its no function
    // function declarations
    b: `points to function`,
    // variable declarations
    a: 1
  },
  this: //not important for this example
}

果然,a1。我们记录下了 1。完成了。

你看,提升是在运行JS代码时实现的。它是将代码位排序的过程,更准确地说,是1.参数 2.函数声明 3.变量声明。当你的代码只有几行或一行,或者根本没有任何函数时,提升就会发生,因为当你尝试扮演解释器并布置代码时,你已经进行了提升。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接