[Discourse] Paraphrase SCOPE in one time

本文深入探讨了JavaScript的动态特性,解析其从源代码到执行的三步过程:标记化/词法分析、解析和代码生成。重点讲解了词法作用域的概念,包括查找变量的流程、作用域链的构建,以及eval()和with语句如何在运行时修改作用域。同时,文章对比了函数声明与表达式的区别,并讨论了立即调用函数表达式(IIFE)的使用。最后,提到了块级作用域的实现方式以及let关键字的引入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

What is scope

There are 3 steps undergo before executed

It may be self-evident, JavaScript falls under the general category of “dynamic” or “interpreted” languages, it is in fact a compiled language. It is not compiled well in advance nor are the results of compilation portable among various distributed systems. Albeit in more sophisticated ways than we may commonly be aware, but it will undergo roughly typically three steps before it is executed.

  • Tokenizing/Lexing

    Breaking up a string of characters into meaningful (to the language) chunks, called tokens. For instance, consider the program var a = 2;. This program would likely be broken up into the following tokens: var, a, =, 2, and ;. Whitespace may or may not be persisted as a token, depending on whether its meaningful or not.

  • Parsing

    taking a stream (array) of tokens and turning it into a tree of nested elements, which collectively represent the grammatical structure of the program. This tree is called an “AST” (abstract syntax tree). The tree for var a = 2; might start with a top-level node called VariableDeclaration, with a child node called Identifier (whose value is a), and another child called AssignmentExpression, which itself has a child called NumericLiteral (whose value is 2).

  • Code-Generation

    The process of taking an AST and turning it into executable code. This part varies greatly depending on the language, the platform it’s targeting, and so on. So, rather than get mired in details, we’ll just handwave and say that there’s a way to take our previously described AST for var a = 2; and turn it into a set of machine instructions to actually create a variable called a (including reserving memory, etc.), and then store a value into a.

For JavaScript, the compilation that occurs happens, in many cases, mere microseconds (or less!) before the code is executed. Any snippet of JavaScript has to be compiled before (usually right before!) it’s executed.

lexical scope

Lexical scope is based on where variables and blocks of scope are authored, by you, at write time, and thus is (mostly) set in stone by the time the lexer processes your code.

Scope look-up always starts at the innermost scope being executed at the time, and works its way outward/upward until the first match, and stops.

The lexical scope look-up process only applies to first-class identifiers, such as the a, b, and c. If you had a reference to foo.bar.baz in a piece of code, the lexical scope look-up would apply to finding the foo identifier, but once it locates that variable, object property-access rules take over to resolve the bar and baz properties, respectively.

1396824-20181030182944447-678413941.png

  • firstly, go innermost
  • then foremost
  • next to the outermost
Lexical can be cheated

How could there possibly be a way to “modify” (a.k.a., cheat) lexical scope at runtime? The answer is yes and the following two mechanisms are frowned upon in the wider community as bad practices to use in your code.

  • eval()

    The eval(..) function can programatically generate code inside of your authored code, and run the generated code as if it had been there at author time.
    eval(..) when used in a strict-mode program operates in its own lexical scope, which means declarations made inside of the eval() do not actually modify the enclosing scope.

function foo(str, a) {

 // When strict model, the result will be "1, 2"

 // "use strict"

 eval( str ); // cheating!

 console.log( a, b );

}

var b = 2;

foo( "var b = 3;", 1 ); // 1, 3
  • with()

    with() is typically explained as a shorthand for making multiple property references against an object without repeating the object reference itself each time.
    There’s much more going on here than just a convenient shorthand for object property access


function foo(obj) {
 with (obj) {
 a = 2;
 }
}

var o1 = {
 a: 3
};

var o2 = {
 b: 3
};

foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2—Oops, leaked global!

When we pass in o2, since it does not have an a property, no such property is created, and o2.a remains undefined.
But then we note a peculiar side-effect, the fact that a global variable a was created by the a = 2 assignment.

with() essentially creates a whole new lexical scope (again, at run‐time) by treating an object reference as a scope and that object’s properties as scoped identifiers.

Performance

To the performance interest, the conclusion will boil down(煮沸,归结为) to the below wrap-up.

The downside to these mechanisms is that it defeats the engine’s ability to perform compile-time optimizations regarding scope look-up, because the engine has to assume pessimistically that such optimizations will be invalid. there’s no getting around the fact that without the optimizations, code runs slower.

Collision Avoidance

function foo() {
 function bar(a) {
 i = 3; // changing the i in the enclosing scope's
 // for-loop
 console.log( a + i );
 }

 for (var i=0; i < 10; i++) {
 bar( i * 2 ); // oops, inifinite loop ahead!
 }
}

foo();
function declaration and function expression

The key difference we can observe here between a function declaration and a function expression relates to where its name is bound as an identifier. The name of normal function is bound to the enclosing scope, it's a function declaration; but the immediate invoking function is not bound to the enclosing scope then it's a function expression.

Function expressions can be anonymous, but function declarations cannot omit the name. We tend to encourage this idiomatic style of code(With a name).

They have several drawbacks to consider if a functioin without a name:

  • Anonymous functions have no useful name to display in stack traces, which can make debugging more difficult.
  • Without a name, if the function needs to refer to itself, for recursion, etc., the deprecated arguments.callee reference is unfortunately required. Another example of needing to self-reference is when an event handler function wants to unbind itself after it fires.
  • Anonymous functions omit a name, which is often helpful in providing more readable/understandable code. A descriptive name helps self-document the code in question.
IIFE(immediately invoked function expression)

A variety of IIFE variation.


var a = 2;
(function IIFE(){
 var a = 3;
 console.log( a ); // 3
})();

console.log( a ); // 2

(function(){
  console.log(123);
}())

The 2 above forms are purely a stylistic choice which you prefer.


var a = 2;
(function IIFE( global ){
 var a = 3;
 console.log( a ); // 3
 console.log( global.a ); // 2
})( window );
console.log( a ); // 2

We have a clear stylistic delineation for global versus nonglobal references.

undefined = true; // setting a land-mine for other code! avoid!
(function IIFE( undefined ){
 var a;
 if (a === undefined) {
 console.log( "Undefined is safe here!" );
 }
})();

This pattern addresses the (minor niche) concern that the default undefined identifier might have its value incorrectly overwritten, causing unexpected results.

var a = 2;
(function IIFE( def ){
 def( window );
})(function def( global ){
 var a = 3;
 console.log( a ); // 3
 console.log( global.a ); // 2
});

Some people find it a little cleaner to understand, though it is slightly more verbose.

JavaScript only has some strange niche behaviors that expose block scope functionality.

Like for and if do not have block scope facility. However, with and try/catch have the block scope.

let

letsits alongside var as another way to declare variables.

The let keyword attaches the variable declaration to the scope of whatever block (commonly a { .. } pair) it’s contained in. In other words, let implicitly hijacks any block’s scope for its variable declaration.

转载于:https://www.cnblogs.com/steadfast-JSer/articles/9878640.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值