如何理解 JS 函数在定义的时候不执行?

函数1:一个函数声明:

function ff(a){
    alert(a);
    }

函数2:一个函数表达式:

var f=function(){ return (1+2);}

函数只在调用时候执行花括号内的语句,那么在定义函数的时候,都做了哪些工作呢?

比如函数2,var f=function(){ return (1+2);} 是原封不动的把{}花括号内的代码赋值给一个变量吗?
还是说,会计算一下1+2==》var f=function(){ return (3);} ,待调用时返回3。

我在网上和书上都找了相关知识,但好像这个问题太简单了,可以说是一带而过,求解答,谢谢。

阅读 11.1k
6 个回答

在定义函数的时候,都做了哪些工作呢?
There is no silly question! 这是一个好问题啊,ECMA 标准里面有一节专门讲的就是这个问题。

我在网上和书上都找了相关知识
你确定你找对地方了么?这种问题不应该去看 ECMA 标准么?

简单来说,在 JavaScript 中,因为函数是一等对象,定义函数的时候,实际上就是创建了一个对象,就这么简单!不过为了保证这个对象能够调用,它有几个特殊的内部属性/方法:

  • [Call]
  • [[Construct]]
  • [[Scope]]
  • [[Code]]
  • [[FormalParameters]]

当调用函数的时候,实际上就是调用该函数对象的[[Call]]内部方法;
当使用 new 调用函数的时候,实际上就是调用该函数对象的 [[Construct]] 内部方法。

定义函数时,{} 之间的东西,不管是什么,哪怕是空的,全部原封不动地保存在 [[Code]] 内部属性里;

13.2 Creating Function Objects
Given an optional parameter list specified by FormalParameterList, a body specified by FunctionBody, a Lexical Environment specified by Scope, and a Boolean flag Strict, a Function object is constructed as follows:

Create a new native ECMAScript object and let F be that object.
Set all the internal methods, except for [[Get]], of F as described in 8.12.
Set the [[Class]] internal property of F to "Function".
Set the [[Prototype]] internal property of F to the standard built-in Function prototype object as specified in 15.3.3.1.
Set the [[Get]] internal property of F as described in 15.3.5.4.
Set the [[Call]] internal property of F as described in 13.2.1.
Set the [[Construct]] internal property of F as described in 13.2.2.
Set the [[HasInstance]] internal property of F as described in 15.3.5.3.
Set the [[Scope]] internal property of F to the value of Scope.
Let names be a List containing, in left to right textual order, the Strings corresponding to the identifiers of FormalParameterList. If no parameters are specified, let names be the empty list.
Set the [[FormalParameters]] internal property of F to names.
Set the [[Code]] internal property of F to FunctionBody.
Set the [[Extensible]] internal property of F to true.
Let len be the number of formal parameters specified in FormalParameterList. If no parameters are specified, let len be 0.
Call the [[DefineOwnProperty]] internal method of F with arguments "length", Property Descriptor {[[Value]]: len, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false}, and false.
Let proto be the result of creating a new object as would be constructed by the expression new Object()where Object is the standard built-in constructor with that name.
Call the [[DefineOwnProperty]] internal method of proto with arguments "constructor", Property Descriptor {[[Value]]: F, { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}, and false.
Call the [[DefineOwnProperty]] internal method of F with arguments "prototype", Property Descriptor {[[Value]]: proto, { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}, and false.
If Strict is true, then
Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
Call the [[DefineOwnProperty]] internal method of F with arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, and false.
Call the [[DefineOwnProperty]] internal method of F with arguments "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false}, and false.
Return F.
NOTE A prototype property is automatically created for every function, to allow for the possibility that the function will be used as a constructor.

调用函数时,Let result be the result of evaluating the FunctionBody that is the value of F's [[Code]] internal property.,才会对 [[Code]] 属性求值 (evaluate)。

13.2.1 [[Call]]

When the [[Call]] internal method for a Function object F is called with a this value and a list of arguments, the following steps are taken:

Let funcCtx be the result of establishing a new execution context for function code using the value of F's [[FormalParameters]] internal property, the passed arguments List args, and the this value as described in 10.4.3.
Let result be the result of evaluating the FunctionBody that is the value of F's [[Code]] internal property. If F does not have a [[Code]] internal property or if its value is an empty FunctionBody, then result is (normal, undefined, empty).
Exit the execution context funcCtx, restoring the previous execution context.
If result.type is throw then throw result.value.
If result.type is return then return result.value.
Otherwise result.type must be normal. Return undefined.

最近看了下《深入理解计算机系统》这本书,小小解答一下

提问者能提出这样的问题,我想您应该具有javascript的基本知识,如解析阶段和执行阶段变量提升等(好像跟我回答的没啥关系...)

直入主题吧

在计算机系统里,整个程序是分部分存储的,大约有这些部分:

  1. 只读的代码和数据,从文件本身加载进来的,固定大小的
  2. 读/写数据,从文件加载经来的,存放变量为原始类型的值,固定大小
  3. 存放引用变量实际指向值的地方,动态扩展的
  4. 用户栈,运行时创建的,用于函数调用啊,事件等等,动态扩展的
  5. CPU里正在执行的代码,包括当前指令和正执行的数据

程序的存储部分大致这样,再讲讲关键点:

  • 在javascript程序运行的过程中,变量本身在程序运行结束之前是不会被销毁的(即内存不会被回收),它们始终存于读/写数据区。被销毁的只是引用数据,这些引用数据是存于运行时堆区的,引用这些数据的变量的实际值是内存地址。
  • 读/写数据区的大小 + 只读的代码和数据区的大小,在加载至内存时(即解析完成后代码的大小)是固定大小不变的。

在加载javascript代码到计算机时,首先,代码本身会首先被解析,包括代码校验二进制翻译变量提升等等,接着会将解析后的代码加载至只读的代码和数据内存区,最后就开始等待执行了。

所以:

javascriptvar f = function () {
  return (1+2);
}

这段代码在解析完成后并加载至内存时,function的执行逻辑会存于只读的代码和数据区,变量f会存于读/写数据区,在定义的时候(1+2)的值不会计算,只有在执行时才会计算。

好像还是没解释清楚,望提问者指出,继续修正!

小白之手,大神勿喷,如有错误,望请更正!

JavaScript会加载或者说是解析两轮

第一轮:解析加载变量声明,函数声明,对函数的参数赋值

第二轮:给变量赋值,执行函数

这里有例子

当然不会执行……需要传參的函数,执行的时候很可能是需要参数的,甚至是用参数分支定界,没有参数根本没法执行啊

定义函数的时候,给我的感觉类似一个压栈的过程,除了把代码赋给变量之外还附带了上下文,一起压栈了,等到调用的时候再弹出来

javascript秘密花园中,看到“函数声明与表达式”一段。
简单总结一下,就是函数声明在执行前会被解析,这也是为什么函数能够在定义之前执行。

Foo();//函数能够正常执行
function Foo(){ /* some code */}

而另一种方式声明函数(函数赋值表达式)的话,就不能在定义之前执行。

Foo();//报错
var Foo = function(){/* some code */}

这是因为Foo是在执行时才创建的。

我觉得上面大家没有说到点子上吧?
我说下我的理解
首先js是个解释型的语言 但是解释型语言也分为两个阶段 ,解析期和执行期
在解释期时候 js将这些以 function fun()这样的结构 作为一种特殊的结构放入栈中,如果同一个<script type下面的函数 后面的会覆盖前面
而 var fun = function(){} 并不是声明了一个叫fun的函数 而是声明了一个匿名函数 然后fun只是这个匿名函数的一个引用 就好像
function 匿名(){} var fun = 匿名; 一样的 所以var fun是变量的话就涉及到变量提升的概念了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏