这是从更严格的角度对您所观察到的现象的(比我最初打算的要长得多)描述。 为什么更严格? 我想调查这个问题,因为我不确定是否有一些关于函数默认参数的特殊规则,或者是否有一些我不理解的关于解构的基本内容。原来,是后者。
我将使用伪语法来描述我的发现,这在一定程度上反映了您在 ECMA-262 中看到的内容。那是我唯一的参考。
关键点:
有解构赋值和解构绑定模式。两者的目的都是引入名称和赋值。
解构赋值:
ObjectAssignmentPattern : '{' AssignmentPropertyList '}' = AssignmentExpression AssignmentPropertyList : AssignmentProperty [',' AssignmentProperty]
这两个只是说明了解构赋值的一般形式。
AssignmentProperty : IdentifierReference [Initializer]
这是 LHS 中名称的“默认值”。
AssignmentProperty : PropertyName ':' AssignmentElement AssignmentElement : LeftHandSideExpression [Initializer]
这让解构嵌套递归,但语义需要定义。
语义
如果您查看
DestructuringAssignmentEvaluation,您可以看到谁被分配了什么。 ObjectAssignmentPattern不是很有趣,它给出了'{' assignments '}'
LHS的基本结构,更有趣的是 12.15.5.3,
PropertyDestructuringAssignmentEvaluation。这显示了当您实际分配默认值以及绑定更深的嵌套名称时会发生什么。
AssignmentProperty : IdentifierReference [Initializer]
在这个算法中,第 3 步很重要,在那里GetV
被调用。在此调用中,它试图从值(RHS) 中获取当前分配给 (LHS) 的名称的值。这可能会抛出,这就是以下代码段抛出的原因:
y = Object.defineProperty({},'foo',{get: () => {throw new Error("get foo");}})
{foo} = y;
下一步,第 4 步,只评估初始值设定项,如果它存在并且从 RHS 获得的
值是未定义的。例如:
y = Object.defineProperty({},'foo',{get: () => undefined})
{foo = 3} = y; // foo === 3
请注意,这一步,以及实际将值“放置”到需要去的地方的步骤,都可以抛出。下一个项目更加棘手,并且是最肯定会出现混淆的地方:
AssignmentProperty : PropertyName ':' AssignmentElement
这里的语义是在 KeyedDestructuringAssignmentEvaluation 的道路上踢罐头,传递PropertyName和当前值 (RHS)。这是其运行时语义的标头:
AssignmentElement : DestructuringAssignmentTarget [Initializer]
后续算法的步骤有些熟悉,但有一些意外和间接。该算法中的几乎任何步骤都可能抛出异常,因此不会明确指出。第 1 步是另一个“递归基础”,说如果目标不是对象或数组文字(例如只是一个标识符),那么让
lref成为那个(注意它不必是一个标识符,只是一些可以分配给,例如
w = {}
{x:w.u = 7} = {x:3} // w == {u:3}
然后,尝试value.propertyName
使用GetV检索“目标值” 。如果此值未定义,则会尝试获取在步骤 6 中放入lref的初始化器值。第 5 步是递归调用,根据需要剥离尽可能多的层,以实现解构分配的基本情况。这里还有一些我认为可以说明这一点的例子。
更清楚的例子:
{x={y:1}} = {} // x == {y:1}
{x:{y=1}} = {} // error, {}.x is undefined, tried to find {}.x.y
x = 'foo'
{x:{y=1}} = {x} // x == 'foo', y == 1.
// x doesn't get assigned in this destructuring assignment,
// RHS becomes {x:x} === {x:'foo'} and since 'foo'.y is
// undefined, y gets the default 1
{x:{y=1}} = {x:{y}} // error, tried to give object value {y} === {y:y} to x
// in RHS, but y is undefined at that point
y = 'foo'
{x:{y=1}} = {x:{y}} // y == 'foo', gave {y} === {y:y} === {y:'foo'} to x in RHS
{x:{y=1}} = {x:{y:2}} // y == 2, maybe what you wanted?
// exercises:
{x=1} = undefined // error
{x=1} = null // error
{x=1} = null || undefined // error
{x=1} = null | undefined // can you guess? x == 1
函数声明
在 react-redux 的源代码中看到以下代码后,我实际上开始研究解构:
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {}) {
所以,我开始挖掘的第一个地方是:
14.1 函数定义
这是一个小小的“堆栈跟踪”,试图追踪相关的作品,让我找到绑定的东西。
函数声明
形式参数
正式参数表
形式参数
-> 13.3.3 解构绑定模式
绑定元素
+ 单名绑定
++ BindingIdentifier,初始化器
+绑定模式
+对象绑定模式
+绑定属性列表
+绑定属性
+ 单名绑定,
+属性名称':' BindingElement
语义:解构绑定与解构赋值
据我所知,Destructuring Binding 和 Destructuring Assignment 之间的唯一区别在于它们可以在哪里使用以及如何处理不同的词法环境。形式参数列表(等)之外的解构绑定需要初始化器,并且解构绑定显式传递一个环境,而赋值,根据它们的定义意味着一个初始化器,“从“环境”中获取它们的值。很高兴听到为什么这是错误的,但这里有一个快速演示:
var {x}; // syntax error
function noInit({x}) { return x; }
// ok
noInit() // runtime error
noInit({}) // undefined
noInit({x:4}) // 4
function binding({x:y} = {x:y}){ return y; }
function assigning(){({x:y} = {x:y}); return y}
binding() // error, cannot access y before initialization
assigning() // error, y is not defined
y = 0
binding() // still error
assigning() // 0 - now y is defined
结论:
我总结如下。解构绑定和赋值的目的是将名称引入当前的词法环境中,可选择地为它们赋值。嵌套解构就是雕刻出你想要的数据的形状,你上面的名字不是免费得到的。您可以将初始化器作为默认值,但是一旦使用它们,您就无法进行更深入的挖掘。如果您雕刻出一个特定的形状(实际上是一棵树),您尝试绑定的对象可能具有未定义的叶子,但分支节点必须与您所描述的(名称和形状)相匹配。
附录
当我开始这个时,我发现在给定一个不支持解构的目标的情况下,看看 tsc(typescript编译器)将这些东西转换成什么是很有帮助和有趣的。
以下代码:
function f({A,B:{BB1=7,BB2:{BBB=0}}}) {}
var z = 0;
var {x:{y=8},z} = {x:{},z};
将 ( tsc --target es5 --noImplicitAny false
)转译为:
function f(_a) {
var A = _a.A,
_b = _a.B,
_c = _b.BB1,
BB1 = _c === void 0 ? 7 : _c,
_d = _b.BB2.BBB,
BBB = _d === void 0 ? 0 : _d;
}
var z = 0;
var _a = { x: {}, z: z },
_b = _a.x.y,
y = _b === void 0 ? 8 : _b,
z = _a.z;