12 | 1 in 1..constructor:这行代码的结果,既可能是true,也可能是false
12 | 1 in 1..constructor:这行代码的结果,既可能是true,也可能是false
讲述:周爱民
时长16:01大小14.66M
JavaScript 1.0~1.3 中的对象
属性访问与可见性
从原型中继承来的属性
字面量与标识符
属性存取的不确定性
知识回顾
思考题
赞 6
提建议
精选留言(12)
- Smallfly2020-02-081. [] 的求值过程 一开始没明白题目的意思,看到留言区的提示才理解,题目考察的是 JS 的类型转换,[] 属于对象类型,对象类型到值类型的转换过程是什么样的? 对象转值类型的规范过程称为:ToPrimitive 分为三个步骤: 1. 判断对象是否实现 [Symbol.toPrimitive] 属性,如果实现调用它,并判断返回值是否为值类型,如果不是,执行下一步。 2. 如果转换类型为 string,依次尝试调用 toString() 和 valueOf() 方法,如果 toString() 存在,并正确返回值类型就不会执行 valueOf()。 3. 如果转换类型为 number/default,依次尝试调用 valueOf() 和 toString(),如果 valueOf() 存在,并正确返回值类型就不会执行 toString()。 数组默认没有实现 [Symbol.toPrimitive] 属性,因此需要考察 2、3 两步。 [] + ’‘ 表达式为 string 转换,会触发调用 toString() 方法,结果为空字符,等价于 ’’ + ‘’ 结果为 ‘’。 +[] 表达式是 number 转换,会先触发调用 valueOf() 方法,该方法返回的是空数组本身,它不属于值类型,因此会再尝试调用 toString() 方法,返回空字符,+‘’ 结果为 0;展开
作者回复: 加分加分。呵呵~ ^^.
30 - 墨灵2020-03-20有个小问题,当一个函数使用了函数外部的变量时,这种情况就能称为“闭包”吗? ``` // 函数f只是使用了全局的变量x let x = 0; function f (y) { return x + y; } // z引用g函数内的匿名函数,而匿名函数使用了g的参数x,而造成g的函数作用域无法释放。 function g (x) { return (y) => x + y; } const z = g(0); ``` 在第一种情况,f函数调用之后就可以释放作用域,而第二种情况,无论z调用多少次,只要z不指向别一个值,函数g的作用域就不会释放。这就是我所理解的函数闭包,但对象闭包就是什么样子的?展开
作者回复: 对象闭包是在非严格模式中才能用的,例如: ``` let f, obj = new Object; with (obj) f = function() {}; ``` 这种情况下,with语句为对象obj创建了一个块级作用域,这个作用域(以及它的链)就被作为一个对象闭包放在函数f的作用域链上了。 在ES6之后已经不再使用对象闭包这样的说法,统一用块级作用域和环境来描述这些语法效果了。在ES6之前,由于函数的作用域(的实例)被称为闭包,所以对象的with作用域(的实例)也就称为对象闭包。 最后,事实上JavaScript的全局global也是一个对象闭包。它总是在其它所有闭包(作用域链)的顶端。 再再再补充一下,闭包跟作用域其实是不完全相同的。作用域通常是语法所对应的块,是静态概念的,而闭包是运行期才使用的概念,函数被调用一次就有一个闭包出现,但函数自身其实只有一个作用域。——所以,看起来作用域像是“类”,而闭包像是“对象”,闭包是作用域的“实例”。 并且,确实的,在ECMAScript规范中,闭包就是一个作用域“实例化”的结果。——并且“实例化”是在“函数调用”时实时地创建和发生的,是动态的、运行期的概念。
8 - 墨灵2020-04-09An object is a collection of properties and has a single prototype object. The prototype may be the null value. 昨天查了一下,ECMAScript规范更新对object的描述了。5
- 青史成灰2020-01-12老师,关于这句话有个疑问:“这个包装的过程发生于函数调用运算“( )”的处理过程中,或者将“x.toString”作为整体来处理的过程中。也就是说,仅仅是“对象属性存取”这个行为本身,并不会触发一个普通“值类型数据”向它的包装类型转换” 只是前半句好理解,但是后半句“对象属性存取这个行为本身”,这个对象是发生包装转化后的对象吗?如果是,那怎么感觉就变成了“先有鸡还是先有蛋”的问题了。。。如果不是,那么原始类型不存在属性存取这一说法啊展开
作者回复: 关键确实就在“对象属性存取这个行为本身”。 原始类型(中的值类型)的对象属性存取行为,例如“`true.toString`”会发生什么呢?它仍然是一次有效的属性存取,但它的结果(Result)并不会被立即求值。之前我们说过了,必须等到决定它是作为rhs/lhs之后,才能确定它是用来“求值”,还是只是“作为一个引用”,对不对? 那么,当得到操作`true.toString`的结果(Result)之后,在决定下一个可能的操作之前,它是一个什么状态呢?——这种情况下,它是作为一个“引用(规范类型)”来传递的。考虑到“引用(规范类型)”作为一个原始语言(例如C)中的结构/记录类型,那么它的ref.base域存放的,将是“值true”,而ref.name域中存放的,将是属性名“toString”。 所以你看,在这个阶段中,“包装(boxing)”这个行其实并没有发生。true还是true值,并没有“变成”Object(true),对不对? 所以说,确实存在一个“将对象属性存取这个行为(的结果)——作为一个整体”的阶段,这个存取行为并没有发生包装。但是,如果如下发生后一步的行为(也就是“作为整体来处理的过程中),那么,“包装”就会发生了。例如,GetValue(ref),那么就会先将ref.base中的值转换成对象;又例如,true.toString(),就会先将ref.true转换为对象然后作为this值传入toString()。 所以,“包装(亦即是‘转换为对象’)”这个行为,其实是发生在`GetValue()这个内部操作`或`()这个运算符`等等这样的运算过程中的。 所以回到最开始的,总之,“对象属性存取”这个行为本身,就是还没有触发“包装”。但它得到了包装要用的材料,亦即是ref.base和ref.name,不过还得需要“下一步”的具体操作,才能决定“包装是否会发生”。
4 - K4SHIFZ2020-03-28抱歉老师我杠一下,自动分号插入在规范11.9章:When, as the source text is parsed from left to right, a token (called the offending token) is encountered that is not allowed by any production of the grammar, then a semicolon is automatically inserted before the offending token if one or more of the following conditions is true: The offending token is separated from the previous token by at least one LineTerminator. The offending token is }. ... 它说是before the offending token,在}之前插入,所以应该变为{;}+{},而不是{};+{}。这么理解对吗?虽然分号插在哪,不影响引擎会首先作为语句执行第一个{}展开
作者回复: 是这样的,我们通常讨论ASI的时候,是有两种语境的,一种是“尽量少写;号”,另一种是“尽量所有语句都写;号”。 ECMAScript在讨论ASI的时候,是直接认为第一种语境的。也就是说,ASI是“为了那些不想写分号的人准备的工具”。这种情况下,才会有了它的第一条规则(这个来自 Isaac Schlueter 的描述): > 在一个 \n 字符总是一个语句的结尾总是“自动加上 ;号” 但是我正好是“总是尽可能写分号派”的。呵呵,真的。因为我是从Pascal语言过来的,所以多数情况下我会为每个语句后面加一个;号。这带来了一种习惯,也就是“语句总是以";"号结束”。比如语句: ``` if (true) { }; // <- 事实上这个分号可以不写 ``` 如果你遵循语句以";"号结束的原则,那么你可以规避ASI的效果,因为任何情况下都有正确的";"号。但是另外的问题是,多数情况下我们在大括号后面是不用";"号的,因为大括号本身就是块语句,块语句最后的"}"本来也是语句结束符,所以一般我们会写成“{...}”而不是"{...};"。 因为是在这种语境讨论ASI,所以我才会说,可以将"{};"后面的这个分号写出来,从而看到: > {}+{} 被解析成了 > {};+{} 这个样子。OK,好吧,无论如何,我得承认,ASI是为了解决“尽量使用\n而不是使用;来结束语句”的问题的。所以尽管都是";"号的问题,但我上面讨论的并不是在ECMAScript所说的那个ASI,我这样归为ASI的问题并不正确。
共 2 条评论1 - 红白十万一只2020-03-01关于[]求值过程无非是隐式类型转换,隐式调用toString 来看看{}+{}这道题 可能有两种结果 1,"[Object Object] [Object Object]" 2,NAN 首先一种理解,代码块{},而不是对象 符合Firefox的结果 {}; +{} +{}.toString() +"[Object Object]" Number("[Object Object]") NAN 第二种把{}当场一个字面量 结果也就是"[Object Object][Object Object]" 符合谷歌结果 查了ES规范,{}什么时候是字面量,什么时候是代码块 1,{}前面有运算符号时,当成字面量 2,{}后面有;或隐式插入;时当场代码块 老师能更详细讲解一下{},什么时候是代码块,什么时候是字面量么展开
作者回复: 这个涉及到“语句的语法规则”,也就是JS解析时处理“识别语句”的问题。 比较简单的说法是, 1. 某些情况下回车和上一语句的自然终结可以作为语句结束符。 2. 除了上一特例之外,分号和文末结束符(EOF)将被理解为语句结束符。 3. 任何情况下,从语句开始解析整个文本块。 然而比较麻烦的就是第1条规则。因为它意味着JavaScript的一个称为“自动分号插入(ASI)”规则生效,这就是有些人不赞同写行末分号的原因。——事实上是因为JavaScript读到那些特定位置的回车符或自然终结,然后自动插入了分号。 以{}为例,如果它正好在上一行的结尾之后(例1),或者是一段代码文本的最开始,那么它就被理解为语句(例2)。如下两例: ``` // 例1:类声明语句的最后一个`}`被理解为上一语句的结束。所以返回是一个空语句的值:undefined x = eval(`class foo {}{}`) // 例2:eval执行的代码块以语句开始解析。所以这里将有一个空语句和一个单值表达式语句,而返回值为1 x = eval(`{}1`) ```
1 - kittyE2019-12-131. 我理解,[] 作为单值表达式,要GetValue(v),但为啥结果是 [],不太明白,ecma关于GetValue的描述,感觉好复杂。 2. []*[]/++[[]][+[]]-[+[]] 我随便写了一个 还真的能有值,不知道这样理解对不对,求老师解惑
作者回复: 第2题你的理解是对的,不过表达式可以再简一些。^^. 关于第一个问题,思考方向不是GetValue,而是toPrimitive。还有,它的结果不是[],它的求值结果是0。
1 - Astrogladiator-埃蒂...2019-12-11试述表达式[]的求值过程。 对照http://www.ecma-international.org/ecma-262/5.1/#sec-9.1 http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8 step1: []不是一个原始类型,需要转化成原始类型求值 step2: 这个隐式转换是通过宿主对象中的[[DefaultValue]]方法来获取默认值 step3: 一般在没有指定preferredType的情况下,会隐式转换为number类型的默认值 step4: []默认值为0 可以这么理解?这个preferredType在什么设置? 在上述表达式中加上符号“+-*/”并确保结果可作为表达式求值。 这个是不是只要保证表达式中是对象或者number类型或者设置了preferredType的其他l类型(除了null, undefined, NaN)展开
作者回复: 第1个问题, 这样解释是不对的。[[DefaultValue]]是用在那些值类型的包装对象上的,例如5和new Number(5)之间的关系。而preferredType是另外一个问题,涉及JavaScript对“预期转换目标类型”的管理,不同的运算之间还不同(但都与具体的运算操作有关),与当前这个问题却没有太大的关系。 第2个问题的意思,是如何使一个表达式里面只出现“+-*/”和"[]",并且表达式还可以通过语法检测并计算求值。
1 - 许童童2019-12-11老师讲得非常好,JavaScript中的面向对象设计确实很独特,早期我们还称其为基于对象,不过随着我们对JavaScript了解的深入,现在都已经改口了。对象存取的结果是面向对象运行时中结果的体现,如果属性不是自有的,就由原型决定,如果属性是存取方法,就由方法求值决定。另外,属性描述符有两种主要形式:数据描述符和存取描述符。1
- 言川2021-07-19也就是说,“Number.prototype.construtctor”与“1..constructor”相同,且都指向 Number() 自身。 其中第一个 'constructor' 拼错了
作者回复: 谢谢🙏已提交编辑老师处理
- 仿生狮子2020-12-23第二题,感觉像是在玩 JSFxck hhhhha~~,试验发现,[]+[] 得空字符串,+[] 得 0,[]**[] 得 1,++[[]**[]][+[]] 得 2,以此类推可获得任何数字,比如“1023”可表示为“2 的 10 次方减 1”,即 ++[[]**[]][+[]]**([]+[]+([]**[])+(+[]))-1。 JSFxck 的逻辑要复杂一些,先从字符串 undefind 拿到 find,再由 [].find + [] 拿到更多字母,以此类推,拼出 constructor 拿到大写字母 S,在拼字符串 toString,可获得任何小写字母。有了数字和许多字母,就(几乎)可以愉快的编程了。(不过题目没有给非运算符,所以字符串 true 和 false 拿不到,在前几步就挂了。🔙)展开
- Elmer2020-01-03我觉得两题的本质都是再说对象如何转换为值类型。[]的求值过程在于[]所处的表达式环境需要number还是string,然后执行array.prototype上对应的方法转换。 题二中+-*/都是需要number,所以只要不出现0/0的情况即可。
作者回复: 题二其实是没有标准答案的。不过多做一点提示,就是+/-其实也是一正值和负值运算符,不一定非得当成加减号来用。正值和负值运算符一样也会导致类型转换。