JavaScript 连续赋值

发布于 2024-06-28 01:29:41 字数 3364 浏览 15 评论 0

let a=10 和 a=10 的区别

前者不是赋值语句,后者是。

  • var/let/const 后面不允许跟随表达式

对于声明语句来说,紧随于“var/let/const”之后的,一定是变量名(标识符),不可以是表达式。

// 这里的 x 应该被视为一个标识符,而非表达式
let x=2

// 会直接报错,因为 obj.x 是一个表达式,不能跟在 var/let/const 后面
let obj.x=2
  • 初始器

我们把声明语句中,去掉 var/let/const x 后的语句剩余部分称为初始器,是一种语法组件。用于在代码执行阶段绑定初始值

let x=2

// =2 是初始器部分,会在 parse 阶段被解析,确定下来“在执行阶段为 x 绑定初始值”这一任务。

值的绑定

那些用 var/let/const 声明的变量,收到初始器的作用,在代码执行(上下文环境已经创建完毕)时,会作值的绑定。

以下代码中,尽管在 fn 执行之前,变量 x 已经被创建(在 fn 上下文环境被创建阶段),但它没有被绑定值。直到 fn 被调用,x 被绑定初始值 undefined,注意这不属于赋值。这种“声明的变量在执行阶段被绑定初始值”的现象被称为执行期语义

function fn(){
    console.log(x) 
	let x // 等价于 let x=undefined,如果是 let x=2,那就初始化为 2
}

fn() // 报错:Cannot access 'x' before initialization

对于 let a=10, = 10 作为初始器,在执行的时候调用的是 InitializeReferencedBinding( a , GetValue(rhs));而 a = 10 作为赋值,在执行的时候调用的是 PutValue( a , GetValue(rhs))。

  • 可是 var 不是会在创建之后就初始化为 undefiend 吗,这是初始器的工作结果吗?

不是的,注意上面的讨论,初始器的工作时间是代码的执行阶段。而 var 声明的变量被初始化为 undefiend,是在上下文环境被创建时候,伴随着变量的创建而被初始化的。

赋值表达式

  • 例 1
var x = y = 100;

x 是一个标识符(不是表达式,准确的说,只是一个表达名字的、静态语法分析期作为标识符来理解的字面文本),而 y 和 100 都是表达式,且 y = 100 是一个赋值表达式。

此外,要注意这里变量泄漏了:y

  • 例 2
let a={n:1}
a.x = a = {n:2}

a.x 是一个表达式,a 是一个表达式,{n:2} 是一个表达式

a.x = a = {n:2}

以下过程应该从内存变化角度理解

let a={n:1}
a.x=a={n:2}
console.log(a.x)// undefined

等效于

// 我其实不太喜欢这种等价写法(虽然很多解释标题的文章都有提到这种等价写法),它多出来的 ref 可能使人迷惑,从内存角度理解这个问题才是最直观的。
let a={n:1}
let ref=a
a={n:2}
ref.x=a 
console.log(a.x) // undefiend
  • 内存变化如下
/*计算表达式 a,{n:1},再计算表达式 a={n:1}*/
let a={n:1}

// 栈内存
a:A1

// 堆内存
A1:{n:1}
/*计算表达式 a.x*/
现在我们获取到了堆内存 A1 处的控制权。如果之后 a.x 作为 LHS,如 a.x=2,则堆内存 A1 处变为 A1:{n:1,x:2}。如果之后 a.x 作为 RHS,如 b=a.x,则返回 undefiend,因为堆内存 A1 处没有存储 x 这个属性的值,即 GetValue(a.x)=undefined
    
/*计算表达式 a*/
现在我们获取到了栈内存 a 处的控制权。

/*计算表达式{n:1}*/
就是 {n:1}
/*计算表达式 a={n:2}*/
这是对栈内存 a 处的值进行修改

// 栈内存
a:B1

// 堆内存
B1:{n:2}
/*计算表达式 a.x=a*/
这是对堆内存 A1 处的值进行修改

// 堆内存
A1:{n:1,x:B1}

现在,我们要输出 a.x,那么来看看栈内存 a 处的值是多少?嗯,是 B1,那么我们只要去访问堆内存 B1 处的属性 x,结果是 undefiend,即 GetValue(a.x)=undefiend,console.log 方法会导致 JS 引擎默认调用 GetValue 方法。

我们注意到,在堆内存 A1 处,x 的属性值是 B1(一个堆内存地址值)。且 B1 的内存分配晚于 A1。这实际上反映了 a.x = a = {n:2} 的真正作用:给旧的变量添加一个指向新变量的属性。这一使用模式在 JQuery 中有所应用。

一个看起来类似,结果不同的例子

let a={n:1}
a={n:2}
a.x=a
console.log(a) // {n: 2, x: {…}}
  • 内存变化如下
/*计算表达式 a,{n:1},再计算表达式 a={n:1}*/
let a={n:1}

// 栈内存
a:A1

// 堆内存
A1:{n:1}
/*计算表达式 a*/
现在我们获取到了栈内存 a 处的控制权。

/*计算表达式{n:2}*/
就是 {n:2}
/*计算表达式 a={n:2}*/
这是对栈内存 a 处的值进行修改

// 栈内存
a:B1

// 堆内存
B1:{n:2}
/*计算表达式 a.x*/
现在我们获取到了堆内存 B1 处的控制权。

/*计算表达式 a*/
现在我们获取到了栈内存 a 处的控制权。
/*计算表达式 a.x=a*/

// 堆内存
B1:{n:2,x:B1}

现在,我们要输出 a.x,那么来看看栈内存 a 处的值是多少?嗯,是 B1,那么我们只要去访问堆内存 B1 处的属性 x,结果是 {n: 2, x: {…}},也就是发生了循环引用(这不是我们这里讨论的重点)。

为什么上面两个例子的输出结果不同?其实最关键的就是子表达式的求值顺序。第一个例子中,子表达式的求值顺序是 a.x,a,{n:2}。而第二个例子中,子表达式的求值顺序是 a,{n:2},a,a.x。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

白馒头

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

qq_E2Iff7

文章 0 评论 0

Archangel

文章 0 评论 0

freedog

文章 0 评论 0

Hunk

文章 0 评论 0

18819270189

文章 0 评论 0

wenkai

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文