CoffeeScript的解構(gòu)賦值

2018-08-25 15:03 更新

CoffeeScript有個語法叫解構(gòu)賦值(Destructuring Assignment),可以將一個對象的不同成員一次性賦值給多個的變量。官網(wǎng)中給了下面一個例子:

futurists =
  sculptor: "Umberto Boccioni"
  painter:  "Vladimir Burliuk"
  poet:
    name:   "F.T. Marinetti"
    address: [
      "Via Roma 42R"
      "Bellagio, Italy 22021"
    ]
{poet: {name, address: [street, city]}} = futurists
alert name + " — " + street
運行結(jié)果自然是 "F.T. Marinetti — Via Roma 42R",因為coffee將其翻譯為下面的JS:


var city, futurists, name, street, _ref, _ref1;
futurists = {
  sculptor: "Umberto Boccioni",
  painter: "Vladimir Burliuk",
  poet: {
    name: "F.T. Marinetti",
    address: ["Via Roma 42R", "Bellagio, Italy 22021"]
  }
};
_ref = futurists.poet, name = _ref.name, (_ref1 = _ref.address, street = _ref1[0], city = _ref1[1]);
alert(name + " — " + street);
這個語法跟Erlang的模式匹配有點類似,不同的是,Erlang會嚴(yán)格匹配等號兩邊,不賦值的要用_作為占位符,否則運行時將拋出異常,而coffee則不會,對于不存在的成員,值為undefined:


{poet: {nameX, address: [streetX, city]}} = futurists
# nameX = "undefined"
# streetX = "F.T. Marinetti"
當(dāng)然,對一個不存在的成員繼續(xù)解析還是會拋出異常的:


{poetX:{a}} = futurists
# TypeError: Cannot read property 'a' of undefined
另外,和JS一樣,coffee也可以連續(xù)賦值:


a=b=100
# a=100
# b=100
假如將上面兩種語法組合在一起,會怎樣呢?就像下面的代碼,最終d=?


a={b:1,c:2,x:3,y:4}
d={b,c}=a
簡單分析下: 
賦值語句的結(jié)合順序是從右到左,所以 d={b,c}=a 等價于 d=({b,c}=a) 
我們還知道,賦值表達(dá)式的值是其本身,那么 {b,c}=a 的值是什么呢, {b,c} 還是 a ? 
可以嘗試下,coffee會將語句 {b,c}=a 轉(zhuǎn)成下面的JS:


b = a.b, c = a.c;
這樣看來,d最終的值會是2,這也太奇怪了吧?


還是看一下coffee把 d={b,c}=a 到底翻譯成什么吧:


d = (b = a.b, c = a.c, a);
結(jié)果在意料之內(nèi),d的值不是 2 
結(jié)果在意料之外,d的值不是 {b,c}


我一直以為,像 {b,c}=a 這樣的表達(dá)式,返回值將會是 {b,c} 而不是 a ,然后 d={b,c}=a 可以按過濾器的方式執(zhí)行。但是,我錯了, {b,c}=a 這個表達(dá)式的值是 a 
再寫幾行代碼驗證一次:


f = (a) -> {b,c}=a
coffee將上面的代碼翻譯成


f = function(a) {
  var b, c;
  return b = a.b, c = a.c, a;
};
好吧,解析賦值表達(dá)式的值確實是等號右邊的值。 
至于 {b,c}=a 為何會被譯成 b = a.b, c = a.c; 估計是因為該表達(dá)式的值不產(chǎn)生副作用,所以coffee把該表達(dá)式的值拋棄了。


那么,在JS里,普通賦值表達(dá)式的值,是不是也是等號右邊的值呢?下面的JS語句,result最終應(yīng)該等于expr1還是expr2呢?


result = expr1 = expr2
乍眼一看,可能會覺得 result==expr1 且 result==expr2 ,等于哪個都一樣。


是的,在大多情況下,這都是成立的。但是,為什么對于上面的 {b,c}=a 這類語句,就不一定成立了呢?


這是因為,不成立的原因是:執(zhí)行 dest = src 后,dest 不一定等于 src


JS中有類似的情形嗎?答案當(dāng)然是肯定的,看下面的JS代碼:


var obj, result;
obj = {};
Object.defineProperties(obj, {
  x: {
    set: function() {},
    get: function() {
      return 10;
    }
  }
});
將obj.x的讀和寫分離,就能產(chǎn)生類似的效果。


obj.x = 11;
result = obj.x;
console.log(result); // 10
回到正題,繼續(xù)測試:


result = obj.x = 11;
console.log(result); // 11
好吧,JS的賦值表達(dá)式的值也是等于等號右邊的值。 
當(dāng)最右值的讀取包含副作用時,會怎樣呢?


var obj, r1, r2, r3;
obj = {_x:1};
Object.defineProperties(obj, {
  x: {
    set: function() {},
    get: function() {
      return ++this._x;
    }
  }
});
r1 = r2 = r3 = obj.x
console.log(r1,r2,r3);  // 2 2 2
嗯...我知道,在get方法包含帶副作用的行為是不對的,這段代碼只是測試而已~ 
結(jié)果表明,連續(xù)賦值時最右值只計算一次。其實,從AST的執(zhí)行過程來分析,最右值只計算一次,是合理的。


ECMA文檔 是這么描述賦值語句的:


> 11.13.1 Simple Assignment ( = )
> The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:
> 1. Let lref be the result of evaluating LeftHandSideExpression.
> 2. Let rref be the result of evaluating AssignmentExpression.
> 3. Let rval be GetValue(rref).
> 4. Throw a SyntaxError exception if the following conditions are all true:
> * Type(lref) is Reference is true
> * IsStrictReference(lref) is true
> * Type(GetBase(lref)) is Environment Record
> * GetReferencedName(lref) is either "eval" or "arguments"
> 5. Call PutValue(lref, rval).
> 6. Return rval.
> NOTE When an assignment occurs within strict mode code, its  LeftHandSide must not evaluate to an unresolvable 
> reference. If it does a  ReferenceError exception is thrown upon assignment. The  LeftHandSide also may not be a 
> reference to a data property with the attribute value  {[[Writable]]:false}, to an accessor property with the attribute value 
> {[[Set]]:undefined}, nor to a non-existent property of an object whose [[Extensible]] internal property has the value false. In 
> these cases a TypeError exception is thrown.
賦值表達(dá)式的返回值是等號右邊的值,連續(xù)賦值表達(dá)式的值自然就是最右值了,而且只會計算一次。



CoffeeScript 實現(xiàn) ECMAScript Harmony 的提議 解構(gòu)賦值 語法, 這樣從復(fù)雜的數(shù)組和對象展開數(shù)據(jù)會更方便一些. 當(dāng)你把數(shù)組或者對象的字面量賦值到一個變量時, CoffeeScript 把等式兩邊都解開配對, 把右邊的值賦值給左邊的變量. 最簡單的例子, 可以用來并行賦值:

theBait   = 1000
theSwitch = 0

[theBait, theSwitch] = [theSwitch, theBait]

var theBait, theSwitch, _ref;

theBait = 1000;

theSwitch = 0;

_ref = [theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1];


用來處理函數(shù)多返回值也很方便.

weatherReport = (location) ->
  # 發(fā)起一個 Ajax 請求獲取天氣...
  [location, 72, "Mostly Sunny"]

[city, temp, forecast] = weatherReport "Berkeley, CA"

var city, forecast, temp, weatherReport, _ref;

weatherReport = function(location) {
  return [location, 72, "Mostly Sunny"];
};

_ref = weatherReport("Berkeley, CA"), city = _ref[0], temp = _ref[1], forecast = _ref[2];


解構(gòu)賦值可以用在深度嵌套的數(shù)組跟對象上, 取出深度嵌套的屬性.

futurists =
  sculptor: "Umberto Boccioni"
  painter:  "Vladimir Burliuk"
  poet:
    name:   "F.T. Marinetti"
    address: [
      "Via Roma 42R"
      "Bellagio, Italy 22021"
    ]

{poet: {name, address: [street, city]}} = futurists

var city, futurists, name, street, _ref, _ref1;

futurists = {
  sculptor: "Umberto Boccioni",
  painter: "Vladimir Burliuk",
  poet: {
    name: "F.T. Marinetti",
    address: ["Via Roma 42R", "Bellagio, Italy 22021"]
  }
};

_ref = futurists.poet, name = _ref.name, (_ref1 = _ref.address, street = _ref1[0], city = _ref1[1]);


解構(gòu)賦值還可以跟 splats 搭配使用.

tag = "<impossible>"

[open, contents..., close] = tag.split("")

var close, contents, open, tag, _i, _ref,
  __slice = [].slice;

tag = "<impossible>";

_ref = tag.split(""), open = _ref[0], contents = 3 <= _ref.length ? __slice.call(_ref, 1, _i = _ref.length - 1) : (_i = 1, []), close = _ref[_i++];
loadrun: contents.join("")


展開式(expansion)可以用于獲取數(shù)組結(jié)尾的元素, 而不需要對中間過程的數(shù)據(jù)進行賦值. 它也可以用在函數(shù)參數(shù)的列表上.

text = "Every literary critic believes he will
        outwit history and have the last word"

[first, ..., last] = text.split " "

var first, last, text, _ref;

text = "Every literary critic believes he will outwit history and have the last word";

_ref = text.split(" "), first = _ref[0], last = _ref[_ref.length - 1];


解構(gòu)賦值也可以用在 class 的構(gòu)造器上, 從構(gòu)造器配置對象賦值到示例屬性上.

class Person
  constructor: (options) -> 
    {@name, @age, @height} = options

tim = new Person age: 4

var Person, tim;

Person = (function() {
  function Person(options) {
    this.name = options.name, this.age = options.age, this.height = options.height;
  }

  return Person;

})();

tim = new Person({
  age: 4
});



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號