ES6新語法

龍恩0707發表於2017-04-23
個人小總結:1年多沒有寫部落格,感覺很多知識點生疏了,雖然工作上能解決問題,但是當別人問到某個知識點的時候,還是迷迷糊糊的,所以堅持寫部落格是硬道理的,因為大腦不可能把所有的知識點記住,有可能某一天忘了,但是我們工作上還是會使用,只是理論忘了,所以寫部落格的好處是可以把之前的東西重新看一遍後會在大腦裡面重新浮現起來,特別在面試的時候,別人問你的知識點的時候答不上來那種尷尬,但是平時經常使用到,只是說不出所以來的,因此寫部落格是最好的思路。

閱讀目錄

1.let And const命令

1. let的含義及let與var的區別:

let 宣告的變數只在它所在的程式碼塊有效;如下:

for (let i = 0; i < 10; i++) {
  console.log(i);
}
console.log('aaa');
console.log(i); // i is not defined

上面程式碼中,計數器i只在for迴圈體內有效,在迴圈體外引用就會報錯。如下var程式碼:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function() {
    console.log(i);
  }
}
a[6](); // 10

變數i是var宣告的,在全域性範圍內都有效,所以每一次迴圈,新的i值都會覆蓋舊值,導致最後輸出的是最後一輪i的值。

但是如果使用let,宣告的變數僅在塊級作用域內有效,最後輸出的是6. 如下:

var b = [];
for (let j = 0; j < 10; j++) {
  b[j] = function() {
    console.log(j);
  }
}
b[6](); // 6

2. 不存在變數提升

let 不像var 那樣會發生 '變數提升' 現象,因此,變數需要先宣告然後再使用,否則報錯;

// var 的情況
console.log(foo);  // undefined
var foo = 2;

// let的情況;
console.log(bar);  // 報錯
let bar = 2;

3. 暫時性死區

快級作用域記憶體在let命令,它所宣告的變數就繫結在這個區域,不再受外部影響;如下程式碼:

var tmp = 123;
if (true) {
  tmp = 'abc';
  let tmp;
  console.log(tmp); // tmp is not defined
}

上面程式碼定於全域性變數tmp,但是在快級作用域內let又宣告瞭一個區域性變數tmp,導致繫結了這個快級作用域;因此列印出tmp會報錯。

4. 不允許重複宣告

let 不允許在相同作用域內,重複宣告同一個變數。如下程式碼排錯

function a() {
  let a = 10;
  var a = 1;
  console.log(a);
}
a();
function a() {
  let a1 = 10;
  let a1 = 1;
  console.log(a1);
}
a();

也不能在函式內部重新宣告引數。

function func1(arg) {
  let arg; // 報錯
}
function func2(arg) {
  {
    let arg; // 不報錯
  }
}

ES6的塊級作用域

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
} 
f1()

上面的程式碼有2個程式碼塊,都宣告瞭變數n,執行後輸出5,說明了外層程式碼塊不受內層程式碼塊的影響,如果使用了變數var,那麼輸出的就是10;

二:const命令

const 宣告一個只讀的常量,一旦宣告,常量的值就不允許改變。如下程式碼:

const a = 1; 
a = 2; 
console.log(a);  //報錯

也就是說 const 一旦宣告瞭變數,就必須初始化,不能留到以後賦值。如果使用const宣告一個變數,但是不賦值,也會報錯;如下程式碼:

const aa;  // 報錯

2-1 const的作用域與let命令相同;只在宣告所在的塊級作用域內有效。

if (true) {
 const aa = 1;
} 
console.log(aa);  // 報錯

2-2 不可重複宣告 (和let一樣)

var message = "Hello!";
let age = 25;
// 以下兩行都會報錯
const message = "Goodbye!";
const age = 30;

但是對於複合型別的變數,比如陣列,儲存的是一個地址,不可改變的是這個地址,即不能把一個地址指向另一個地址,但是物件本身是可變的,比如可以給它新增新的屬性;如下程式碼:

const a33 = [];
a33.push('Hello'); // 可執行
a33.length = 0;    // 可執行
a33 = ['55']  // 報錯

2.變數的解構賦值

 ES6可以寫成這樣

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

可以從陣列中提取值,按照對應位置,對變數賦值。下面是一些使用巢狀陣列進行解構的列子;

let [foo2, [[bar2], baz]] = [1, [[2], 3]];
console.log(foo2);  // 1
console.log(bar2);  // 2
console.log(baz);  // 3

let [, , third] = ['foo', 'bar', 'baz'];
console.log(third); // baz

let [x1, , y1] = [1, 2, 3];
console.log(x1); // 1
console.log(y1); // 3

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

let [x2, y2, ...z2] = ['a'];
console.log(x2); // 'a'
console.log(y2); // undefined
console.log(z2); // []

var [foo3] = [];
var [bar4, foo4] = [1];
console.log(foo3);  // undefined
console.log(bar4);  // 1
console.log(foo4);  // undefined

另一種情況是不完全解構,即等號左邊的模式,只匹配一部分等號右邊的陣列;如下程式碼:

let [x3, y3] = [1, 2, 3];
console.log(x3); // 1
console.log(y3); // 2

let [a2, [b2], d2] = [1, [2, 3], 4];
console.log(a2); // 1
console.log(b2); // 2
console.log(d2); // 4

如果左邊是陣列,右邊不是一個陣列的話,那麼將會報錯。

// 報錯
let [f] = 1;
let [f] = false;
let [f] = NaN;
let [f] = undefined;
let [f] = null;
let [f] = {};

2. 預設值

解構賦值允許指定預設值。如下程式碼:

var [foo = true] = [];
console.log(foo); // true

var [x, y = 'b'] = ['a'];
console.log(x); // a
console.log(y); // b

var [x2, y2 = 'b'] = ['a', undefined];
console.log(x2); // a
console.log(y2); // b

ES6內部使用嚴格相等運算子(===),判斷一個位置是否有值,所以,如果一個陣列成員不嚴格等於undefined,預設值是不會生效的。

var [x = 1] = [undefined];
console.log(x); // 1

var [x2 = 2] = [null];
console.log(x2); // null

上面程式碼中,如果一個陣列成員是null,預設值就不會生效的;因此x2為null;

但是如果預設值是一個表示式,那麼這個表示式是惰性求值的,即只有在用到的時候,才會求值的。如下程式碼:

function f() {
  console.log('aaa');
}
let [x2 = f()] = [1]; 
console.log(x2); // 1

3. 物件的解構賦值

解構不僅可以用於陣列,還可以用於物件。

var { foo, bar } = { foo: 'aaa', bar: 'bbbb'};
console.log(foo); // aaa
console.log(bar); // bbbb

物件的解構與陣列有一個重要的不同,陣列的元素是按順序排序的,變數的取值是由他的位置決定的,而物件的屬性沒有順序的,變數必須和屬性同名,才能取到正確的值.

var { baz } = { foo: "aaa", bar: "bbb" };
console.log(baz); // undefined

4. 字串的解構賦值

字串被轉換成了一個類似陣列的物件。

const [a, b, c, d, e] = 'hello';
console.log(a); // h
console.log(b); // e
console.log(c); // l
console.log(d); // l
console.log(e); // o

類似陣列的物件有一個length屬性,還可以對這個物件進行解構賦值。

let {length: len} = 'hello';
console.log(len); // 5

5. 函式引數的解構賦值

函式引數也可以使用解構賦值,如下程式碼:

function add([x, y]) {
  return x + y;
}
console.log(add([1, 2])); // 3

function move({x = 0, y = 0} = {}) {
  return [x, y];
}
console.log(move({x:3, y:8})); // [3, 8]
console.log(move({x: 3})); // [3, 0];
console.log(move({})); // [0, 0]
console.log(move()); // [0, 0]

但是下面的寫法會得到不一樣的結果,如下程式碼:

function move({x, y} = {x: 0, y: 0}) {
  return [x, y];
} 

console.log(move({x: 3, y: 8})); // [3, 8]
console.log(move({x:3})); // [3, undefined]

因為move函式指定的引數有預設值,而不是給變數x和y指定預設值,所以會得到與前一種寫法不同的結果;

5.變數解構的用途

5-1 交換變數的值

[x, y] = [y, x];

從函式返回多個值

函式只能返回一個值,如果需要返回多個值,只能將他們放在陣列或者物件裡面返回,但是有了解構賦值,取出這些值就非常方便了;

// 返回一個陣列
function example() {
  return [1, 2, 3];
}
var [a, b, c] = example();
console.log([a, b, c]); // [1, 2, 3]
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3

// 返回一個物件
function example2() {
  return {
    foo: 1,
    bar: 2
  }
}
var {foo, bar} = example2();
console.log(foo); // 1
console.log(bar); // 2

5-2 函式引數的定義

解構賦值可以方便地將一組引數與變數名對應起來

// 引數是一組有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 引數是一組無次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

5-3 提取JSON資料

var jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number); // 42, 'ok', [867, 5309]

5-4. 函式引數的預設值,如下程式碼:

function move({x = 0, y = 0} = {}) {
  return [x, y];
}
console.log(move({x:3, y:8})); // [3, 8]
console.log(move({x: 3})); // [3, 0];
console.log(move({})); // [0, 0]
console.log(move()); // [0, 0]

5-5 遍歷map結構 如下程式碼:

var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

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

// first is hello
// second is world

5-6 輸入模組的指定方法

載入模組的時候,往往需要指定輸入那些方法,解構賦值可以做這一點

const { Afun, Bfun } = require('./A')

3.陣列的擴充套件

 1. Array.from() 將類陣列物件轉化為真正的陣列。

let arrayLike = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  length: 3
};

// ES5
var arr1 = [].slice.call(arrayLike); 
console.log(arr1); // ['a','b','c'];

// ES6的寫法
let arr2 = Array.from(arrayLike);
console.log(arr2);  // ['a', 'b', 'c']

// 對於類似陣列的物件是DOM操作返回的NodeList集合,以及函式內部的arguments物件。
// Array.from都可以將它們轉為真正的陣列。
let doms = document.querySelectorAll('p');
Array.from(doms).forEach(function(p) {
  console.log(p); // 列印dom節點
});
// 上面的程式碼中,querySelectorAll方法返回一個類似的陣列物件,只有將該返回類似陣列物件轉化為真正的陣列物件,才能使用forEach.
// arguments 物件
function foo(arrs) {
  var args = Array.from(arguments);
  console.log(args);  // ['aa']
}
var obj = {
  "0": 'aa'
};
foo(obj);

// 只要部署了Iterator介面的資料結構,比如字串和Set結構,都可以使用Array.from將其轉為真正的陣列。
// 字串
console.log(Array.from('hello')); // ['h', 'e', 'l', 'l', 'o']
// set結構的
let namesSet = new Set(['a', 'b']);
console.log(Array.from(namesSet)); // ['a', 'b'];

// 如果引數是一個真正的陣列,那麼使用Array.from 也會返回一個真正的陣列。
console.log(Array.from([1, 2, 3])); // [1, 2, 3]

// 擴充套件運算子,也可以將其轉化為陣列的,如下
// arguments物件
function foo() {
  var args = [...arguments];
  console.log(args); // []
}
foo();

// NodeList物件
console.log(...document.querySelectorAll('p')); // 列印dom節點

// Array.from 還可以接受第二個引數,作用類似於陣列的map方法,用來對每個元素進行處理,將處理後的值放入返回的陣列。
// 形式如下:
console.log(Array.from([1, 2, 3], (x) => x * x));  // [1, 4, 9]
// 等價如下:
console.log(Array.from([1, 2, 3]).map(x => x * x)); // [1, 4, 9]

// 下面可以將陣列中布林值的成員轉為0
console.log(Array.from([1, , 2, , 3], (n) => n || 0)); // [1, 0, 2, 0, 3]

2. Array.of() 用於將一組值,轉換為陣列。

console.log(Array.of(3, 10, 8)); // [3, 10, 8];
console.log(Array.of(3)); // [3]
console.log(Array.of(3).length); // 1

console.log(Array.of()); // []
console.log(Array.of(undefined)); // [undefined]
console.log(Array.of(1)); // [1]
console.log(Array.of(1, 2)); // [1, 2]

// Array.of方法可以使用下面的程式碼模擬實現。
function ArrayOf() {
  return [].slice.call(arguments);
}

3. 陣列例項 copyWithin()

該例項方法,在當前陣列內部,將指定位置的成員複製到其他位置上(會覆蓋原有的成員), 然後返回當前的陣列。

Array.prototype.copyWithin(target, start = 0, end = this.length)

target (必須):從該位置開始替換資料

start (可選): 從該位置開始讀取資料,預設為0,如果為負值,表示倒數。

end(可選): 到該位置前停止讀取資料,預設等於陣列的長度,如果為負值,表示倒數。

console.log([1, 2, 3, 4, 5].copyWithin(0, 3)); // [4, 5, 3, 4, 5]
// 將3號位複製到0號位
console.log([1, 2, 3, 4, 5].copyWithin(0, 3, 4)); // [4, 2, 3, 4, 5]

// -2 相當於倒數第二個數字,-1相當於倒數最後一位
console.log([1, 2, 3, 4, 5].copyWithin(0, -2, -1)); // [4, 2, 3, 4, 5]

4. 陣列例項的 find()和findIndex()

find()方法用於找出第一個符合條件的陣列成員,該引數是一個回撥函式,所有的陣列成員依次執行該回撥函式,直到找到第一個返回值為true的成員,

然後返回該成員,如果沒有找到的話,就返回undefined console.log([1, 4, 5, 10].find((n) => n > 5)); // 10

console.log([1, 4, 5, 10].find(function(value, index, arrs){
  return value > 9;
}));  // 10

findIndex()方法 返回第一個符合條件的陣列成員的位置,如果沒有找到,就返回-1

console.log([1, 5, 10, 15].findIndex(function(value, index, arrs) {
  return value > 9; // 2 
}));

5. 陣列例項 fill()

fill方法使用給定值,填充一個陣列

// fill 方法用於空陣列初始化非常方便,陣列中已有的元素,會被全部抹去
console.log(['a', 'b', 'c'].fill(7)); // [7, 7, 7]

// fill方法接收第二個和第三個引數,用於指定填充的起始位置和結速位置
console.log(['a', 'b', 'c'].fill(7, 1, 2)); // ['a', '7', 'c']

4.函式的擴充套件

 1. 函式引數的預設值

ES6允許為函式的引數設定預設值,即直接寫在引數定義的後面。

function log(x, y = 'world') {
  console.log(x, y);
}
log('Hello'); // Hello world
log('Hello', 'China');  // Hello China
log('Hello', ''); // Hello

function Point(x = 0, y = 0) {
  this.x = x;
  this.y = y;
}
var p = new Point();
console.log(p.x); // 0
console.log(p.y); // 0

ES6的寫法好處:

1. 閱讀程式碼的人,可以立刻意識到那些引數是可以省略的,不用檢視函式體或文件。
2. 有利於將來的程式碼優化,即使未來的版本在對外介面中,徹底拿調這個引數,也不會導致以前的程式碼無法執行;
引數變數預設宣告的,所以不能用let或const再次宣告。
function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

2-2. 與解構賦值預設值結合使用。

引數預設值可以與解構賦值的預設值,結合起來使用。

function foo({x, y = 5}) {
  console.log(x, y);
}
foo({});  // undefined, 5
foo({x: 1});  // 1, 5
foo({x:1, y:2});  //1, 2
foo(); // Uncaught TypeError: Cannot read property 'x' of undefined

只有當函式的引數是一個物件時,變數x與y 才會通過解構賦值而生成,如果函式呼叫的不是一個物件的話,變數x就不會生成,從而導致出錯;

下面是另一個物件的解構賦值的列子

function fetch(url, { body = '', method='GET', headers = {}}) {
  console.log(method);
}
fetch('http://www.baidu.com', {}); // 'GET'
fetch('http://www.baidu.com'); // 報錯  Uncaught TypeError: Cannot read property 'body' of undefined

上面的程式碼不能省略第二個引數,如果結合函式引數的預設值,就可以省略第二個引數。這樣就出現了雙重預設值;如下程式碼:

function fetch(url, { method = 'GET', body = '' } = {} ) {
  console.log(method);
}
fetch('http://www.baidu.com'); // GET

再看下面兩種寫法有什麼差別

// 寫法1
function m1({x = 0, y = 0} = {}) {
  return [x, y];
}

// 寫法2
function m2({x, y} = { x: 0, y: 0}) {
  return [x, y];
}

// 函式沒有引數的情況下 
console.log(m1());  // [0, 0]
console.log(m2());  // [0, 0]

// x 和 y都有值的情況下
console.log(m1({x:3, y: 8}));   // [3, 8]
console.log(m2({x:3, y: 8}));   // [3, 8]

// x有值,y無值的情況下 
console.log(m1({x:3}));  // [3, 0]
console.log(m2({x: 3}));  // [3, undefined]

// x 和 y都無值的情況下 
console.log(m1({}))  // [0, 0]
console.log(m2({}))  // [undefined, undefined]

console.log(m1({z:3}));  // [0, 0]
console.log(m2({z:3}));  // [undefined, undefined]

上面兩種寫法都對函式的引數設定了預設值,區別是寫法一函式的預設值是空物件,但是設定了物件解構賦值的預設值,寫法2函式的引數的預設值是一個有具體屬性的物件,

但是沒有設定物件解構賦值的預設值。

3. 引數預設值的位置

一般情況下,定義了預設值的引數,應該是函式的尾引數,因為這樣比較容易看出來,到底省略了那些引數,如果非尾部引數設定預設值,這個引數是無法省略的。

// demo 1
function f(x = 1, y) {
  return [x, y];
}
console.log(f()); // [1, undefined]
console.log(f(2)); // [2, undefined]
f(, 1); // 報錯

// demo 2
function f(x, y = 5, z) {
  return [x, y, z];
}
console.log(f());  // [undefined, 5, undefined]
console.log(f(1));  // [1, 5, undefined]
console.log(f(1, , 2)); // 報錯
console.log(f(1, undefined, 2));  // [1, 5, 2]

function foo(x = 5, y = 6) {
  console.log(x, y);
}
foo(undefined, null);  // 5, null

4. 函式的作用域

如果引數預設值是一個變數,則該變數所處的作用域,與其他變數的作用域規則是一樣的,則先是當前作用域,再是全域性作用域。

var x = 1;
function f(x, y = x) {
  console.log(y);
}
console.log(f(2));  // 2

如果在呼叫時候,函式作用域內部的變數x沒有生成,結果不一樣。

let x = 1;
function f(y = x) {
  let x = 2;
  console.log(y); // 1
}
f();

但是如果,全域性變數x不存在的話, 就會報錯

function f( y = x) {
  let x = 2;
  console.log(y);
}
f(); // 報錯 x is not defined

5. rest引數

ES6引入rest引數(形式為 "...變數名"),用於獲取函式的多餘引數,rest引數搭配的變數是一個陣列,該變數將多餘的引數放入一個陣列中。

function add(...values) {
  let sum = 0;
  for(let i of values) {
    sum += i;
  }
  return sum;
}
console.log(add(2, 3, 5)); // 10

rest引數中的變數代表一個陣列,所以陣列特有的方法都可以用於這個變數。下面是一個利用rest引數改寫陣列push方法的例子。

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3);  // 1,2,3

注意,rest引數之後不能再有其他引數(即只能是最後一個引數),否則會報錯。

// 報錯
function f(a, ...b, c) {
  // ...
}

6. 擴充套件運算子

擴充套件運算子(spread) 是三個點 (...), 它好比rest引數的逆運算,將一個陣列轉為用逗號分隔的引數序列。

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

function add(x, y) {
  return x + y;
}

var numbers = [4, 38];
console.log(add(...numbers)); // 42

上面的程式碼 使用了擴充套件運算子,該運算子是一個陣列,變為引數序列。

替代陣列的apply方法

擴充套件運算子可以展開陣列,所以不需要apply方法,將陣列轉為函式的引數。

// ES6
function f2(x, y, z) {
  console.log(x); // 0
  console.log(y); // 1
  console.log(z); // 2
}
var args = [0, 1, 2];
f(...args); 

// ES5的寫法
console.log(Math.max.apply(null, [14, 3, 77]))  // 77
// ES6的寫法
console.log(Math.max(...[14, 3, 77]))  // 77
// 等同於
console.log(Math.max(14, 3, 77));  // 77

// ES5的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // 0,1,2,3,4,5

// ES6的寫法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);
console.log(arr1); // 0,1,2,3,4,5

擴充套件運算子的運用

1. 合併陣列
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

// ES5 合併陣列
var rets1 = arr1.concat(arr2, arr3);
console.log(rets1); // ["a", "b", "c", "d", "e"]

// ES6合併陣列
var rets2 = [...arr1, ...arr2, ...arr3];
console.log(rets2); // ["a", "b", "c", "d", "e"]
2. 與解構賦值組合
擴充套件運算子可以與解構賦值結合起來,用於生成陣列。
const [first, ...rest1] = [1, 2, 3];
console.log(first); // 1
console.log(rest1); // [2, 3]

const [first2, ...rest2] = [];
console.log(first2); // undefined
console.log(rest2); // []

const [first3, ...rest3] = ['foo'];
console.log(first3); // foo
console.log(rest3); // []

如果將擴充套件運算子用於陣列賦值,只能放在引數的最後一位,否則會報錯

const [...butLast, last] = [1, 2, 3, 4, 5]; // 報錯
const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 報錯

擴充套件運算子可以將字串轉為真正的陣列

[...'hello']
// [ "h", "e", "l", "l", "o" ]

var f = v => v;
console.log(f(2)); // 2

// 等價於如下函式
var f = function(v) {
  return v;
}

// 如果箭頭函式不需要引數或需要多個引數的話,可以使用圓括號代表引數的部分
var f2 = () => 5;
// 等價於如下
var f = function() {
  return 5;
}

var sum = (n1, n2) => n1 + n2;

console.log(sum(1, 2)); // 3

// 等價於 如下函式
var sum = function(n1, n2) {
  return n1 + n2;
}

//  或者也可以這樣寫 
var sum2 = (n1, n2) => {
  return n1 + n2;
}
console.log(sum2(2, 3)); // 5

// 如果要返回一個物件的話, 需要使用圓括號括起來
var getData = id => ({id: id});
console.log(getData(1)); // {id: 1}

// 正常函式的寫法 
[1, 2, 3].map(function(x) {
  return x * x;
});

// 箭頭函式的寫法 
[1, 2, 3].map(x => x * x);

7. 函式引數的尾逗號

ECMASCRIPT2017 允許函式的最後一個引數有尾逗號。
// ES217
function douhao(a, b,) {
  // 正常
}
console.log(douhao(1,2,))

5.物件的擴充套件

 1. 屬性的簡潔表示法

var foo = 'bar';
var baz = {foo};
console.log(baz); // {foo: 'bar'}

// 等價於 
var baz = {foo: foo};

// ES6 允許在物件之中,直接寫變數,這時,屬性名為變數名,屬性值為變數值;如下:
function f(x, y) {
  return {x, y};
}
console.log(f(5, 6)); // {x: 5, y: 6}

// 除了屬性簡寫,方法也可以簡寫
var o = {
  method() {
    return 'Hello';
  }
};
// 上面的程式碼等價於如下程式碼:
var o = {
  method: function() {
    return 'Hello';
  }
};
// 如下程式碼:
var birth = '2017/1/1';
var Person = {
  name: '張三',
  birth,
  hello() {
    console.log('我的名字是', this.name);
    console.log('我的生日是', this.birth);
  }
}; 
Person.hello(); // 我的名字是 張三, 生日是 2017/1/1

// 用於函式的返回值,非常方便
function getPoint() {
  var x = 1;
  var y = 10;
  return {x, y};
}
console.log(getPoint());// {x:1, y:10}

2. 屬性名錶達式

// ES6 允許字面量 定義物件時,用作為物件的屬性名,即把表示式放在括號內
let propKey = 'foo';
var obj = {
  propKey: true,
  ['a' + 'bc']: 123
}
console.log(obj.propKey); // true
console.log(obj.abc); // 123

var lastWord = 'last world';
var a = {
  'first world': 'hello',
  [lastWord]: 'world'
};
console.log(a['first world']);  // hello
console.log(a['last world']);   // world
console.log(a[lastWord]);  // world

// 表示式還可以定義方法名
let obj2 = {
  ['h' + 'ello']() {
    return 'hi';
  }
}
console.log(obj2.hello()); // hi

3. Object.is()

ES5比較兩個值是否相等,只有兩個運算子,相等運算子("==")和嚴格相等運算子("==="), 他們都有缺點,前者會自動轉換資料型別,後者NAN不等於自身,以及+0 等於 -0;ES6有該方法是同值相等演算法,Object.is()是比較兩個值是否嚴格相等,和('===')是一個含義

console.log(Object.is('foo', 'foo')); // true

console.log(Object.is({}, {}));  // false

console.log(+0 === -0) // true
console.log(NaN === NaN); // false

console.log(Object.is(+0, -0)); // false
console.log(Object.is(NaN, NaN)); // true

4. Object.assign()

// Object.assign()方法用於物件的合併,將源物件的所有可列舉屬性,複製到目標物件中。第一個引數是目標物件,後面的引數都是源物件。
var target = { a: 1 };
var s1 = { b: 2};
var s2 = { c: 3};
Object.assign(target, s1, s2);
console.log(target); // {a:1, b:2, c:3}

// 注意:如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性。
var target2 = { a: 1, b: 1};
var s11 = { b:2, c:2 };
var s22 = {c:3};
Object.assign(target2, s11, s22);
console.log(target2);  // { a: 1, b:2, c:3 }

擴充套件運算子(...) 也可以用於合併兩個物件,比如如下程式碼:

let ab = { ...a, ...b }; 

等價於

let ab = Object.assign({}, a, b);
// 如果該引數不是物件,則會先轉成物件,然後返回
console.log(typeof Object.assign(2)); // 'object'

由於undefined和null無法轉成物件,所以如果它們作為引數,就會報錯。

Object.assign(undefined) // 報錯
Object.assign(null) // 報錯

// Object.assign 方法實現的是淺拷貝,而不是深拷貝,如果物件某個屬性值發生改變,那麼合併物件的屬性值也會發生改變。如下程式碼:
var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
console.log(obj2.a.b); // 2

// 同名屬性覆蓋
var target = { a: {b: 'c', d: 'e' }};
var source = {a: {b: 'hello'}};
console.log(Object.assign(target, source)); // { a: {b: 'hello'}}

// Object.assign 可以用來處理陣列,但是會把陣列視為物件, 
console.log(Object.assign([1,2,3], [4, 5])); // [4, 5, 3]
// 上面程式碼中,Object.assign把陣列視為屬性名0,1,2的物件,因此原陣列的0號屬性4覆蓋了目標陣列的0號屬性。

5. 常見用途

5-1 為物件新增屬性

// 如下程式碼:
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}
console.log(new Point(1, 2)); // {x:1, y:2}

5-2 為物件新增方法

// 2. 為物件新增方法
var A = function() {};
A.prototype = {
  init: function() {}
}
Object.assign(A.prototype, {
  test(a, b) {

  },
  test2() {

  }
});

var AInter = new A();
console.log(AInter);  // {init: function(){}, test: function(a, b){}, test2: function(){} }

5-3 克隆物件

// 3. 克隆物件 將原始物件拷貝到一個空物件內,就得到了原始物件的克隆
function clone(origin) {
  return Object.assign({}, origin);
}
var obj = {
  name: 'kongzhi'
};
console.log(clone(obj)); // {name: 'kongzhi'}

採用這種方法克隆,只能克隆原始物件自身的值,不能克隆它繼承的值,如果想要保持原型繼承,如下程式碼:

function clonePrototype(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}
var AA = {
  init: function() {

  }
}
AA.prototype = {
  BB: function() {

  }
}
var AAP = clonePrototype(AA);
console.log(AAP);  // {init: function(){}, prototype:xx}

6-1  Object.keys() 物件轉為陣列

// ES5引入了Object.keys方法,返回一個陣列(物件轉為陣列),成員是引數物件自身的(不含繼承的)所有可遍歷屬性的鍵名。
var obj1 = {
  foo: 'bar',
  baz: 42
};
console.log(Object.keys(obj1));  // ['foo', 'baz']

6-2 Object.values()

該方法返回一個陣列,成員是引數物件自身(不含繼承的)所有可遍歷屬性的鍵值。

var obj2 = {
  foo: 'bar',
  baz: 42
};

console.log(Object.values(obj2)); // ["bar", 42];

// 如果引數不是物件,Object.values 會先將其轉為物件。最後返回空陣列
console.log(Object.values(42)); // []
console.log(Object.values(true)); // []

6-3 Object.entries()

該方法返回一個陣列,成員是引數自身(不含繼承的)所有可遍歷屬性的鍵值對陣列。

var obj3 = { foo: 'bar', baz: 42};
console.log(Object.entries(obj3)); // [ ["foo", "bar"], ["baz", 42] ]

6.set 和 Map的資料結構

 set類似於陣列,但是成員值都是唯一的,沒有重複的值。

set 有如下方法

1. add(value): 新增某個值,返回Set結構本身

2. delete(value): 刪除某個值,返回一個布林值,表示刪除是否成功。

3. has(value): 返回一個布林值,表示該值是否為Set的成員。

4. clear() 清除所有的成員,沒有返回值。

var s = new Set();
[2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
for (let i of s) {
  console.log(i); // 2, 3, 5, 4
}

// Set函式可以接受一個陣列(或類似陣列的物件)作為引數,用來初始化。
var set = new Set([1, 2, 3, 4, 4]);
console.log([...set]); // [1, 2, 3, 4]

var items = new Set([1, 2, 3, 4, 4, 5, 6, 5]);
console.log(items.size); // 6

function getElems() {
  return [...document.querySelectorAll('p')];
}
var s2 = new Set(getElems());
console.log(s2.size); // 2

去除陣列重複的成員

var arrs = [1, 2, 3, 1, 3, 4];
console.log([...new Set(arrs)]); // [1, 2, 3, 4]

// 向Set加入值的時候,不會發生型別轉換,因此 5 和 "5" 是兩個不同的值,但是NaN等於NaN, 如下:
let s3 = new Set();
let a = NaN;
let b = NaN;
s3.add(a);
s3.add(b);
console.log(s3); // {NaN}

// 但是兩個物件是不相等的
let s4 = new Set();
s4.add({});
console.log(s4.size); // 1

s4.add({});
console.log(s4.size); // 2

var s5 = new Set();
s5.add(1).add(2).add(2);
console.log(s5.size); // 2
console.log(s5.has(1)); // true
console.log(s5.has(2)); // true
console.log(s5.has(3)); // false
s5.delete(2);
console.log(s5.has(2)); // false

// Array.from 方法也可以將Set結構轉為陣列
var items2 = new Set([1, 2, 3, 4, 5]);
var array = Array.from(items2);
console.log(array); // [1, 2, 3, 4, 5]

去除陣列重複的另外一種方法

function unique(array) {
  return Array.from(new Set(array));
}
console.log(unique([1, 2, 2, 3])); // [1, 2, 3]

遍歷操作

Set結構的實列有四個遍歷方法

1. keys() 返回鍵名的遍歷器。

2. values() 返回鍵值的遍歷器。

3. entries() 返回鍵值對的遍歷器。

4. forEach() 使用回撥函式遍歷每個成員。

let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
  console.log(item); // red, green blue
}

for (let item of set.values()) {
  console.log(item); // red, green blue
}

for (let item of set.entries()) {
  console.log(item); // ['red', 'red']  ['green', 'green'] ['blue', 'blue']
}

// Set結構的實列預設可遍歷,它的預設遍歷生成函式就是它的values方法
// 因此可以省略values方法,直接使用for.. of 迴圈遍歷Set
let set2 = new Set(['red', 'green', 'blue']);
for (let x of set2) {
  console.log(x); // red green blue
}

// forEach()方法
let s3 = new Set([1, 2, 3]);
s3.forEach((value, key) => console.log(value * 2)); // 2 4 6

// 遍歷的應用 
// 擴充套件運算子(...) 內部使用 for...of迴圈,
let s6 = new Set(['a', 'b', 'c']);
let arr6 = [...s6];
console.log(arr6); // ['a', 'b', 'c']

// 使用Set可以很容易地實現並集, 交集和差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 並集
let union = new Set([...a, ...b]);
console.log(union); // {1, 2, 3, 4}

// 交集
let jj = new Set([...a].filter(x => b.has(x)));
console.log(jj); // {2, 3}

// 差集
let diff = new Set([...a].filter(x => !b.has(x)));
console.log(diff); // {1}

Map

// Object結構提供了 '字串-值'的對應,Map結構提供了值-值的對應,但是'鍵'的範圍不限於字串,各種型別的值(包括物件)都可以當做鍵

var m = new Map();
var o = {p: 'hello world'};

m.set(o, 'content');
console.log(m.get(o)); // 'content'
console.log(m.has(o)); // true
console.log(m.delete(o)); // true
console.log(m.has(o)); // false

// 作為建構函式,Map也可以接受一個陣列作為引數,該陣列的成員是一個個表示鍵值對的陣列。
var map = new Map([
  ['name', '張三'],
  ['title', 'aa']
])
console.log(map.size); // 2
console.log(map.has('name')); // true
console.log(map.get('name')); // 張三
console.log(map.has('title')); // true
console.log(map.get('title')); // aa

// 字串true 和 布林值true是兩個不同的鍵
var m2 = new Map([
  [true, 'foo'],
  ['true', 'bar']
]);
console.log(m2.get(true)); // 'foo'
console.log(m2.get('true')); // 'bar'

// 如果對同一個鍵多次賦值,後面的值將覆蓋前面的值
let m3 = new Map();
m3.set(1, 'aaa').set(1, 'bbb');
console.log(m3.get(1)); // 'bbb'

// 如果讀取一個未知的鍵,則返回undefined
console.log(new Map().get('aaaaassd')); // undefined

// 對同一個物件引用是讀取不到值的,因為物件是比較記憶體地址的
var m4 = new Map();
m4.set(['a'], 555);
console.log(m4.get(['a'])); // undefined

m4.set(NaN, 123);
console.log(m4.get(NaN)); // 123
m4.set(-0, 123);
console.log(m4.get(+0)); // 123

// map 提供三個遍歷器生成函式和一個遍歷方法
// 1. keys(), 2. values(), 3. entries(). 4. forEach() Map遍歷的順序就是插入的順序
let m5 = new Map([
  ['a', 'no'],
  ['y', 'yes']
]);
for (let key of m5.keys()) {
  console.log(key); // a,y
}

for(let value of m5.values()) {
  console.log(value); // 'no', 'yes'
}

for (let item of m5.entries()) {
  console.log(item[0], item[1]); // 'a' 'no' 'y' 'yes'
}

// Map結構的預設遍歷器介面 (Symbol.iterator屬性) 就是 entries方法, map[Symbol.iterator] === map.entries
// Map結構轉為陣列結構,比較快速的方法可以使用擴充套件運算子(...)
let m6 = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three']
]);
console.log([...m6.keys()]); // [1, 2, 3]

console.log([...m6.values()]); // ['one', 'two', 'three']

console.log([...m6.entries()]); //[[1, 'one'], [2, 'two'], [3, three]]

console.log([...m6]); //[[1, 'one'], [2, 'two'], [3, three]]

// 結合陣列的map方法和filter方法,可以實現map的遍歷和過濾
let m0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c');
let m00 = new Map([...m0].filter(([k, v]) => k < 3)); // {1 => 'a', 2 => 'b'}

let m11 = new Map([...m0].map(([k, v]) => [k*2, '_'+v])); 
// {2 => '_a', 4 => '_b', 6 => '_c'}

// Map轉為陣列
let amap = new Map().set(true, 7).set({foo: 3}, ['abc']);
console.log([...amap]); // [ [true, 7], [{foo: 3}, ['abc'] ] ]

// 陣列轉為Map
console.log(new Map([[true, 7],[{foo:3}, ['abc']]]));// Map {true => 7, Object {foo: 3} => ['abc']}

// Map轉為物件
function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k, v] of strMap) {
    obj[k] = v;
  }
  return obj;
}
let myMap = new Map().set('yes', true).set('no', false);
console.log(strMapToObj(myMap)); // {yes: true, no: false}

// 物件轉為map
function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}
console.log(objToStrMap({yes: true, no: false})); // [ ['yes', ture], ['no', false]]

// Map轉為json
// Map轉為JSON要區分二種情況,一種情況是,Map的鍵名都是字串,這時可以選擇轉為物件的JSON
function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}
let myMap2 = new Map().set('yes', true).set('no', false);
console.log(strMapToJson(myMap2)); // '{"yes": true, "no": false}'

// 第二種情況是,Map的鍵名有非字串,這時候可以選擇轉為陣列的JSON
function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}
let myMap3 = new Map().set(true, 7).set({foo: 3}, ['abc']);
console.log(mapToArrayJson(myMap3)); // '[[true, 7], [{"foo": 3}, ["abc"]]]'

// JSON轉為Map  
// 正常情況下,所有鍵名都是字串
function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}
console.log(jsonToStrMap('{"yes": true, "no": false}')); // Map {'yes' => true, 'no' => false}

// 有一種特殊情況,整個JSON就是一個陣列,且每個陣列成員本身,又是一個有兩個成員的陣列。這時,它可以一一對應地轉為Map。這往往是陣列轉為JSON的逆操作。
function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}

7.class

 ES6的class

// ES5 定義類
function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function() {
  return '(' + this.x + ', ' + this.y + ')';
}

var p = new Point(1, 2);
console.log(p);

// ES6的語法,方法之間不需要使用逗號分隔
class Point2 {
  // 建構函式
  constructor(x, y) {
    
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

var p2 = new Point2(1, 2);
console.log(p2);

// 類的資料型別就是函式,類本身就指向建構函式
class Point3 {

}
console.log(typeof Point3); // 'function'
console.log(Point === Point.prototype.constructor); // true

// ES6 和 ES5 定義原型的區別如下:
class Point4 {
  constructor(props) {
    
  }
  toString() {}
  toValue() {}
}
// 等價於如下ES5的程式碼
Point4.prototype = {
  toString() {},
  toValue() {}
}

// 也可以使用Object.assign方法合併物件到原型去,如下程式碼:
class Point5 {
  constructor(props) {
    
    // ...
  }
}
Object.assign(Point5.prototype, {
  toString() {},
  toValue() {}
});

class的繼承

// 父類
class Point {
  init(x, y) {
    console.log('x:'+x, 'y:'+y);
  }
}

// 子類繼承父類
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 呼叫父類的 constructor(x, y)
    this.color = color;
  }
}
var c = new ColorPoint(1, 2, 'red');
console.log(c.init(1, 2)); // 繼承父類,因此有init方法 x:1, y:2
console.log(c.color); // 'red'

// 子類必須在constructor方法中呼叫super方法,否則新建例項會報錯,這是因為子類沒有自己的this物件,而是繼承父類的this物件,
// 如果不呼叫super方法,子類就得不到this物件。如下程式碼:
class Point2 {

}
class Color2 extends Point2 {
  constructor() {
    
  }
}
let cp = new Color2(); // 報錯

注意: 在子類建構函式中,只有呼叫super之後,才可以使用this關鍵字,否則會報錯,只有super方法才能返回父類的例項。

Object.getPrototypeOf()

該方法可以用來從子類上獲取父類;如下程式碼:

// 父類
class Point {
  init(x, y) {
    console.log('x:'+x, 'y:'+y);
  }
}

// 子類繼承父類
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 呼叫父類的 constructor(x, y)
    this.color = color;
  }
}
console.log(Object.getPrototypeOf(ColorPoint) === Point); // true

Class的靜態方法

// 如果在一個方法中,加上static關鍵字,就表示該方法不會被例項繼承,而是直接通過類來呼叫
class Foo {
  static init() {
    return 'hello'
  }
}
console.log(Foo.init()); // 'hello'

var foo = new Foo();
console.log(foo.init()); // 報錯


// 父類的靜態方法,可以被子類繼承
class Foo {
  static init() {
    return 'hello';
  }
}
class Bar extends Foo {

}
console.log(Bar.init()); // 'hello'

// 靜態方法也可以從super物件上呼叫
class Foo2 {
  static init() {
    return 'world';
  }
}

class Bar2 extends Foo2 {
  static init2() {
    return super.init() + ', hello';
  }
}
console.log(Bar2.init2()); // world, hello

Class的靜態屬性和例項屬性

// 靜態屬性 其他的寫法不可以
class Foo {

}
Foo.prop = 1;
console.log(Foo.prop); // 1

// 類的例項屬性 可以使用等式,寫入類的定義之中
class MyClass {
  myProp = 42;
  constructor() {
    console.log(this.myProp); // 42
  }
}
// react 的寫法
class ReactCounter extends React.Component {
  state = {
    count: 0
  };
}
// 相當於如下的寫法
class ReactCounter2 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    }
  }
}

// 還可以使用如下新寫法
class ReactCounter3 extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  state;
}

相關文章