JavaScript核心原理解析(更新到第四节)
文章目录
- JavaScript核心原理解析(更新到第四节)
- 第一节 如何解决语言问题
- 第二节 delete 0:JavaScript中到底有什么是可以销毁的
- 第三节 var x = y = 100:声明语句与语法改变了JavaScript语言核心性质
- 第四节 a.x=a={n2}:一道被无数人无数次地解释过的经典面试题
- 第七节 x: break x
第一节 如何解决语言问题
1、JS是一门多范型语言,或者,也称为混合范型语言
第二节 delete 0:JavaScript中到底有什么是可以销毁的
1、对象和数组的字面量语法长这样:
let obj = {
niko: 'shot'
}
2、所有删除值(不是引用)的 delete 就直接返回 true
3、delelet 这个操作的正式语法设计并不是“删除某个东西”,而是“删除一个表达式的结果”
4、:单值表达式(没有运算符参与的表达式)的运算结果返回那个“对象字面量”的单值
delete {} //这里其实也是值,因为是表达式运算
delete运算发现它的操作数是“值 / 非引用类型”,就直接返回了 true。所以,什么也没有发生
5、归根到底
delete x
是在删除一个表达式的、引用类型的结果(Result)
总结:
delete 运算符尝试删除值数据时,会返回 true,表示没有错误(Error)。
delete 0 的本质是删除一个表达式的值(Result)。
delete x 与上述的区别只在于 Result 是一个引用(Reference)。
delete 其实只能删除一种引用,即对象的成员(Property)。
第三节 var x = y = 100:声明语句与语法改变了JavaScript语言核心性质
1、变量声明后的初始化
JavaScript 环境在变量声明(varDelcs)【比如创建一个“变量名(varName in varDecls)”】后,会为它初始化绑定一个 undefined 值。
而”词法名字(lexicalNames,比如let创建的变量)”在创建之后就没有这项待遇,所以它们在缺省情况下就是“还没有绑定值”的标识符。
如果提前去访问他们还会被拒绝(但它们这时候其实已经在上下文环境里存在了)
2、向一个不存在的变量赋值
赋值的本质:将右操作数(的值)赋给左操作数(的引用)
一个赋值表达式的左边和右边其实“都是”表达式! “= ”是初始化器,不是一个严格意义上的“赋值运算”
x = y = 100
x被严格地称为“赋值表达式的左手端(lhs)操作数”,是表达式
而var x只是声明
全局环境的本质:在早期设计中,JavaScript 的全局环境是引擎使用一个称为“全局对象”的东西管理起来,这个全局对象几乎类似或完全等同于一个普通对象。
(JavaScript 引擎将全局的一些缺省对象、运行期环境的原生对象等东西都初始化在这个全局对象的属性中)
不命名直接赋值:JavaScript 将变量名作为属性名添加给全局对象,我们可以通过delete删除这个对象上的属性。
ECMAScript的优化:ECMAScript 规定在这个全局对象之外再维护一个变量名列表(varNames),所有在静态语法分析期或在 eval() 中使用var声明的变量名就被放在这个列表中。然后约定,这个变量名列表中的变量是“直接声明的变量”,不能使用delete删除
var a = 100;
x = 200;
> a不能删除,x可以被删除
delete a
> false
delete x
> true
> 当var声明发生在 eval() 中的时候
eval('var b = 300')
> 它的性质是可删除的,这种情况下使用var声明的变量名也会添加到 varNames 列表.
> 但它也可以从varNames 中移除(这是唯一一种能从 varNames 中移除项的特例,
> 而 lexicalNames 中的项是不可移除的)
delete b
> true
所以标题中的代码到底发生了什么
var x = y = 100
- y = 100,它发生了一次“向不存在的变量赋值”,所以它隐式声明了一个全局变量y,并赋值为 100。
- var x = y,它将y=100的这个表达式的结果,赋值给变量申明var x
声明和语句的区别
声明发生在编译期,语句发生在运行期。
声明发生在编译期,由编译器为所声明的变量在相应的变量表,增加一个名字。语句是要在运行时执行的程序代码。
因此,如果声明不带初始化,那么可以完全由编译器完成,不会产生运行时执行的代码。
声明不是语句,声明只是在告诉编译器该干嘛,并没有对运行产生影响
何时进行词法分析
在加载.js文件之后,执行第一行用户代码之前,就已经完成了全部的词法分析(当然,除了eval执行的)
第四节 a.x=a={n2}:一道被无数人无数次地解释过的经典面试题
一切都是表达式,一切都是运算。
过程式的,或编译型语言,,所谓“标识符 / 变量”就是一个计算对象,它可能直接表达为某个内存地址、指针,或者是一个编译器处理的东西。
a = 100
b * c
传统理解:
第一行理解为“a 有了值 100”;
第二行理解为“b 与 c 的乘积”
JavaScript 中,一共有六个操作,第二号举例包括:
1、将b理解为单值表达式,求值并得到GetValue(evalute(‘b’));
2、 将c理解为单值表达式,求值并得到GetValue(evalute(‘c’));
3、将上述两个值理解为求积表达式’*'的两个操作数,计算
标题中“a.x”这个表达式的语义是:
表达式“a.x”的计算结果是一个引用,因此通过这个引用保存了一些计算过程中的信息——例如它保存了“a”这个对象,以备后续操作中“可能会”作为this来使用。
回到标题中的示例
答案:
有一个新的a产生,它覆盖了原始的变量a,它的值是{n:2};
最左侧的“a.x”的计算结果中的“原始的变量a”在引用传递的过程中丢失了,且“a.x”被同时丢弃。
所以第二次赋值操作“a.x = …”是无意义的。它所操作的对象,也就是“原始的变量a”被废弃了。
但是,如果有如变量、属性或者闭包等,持有了这个“原始的变量a”,那么上面的代码的影响仍然是可见的。
let a = {n:1}, ref = a;
a.x = a = {n:2};
console.log(a.x); // --> undefined
console.log(ref.x); // {n:2}
总结:
这道面试题与运算符优先级无关;
这里的运算过程与“栈”操作无关;
这里的“引用”与传统语言中的“指针”没有可比性;
这里没有变量泄露;
这行代码与上一讲的例子有本质的不同;
上一讲的例子“var x = y = 100”严格说来并不是连续赋值。
什么是引用
引用就是引用,它就是一个计算的结果,它存放结果中包括的那几个东西。它是一个数据结构,用在引擎层面来存储计算过程的中间信息,以及在连续计算中传递这些信息。
问答
with 只指定属性查找的优先级,所以 with 里面的语句 x = 100 还是会泄漏到全局
第七节 x: break x
问答
1、块级作用域与它内部声明了什么没关系。
例如,一个“块语句{ }”就有一个块级作用域,哪怕它内部一行代码也没有。
if(1) {
let b=10
}
上述语句中有两个语句:
一个是if语句本身,它是个“单语句”(ECMAScript就是这么定义的,NodeJS的错误提示里也有),它没有块级作用域
后面的一对大括号“{}”,是一个“块语句”,有一个块