• fatme
    2019-11-14
    声明和语句的区别在于发生的时间点不同,声明发生在编译期,语句发生在运行期。声明发生在编译期,由编译器为所声明的变量在相应的变量表,增加一个名字。语句是要在运行时执行的程序代码。因此,如果声明不带初始化,那么可以完全由编译器完成,不会产生运行时执行的代码。

    作者回复: +1 ^^.

    
     28
  • 铭
    2019-11-13
    〈以下是小生愚见〉
    概念纷繁,建议老师将讲解重心放到这门语言的现有特性,贯之历史脉络,是否(怎样)解决了某种设计缺陷。这样,知识纵深感更强,并可指导实际工作以避免踩坑。适当穿插示例代码和图文更佳。

    作者回复: 多谢。我在后面的课程中尽量注意这个 ^^.

    
     10
  • 海绵薇薇
    2019-11-19
    hello,老师好啊,研读完文章和评论后还存在如下疑问:

    1.

    var y = "outer";

    function f() {

        console.log(y); // undefined

        console.log(x); // throw a Exception

        let x = 100;

        var y = 100;

        ...

    }

    老师解释函数内部读取不到外部变量的原因是“函数创建的时候标识符x和y就被创建了”。因为这是一个函数声明,也就是在编译的时候就创建了。

    高程上的意思是函数执行的时候会生成一个活动对象当做变量对象,这时候标识符才会生成,包括arguments,形参实参,声明的变量,挂在活动对象上。

    两个解释好像都能说明上面的现象。

    有点糊涂了。

    2.

    function a() {

        function b() {}

    }

    在代码执行前连函数b都被创建了吗?

    3. 老师对一定了解闭包的本质,后面有机会说到吗?
    展开

    作者回复: “因为这是一个函数声明,也就是在编译的时候就创建了”
    ===
    这个是你混乱的根源,因为从“函数声明”到“闭包”之间还有好几个环节的。

    “函数创建的时候标识符x和y就被创建了”,这个函数内的x和y都是可以被parser到的,因此在函数静态解析完之后,函数就可以确定内部有x和y了,并且相应的静态作用域也(在语法处理相关的内核逻辑部分)被创建了。但是这些东西是用户代码完全不可见的,你不会知道,也用不到。

    然后是一个函数作为表达式得到它的一个实例的过程,也就是

    > (function () {}).xxxxx

    中“.xxxx”之前的部分作为表达式被独立处理的时候,这种情况下函数会有一个自己的环境,该环境也是“基于前面得到的静态作用域”来创建的。这个环境仍然不为用户所知,只是它能传递,例如你可以把这样的东西“返回(return)”或“赋值(=)”给别的东西,你会发现这个东西的“环境/执行上下文”并没有变,所以它们在“返回(return)”或“赋值(=)”的过程之前就被创建了,并且能被这些操作所“传递”。

    最后才是闭包,闭包只发生于“f()”的这个“调用操作()”之后——注意是“之后”,所以高程等等都是说它在“执行之后”标识符才会生成,而我这一章都是在讲“静态语义”,所以我会说他在函数(作为一个静态的对象)创建的时候就已经有这些标识符了。

    这两者都不矛盾。根本上来说,函数实例、函数闭包,都不过是“静态词法解析结果(函数定义/(un)Anonymous Function Definition)”的一个映像,这个“函数定义”就是第4小节讲的那个东东。

    Ok,既然“x,y都是代码执行前就被(静态分析)创建了的”,那么哪种情况下"x"才不是“词法的”呢?

    function f() { let y; return x+y }

    注意在这个例子中,"x"就不是词法的,它在任何情况下都不被创建,不在f()的标识符列表中,也不在语法/词法分析中。

    Ok. 这是第1个问题。

    关于第2个问题,答案其实如上所述:如果“创建”是指静态语义中的一个“函数”,那么确实是创建了的。——但它还没有“绑定”到一个闭包的执行上下文中。

    关于第3个问题,是这样的,这一整个课程(系列)其实不只20讲,大概会是40~45讲的样子。但编辑同学不允许我公布后续内容的计划(呜……),所以……我只能告诉你,在下一个课程里面,才会讲到闭包。

     1
     8
  • Mr_Liu
    2019-11-13
    思考题: 小白的我,没有太明确的答案,暂时还不能明确自己理解究竟是否正确,希望听老师后续的课程能够明白

    读完今天的这篇理解了昨天的提问,为什么var x = '123' delete x 是false,
    即使是
    var obj ={
     a: '123',
     b: {
       name: '123'
      }
    }
    var z = obj.b
    delete z 返回也是false
    所以问了那么delete x 存在有什么意义。
    今天老师的科解答了
    x = '123'
    delete x 返回true
    是因为你可以删除掉这个动态添加的“变量”,因为本质上就是在删除全局对象的属性。同时也理解了上一讲的“只有在delete x等值于delete obj.x时 delete 才会有执行意义。例如with (obj) ...语句中的 delete x,以及全局属性 global.x。”这句
    但上一节关于“delete x”归根到底,是在删除一个表达式的、引用类型的结果(Result),而不是在删除 x 表达式,或者这个删除表达式的值(Value)。后一句理解了,但前一句是否可以理解为实际上是删除引用呢,希望老师解答一下
    立一个flag ,每个争取评论下面都有我的,不为别的,就为增加自己的思考
    展开

    作者回复: 是的。

    delete从不删除值。delete只能删除引用,例如obj.x,或者with(obj) delete x,这些都是以引用的方式得到的,所以delete才能删除它们。

    `delete 0`这种行为并不真的能够发生,它什么也没做,只是返回了true而已。

    
     8
  • 佳民
    2019-11-13
    思考题:var声明会声明提升,在语法解析(静态分析)阶段进行,不是在运行阶段执行,这样理解对吗?

    作者回复: 是的。不过所有的6种声明都是如此,非独var声明。

     1
     3
  • 蓝配鸡
    2019-11-13
    醍醐灌顶,但是有一些疑问:
    文中说,
    "如果是在一门其它的(例如编译型的)语言中,“为变量 x 绑定一个初值”就可能实现为“在创建环境时将变量 x 指向一个特定的初始值”。这通常是静态语言的处理方法,然而,如前面说过的,JavaScript 是门动态的语言,所以它的“绑定初值”的行为是通过动态的执行过程来实现的,也就是赋值操作。"

    为什么动态语言就不可以给变量初始化, 一定要使用动态赋值呢?
    我对动态语言的理解是,变量的类型可以在运行时改变,静态语言变量的类型不可以改变。 但是这性质好像并不影响初始化?
    展开

    作者回复: 动态语言的诸多细节,其实要到18讲之后才会讨论到。不过有个概念上的问题,并不是说有动态类型就是动态语言,或者说支持动态执行就是动态语言。有很多方面的“动态语言的特性”,这个需要详细解析。

    仅是说初始化这一项,使用动态赋值的原因是因为这个值必须要到执行期环境中才能确定下来,而在静态语法分析阶段是确认不了的。——所以它不可能“先于环境”而执行。

     1
     3
  • Marvin
    2019-11-14
    相当于
    var/let/const x = (y =100)
    再拆就是
    y=100 // 变量泄漏
    var x=y // 模拟表达式返回值赋值

    作者回复: 是的。简洁,正确。^^.

    
     2
  • Geek__kkkkkkkk
    2019-11-13
    老师,您文章中讲到“ECMAScript 规定在这个全局对象之外再维护一个变量名列表(varNames),所有在静态语法分析期或在 eval() 中使用var声明的变量名就被放在这个列表中。然后约定,这个变量名列表中的变量是“直接声明的变量”,不能使用delete删除。”,这里面的意思我理解了 eval() 中使用var声明的变量名,不可用delete删除,但是下面的代码中eval()声明了b,configurable是true,可删除,是否矛盾了?还是我理解的不够全面?

    作者回复: > 这种情况下使用var声明的变量名尽管也会添加到varNames列表,但它也可以从varNames中移除(这是唯一一种能从varNames中移除项的特例...

    文章中是解释过的。可能我没有足够地强调这个特例,或者它的特殊性吧。

     1
     2
  • Makus
    2019-11-13
    附「语句和声明」的链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements
    严格意义上来说,除了本篇中出现的六种和链接中的五大类都不能算作语句吧。
    例如 :标题中的 y = 100
    麻烦老师指正一下,不太懂

    作者回复: 不着急。会慢慢讲到的。^^.

    还有,我所讲的有些会与MDN有出入,这个你可以先存疑。不必急着得到“YES/NO”这样的答案,慢慢地听到后面就会知道我为什么这么讲了。

    
     2
  • 家家家家
    2019-12-02
    忘了在哪本书中还是哪篇文章中讲过,变量的生命周期:声明阶段、初始化阶段、赋值阶段。
    老师这里讲的静态分析阶段就是指的变量生命周期中的声明阶段对吗?
    对于var,它的声明阶段和初始化阶段是一起发生的,都在静态分析中;对于let,它的声明阶段和初始化阶段是分开的,只有声明阶段在静态分析中,是这样理解的嘛?

    作者回复: 这个讲法倒也不错,比较容易理解。
    不过有些不严谨的地方,比如说,“var x;”这样的声明,“声明阶段和初始化阶段一齐发生”是对的,因为var声明的时候,名字声明并初始化为undefined确实是同时发生的。但是,如果是“var x = 100;”,那么就比较容易混淆了。因为“x = 100”其实是赋值,而不是引擎层面的“初始化”。

    对于let来说,“声明阶段和初始化阶段是分开”其实更不严谨。确切地说,let就没有“初始化阶段”。而用户代码“let x = 100”中的“x = 100”就是赋值阶段了。“let 没有初始化阶段”,所以才会出现它在未赋值之前不能读的现象。

    用这样三个阶段来解释这件事情,跟ECMAScript中的逻辑并不矛盾(而且也可以正确解释),只是细节上需要严谨一点、留意一点就行。

    
     1
  • 授人以摸鱼
    2019-11-22
    发现我好像对全局作用域的理解有一些偏差:
    var,或者没有声明直接赋值,这样的创建标识符(引用)是作为global对象的字段存在的,可以用Object.getOwnPropertyDescriptor从global上读到。
    全局作用域里用let,const创建的变量,虽然也是全局可见,但它并没有创建在global上,而是创建在了另一个地方。从作用域链的视角来看的话,这个作用域要比global低一级这样子。

    作者回复: 你是对的。在JavaScript中,“全局环境”里面的var与let/const是用了两个东西来管理的,所以他们也确实是创建在不同的地方。
    但是从“作用域链”的角度上来说,它们并没有级别高低(也就是parent没有相互指向)。使得它们存取的效果有差别的,是因为“全局环境”采用了词法环境优先(也就是let/const声明)的顺序。

    
     1
  • 陆昱嘉
    2019-11-17
    老师,一个赋值表达式的左边和右边其实“都是”表达式,那么var x=(var y=100);这样就报错,原因是什么?varNames里面的冲突?

    作者回复: “var y = 100”不是表达式,而是语句。

    所以“var x = y = 100”里面:

    - “var x ...”是语句语法(Variable Statement)
    - “= ...”是初始化器(Initializer,严格来说,也不是表达式,而是语法的一部分)
    - “y = 100”是表达式(expression)。

    所以你列的问题,报错的原因是“语句在语法上不支持这种写法”,即在“ = ...”中的“...”上面,必须是一个用来做“初始器”的表达式,而不能“再是一个语句”。

    参见ECMAScript:
    https://tc39.es/ecma262/#sec-variable-statement
    https://tc39.es/ecma262/#prod-VariableDeclaration

    
     1
  • blockman-luz
    2019-11-15
    这一节不知主题是什么?这样讲会造成知识点东一快西一快没有发挥系统学习的效果,更像很多很散的集合

    作者回复: 有主题的哦~ ^^.

    目录中对这一个段落的定义是“JavaScript语言是如何构建起来的”。基本上是讲静态语言语义,以及它在引擎级别上的实现。5个小节,从值、引用等基础,到模块和语句,基本上覆盖构成语言的核心组件。

    可不是在乱讲哦。^^.

     1
     1
  • ssala
    2019-11-13
    老师,像变量提升这种不符合直觉的设计,难道规范不能将其移除吗?不移除是为了兼容老代码吗?我想应该没有代码会依赖这个特性吧?

    作者回复: 一是因为向下兼容,这是规范订制过程中面临的主要挑战之一。第二个原因,在于按照JavaScript的核心设计,在原理上这就很难绕过去。例如就包括不受兼容性影响的import,其实也是有变量提升效果的。例如下面这样的代码也是成立的:

    ```
    console.log(x);
    import x from './test.js';
    ```

    
     1
  • 凯文1985
    2019-11-13
    对于js在词法分析和语法分析中发生的事情还不够了解 希望也能借这个课程讲解一下 :)
    
     1
  • Smallfly
    2019-11-13
    老师文中说变量声明分为两步,静态分析和动态绑定,JS 是在进入每个作用域后,执行前进行词法分析的么?

    作者回复: 是在加载.js文件之后,执行第一行用户代码之前,就已经完成了全部的词法分析(当然,除了eval执行的)。

    
     1
  • 旺旺
    2019-11-13
    简单语法语句背后还有这么多门道.
    声明不是语句,声明只是在告诉编译器,并没有对运行产生影响,是这样吗?

    作者回复: 是的。

    
     1
  • Zheng
    2020-01-14
    老师,我用node执行这段代码,结果是undefined,但是换成浏览器打印就可以打印出来a的配置信息,这是因为node环境和浏览器的差异还是什么,我试过好多次了,应该不是偶然:

    var a = 100;
    x = 200;
    console.log(Object.getOwnPropertyDescriptor(global,"a")); //浏览器执行的时候global改为globalThis
    展开

    作者回复: 是这样的,node缺省情况下是把.js文件当成模块来加载的,它会为每一个模块(亦即是.js文件)包一层所谓的“模块封装器”。这个封装器是一个函数。所以,.js文件中的代码事实上是运行在函数中的。这样一来,`var a`就变成了声明函数内的局部变量,而不是在全局global上的。

    在node的控制台里面输入的代码是作为全局环境下的代码来执行的,因此如果你的代码是在node控制台上逐行执行,就没有问题。另外,如果你使用-e参数来执行,那么也能得到这样的效果(这种情况下node不会添加模块封装器):

    ```
    > node -e "$(cat test.js)" -p
    { value: 100,
      writable: true,
      enumerable: true,
      configurable: false }
    ```


    > NOTE: 模块封装器,参见:http://nodejs.cn/api/modules.html#modules_the_module_wrapper

    
    
  • 許敲敲
    2020-01-10
    var x=y=100;
    delete x;
    delete y;
    ——————
    返回 false,
    返回 true,对吗?
    展开

    作者回复: 是的。
    其中的y是一个“向未声明变量赋值”导致的变量名创建(变量泄露)。这种变量与`var x`这种“var变量声明”的区别,就在于变量泄露名字可以删除。

    
    
  • Elmer
    2019-12-23
    文中提到:ECMAScript 规定在这个全局对象之外再维护一个变量名列表(varNames)
    那么window是怎么取到这些变量的值的,如window.a
    不是平级么。在global scope中, window var let const 的关系是什么。
    求讲解

    作者回复: 这里说到的 varNames在绝大多数情况下是没用的,与你这里想讨论的东西关系也不大。

    window在浏览器环境中等同于global。你在浏览器控制台上查看一下就明白了:

    ```
    # 这个globalThis是新EMAScript规范中声明的,它等义于传统的global
    > window === globalThis
    true

    # 用下面的代码可以获得“传统的global”,并比较之
    > window === Function('return this')()
    true
    ```

    所以所谓window,就是global,相同的东西。那么window中取变量也就是查global这个对象的属性表,之前也都说过了,不再讲了。至于var/let/const之间的关系,在全局域(global scope)中,var声明在global的属性表中,并在varNames中有一个登记;let/const共享使用同一个称为词法环境(lexicallyEnv)的东西,所以它们不能同名。词法环境在后面会讲到。

    因此从原理上来说,全局作用域中的global对象属性,与词法环境中的名字其实是可以重名的。——所以,var与let/const的重名限制,主要是来自于语法。

    例如:

    ```
    # 添加属性
    > global.n = 100

    # 看起来有了全局变量的样子(实际上没有在varNames中登记)
    > n
    100

    # 现在可以声明let
    > let n = 200

    # 这是一个Let变量
    > n
    200

    # 如果你使用var声明,则与let/const会有重名冲突了
    > var x = 300
    > let x = 400
    SyntaxError: Identifier 'x' has already been declared
    ```

    
    
我们在线,来聊聊吧