25

I understand that JavaScript is interpreted and not compiled. No problem there. However, I keep reading here that JavaScript is executed "on the fly" and that lines are read one at a time. This idea is confusing me quite a bit when it comes to the following example:

writeToConsole();

function writeToConsole() {
    console.log("This line was reached.");
}

For the record, this bit of code will write to the console just fine. Still, how would the browser know of the existence of exampleFunction() if it had not yet reached the function?

In other words, when exactly is this function first interpreted?

4

6 回答 6

60

First, you make an incorrect assumption: modern JavaScript is compiled. Engines like V8, SpiderMonkey, and Nitro compile JS source into the native machine code of the host platform.

Even in older engines, JavaScript isn't interpreted. They transform source code into bytecode, which the engine's virtual machine executes.

This is actually how things in Java and .NET languages work: When you "compile" your application, you're actually transforming source code into the platform's bytecode, Java bytecode and CIL respectively. Then at runtime, a JIT compiler compiles the bytecode into machine code.

Only very old and simplistic JS engines actually interpret the JavaScript source code, because interpretation is very slow.

So how does JS compilation work? In the first phase, the source text is transformed into an abstract syntax tree (AST), a data structure that represents your code in a format that machines can deal with. Conceptually, this is much like how HTML text is transformed into its DOM representation, which is what your code actually works with.

In order to generate an AST, the engine must deal with an input of raw bytes. This is typically done by a lexical analyzer. The lexer does not really read the file "line-by-line"; rather it reads byte-by-byte, using the rules of the language's syntax to convert the source text into tokens. The lexer then passes the stream of tokens to a parser, which is what actually builds the AST. The parser verifies that the tokens form a valid sequence.

You should now be able to see plainly why a syntax error prevents your code from working at all. If unexpected characters appear in your source text, the engine cannot generate a complete AST, and it cannot move on to the next phase.

Once an engine has an AST:

  • An interpreter might simply begin executing the instructions directly from the AST. This is very slow.
  • A JS VM implementation uses the AST to generate bytecode, then begins executing the bytecode.
  • A compiler uses the AST to generate machine code, which the CPU executes.

So you should now be able to see that at minimum, JS execution happens in two phases.

However, the phases of execution really have no impact on why your example works. It works because of the rules that define how JavaScript programs are to be evaluated and executed. The rules could just as easily be written in a way such that your example would not work, with no impact on how the engine itself actually interprets/compiles source code.

Specifically, JavaScript has a feature commonly known as hoisting. In order to understand hoisting, you must understand the difference between a function declaration and a function expression.

Simply, a function declaration is when you declare a new function that will be called elsewhere:

function foo() {

}

A function expression is when you use the function keyword in any place that expects an expression, such as variable assignment or in an argument:

var foo = function() { };

$.get('/something', function() { /* callback */ });

JavaScript mandates that function declarations (the first type) be assigned to variable names at the beginning of an execution context, regardless of where the declaration appears in source text (of the context). An execution context is roughly equatable to scope – in plain terms, the code inside a function, or the very top of your script if not inside a function.

This can lead to very curious behavior:

var foo = function() { console.log('bar'); };

function foo() { console.log('baz'); }

foo();

What would you expect to be logged to the console? If you simply read the code linearly, you might think baz. However, it will actually log bar, because the declaration of foo is hoisted above the expression that assigns to foo.

So to conclude:

  • JS source code is never "read" line-by-line.
  • JS source code is actually compiled (in the true sense of the word) in modern browsers.
  • Engines compile code in multiple passes.
  • The behavior is your example is a byproduct of the rules of the JavaScript language, not how it is compiled or interpreted.
于 2013-03-13T21:16:08.993 回答
11

All functions will be examined first by the browser before any code is executed.

However,

var foo = function(){};

This will not be examined, and thus the following will throw a TypeError: undefined is not a function

foo();
var foo = function(){};
于 2013-03-13T20:05:55.873 回答
10

It does take 2 passes. The first pass parses the syntax tree, a part of which is performing hoisting. That hoisting is what makes your posted code work. Hoisting moves any var or named function declarations function fn(){} (but not function expressions fn = function(){}) to the top of the function they appear in.

The second pass executes the parsed, hoisted, and in some engines compiled, source code tree.

Check out this example. It shows how a syntax error can prevent all execution of your script by throwing a wrench in the first pass, which prevents the second pass (actual code execution) from ever happening.

var validCode = function() {
  alert('valid code ran!');
};
validCode();

// on purpose syntax error after valid code that could run
syntax(Error(

http://jsfiddle.net/Z86rj/

No alert() occurs here. The first pass parsing fails, and no code executes.

于 2013-03-13T20:09:33.383 回答
3

The script is first parsed, then interpreted, and then executed. When the first statement (writeToConsole();) is executed, the function declaration has already been interpreted.

Since all variable and function declarations are hoisted in the current scope (in your case, the global script scope), you will be able to call a function that was declared below.

于 2013-03-13T20:07:58.107 回答
1

JavaScript is actually interpreted line by line. BUT, before it's executed, there is a first pass made by the compiler, reading certain things in (pretty geeky, have a look at this: https://www.youtube.com/watch?v=UJPdhx5zTaw if you're really interested).

The point is, JavaScript will be first "read" by the compiler, who stores functions defined as function foo(){...} already. You can call those ones at any given time in the script, given you are calling them from the same or a subordinate scope. What modern compilers also do is preallocate objects, so as a side effect it does make sense to strongly type your variables for the matter of performance.

var foo = function(){...} will not be stored by the compiler, due to the fact that JavaScript is loosely typed and the type of a variable could change during execution time.

于 2013-03-13T20:14:56.790 回答
0

The javascript engine create a execution context before executing your code. The execution context is created mainly in two phases:

  • Creation phase

  • Excecution phase

Creation Phase

In the creation phase we have the Global object, this and outer environment reference.In the creation phase as the parser runs through the code and begins to setup what we have written for translation, it recognizes where we have created variables and where we have created function. Basically it setup memory space for variable and functions Hoisting.

Execution Phase

In this the code is run line by line(interpreting, converting, compiling and executing it on the computer).

consider the following example, in this we are able to call the function b even before its declaration. This is because the javascript engine already knows the existence of the function b. And this is also referred to as hoisting in javascript.

b();

function b() {
  console.log("I have been hoisted");
}

于 2019-03-24T10:03:48.917 回答