ES6解构对象赋值函数参数默认值
ES6 destructuring object assignment function parameter default value
您好,我在这里查看了在传递函数参数时使用对象解构的示例 Object Destructuring Demo
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = **{}**) {
console.log(size, cords, radius);
// do some chart drawing
}
// In Firefox, default values for destructuring assignments are not yet
implemented (as described below).
// The workaround is to write the parameters in the following way:
// ({size: size = 'big', cords: cords = { x: 0, y: 0 }, radius: radius =
25} = **{}**)
drawES6Chart({
cords: { x: 18, y: 30 },
radius: 30
});
谁能告诉我在上面用粗体(嵌入双星)标记的函数参数末尾使用空对象赋值的原因是什么?
您有一个具有默认值的对象,但该对象也是一个参数,因此它需要一个空对象作为第一个 参数 的默认值,这是具有填充值的对象。
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
}
在伪代码中,将是:
function drawES6Chart({**first argument**} = {**default value for first argument**}) {
}
如果你使用它,并且调用不带参数的函数,它会起作用:
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
console.log(size, cords, radius);
// do some chart drawing
}
drawES6Chart();
如果不是,则抛出错误:
TypeError: can't convert undefined to object
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25}) {
console.log(size, cords, radius);
// do some chart drawing
}
drawES6Chart();
只有当你传递一个没有相应属性的对象时,默认的解构才会起作用。 = {}
整个参数的默认值允许根本不传递(空)对象。
它使 drawES6Chart()
等同于 drawES6Chart({})
。
这是函数参数的默认值。不使用 = {}
JavaScript 解释器会在没有对象传递给函数时抛出错误,因为它无法解构 undefined
值。
这是对该现象的描述(比我原先预期的要长得多)
您正在从更严格的角度观察。 为什么更严谨?我
想调查这个问题,因为我不确定是否有一些
关于函数默认参数的特殊规则,或者如果有什么
我不明白的关于解构的基础知识。原来,这是
后者。
我将使用 pseudo-grammar 来描述我的发现,这在某种程度上反映了您的想法
请参见 ECMA-262。这是我唯一的参考。
要点:
有解构赋值和解构绑定
图案。两者的目的都是引入名字和赋值
值。
解构赋值:
ObjectAssignmentPattern : '{' AssignmentPropertyList '}' = AssignmentExpression
AssignmentPropertyList : AssignmentProperty [',' AssignmentProperty]
这两个只是说明了解构赋值的一般形式。
AssignmentProperty : IdentifierReference [Initializer]
这是 "default value" LHS 中的名称。
AssignmentProperty : PropertyName ':' AssignmentElement
AssignmentElement : LeftHandSideExpression [Initializer]
这让解构递归嵌套,但语义需要
定义。
语义
如果你看
DestructuringAssignmentEvaluation,
你可以看到谁被分配到什么。 ObjectAssignmentPattern 不是很
有趣的是,它给出了 LHS 的基本 '{' assignments '}'
结构,
更有趣的是12.15.5.3,
PropertyDestructuringAssignmentEvaluation。这表明当你
实际上分配默认值,当你绑定更深的嵌套名称时。
AssignmentProperty : IdentifierReference [Initializer]
这个算法中的第3步很重要,这里调用了GetV
。在这个调用中,它
正在尝试获取当前分配给的名称的值
(左轴)来自 值(右轴)。这可以抛出,这就是为什么下面的代码片段
抛出:
y = Object.defineProperty({},'foo',{get: () => {throw new Error("get foo");}})
{foo} = y;
下一步,即第 4 步,仅评估初始化器是否存在以及
value 从 RHS 获得未定义。例如:
y = Object.defineProperty({},'foo',{get: () => undefined})
{foo = 3} = y; // foo === 3
注意这一步,而这一步其实"putting"需要值的地方
去,都可以扔。下一项更棘手,是混淆的地方
最肯定会出现:
AssignmentProperty : PropertyName ':' AssignmentElement
这里的语义是把罐头踢到
KeyedDestructuringAssignmentEvaluation,
传递 PropertyName 和当前值 (RHS)。这是它的 header
运行时语义:
AssignmentElement : DestructuringAssignmentTarget [Initializer]
后续算法的步骤有点熟悉,有一些惊喜
和间接。这个算法几乎任何一步都可以抛出,所以不会
明确指出。第 1 步是另一个 "base of recursion," 说如果
目标不是 object 或数组文字(例如,只是一个标识符),然后让
lref 是那个(注意它不一定是标识符,只是一些东西
可以分配给,例如
w = {}
{x:w.u = 7} = {x:3} // w == {u:3}
然后,尝试检索 "target value" 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 函数定义
这里有点"stack trace"试图追踪相关的作品
带我去绑定东西。
-> 13.3.3 解构绑定模式
++BindingIdentifier, Initializer
+SingleNameBinding,
+PropertyName ':' BindingElement
语义:解构绑定与解构赋值
据我所知,解构绑定和解构绑定之间的唯一区别
Destructuring Assignment 是它们可以使用的地方以及词法有何不同
环境得到处理。在形式参数之外解构绑定
列表(和类似的)需要初始化器,并且通过 Destructuring Binding
一个明确的环境,而任务,根据他们的定义意味着
一个初始值设定项”,从 "ambience." 中获取它们的值 我会很高兴听到
为什么这是错误的,但这里有一个快速演示:
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(打字稿编译器)将把这些东西转译成什么是很有帮助和有趣的。
以下代码:
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;
您好,我在这里查看了在传递函数参数时使用对象解构的示例 Object Destructuring Demo
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = **{}**) {
console.log(size, cords, radius);
// do some chart drawing
}
// In Firefox, default values for destructuring assignments are not yet
implemented (as described below).
// The workaround is to write the parameters in the following way:
// ({size: size = 'big', cords: cords = { x: 0, y: 0 }, radius: radius =
25} = **{}**)
drawES6Chart({
cords: { x: 18, y: 30 },
radius: 30
});
谁能告诉我在上面用粗体(嵌入双星)标记的函数参数末尾使用空对象赋值的原因是什么?
您有一个具有默认值的对象,但该对象也是一个参数,因此它需要一个空对象作为第一个 参数 的默认值,这是具有填充值的对象。
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
}
在伪代码中,将是:
function drawES6Chart({**first argument**} = {**default value for first argument**}) {
}
如果你使用它,并且调用不带参数的函数,它会起作用:
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
console.log(size, cords, radius);
// do some chart drawing
}
drawES6Chart();
如果不是,则抛出错误:
TypeError: can't convert undefined to object
function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25}) {
console.log(size, cords, radius);
// do some chart drawing
}
drawES6Chart();
只有当你传递一个没有相应属性的对象时,默认的解构才会起作用。 = {}
整个参数的默认值允许根本不传递(空)对象。
它使 drawES6Chart()
等同于 drawES6Chart({})
。
这是函数参数的默认值。不使用 = {}
JavaScript 解释器会在没有对象传递给函数时抛出错误,因为它无法解构 undefined
值。
这是对该现象的描述(比我原先预期的要长得多) 您正在从更严格的角度观察。 为什么更严谨?我 想调查这个问题,因为我不确定是否有一些 关于函数默认参数的特殊规则,或者如果有什么 我不明白的关于解构的基础知识。原来,这是 后者。
我将使用 pseudo-grammar 来描述我的发现,这在某种程度上反映了您的想法 请参见 ECMA-262。这是我唯一的参考。
要点:
有解构赋值和解构绑定 图案。两者的目的都是引入名字和赋值 值。
解构赋值:
ObjectAssignmentPattern : '{' AssignmentPropertyList '}' = AssignmentExpression AssignmentPropertyList : AssignmentProperty [',' AssignmentProperty]
这两个只是说明了解构赋值的一般形式。
AssignmentProperty : IdentifierReference [Initializer]
这是 "default value" LHS 中的名称。
AssignmentProperty : PropertyName ':' AssignmentElement AssignmentElement : LeftHandSideExpression [Initializer]
这让解构递归嵌套,但语义需要 定义。
语义
如果你看
DestructuringAssignmentEvaluation,
你可以看到谁被分配到什么。 ObjectAssignmentPattern 不是很
有趣的是,它给出了 LHS 的基本 '{' assignments '}'
结构,
更有趣的是12.15.5.3,
PropertyDestructuringAssignmentEvaluation。这表明当你
实际上分配默认值,当你绑定更深的嵌套名称时。
AssignmentProperty : IdentifierReference [Initializer]
这个算法中的第3步很重要,这里调用了GetV
。在这个调用中,它
正在尝试获取当前分配给的名称的值
(左轴)来自 值(右轴)。这可以抛出,这就是为什么下面的代码片段
抛出:
y = Object.defineProperty({},'foo',{get: () => {throw new Error("get foo");}})
{foo} = y;
下一步,即第 4 步,仅评估初始化器是否存在以及 value 从 RHS 获得未定义。例如:
y = Object.defineProperty({},'foo',{get: () => undefined})
{foo = 3} = y; // foo === 3
注意这一步,而这一步其实"putting"需要值的地方 去,都可以扔。下一项更棘手,是混淆的地方 最肯定会出现:
AssignmentProperty : PropertyName ':' AssignmentElement
这里的语义是把罐头踢到 KeyedDestructuringAssignmentEvaluation, 传递 PropertyName 和当前值 (RHS)。这是它的 header 运行时语义:
AssignmentElement : DestructuringAssignmentTarget [Initializer]
后续算法的步骤有点熟悉,有一些惊喜 和间接。这个算法几乎任何一步都可以抛出,所以不会 明确指出。第 1 步是另一个 "base of recursion," 说如果 目标不是 object 或数组文字(例如,只是一个标识符),然后让 lref 是那个(注意它不一定是标识符,只是一些东西 可以分配给,例如
w = {}
{x:w.u = 7} = {x:3} // w == {u:3}
然后,尝试检索 "target value" 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 函数定义
这里有点"stack trace"试图追踪相关的作品 带我去绑定东西。
-> 13.3.3 解构绑定模式
++BindingIdentifier, Initializer
+SingleNameBinding,
+PropertyName ':' BindingElement
语义:解构绑定与解构赋值
据我所知,解构绑定和解构绑定之间的唯一区别 Destructuring Assignment 是它们可以使用的地方以及词法有何不同 环境得到处理。在形式参数之外解构绑定 列表(和类似的)需要初始化器,并且通过 Destructuring Binding 一个明确的环境,而任务,根据他们的定义意味着 一个初始值设定项”,从 "ambience." 中获取它们的值 我会很高兴听到 为什么这是错误的,但这里有一个快速演示:
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(打字稿编译器)将把这些东西转译成什么是很有帮助和有趣的。
以下代码:
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;