Gist: 为什么Console中允许多次声明同名变量、常量、类?

363 阅读2分钟

为什么在Console中分多次输入同名const aconst aclass a并不提示Identifier 'a' has already been declared 搜索上面提示中的字符串,找到在 node/deps/v8/src/execution/execution.cc 中有一个NewScriptContext函数:

NewScriptContext

MaybeHandle<Context> NewScriptContext(Isolate* isolate,
                                      Handle<JSFunction> function) {
  // Creating a script context is a side effect, so abort if that's not
  // allowed.
  if (isolate->debug_execution_mode() == DebugInfo::kSideEffects) {
    isolate->Throw(*isolate->factory()->NewEvalError(
        MessageTemplate::kNoSideEffectDebugEvaluate));
    return MaybeHandle<Context>();
  }
  SaveAndSwitchContext save(isolate, function->context());
  SharedFunctionInfo sfi = function->shared();
  Handle<Script> script(Script::cast(sfi.script()), isolate);
  Handle<ScopeInfo> scope_info(sfi.scope_info(), isolate);
  Handle<NativeContext> native_context(NativeContext::cast(function->context()),
                                       isolate);
  Handle<JSGlobalObject> global_object(native_context->global_object(),
                                       isolate);
  Handle<ScriptContextTable> script_context(
      native_context->script_context_table(), isolate);

  // Find name clashes.  ① 查找名字冲突
  for (int var = 0; var < scope_info->ContextLocalCount(); var++) {
    // 遍历上下文中所有local变量
    Handle<String> name(scope_info->ContextLocalName(var), isolate);
    // local变量被声明的方式
    // let   -> VariableMode::kLet
    // const -> VairableMode::kConst
    // var   -> VariableMode::kVar
    VariableMode mode = scope_info->ContextLocalMode(var);
    ScriptContextTable::LookupResult lookup;
    if (ScriptContextTable::Lookup(isolate, *script_context, *name, &lookup)) {
      if (IsLexicalVariableMode(mode) || IsLexicalVariableMode(lookup.mode)) {
        Handle<Context> context = ScriptContextTable::GetContext(
            isolate, script_context, lookup.context_index);
            
        // If we are trying to re-declare a REPL-mode let as a let or REPL-mode
        // const as a const, allow it.
        // REPL-mode允许多次声明
        
        if (!(((mode == VariableMode::kLet &&
                lookup.mode == VariableMode::kLet) ||
               (mode == VariableMode::kConst &&
                lookup.mode == VariableMode::kConst)) &&
              scope_info->IsReplModeScope() &&
              context->scope_info().IsReplModeScope())) {
          // ES#sec-globaldeclarationinstantiation 5.b:
          // If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError
          // exception.
          // ② 这部分算法描述在:
          // https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
          MessageLocation location(script, 0, 1);
          return isolate->ThrowAt<Context>(
              isolate->factory()->NewSyntaxError(
                  MessageTemplate::kVarRedeclaration, name),
              &location);
        }
      }
    }

    // 如果是let、const声明的变量,还要检查是否与global上的“不可删除的属性名”(configurable=false)冲突
    if (IsLexicalVariableMode(mode)) {
      LookupIterator it(isolate, global_object, name, global_object,
                        LookupIterator::OWN_SKIP_INTERCEPTOR);
      Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it);
      // Can't fail since the we looking up own properties on the global object
      // skipping interceptors.
      CHECK(!maybe.IsNothing());
      if ((maybe.FromJust() & DONT_DELETE) != 0) {
        // ES#sec-globaldeclarationinstantiation 5.a:
        // If envRec.HasVarDeclaration(name) is true, throw a SyntaxError
        // exception.
        // ES#sec-globaldeclarationinstantiation 5.d:
        // If hasRestrictedGlobal is true, throw a SyntaxError exception.
        MessageLocation location(script, 0, 1);
        return isolate->ThrowAt<Context>(
            isolate->factory()->NewSyntaxError(
                MessageTemplate::kVarRedeclaration, name),
            &location);
      }

      JSGlobalObject::InvalidatePropertyCell(global_object, name);
    }
  }
  Handle<Context> result =
      isolate->factory()->NewScriptContext(native_context, scope_info);

  result->Initialize(isolate);

  Handle<ScriptContextTable> new_script_context_table =
      ScriptContextTable::Extend(script_context, result);
  native_context->synchronized_set_script_context_table(
      *new_script_context_table);
  return result;
}

IsLexicalVariableMode

所有mode=kLet或kConst返回true

inline bool IsLexicalVariableMode(VariableMode mode) {
  STATIC_ASSERT(static_cast<uint8_t>(VariableMode::kLet) ==
                0);  // Implies that mode >= VariableMode::kLet.
  return mode <= VariableMode::kLastLexicalVariableMode;
}

VariableMode

// The order of this enum has to be kept in sync with the predicates below.
enum class VariableMode : uint8_t {
  // User declared variables:
  kLet,  // declared via 'let' declarations (first lexical)

  kConst,  // declared via 'const' declarations (last lexical)

  kVar,  // declared via 'var', and 'function' declarations

  // Variables introduced by the compiler:
  kTemporary,  // temporary variables (not user-visible), stack-allocated
               // unless the scope as a whole has forced context allocation

  kDynamic,  // always require dynamic lookup (we don't know
             // the declaration)

  kDynamicGlobal,  // requires dynamic lookup, but we know that the
                   // variable is global unless it has been shadowed
                   // by an eval-introduced variable

  kDynamicLocal,  // requires dynamic lookup, but we know that the
                  // variable is local and where it is unless it
                  // has been shadowed by an eval-introduced
                  // variable

  // Variables for private methods or accessors whose access require
  // brand check. Declared only in class scopes by the compiler
  // and allocated only in class contexts:
  kPrivateMethod,  // Does not coexist with any other variable with the same
                   // name in the same scope.

  kPrivateSetterOnly,  // Incompatible with variables with the same name but
                       // any mode other than kPrivateGetterOnly. Transition to
                       // kPrivateGetterAndSetter if a later declaration for the
                       // same name with kPrivateGetterOnly is made.

  kPrivateGetterOnly,  // Incompatible with variables with the same name but
                       // any mode other than kPrivateSetterOnly. Transition to
                       // kPrivateGetterAndSetter if a later declaration for the
                       // same name with kPrivateSetterOnly is made.

  kPrivateGetterAndSetter,  // Does not coexist with any other variable with the
                            // same name in the same scope.

  kLastLexicalVariableMode = kConst,
};