CoffeeScript的解构赋值
CoffeeScript有个语法叫解构赋值(Destructuring Assignment),可以将一个对象的不同成员一次性赋值给多个的变量。官网中给了下面一个例子:
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
运行结果自然是 "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会严格匹配等号两边,不赋值的要用_作为占位符,否则运行时将抛出异常,而coffee则不会,对于不存在的成员,值为undefined:
{poet: {nameX, address: [streetX, city]}} = futurists
# nameX = "undefined"
# streetX = "F.T. Marinetti"
当然,对一个不存在的成员继续解析还是会抛出异常的:
{poetX:{a}} = futurists
# TypeError: Cannot read property 'a' of undefined
另外,和JS一样,coffee也可以连续赋值:
a=b=100
# a=100
# b=100
假如将上面两种语法组合在一起,会怎样呢?就像下面的代码,最终d=?
a={b:1,c:2,x:3,y:4}
d={b,c}=a
简单分析下:
赋值语句的结合顺序是从右到左,所以 d={b,c}=a 等价于 d=({b,c}=a)
我们还知道,赋值表达式的值是其本身,那么 {b,c}=a 的值是什么呢, {b,c} 还是 a ?
可以尝试下,coffee会将语句 {b,c}=a 转成下面的JS:
b = a.b, c = a.c;
这样看来,d最终的值会是2,这也太奇怪了吧?
还是看一下coffee把 d={b,c}=a 到底翻译成什么吧:
d = (b = a.b, c = a.c, a);
结果在意料之内,d的值不是 2
结果在意料之外,d的值不是 {b,c}
我一直以为,像 {b,c}=a 这样的表达式,返回值将会是 {b,c} 而不是 a ,然后 d={b,c}=a 可以按过滤器的方式执行。但是,我错了, {b,c}=a 这个表达式的值是 a
再写几行代码验证一次:
f = (a) -> {b,c}=a
coffee将上面的代码翻译成
f = function(a) {
var b, c;
return b = a.b, c = a.c, a;
};
好吧,解析赋值表达式的值确实是等号右边的值。
至于 {b,c}=a 为何会被译成 b = a.b, c = a.c; 估计是因为该表达式的值不产生副作用,所以coffee把该表达式的值抛弃了。
那么,在JS里,普通赋值表达式的值,是不是也是等号右边的值呢?下面的JS语句,result最终应该等于expr1还是expr2呢?
result = expr1 = expr2
乍眼一看,可能会觉得 result==expr1 且 result==expr2 ,等于哪个都一样。
是的,在大多情况下,这都是成立的。但是,为什么对于上面的 {b,c}=a 这类语句,就不一定成立了呢?
这是因为,不成立的原因是:执行 dest = src 后,dest 不一定等于 src
JS中有类似的情形吗?答案当然是肯定的,看下面的JS代码:
var obj, result;
obj = {};
Object.defineProperties(obj, {
x: {
set: function() {},
get: function() {
return 10;
}
}
});
将obj.x的读和写分离,就能产生类似的效果。
obj.x = 11;
result = obj.x;
console.log(result); // 10
回到正题,继续测试:
result = obj.x = 11;
console.log(result); // 11
好吧,JS的赋值表达式的值也是等于等号右边的值。
当最右值的读取包含副作用时,会怎样呢?
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方法包含带副作用的行为是不对的,这段代码只是测试而已~
结果表明,连续赋值时最右值只计算一次。其实,从AST的执行过程来分析,最右值只计算一次,是合理的。
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.
赋值表达式的返回值是等号右边的值,连续赋值表达式的值自然就是最右值了,而且只会计算一次。
CoffeeScript 实现 ECMAScript Harmony 的提议 解构赋值 语法, 这样从复杂的数组和对象展开数据会更方便一些. 当你把数组或者对象的字面量赋值到一个变量时, 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];
用来处理函数多返回值也很方便.
weatherReport = (location) ->
# 发起一个 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];
解构赋值可以用在深度嵌套的数组跟对象上, 取出深度嵌套的属性.
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]);
解构赋值还可以跟 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)可以用于获取数组结尾的元素, 而不需要对中间过程的数据进行赋值. 它也可以用在函数参数的列表上.
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];
解构赋值也可以用在 class 的构造器上, 从构造器配置对象赋值到示例属性上.
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
});
更多建议: