语法篇:出现在赋值表达式左侧的变量一定是引用吗

出现在赋值表达式左侧的变量一定是引用吗

语法篇旨在帮助读者了解语法构成的方式,以及如何通过规范去探索更多的“未知”,从而自身形成如何系统去学习编程语言的能力,而不仅仅是局限在javascript这一门语言上,抑或是停留在使用api的层面上。在这一篇中我也会继续沿用最少知识原则:用到什么就学什么,减少无关语法和新概念带来的知识混乱。

在开始正文之前我先说明一些前置概念,js的数据类型可以归为两类分别是:基本数据类型(也可以称为值类型)和引用类型,这一分类是从值的存储角度去描述的。我们接下来所要讨论的主题虽然和其听起来十分相似,却是截然不同的概念。它们是从操作值的行为(也可以说用处)去分类的:引用和值。这两种类型是相互排斥的,当出现它们中的一个就无须再考虑另一个,比如当我们需要用到一个变量的值,那么它的引用就无需考虑,因为接下来它的所有的操作都是基于值而不会对对它的引用产生操作。

要搞清楚标题的问题是如何产生的,我们先来学一样东西:赋值表达式。你肯定会觉得这过于简单了。若我换个问法:赋值的合法性是什么,怕是很多读者就会一脸茫然了,或者脑中飘过的就是赋值给一个变量,这么说确实也没有什么大错,毕竟最常用的可不就是变量赋值吗。接下来我们就来好好聊一聊这个变量到底是什么。

顺便提一下规范中的语法描述是采用上下文无关文法来描述的,当文法不足以描述时会使用静态语义加以补充,现在读者只要知道这两个概念就行了,后面用到的时候我会加以说明的,先来看一下一般形式的变量赋值是如何定义的

LeftHandSideExpression = AssignmentExpression

x = y

如果你看过《你不知道的javascript》那么你一定对LeftHandSide和rightHandSide的概念有所了解了。LeftHandSide可以是我们常用的标识符引用(IdentifierReference)或解构赋值模式(AssignmentPattern),对于上面的赋值表达式这是符合我们的知觉的:将y(rhs)的值赋值给x(lhs)的引用,

NOTE 在javascript语法规范中是允许无目标赋值的也就是说(a || b)这种短路运算也是赋值即使它没有lhs,这里就不详细介绍了

现在回到本节的主题:出现在赋值表达式左侧的变量一定是引用吗,答案不然这取决于使用方式,先让我们看一下规范中允许的语法形式

//13.3 Left-Hand-Side Expressions
Syntax
	CallExpression:
                CoverCallExpressionAndAsyncArrowHead
                SuperCall
                ImportCall
                CallExpression Arguments
                CallExpression [Expression]
                CallExpression . IdentifierName
                CallExpression TemplateLiteral[?Yield, ?Await, +Tagged]
                CallExpression . PrivateIdentifier

现在我们通过CallExpression Arguments去构造下面的代码片段,它仅仅是一个简单的函数调用:

let name = 'js'
function outer(){
	function f(){ 
		console.log('f被调用了')
		return name
	}
	f()	= 'php'
}
//outer()
console.log('执行了')

这在编译期是可以通过的(毕竟我们就是根据它的语法规则写的嘛),当我们尝试调用outer会出现以下错误:

Uncaught ReferenceError: Invalid left-hand side in assignment

让我们来分析一下:首先在语法层面分析是允许的,因为Left-Hand-Side Expressions的定义是允许出现CallExpression的。其内部函数f返回了外部定义的变量name,我们的期望是将php赋值给这个变量name,并且f()这个函数也如期的被调用执行了,并将name返回。到这里我们已经将错误的范围缩小到了name上,如果它作为一个ref去返回那就不应该出现任何错误,但实际情况与我们的想法相悖,它是作为一个val被返回的所以导致了上面的错误。这里我们通过一个有意为之的语法将lhsExp成功转化成了一个rhsExp,JavaScript标准是允许我们这么做的,但为了指出这样的错误,又给lhs加了静态语义的规则,通过AssignmentTargetType(lhs的类型)静态语义指出这一违规操作:

//13.15.1 Static Semantics: Early Errors
It is a Syntax Error if AssignmentTargetType of LeftHandSideExpression is not simple.

我们来翻译一下:如果LeftHandSideExpression的AssignmentTargetType类型不是simple就会产生错误。下面再让我们看看AssignmentTargetType的计算方式:

//8.6.4 Static Semantics: AssignmentTargetType
CallExpression :
	CallExpression [ Expression ]
	CallExpression . IdentifierName
	CallExpression . PrivateIdentifier

return simple

CallExpression :
	CoverCallExpressionAndAsyncArrowHead
	SuperCall
	ImportCall
	CallExpression Arguments
	CallExpression TemplateLiteral

Return invalid.

我们的语法符合CallExpression Arguments所以是一个无效的表达式,原因在于你永远无法从函数中返回一个引用(lhs),因为在return的运行时语义中限定了返回的是一个值GetValue(exprRef)。这是符合函数式编程的特性的:函数可以接受函数当作输入(参数)和输出(返回值),无论哪种情况我们需要的都是值而非引用。

现在让我们对程序进行一些改造,让它可以支持函数调用作为左侧。在静态语义中AssignmentTargetType类型不是simple会产生错误,所以我们只要将它转化成返回simple的形式就可以了。改造后的代码如下

let name = {type: "javascript"}
function outer(){
	function f(){ 
		console.log('f被调用了')
		return name
	}
	f()['type']	= 'php'
}
outer()
console.log('执行了')

这里的函数依旧是返回一个“值”,唯一不同的是我们通过操作这个值并获得了这个值上的一个引用“type”,至此就符合了赋值语句的合法性。

小结

这一小节的内容并不多,全篇围绕变量的“值”与“引用”两个形态展开讨论,为大家揭示编译器在对待变量的处理方式。同时也简明扼要的提及了上下无关文法和静态语义,希望对大家对编程有一个新的认识,不再基于文档和直觉编程。本系列文章面向有一年javascript使用经验的读者,为了突出主题文中不会对语法进行过多的介绍。另外本文中所引用的规范全部出自最新版的ECMAScript标准,并标明了具体章节,限于篇幅不便展开讨论,大家可以基于此文中提到的内容自行查阅文档

专栏全部内容请查看https://juejin.cn/post/7178727260143222821

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 这是一段编码为b'\xe8\x8b\xa5\xe5\x8f\x98\xe9\x87\x8f\xe5\xb7\xb2\xe6\xad\xa3\xe7\xa1\xae\xe5\xae\x9a\xe4\xb9\x89\xe5\xb9\xb6\xe8\xb5\x8b\xe5\x80\xbc\xef\xbc\x8c\xe7\xac\xa6\xe5\x90\x88c\xe8\xaf\xad\xe8\xa8\x80\xe8\xaf\xad\xe6\xb3\x95\xe7\x9a\x84\xe8\xa1\xa8\xe8\xbe\xbe\xe5\xbc\x8f\xe6\x98\xaf'的文本,需要解码后才能看到中文意思。 ### 回答2: 在C语言中,一个变量必须经过定义和赋值之后才能被使用。一旦变量成功定义并赋值之后,就可以在程序的任何地方使用它。那么,符合C语言语法表达式应该是怎么样的呢? 首先,可以通过使用变量的名称来引用它。例如,如果我们已经定义并赋值了一个名为num的整型变量,那么可以通过num来引用它,如下所示: int num = 10; printf("num的值为:%d\n", num); 除了引用已定义并赋值变量之外,也可以通过对它们执行各种算术和逻辑操作来创建表达式。这些操作符包括加、减、乘、除、取余、与、或、非、异或等。如果我们想对num进行加1操作,可以这样写: num = num + 1; printf("num加1后的值为:%d\n", num); 另外,我们还可以使用条件运算符(?:)来创建表达式。条件运算符允许我们在条件成立时执行一个操作,否则执行另一个操作。例如,下面的代码会根据num的值来打印不同的信息: printf("num的值为%d, ", num); (num > 0) ? printf("它是正数\n") : printf("它是负数\n"); 最后,C语言还提供了许多其他的运算符和函数,用于处理不同的数据类型和情况。这些包括位运算符、类型转换函数、比较函数等。总之,符合C语言语法表达式可以包括各种运算、函数调用、变量引用等。只要遵循C语言的规则和语法,就可以构建出有效的表达式。 ### 回答3: 在C语言中,我们可以通过给变量赋值来进行计算,计算结果也可以作为变量的值被存储。符合C语言语法表达式需要满足以下要求: 1. 表达式的格式必须正确,包括符号、括号、变量之间的关系、运算符等。 2. 表达式中使用的所有变量必须已定义并赋值,否则编译器会报错。 3. 表达式中使用的所有运算符必须是C语言所支持的运算符,如加减乘除、取模、赋值、逻辑运算符等。 4. 表达式中的计算顺序要符合C语言的优先级规则,例如高于低于运算符、算术运算符、移位运算符、逻辑运算符等等。 5. 表达式计算的结果要符合变量的数据类型,例如整型变量的值只能是整数,浮点型变量的值可以是小数或指数,字符型变量的值必须是ASCII编码。 下面是一些符合C语言语法表达式的例子: int a=2,b=3,c; c=a+b; // c的值为5 c+=a*b; // c的值为11 c=c-2; // c的值为9 c++; // c的值为10 c=(a>b)?a:b; // c的值为3 这些表达式都是符合C语言语法的,通过变量赋值和运算符进行计算,最终得到了一个正确的结果。当我们写代码时,需要注意表达式的格式和计算顺序,确保表达式的结果符合预期。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值