JavaScript核心原理解析
周爱民
《JavaScript语言精髓与编程实践》作者,南潮科技(Ruff)首席架构师
立即订阅
3721 人已学习
课程目录
已更新 26 讲 / 共 21 讲
0/3登录后,你可以任选3讲全文学习。
开篇词 (1讲)
开篇词 | 如何解决语言问题?
免费
从零开始:JavaScript语言是如何构建起来的 (5讲)
01 | delete 0:JavaScript中到底有什么是可以销毁的
02 | var x = y = 100:声明语句与语法改变了JavaScript语言核心性质
03 | a.x = a = {n:2}:一道被无数人无数次地解释过的经典面试题
04 | export default function() {}:你无法导出一个匿名函数表达式
05 | for (let x of [1,2,3]) ...:for循环并不比使用函数递归节省开销
从表达式到执行引擎:JavaScript是如何运行的 (6讲)
06 | x: break x; 搞懂如何在循环外使用break,方知语句执行真解
07 | `${1}`:详解JavaScript中特殊的可执行结构
08 | x => x:函数式语言的核心抽象:函数与表达式的同一性
09 | (...x):不是表达式、语句、函数,但它却能执行
10 | x = yield x:迭代过程的“函数式化”
11 | throw 1;:它在“最简单语法榜”上排名第三
从原型到类:JavaScript是如何一步步走向应用编程语言的 (6讲)
12 | 1 in 1..constructor:这行代码的结果值,既可能是true,也可能是false
13 | new X:从构造器到类,为你揭密对象构造的全程
14 | super.xxx():虽然直到ES10还是个半吊子实现,却也值得一讲
15 | return Object.create(new.target.prototype):做框架设计的基本功:写一个根类
16 | [a, b] = {a, b}:让你从一行代码看到对象的本质
17 | Object.setPrototypeOf(x, null):连Brendan Eich都认错,但null值还活着
从粗通到精通的进阶之路:唯一不变的是变化本身 (5讲)
18 | a + b:动态类型是灾难之源还是最好的特性?(上)
19 | a + b:动态类型是灾难之源还是最好的特性?(下)
20 | (0, eval)("x = 100") :一行让严格模式形同虚设的破坏性设计(上)
21 | (0, eval)("x = 100") :一行让严格模式形同虚设的破坏性设计(下)
22 | new Function('x = 100')();:函数的类化是对动态与静态系统的再次统一
不定期加餐 (3讲)
加餐 | 捡豆吃豆的学问(上):这门课讲的是什么?
免费
加餐 | 捡豆吃豆的学问(下):这门课该怎么学?
免费
加餐 | 让JavaScript运行起来
免费
JavaScript核心原理解析
登录|注册

20 | (0, eval)("x = 100") :一行让严格模式形同虚设的破坏性设计(上)

周爱民 2019-12-30
你好,我是周爱民。
今天我们讨论动态执行。与最初的预告不同 ,我在这一讲里把原来的第 20 讲合并掉了,变成了 20~21 的两讲合讲,但也分成了上、下两节。所以,其实只是课程的标题少了一个,内容却没有变。
动态执行是 JavaScript 最早实现的特性之一,eval() 这个函数是从 JavaScript 1.0 就开始内置了的。并且,最早的 setTimeout() 和 setInterval() 也内置了动态执行的特性:它们的第 1 个参数只允许传入一个字符串,这个字符串将作为代码体动态地定时执行。
NOTE:setTimeout/setInterval 执行字符串的特性如今仍然保留在大多数浏览器环境中,例如 Safari 或 Mozilla,但这在 Node.js/Chrome 环境中并不被允许。需要留意的是,setTimeout/setInterval 并不是 ECMAScript 规范的一部分。
关于这一点并不难理解,因为 JavaScript 本来就是脚本语言,它最早也是被作为脚本语言设计出来的。因此,把“装载脚本 + 执行”这样的核心过程,通过一个函数暴露出来成为基础特性既是举手之劳,也是必然之举。
然而,这个特性从最开始就过度灵活,以至于后来许多新特性在设计中颇为掣肘,所以在 ECMAScript 5 的严格模式出现之后,它的特性受到了很多的限制。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《JavaScript核心原理解析》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(7)

  • Smallfly
    看了几遍,也参考了规范文档,还是有不少疑问点,希望老师能指点一二。

    1. 自 ES5 开始引入词法环境,但还没有 let 关键字,只能用 var 关键字声明变量,而 var 声明的变量又是属于变量环境的(Variable Environment),那 ES5 是出于什么原因考虑引入词法环境呢?

    2. 词法变量和 var 变量共用一个名字表,因此不能用 let 和 var 声明同名变量,为什么用 var 声明同名变量却可以?

    3. 「环境的本质是”作用域的映射“」 ,这句话应该怎么理解呢。我现有的理解是,环境类似于一个链表形式的作用域,变量的查找就是从当前的作用域逐级向上查找,环境不应该是作用域的集合么?

    这篇文章的标题看都看不懂,期待老师的下一讲……

    作者回复: 1. 这个问题我之前没细想过。但是ES5本来也不是一个突然提出来的东西,它其实是在ES4的基础上的一次“缩减”,并为以后的ES6之类做一个奠基性的事情。所以他们应该是考虑到了今后对词法环境的需求,并且事实上严格模式的提出,也是这样的目的,都是为后面的语言设计做一些基础性的工作。此外,事实上在当时他们已经有意地在设计和使用词法环境了,例如try...catch(x)中的这个x,在IE和Firefox里面它的作用域一直就不同,IE是用变量环境的,而Firefox是用词法环境的。这个争端早于ES5之前就有的,所以问题出在哪里,以及将来需要做成怎样,这些在当时应该已经有明确的选择了,而ES5只不过把其中的冰山一角显露出来了而已。

    2. var的多次声明只算第一次,其它的都是按赋值处理的(准确地说,是按多次绑定值处理的)。

    3. 环境跟作用域的关系,浅层地说,它是后者的一个扩展版本的实现。——这样理解并没有问题。但是从概念层面来说,作用域在文本解析和引擎执行中的语义并不相同,而环境更是清晰地区分了这种不同。最后,环境与作用域的关系,有点类似于“类与对象实例”之间的关系。总之,他们有差异,但不明显。关于这个概念的进化演变过程,我在给D2的演讲《 JS 语言在引擎级别的执行过程》中有讲过,只是目前D2组委会那边还没有视频放出来,回头你可以找来看看。

    2019-12-30
    2
  • 行问
    其实函数中也存在一个类似的例外。但这个处理过程是在函数的环境创建之后,在函数声明实例化阶段来完成的,……

     据我的理解,函数在 JavaScript 中是一等公民,函数提升,但我不懂的是“函数每调用一次,是否函数声明的实例化一次吗”。一直以来就是“定义(声明)一个函数”,再调用,但不知道函数实际背后的逻辑是怎样。

     不知道我的问题,周大看懂了没。感谢

    作者回复: 是的。函数每调用一次,“函数(的声明)”就会实例化一次。

    不过,这个“函数声明的实例化”与你所理解的并不同,它不是说“function f() { ... }”这个函数要被实例化,而是“函数内的那些声明(包括形式参数名和var/let/const声明名字等)”——这些东西需要被实例化。

    也就是说,之所以每次调用一下这个函数f,函数内的那些变量都被初始化为undfefined,且形式参数要跟调用时的实际参数绑定一下,这些都是“函数(的声明)实例化”阶段帮你做掉的。

    还有,这与“函数是一等公民”并没有什么关系,以及与“函数提升”也没有关系。“函数是一等公民”与“函数提升”之间,也没有关系。这些不要混着讲,他们是不同领域或不同层面的概念。——在第22讲的时候,我会略略地提及到“一等公民”的意义的。

    最后,函数(作为声明文本),与函数的引用(作为一个变量名),以及函数的实例(作为一个对象),还有作为函数的闭包(是一个环境/作用域的映射/上下文的参考),这些概念之间有相关性,但并不是一一对应的。例如我常常说的,一个函数的实例,可以有多个闭包。——关于这个示例,你想一下下面这种调用就明白了:

    ```
    function f() {
      // 当前闭包,return当前函数实例,而在外部又调用该函数实例再创建了一个闭包
      // 因此一个函数实例就有了两个闭包(递归就是更多个了)
      return arguments.callee;
    }

    // (类似递归,)与递归的效果是相同的
    f()();
    ```

    2019-12-30
    2
  • qqq
    eval 的间接调用会使用全局环境的对象环境,所有绕过了严格模式,是不是呀

    作者回复: :) 对的。

    但还可以有更深入的分析,把这些东西讲透,也是下一讲的主要目的。

    2019-12-30
    2
  • 许童童
    老师的文章确实是非常有深度的。和其它的文章是完全不一样的。

    作者回复: 多谢多谢。^^.

    2020-01-01
  • 晓小东
    老师还是接上一个问题如下(我试着在之前打印下变量b):
    const a = 100;

    console.log(b);

    let b = 200;
    var a1 = 300;

    为什么console.log(b); 不是出现暂时性死区报错 ReferenceError: Cannot access 'b' before initialization
    而是 Uncaught ReferenceError: b is not defined
    执行脚本前貌似只处理varDecl(变量声明) lexicalDecls(词法声明)没有处理, 与函数环境有所不同,global全局环境的这种处理方式,有什么好处呢(或者出于什么目的,而这样设计,或者就是对象环境与声明环境这种复合环境的特性)。

    作者回复: 'b is not defined'应该是一个不太友好的错误提示。但是ECMAScript只规范了错误的类型,并不规范错误的提示,所以……不同的引擎在这上面的表现确实不一样哦,也没有什么道理可言哦~

    “执行脚本前貌似只处理varDecl(变量声明)lexicalDecls(词法声明)没有处理”,这样的理解不太正确。因为引擎对这两种都是处理的,只不过var变量是绑定了初值的,而let/const不绑定初值(直到执行到它们声明并赋值的那行语句为止)。由于后者不绑定初值,所以出错提示“Cannot access 'b' before initialization”才是正确的意思,并且你按照这个来理解varDecl(变量声明)lexicalDecls(词法声明)就好了。

    至于“b is not defined”,老实说我之前也发现这个东东,我觉得这样提示不对,但没可奈何不是么。:(~

    2019-12-31
  • 晓小东
    老师我遇一个问题,我在刷一个面试题,说全局环境下let,const 没有在window属性下面, 我用chrome Source面版创建一个test.js, 测试了下, 代码如下:

    const a = 100;
    let b = 200;
    var a1 = 300;

    在右侧scope中发现两个scope
    Script:
       a:100
       b: 200
    Global:
        a1: 300

    非严格模式Script貌似跟eval执行有些类似
    > ScriptCtx.VariableEnvironment === globalCtx.LexicalEnvironment
    true
    > ScriptCtx.VariableEnvironment === ScriptCtx.LexicalEnvironment
    false

    但在严格模式下却又与eval不同 与非严格模式貌似没有区别

    可不可以介绍下浏览器执行环境下Script相关执行环境与上下文知识点。

    chrome-devtool > source > Snippets> test.js

    作者回复: 是这样的,全局环境GlobalEnv是一个复合环境,包括一个由global构成的对象环境(objEnv)和一个一般的声明环境(declsEnv),它是双环境组成的,统一交付一个环境存取界面。这个是它的结构(objEnv/declsEnv很类似或者是直接对应你在DevTools's scope中看到的Global/Script)。

    let/const声明会放在declsEnv里面,而var的变量名会通过objEnv来声明,这个之前也讲过了。它们组合而成的,整个的环境叫GlobalEnv,并且用来作为ScriptCtx中的变量环境和词法环境,并且后面的两种环境都指向“同一个”GlobalEnv,没有差别,也不会分开。——再次强调,ScriptCtx的“变量环境和词法环境”是相同的。

    但是你在使用eval的时候,对“变量环境和词法环境”的使用却是有所不同的。eval会自己创建一个执行上下文,称为evalCtx。那么这个上下文中也有“变量环境和词法环境”,它们指向却是不同的。evalCtx.LexicalEnvironment将是新创建的、独立的,用来存放eval(x)的`x`中声明的const/let,并且当eval结束时,就销毁了。而evalCtx.VariableEnvironment将指向全局的ScriptCtx.VariableEnvironment,这样也就指向了GlobalEnv。所以在`x`中声明的var,就丢到了全局环境中,也就写到了global这个对象上面,不会受eval结束的影响。这就是它的完整机制了。

    你可以写一个test.js用node运行起来看看:
    ```
    // 运行中x, y有值
    eval('var x = 100; let y = 200; console.log(x, y)');

    // eval结束后x留在了全局,而y没有了
    console.log(typeof x, typeof y)
    ```

    关于在浏览器环境中的情况,也与这个逻辑没差。只不过DevTools展示那些内部组件(例如Script/Globa/Scope)的时候,使用的术语或者展示方式跟ECMAScript约定不一致罢了。

    2019-12-31
  • Astrogladiator-埃蒂纳度斯
    (0, eval)("x = 100")
    我用typeof (0,eval) 显示这个是函数类型,应该是一个立即执行的函数
    类似 (function(params){})(params);
    "use strict" , eval)("x = 100")
    我用typeof (0,eval) 显示这个是函数类型,应该是一个立即执行的函数
    类似 (function(params){})(params);
    "use strict" 是不是说只是限制了当前上下文的声明环境,但在这个闭包构造的声明环境中并不受此限制?

    作者回复: 不是的,“use strict”其实并没有这样的作用。关于这些,请期待下一讲。^^.

    2019-12-30
收起评论
7
返回
顶部