ES6解構賦值運算符

本次我領到的任務是:html

在ES6中有一個解構賦值運算符,能夠大大方便數據字段的獲取。 好比

const [a, b] = [1, 2, 3];
const {name, age} = {name: 'helijia', age: 3};

上面的語句是咱們經常使用的,但是你能解釋爲何下面的語句也能正常工做嗎?

const [a, b] = 'abc';
const {toString: s} = 123;

任務:

1. 解釋上面兩個解構語句的工做原理
2. 你可以實現自定義類的數組解構嗎?

好比:

class A = …
const a = new A();
const [e1, e2] = a;  //  怎麼樣才能讓自定義的類也能支持支持數組的解構呢?

應用

默認值

ES5時,處理默認值的慣用法:node

const scale = opts.scale || 1;

如今能夠:git

const {scale = 1} = opts;

不過二者並不等價,默認值只會在目標值是undefined時纔會生效。es6

const {scale = 1} = {}
// scale === 1

const {scale = 1} = {scale: null}
// scale === null

const {scale = 1} = {scale: undefined}
// scale === 1

交換

有個技巧是能夠在一條語句內實現變量值的交換。github

原來須要三句話:正則表達式

var tmp = a;
var a = b;
var b = a;

如今只須要:算法

const [a, b] = [b, a];

這讓咱們在實現一些基礎算法時更加精練。chrome

函數返回多個值

在ES5中函數只能返回一個值,有了解構賦值,能夠模擬出多個返回值。c#

const [a, b] = f();

固然從設計上說,js的函數的返回值仍是應該是單一的模型比較合適。 數組

很小看到接口層面返回一個數組做爲多個值。 可能在編寫一些專業領域或DSL應用時會用獲得。

而在實現時常常會使用解構賦值帶來的便利:

const {name, age} = getInfo();

忽略數組中的一些值

const [a, , c] = [1, 2, 3];
// a === 1
// c === c

我想到的一個應用是一會兒從正則表達式match對象中取出多個元素。

const re = /^([^=]+)=(.*)$/;
const [, key, value] = re.exec('name=helijia');
// key === 'name'
// value === 'helijia'

spread

解構賦值運算符配合spread會比較有用。

const {name, age, ...exts} = item;

return <Item {...exts} />;

exts對象中並不包含name和age,若是在ES5中要費好幾句語句。

數組也支持spread,可是數組自己具備slice等函數,因此通常用不上。

var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]

重命名

對象的解構還支持從新命名,這在名字衝突,或者簡化代碼時會比較有用。

const item = {
  artisanNick: '玉米'
  artisanLevel: 10,
  artisanType: 3
};

const {artisanNick:nick, artisanLevel:level, artisanType:type} = item;

原來咱們寫成

const nick = item.artisanNick;
const level = item.artisanLevel;
const type = item.artisanType;

可配合默認值一塊兒用:

const {a:aa = 10, b:bb = 5} = {a: 3}

函數參數的解構

這在實際開發中就用的比較多了,好比在React組件開發中:

const Product = ({name, price}) => (
  <div>
    <div>name: {name}</div>
    <div>price: {price}</div>
  </div>
);

babel對解構賦值的支持

以上描述的特性使用babel編譯器就能在主流瀏覽器中工做,babel對ES6的支持是經過將代碼編譯成ES5代碼來實現的;
而nodejs和chrome原生是直接支持es6的,它們是基於V8引擎在解釋器層面支持ES6,所以二者能力是有差別的。

經過babel編譯的ES6,最後本質是ES5代碼,是靜態的,因此只能支持一些語法糖的功能;

下面是一些示例:

常量

// ES6
const [a, b, c, d] = [1, 2, 3, 4];

// 對應的ES5
var a = 1;
var b = 2;
var c = 3;
var d = 4;

數組

// ES6
const list = [1, 2, 3, 4];
const [a, b, c, d] = list;

// ES5
var list = [1, 2, 3, 4];
var a = list[0];
var b = list[1];
var c = list[2];
var d = list[3];

別名和默認值

// ES6
const {a:aa = 10, b:bb = 5} = {a: 3}

// ES5
var _a = { a: 3 };
var _a$a = _a.a;
var aa = _a$a === undefined ? 10 : _a$a;
var _a$b = _a.b;
var bb = _a$b === undefined ? 5 : _a$b;

重命名和默認值的處理。

字符串

// ES6
const [a, b, c] = '1234';

// ES5
var _ = '1234';
var a = _[0];
var b = _[1];
var c = _[2];

字符串也當成數組同樣處理了,因此恰好正常工做。

迭代器

其實只要實現迭代器接口,就可以解構。

const set = new Set([1, 2, 3, 4])
const [a, b, c] = set;
console.log(a, b, c);
// 1 2 3

這段代碼在chrome的cosnole和nodejs中都能正常工做,不過在babel中就歇菜了。

由於它編譯後的結果爲:

var set = new Set([1, 2, 3, 4]);
var a = set[0];
var b = set[1];
var c = set[2];

console.log(a, b, c);
// undefined undefined undefined

固然Map也是實現了迭代器接口的。

const map = new Map();
map.set('window', window);
map.set('document', document);

for (const [key, value] of map) {
  console.log(key + " is " + value);
}

const [[k1, v1], [k2, v2]] = map;   // destructring

再來一個例子:

function* iter() {
  yield 1;
  yield 2;
  yield 3;
}

const [a, b, c] = iter();
console.log(a, b, c);
// 1 2 3

一樣這段代碼在babel中也不能正常工做。

回到任務

const [a, b] = 'abc';
const {toString: s} = 123;

任務:

1. 解釋上面兩個解構語句的工做原理
2. 你可以實現自定義類的數組解構嗎?

因此以上兩個語句能正常工做,緣由是分場景的,在經過babel編譯成ES5和經過解釋器直接執行原理是不同的。

babel編譯器會把它編譯成

// ES6
const [a, b, c] = '1234';
const {toString: s} = "123";

// ES5
var _ = '1234';
var a = _[0];
var b = _[1];
var c = _[2];
var _2 = "123";
var s = _2.toString;

而js引擎執行ES6是由於字符串實現了迭代器接口,以及支持對象屬性訪問。

對於第2個問題,咱們可讓自定義類實現迭代器接口來支持,只是在babel中不能正常工做。

如下是一個示例:

class Random {
  [Symbol.iterator]() {
    return {
      next() {
        return {value: Math.random(), done: false};
      }
    }
  }
}

const random = new Random();
for (const n of random) {
  if (n > 0.8) {
    break;
  }
  console.log(n);
}

const [e1, e2, e3, e4] = new Random();
console.log(e1, e2, e3, e4);

規範和V8對解構賦值的支持

運行語義

看到數組的解構處理,第一步老是取得一個迭代器,而後操做這個迭代器。

從規範中知道,解構賦值操做符對應的元素就是 DestructuringAssignment,查詢V8代碼可知,

V8在parser階段就會把解構賦值語句重寫成等效的賦值語句, 這樣解釋器不須要作修改就能夠運行新的語法,也保證了效率。

關鍵代碼片斷:

Pattern Match

使用了近一年半的Elixir,有許多語言特性另人着迷,其中模式匹配就是一個。

在ES6中引入了和模式匹配語法有點接近的解構賦值(Destructring Assigmnent)語句,可是僅僅是部分精簡代碼的語法糖,而在語義和表達上並無本質變化。

不過搜索github上,看到已有相關的proposal,以及babel實現的issue,因此藉此機會熟悉瞭解一番。

另外發現一個js庫js-pattern-matching,提供了一個函數來支持模式匹配

其中這個JS庫在不引入新語法特性的基礎上支持較好的模式匹配語法,我以爲挺讚的。 它的原理是利用function.toString,獲得函數字符串,再生成匹配的新的函數。

我寫了幾個簡單的示例試用了一下,感受還不錯,不過在類型匹配時有些BUG。

基於解構賦值的匹配

const match = require('js-pattern-matching');

const sum = (list) => match(list) (
  ([x,...xs]) => x + sum(xs),
  ([]) => 0
);

console.log(sum([]));
console.log(sum([1, 2, 3, 4]));

常量匹配

由於要符合語法,因此加個前續v=,文檔說是可改爲其餘字母。

const fibs = (n) => match(n) (
  (v= 0) => 0,
  (v= 1) => 1,
  _ => fibs(n - 2) + fibs(n - 1)
);

for (let i = 0; i < 10; i++) {
  console.log(fibs(i));
}

類型匹配

const type = (v) => match(v) (
  (Array) => 'array',
  (Object) => 'object',
  _ => 'unknow'
);

我也看了proposal的語法,感受風格和原來的js差別太大,設計成Expression,能夠在任何地方使用,可能會由於功能太強而致使濫用,反而起不到原來模式匹配優雅簡潔的目的。

其餘人的一些探索,不過這個語法不是很美觀。

相關文章
相關標籤/搜索