ES6解构对象赋值函数参数默认值

11

嗨,我在这里查看了有关对象解构在传递函数参数中使用的示例。 对象解构演示

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
});

有没有人能告诉我为什么在函数参数的结尾处使用空对象赋值,这是我在上面用粗体标记(嵌套在双星号中)的原因?

5个回答

18

If you use it, and call the function with no parameters, it works:

function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
  console.log(size, cords, radius);
 // do some chart drawing
}

drawES6Chart();

否则,将会抛出错误:

类型错误:无法将未定义的值转换为对象

function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25}) {
  console.log(size, cords, radius);
 // do some chart drawing
}

drawES6Chart();


11

默认解构仅在您传递一个没有相应属性的对象时使用。整个参数的 = {} 默认值允许完全不传递(空)对象。

这使得 drawES6Chart() 等价于 drawES6Chart({})


这是我找到的最好的解释,解释了={}语法! - skiabox

6

您有一个带有默认值的对象,但该对象也是一个参数,因此它需要一个空对象作为第一个参数的默认值,而第一个参数是填充值的对象。

function drawES6Chart({size = 'big', cords = { x: 0, y: 0 }, radius = 25} = {}) {
}

伪代码如下:

function drawES6Chart({**first argument**} = {**default value for first argument**}) {
}

4

这里是一个(比我最初预期的)现象描述,从更严谨的角度来看。 为什么更严谨? 我想调查这个问题,因为我不确定是否存在关于函数默认参数的特殊规则,或者是否有一些关于解构的基本知识我没有理解。结果证明是后者。

我将使用伪语法来描述我的发现,这与您在ECMA-262中看到的情况有些相似。那是我的唯一参考资料。

关键点:

有解构赋值和解构绑定模式。两者的目的都是引入名称并分配值。

解构赋值:

ObjectAssignmentPattern: '{' AssignmentPropertyList '}' = AssignmentExpression AssignmentPropertyList: AssignmentProperty [',' AssignmentProperty]

这两个只是说明了解构赋值的一般形式。

AssignmentProperty:IdentifierReference [Initializer]

这是LHS中名称的“默认值”。

赋值属性: 属性名 ':' 赋值元素 赋值元素: 左手表达式 [初始化器]

这使得析构嵌套递归,但语义需要被定义。

语义

如果你看一下DestructuringAssignmentEvaluation, 就可以看到谁被指派给了什么。 ObjectAssignmentPattern 不是很有趣,它给出了LHS的基本的'{' assignments '}' 结构,更有意思的是12.15.5.3,PropertyDestructuringAssignmentEvaluation。这展示了当你实际分配默认值时以及绑定更深层次嵌套名称时会发生什么。

赋值属性: 标识符引用 [初始化器]

算法中的第三步非常重要,调用了GetV。在这个调用中,它试图从RHS的value 中获取当前正在被赋值的名称(LHS)的值。这可能会抛出异常,这就是为什么以下代码片段会抛出异常的原因:

 y = Object.defineProperty({},'foo',{get: () => {throw new Error("get foo");}})
 {foo} = y;

下一步,第四步,只有在存在初始化程序且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}

然后,使用GetV尝试检索“目标值”value.propertyName, 如果该值未定义,则尝试获取初始化程序值,在第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
} = {}) {

"So,我开始挖掘的第一个地方是:"
"

14.1 函数定义

"
"这里是一个小的“堆栈跟踪”,试图追踪相关的产生式,以便让我进入绑定内容。"
"

函数声明

"
"

形式参数

"
"

形式参数列表

"
"

形式参数

"
"

-> 13.3.3 解构绑定模式

"
"

绑定元素

"
"

+单名称绑定

"
"

++绑定标识符初始化器

"

+绑定模式

+对象绑定模式

+绑定属性列表

+绑定属性

+SingleNameBinding

+PropertyName ':' BindingElement

语义:解构绑定与解构赋值

据我所知,解构绑定和解构赋值的唯一区别在于它们可以使用的地方以及如何处理不同的词法环境。在形式参数列表(等等)之外使用解构绑定需要初始值,并且显式地传递了一个环境,而赋值则从“环境”中获取其值,因为按照其定义,它们意味着要有初始值。如果我的理解有误,我将非常乐意听取更正,以下是一个快速演示:

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;

3

这是函数参数的默认值。如果不使用= {},当没有对象传递给函数时,JavaScript解释器会抛出错误,因为它无法解构一个undefined值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接