作者回复: Oh~ 哈哈,你是说昨天有一个问题我只回复了3,没有回复1和2两条吗?那两条,是全对的,所以……嗯嗯,我只是没有回复确认而已。你对ECMAScript中的“引用规范类型”的使用场景和过程推演都是正确的。
关于今天的前3个问题,1是正确的。
2你也是对的。但是有一点,这个x的确会得到一个引用,称为(UnresolvableReference)。而这一段逻辑在ECMAScript里面写的是“if IsUnresolvableReference, then return true”。也就是说,ECMAScript约定对于这种情况就是这么返回的,这属于规范约定(并且如果在这时发现是严格模式,就抛异常了)。关于这里,你可以看一看:
https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation
不过问题3,你倒是提到了一个“少有人知”问题,哈哈,这个问题我是漏讲了,而且其实还挺有趣、挺关键的。
是这样,早期的JavaScript中,undefined是一个特殊值,是在运行期中通过void运算,或者不返回值的函数,又或者一个声明了但未赋值的变量,等等类似这样的情况来“计算得到”的。所以在JavaScript的早期版本中,你没有办法直接判断“undefined是undefined”,例如无法写出“x === undefined”这样的代码,而你只能写类似“typeof(x) ==='undefined'”这样的代码。
后来(其实也没有太久),规范就约定把undefined作为可以缺省访问的名字,类似于null。但是这个时候就带来了一个矛盾,因为这个undefined很重要,早期的绝大多数框架或引擎都把它作为一个“全局名字”给声明了。也就是说,ECMAScript现在既没有办法将它规范成一个keyword,也没有办法处理成保留字等等,它看起来像null,但又没有办法在规范层面强制它。所以……ECMAScript就搞了一个“奇招”:
> 我们把undefined声明成全局的属性,怎么样?!
嗯嗯,很好。所以你看,现在的引擎上面undefined看起来长得跟null值差不多,而且在ECMAScript规范中它们都还是平级的(是原始值),而且它们的作用也很接近,最后他们都还是从最初的JavaScript 1.x中就存在的概念,但是undefined/null两者却在实现上完全不同:undefined是一个全局属性,而null是一个关键字。
由于undefined是全局属性,所以`delete undefined`其实就是`delete global.undefined`,是删除引用,而不是删除值。而这个属性是只读的,所以就返回false了。
例如你可以试试下面的代码:
> Object.getOwnPropertyDescriptor(global, 'undefined')
{ value: undefined,
writable: false,
enumerable: false,
configurable: false }
作者回复: 赞的!+1
其实第1个问题的潜在问题是:这种情况下,x是什么呢?它显然是语法可以识别的东西,但如果这样,在语法上它是什么,且在执行环境中它又是什么?
而第二个问题的答案,其实也会回到第一个问题上。如果是在严格模式上,第一个问题的答案是什么?并且,为什么它们不同?
所以,呵呵,其实细一点的看,这两个问题还可以挖更多的呢。^^.
作者回复: 谢谢 @潇潇雨歇
作者回复: 强烈点赞!你这个就属于一通百通的例子。弄明白了Result用来做引用和值的方法/原理,一些具体现象就迎刃而解了!
^^.
作者回复: 其实这一讲的核心是关于“引用/值”在ECMAScript规范类型中的使用与理解,而不是(不仅仅是)delete的使用。所以呢,解释delete这个操作的种种现象,最好是在ECMAScript规范所讨论的语言模型中来叙述,这样更容易讲得清楚。
比如说,`x`如果是一个属性(包括是global的属性),那么`delete x`的是否成功就取决于属性描述符,以及属性存取的过程(是否在严格模式中等等)。这样就Ok了,而不需要细致地列举每一种情况。
作者回复: 这个问题就牵扯得大了。
最早javascript中是没有明确、显式的global这个对象的,在宿主环境(例如浏览器中)你可以用window.x去访问它,这算是宿主在实现引擎的时候的约定。但是,仅只从引擎的角度上来说,既没有window,也没有global,更没有Global。所以,全局的变量虽然是作为全局属性名存在着,却没有办法写成global.x这样的引用。
而global这个全局名字,直到现在在ECMAScript中都还是个没被规范的东西。TC39有一个提案(https://github.com/tc39/proposal-global)专门来定义它,现在到了stage3,应该不会被否决了。但即使如此,这个东东也不叫global,而改名成了globalThis。——原本提案阶段是叫global的,但应用中有问题,所以就改了。
关于globalThis这个说法,又得是一段历史了。因为早期的JavaScript约定普通函数在“不作为对象方法调用”的时候,this值默认指向这个全局的global。所以,这也就是著名的代码“global = (new Function('return this')()”,或“global = Function('return this')()”的由来。
^^.
作者回复: 对于array来说,你理解为一个普通对象就可以了,只是一些array原型上的方法能帮助你处理array.length这个属性而已。
有多少个有效的element,那么就有多少个同名的(数字下标的)属性;而array.length记录着这个最大值。除了这一点,没有任何与其它对象不同。
所以你用array.pop()或array.push()等操作,甚至直接使用array[i]都可以影响到array.length这个属性——因为这些操作内部都会处理它。但是,你用delete去根本不会处理这个属性——因为delete是把array[i]当一个一般属性处理的,根本不知道array.length的存在。
例如:
```
> x = new Array(8)
> x.length
8
> x[x.length] = 8 // add to last
> x.length
9
> x.push(10) // push
10
> x.pop() // pop
> x.length
9
> delete x[8]
true
> x.length
9
```
至于删除delete array[2],则array[2]位置上是undefined,这个与delete操作无关。而是因为你“读取一个不存在的属性,它的值就是undefined”。
作者回复: 关于3,我一般是用Result来表达它是表达式执行结果的“未决状态”。就是执行出结果来了,但没确定是作为lrs还是rhs,所以这种情况下,它就是未决的。
当你确定了一个Result用作lrs,那么它就是引用;如果确定它用作rhs,那么它就是值(将由引擎隐式地调用`GetValue()`)。
作者回复: :)
+1
作者回复: 在后面我会再着重地讲到JavaScript对面向对象的理解。
如今我们对OOP的理解其实添加了太多应用的色彩。事实上,JavaScript对OOP的理解是很精彩、很学术,以及很完整的。不过这些内容大概要到第11讲之后了。
至于“面向对象”还是“基于对象”,其实JavaScript 1.0是有类而无继承的,而JavaScript 1.1才开始使用原型来实现继承,这个时候它又抛弃了(严格意义上的)类。
当然,上面看起来有点儿绕着你的问题在讲。所以,如果再确切地、准确无误地回复你的问题,那么应该是说:所谓面向对象的三个原则(封装、继承与多态),在严格意义上,后两者是多余的。所以不必过度去强调这些性质之于面向对象的重要性。
作者回复: 这个这个,惭愧呀惭愧~
我的口语不是一点半点的糟糕(当然,其实不仅仅只是口语糟糕)。我尽量……注意……后面的课程~ 多谢多谢~
惭愧呀~
:(~
作者回复: 赞的!就是这样!
作者回复: 那三本书很不错!值得一读。^^.
作者回复: 这就是“传统中的‘引用’”用来解释这类现象的时候出现的麻烦。很典型的一个例子,话表达的是正确的,内容是正确的,说法也正确,就是特别特别难于理解。
首先,“值类型中的字符串”是指什么呢?是指typeof(x) === 'string'中的那个`x`。在传统的javaScript概念中,这样的x是值类型,而不是引用类型。
那么值“该怎么赋值和传递”呢?如果x的值是1,那么y = x的话,就是把1这个值“抄写”到y里面去。这是“正常的值”的处理方法,但是如果“字符串值”也这么处理,就完蛋了,因为字符串可能无数多个字符,那么当`y = x`按照“正常的值处理方法”来实现的话,这个“值的复制”的开销就受不了。
所以:
1. “值类型中的字符串”,是指照
2. “引用来赋值和传递引用”的;且,
3. 它是只传递引用(而不是传递值)的。
如上。只是说起来特别麻烦而已。
作者回复: 问题1中,你的思考方向错了。`delete a`不起作用的原因是`var`声明导致的,而不是因为`a 是基本数据类型`。举例来说,
`with (x = {a: 100}) delete a;`
这个例子的结果中x.a是不存在的,但`a 也是基本数据类型`呀。所以是无关的。
“delete x 还有什么存在的意义么”这个问题我之前回复过另一个留言,你找找。
关于问题二,关键在于你所理解的“引用与值”,跟JavaScript内部所理解的“引用与值”是不一样的。也正是因此,我在这一讲的一开始用大量文字讨论了二者的区别。简单地来说,如果有表达式`x = x`,那么同一个变量`x`,在上述表达式中,左侧的这个是它的引用,左侧的是它的值。如果放在代码中看:
x = 5; // 在JavaScript语言中,'5'是“值类型”
x = x; // 在ECMAScript规范中,左侧是“引用x”,右侧是“值x”。
我一直用“结果(Result)”来强调表达式“表达式计算的结果”,就是因为对于`x = x`来说,左侧和右侧都是表达式,左侧的结果是“lhs/引用(reference)”,而右侧的结果是“rhs/值(value)”。
所以所谓“结果(Result)”,在不明确它的手性或用处之前,是二个意思都包含的。
作者回复: > 在 ECMAScript 规范中,引用的构成至少需要 base value、referenced name、strict reference flag,具体引擎实现应该会把它们封装成一种数据结构来,从而来操作引用。
在ECMAScript中,它就是一种数据结构啊。我们称byte/word/array/map为数据结构,为什么“Specification Types”就不是呢?你看ECMAScript规范里面,“Specification Types”就是在“Data Types”这一章中的一节啊。
> 而文中把 x = x 中的 x 叫做一个引用,应该不是很精确,x 只是引用的 referenced name。
不对。左侧的x就是引用,而不仅仅是referenced name。只是代码文本(在静态读代码的情况下)它是个名字x而已。
> 因为我们代码层面无法获取引用,也就没有名字,所以这里用 x 指代规范中的引用。
代码层面是可以获取所谓“引用”的,而且是“规范类型”中的引用。例如delete obj.x中的`obj.x`整体上就是一个引用。
作者回复: 多谢。我努力~努力~持续努力~
^^.
作者回复: 1和2都没有问题,3稍稍一点不同。
在3里面,如果是“object.x”这个语法,那么这个`x`将是语法解析阶段得到的一个标识符名(IdentifierName),所以它不会视为“表达式的值”。但是,在执行“object.x”这个东西时,它确实是以“某个值”的形式传给“点号”这个运算符的。你可以理解为,“object.x”是指:opPropertyAccessor(object, IdentifierName_to_NameString(x))
亦即是说,对于1和2来说,x是一个单值表达式,而在3中,x不是表达式。
作者回复: 是。writable不决定是否删除。属性表像一个数据库的表一样,可以增删查改,显然writable决定的是“改”,而不是“删”。