簡體   English   中英

ES6解構對象賦值函數參數默認值

[英]ES6 destructuring object assignment function parameter default value

嗨,我正在查看對象解構在傳遞函數參數時使用的示例對象解構演示

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} = {}) { 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();

使用默認值的解構僅在您傳遞不具有相應屬性的對象時才起作用。 整個參數的= {}默認值允許根本不傳遞(空)對象。

它使drawES6Chart()等同於drawES6Chart({})

您有一個帶有默認值的對象,但該對象也是一個參數,因此它需要一個空對象作為第一個參數的默認值,即帶有填充值的對象。

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

在偽代碼中,這將是:

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

這是從更嚴格的角度對您所觀察到的現象的描述(比我最初打算的要長得多)。 為什么更嚴格? 我想調查這個問題,因為我不確定是否有一些關於函數默認參數的特殊規則,或者是否有一些我不理解的關於解構的基本內容。 原來,是后者。

我將使用偽語法來描述我的發現,這在一定程度上反映了您在 ECMA-262 中看到的內容。 那是我唯一的參考。

要點:

有解構賦值和解構綁定模式。 兩者的目的都是引入名稱和賦值。

解構賦值:

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

這兩個只是說明了解構賦值的一般形式。

AssignmentProperty : IdentifierReference [Initializer]

這是 LHS 中名稱的“默認值”。

AssignmentProperty : PropertyName ':' AssignmentElement AssignmentElement : LeftHandSideExpression [Initializer]

這讓解構嵌套遞歸,但語義需要定義。

語義

如果您查看DestructuringAssignmentEvaluation ,您可以看到誰被分配了什么。 ObjectAssignmentPattern不是很有趣,它給出了 LHS 的基本'{' assignments '}'結構,更有趣的是 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(打字稿編譯器)將這些東西轉換成什么是很有幫助和有趣的。

以下代碼:

function f({A,B:{BB1=7,BB2:{BBB=0}}}) {}

var z = 0;
var {x:{y=8},z} = {x:{},z};

將 ( tsc --target es5 --noImplicitAny false ) 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;

這是函數參數的默認值。 如果不使用= {} JavaScript 解釋器會在沒有對象傳遞給函數時拋出錯誤,因為它無法解構undefined值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM