學習ES6新語法

Risker-C發表於2018-08-11

ES6新語法

前言:


ECMAScript 6.0(以下簡稱ES6)是JavaScript語言的下一代標準,已經在2015年6月正式釋出了。它的目標,是使得JavaScript語言可以用來編寫複雜的大型應用程式,成為企業級開發語言。


ECMAScript的歷史

ES6 從開始制定到最後釋出,整整用了 15 年。

ECMAScript 1.0 是 1997 年釋出的,接下來的兩年,連續釋出了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一個巨大的成功,在業界得到廣泛支援,成為通行標準,奠定了 JavaScript 語言的基本語法,以後的版本完全繼承。直到今天,初學者一開始學習 JavaScript,其實就是在學 3.0 版的語法。

2000 年,ECMAScript 4.0 開始醞釀。這個版本最後沒有通過,但是它的大部分內容被 ES6 繼承了。因此,ES6 制定的起點其實是** 2000 年**。

為什麼 ES4 沒有通過呢?因為這個版本太激進了,對 ES3 做了徹底升級,導致標準委員會的一些成員不願意接受。ECMA 的第 39 號技術專家委員會(Technical Committee 39,簡稱 TC39)負責制訂 ECMAScript 標準,成員包括 Microsoft、Mozilla、Google 等大公司。

2007 年 10 月,ECMAScript 4.0 版草案發布,本來預計次年 8 月釋出正式版本。但是,各方對於是否通過這個標準,發生了嚴重分歧。以 Yahoo、Microsoft、Google 為首的大公司,反對 JavaScript 的大幅升級,主張小幅改動;以 JavaScript 創造者 Brendan Eich 為首的 Mozilla 公司,則堅持當前的草案。

2008 年 7 月,由於對於下一個版本應該包括哪些功能,各方分歧太大,爭論過於激烈,ECMA 開會決定,中止 ECMAScript 4.0 的開發,將其中涉及現有功能改善的一小部分,釋出為 ECMAScript 3.1,而將其他激進的設想擴大範圍,放入以後的版本,由於會議的氣氛,該版本的專案代號起名為 Harmony(和諧)。會後不久,ECMAScript 3.1 就改名為 ECMAScript 5。

2009 年 12 月,ECMAScript 5.0 版正式釋出。Harmony 專案則一分為二,一些較為可行的設想定名為 JavaScript.next 繼續開發,後來演變成 ECMAScript 6;一些不是很成熟的設想,則被視為 JavaScript.next.next,在更遠的將來再考慮推出。TC39 委員會的總體考慮是,ES5 與 ES3 基本保持相容,較大的語法修正和新功能加入,將由 JavaScript.next 完成。當時,JavaScript.next 指的是 ES6,第六版釋出以後,就指 ES7。TC39 的判斷是,ES5 會在 2013 年的年中成為 JavaScript 開發的主流標準,並在此後五年中一直保持這個位置。

2011 年 6 月,ECMAscript 5.1 版釋出,並且成為 ISO 國際標準(ISO/IEC 16262:2011)。

2013 年 3 月,ECMAScript 6 草案凍結,不再新增新功能。新的功能設想將被放到 ECMAScript 7。

2013 年 12 月,ECMAScript 6 草案發布。然後是 12 個月的討論期,聽取各方反饋。

2015 年 6 月,ECMAScript 6 正式通過,成為國際標準。從 2000 年算起,這時已經過去了 15 年。

等待了多年,總算等到了ES6,下面介紹一部分的新語法,本文介紹的語法如下:

1. 塊級作用域

由於在ES5之前,沒有塊級作用域,有些時候很不方便,所以ES6新增了塊級作用域。

塊級宣告是指該宣告的變數無法被程式碼塊外部訪問。塊作用域,又被稱為詞法作用域(lexical scopes),可以在如下的條件下建立:

  1. 在函式內部定義
  2. 在程式碼塊(即 {} )內部定義

塊級作用域是很多類C語言的工作機制,ECMAScript 6 引入塊級宣告的目的是增強 JavaScript 的靈活性,同時又能與其它程式語言保持一致.

1.1 Let宣告

let定義塊級作用域變數

1.沒有變數的提升,必須先宣告後使用.

2.let宣告的變數,不能與前面的let,var,conset宣告的變數重名


   //全域性執行上下文
    {
    //區域性執行上下文
    //console.log(a)//此處瀏覽器報錯未宣告
    let a = 10;//let定義只能在當前上下文使用
    var b = "abc";//全域性作用域變數
    console.log(a);//10
    //let a = 10//瀏覽器提示錯誤 Uncaught SyntaxError: Identifier 'a' has already been declared
    console.log(b);//bac
    }
    console.log(b);//abc
    // console.log(a);//瀏覽器提示錯誤


複製程式碼

1.2 Const宣告

const 定義只讀變數

1.const宣告變數的同時必須賦值,const宣告的變數必須初始化,一旦初始化完畢就不允許修改

2.const宣告變數也是一個塊級作用域變數

3.const宣告的變數沒有“變數的提升”,必須先宣告後使用

4.const宣告的變數不能與前面的let,var,const宣告的變數重名

{
    const con = "abc"
    console.log(con)//也只能在當前執行上下文中使用
}
//console.log(con)//con is not defined  瀏覽器提示錯誤,con未宣告

複製程式碼

1.3 迴圈中的塊級繫結

使用var宣告的迴圈變數在迴圈結束後仍然可以訪問到。 使用let宣告的迴圈變數,在迴圈結束之後會立即銷燬。

    for(let i = 0; i < 3; i++){ // 迴圈結束之後會立即銷燬 i
        console.log(i);
    }
    console.log(i);  //此處無法訪問到 i 。
複製程式碼

1.4 迴圈中的函式

看下面的程式碼,是輸出10個10,而不是0,1,2,…

    var funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs.push(function () {
            console.log(i);
        });
    }
    funcs.forEach(function (func) {
        func();     // 輸出 "10" 共10次
    });
複製程式碼

解決辦法需要使用函式的自執行特性。

var funcs = [];
for (var i = 0; i < 10; i++) {
    funcs.push((function(value) {
        return function() {
            console.log(value);
        }
    }(i)));
}
funcs.forEach(function(func) {
    func();     // 輸出 0,1,2 ... 9
});
複製程式碼

如果使用let宣告變數,則完全可以避免前面的問題。 這是ES6規範中專門定義的特性。在for … in和for … of迴圈中也適用

    var funcs = [];
    for (let i = 0; i < 10; i++) {
        funcs.push(function () {
            console.log(i);
        });
    }
    funcs.forEach(function (func) {
        func();     // 輸出 0,1,2 ... 9
    })
複製程式碼

說明:let 宣告使得每次迭代都會建立一個變數 i,所以迴圈內部建立的函式會獲得各自的變數 i 的拷貝。每份拷貝都會在每次迭代的開始被建立並被賦值。

2.神奇的解構賦值

2.1 什麼是解構

ES6允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring)。解構只能用於陣列或物件,所以應該注意,其他原始型別的值都可以轉為相應的物件 ,除了undefined和null本質上。

解構寫法屬於“模式匹配”,只要等號兩邊的模式相同,左邊的變數就會被賦予對應的值。

2.2 物件的解構

2.2.1 物件解構的基本形式

物件結構的語法就是在賦值語句的左側使用類似物件字面量的結構。

let node = {
    name: "阿貓",
    age:20
};
//這裡就相當於宣告瞭兩個變數: type = node.type;  name:node.name
let { name,age } = node;

console.log(name);      // "阿貓"
console.log(age);      // 20
複製程式碼

在上面的結構中必須要初始化。否則會出現語法錯誤。


var { name,age };// 語法錯誤!

let { name,age };// 語法錯誤!

const { name,age };// 語法錯誤!
複製程式碼

2.2.2 解構賦值表示式

如果宣告的變數想改變他們的值,也可以使用解構表示式。

let node = {
  name: "阿貓",
  age:20
},
name = "阿狗",
age = 5;

//注意:此處必須要在圓括號內才能使用解構表示式
({ name,age } = node);

console.log(name);      // "阿貓""
console.log(age);      // 20
複製程式碼

2.2.3 物件解構時的預設值

如果賦值號右邊的物件中沒有與左邊變數同名的屬性,則左邊的變數會是 undefined

let node = {
  name: "阿貓",
  age:20
};
//因為node中沒有叫value的屬性,所以valued的值將會是undefined
let { name, age, value } = node;

console.log(name);      // "阿貓"
console.log(age);      // 20
console.log(value);     // undefined
複製程式碼

不過我們也可以手動指定他的預設值。(這個和函式的引數預設值很像)

let node = {
  name: "阿貓",
  age:20
};
//手動新增value的預設值為3
let { name, age, value = 3} = node;

console.log(name);      // "阿貓"
console.log(age);      // 20
console.log(value);     // 3
複製程式碼

2.2.4 賦值給不同的變數名

在前面的操作中,都是把物件的屬性值,賦值給同名變數。

其實也可以賦值給不同名的變數。

let node = {
  name: "阿貓",
  age:20
};
// localName才是要定義的新的變數。  name是node的屬性
let {name: localName, age: localAge} = node;

console.log(localName);     // "阿貓"
console.log(localAge);     // 20
複製程式碼

注意:冒號後面才是要定義的新的變數,這個可以我們的物件字面量不太一樣!

這個地方也可以使用預設值。

let node = {
        type: "Identifier"
    };

let { type: localType, name: localName = "bar" } = node;

console.log(localType);     // "Identifier"
console.log(localName);     // "bar"
複製程式碼

2.3 陣列的解構

2.3.1 陣列解構基本語法

資料解構的語法和物件解構看起來類似,只是將物件字面量替換成了陣列字面量,而且解構操作的是陣列內部的位置(索引)而不是物件中的命名屬性,例如:

let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

如果只想取陣列中的某一項,則可以不用命名。

let colors = [ "red", "green", "blue" ];
//只取陣列中的第三項。
let [ , , thirdColor ] = colors;

console.log(thirdColor);        // "blue"
複製程式碼

2.3.2 解構表示式

你可以想要賦值的情況下使用陣列的解構賦值表示式,但是和物件解構不同,沒必要將它們包含在圓括號中,例如:

let colors = [ "red", "green", "blue" ],
    firstColor = "black",
    secondColor = "purple";

[ firstColor, secondColor ] = colors;  //可以不用加括號。當然新增也不犯法

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"
複製程式碼

陣列解構表示式有一個很常用的地方,就是交換兩個變數的值。在以前一般定義一個第三方變數進行交換,例如下面的程式碼:

let a = 3,
    b = 4,
    temp;
temp = a;
a = b;
b = temp;
console.log(a);
console.log(b)
複製程式碼

那麼在ES6中完全可以拋棄第三方變數這種方式,使用我們的陣列解構表示式

let a = 3,
    b = 4;
//左側和前面的案例是一樣的,右側是一個新建立的陣列字面量。
[a, b] = [b, a];
console.log(a);
console.log(b)
複製程式碼

2.4 解構JSON

var jike = {"name":"tom","age":"23","sex":"男"};
var {name,age,sex}=jike;
console.log(name,age,sex)//tom 23 男

function cal(a,b){
    var ret1 = a+b;
    var ret2 = a-b;
    var ret3 = a*b;
    var ret4 = a/b;
    return [ret1,ret2,ret3,ret4]
}
var [r1,r2,r3,r4] = cal(10,5);
console.log(r1,r2,r3,r4)//15 5 50 2
複製程式碼

3. 字串功能的增強

3.1 查詢子字串

  1. includes(要查詢的文字) 判斷字串內是否有此文字
  2. startsWith(要查詢的文字) 判斷文字是否在字串開頭
  3. endsWith(要查詢的文字) 判斷文字是否在字串結尾

使用如下:

var msg = "Hello world!";

console.log(msg.startsWith("Hello"));       // true
console.log(msg.startsWith("o"));           // false
console.log(msg.startsWith("o", 4));        // true

console.log(msg.endsWith("!"));             // true
console.log(msg.endsWith("world!"));        // true
console.log(msg.endsWith("r", 9));          // true

console.log(msg.includes("o"));             // true
console.log(msg.includes("x"));             // false
console.log(msg.includes("o", 8));          // false

複製程式碼
  1. trim():除去字串左右空格的。
  2. trimLeft():除去字串的左邊空格
  3. rimRight():除去字串的右邊空
var msg = " Hello "

console.log(msg)//" Hello "
console.log(msg.trim())//"Hello"
console.log(msg.trimLeft())//"Hello "
console.log(msg.trimRight())//" Hello"

複製程式碼

一般配合正規表示式,用的比較的。

  1. repeat(複製的次數):複製字串
var msg = "abc"
console.log(msg.repeat(3));//abcabcabc

複製程式碼
  1. padStart(補齊的位數,填充的的字元):用提供的字元,向前補齊位數
var msg = "abc"
console.log(msg.padStart(10,"*"));//*******abc

複製程式碼
  1. padEnd(補齊的位數,填充的的字元):用提供的字元,向前補齊位數
var msg = "abc"
console.log(msg.padEnd(10,"*"));//abc*******

複製程式碼

3.2 模板字串

模板字面量是 ECMAScript 6 針對 JavaScript 直到 ECMAScript 5 依然缺失的如下功能的迴應:

多行字串 針對多行字串的形式概念(formal concept)。 基本的字串格式化 將字串中的變數置換為值的能力。 轉義 HTML 能將字串進行轉義並使其安全地插入到 HTML 的能力。 模板字面量以一種全新的表現形式解決了這些問題而不需要向 JavaScript 已有的字串新增額外的功能。

3.2.1 基本語法

使用一對反引號 “(tab正上方的按鍵)來表示模板字面量。

let message = `Hello world!`;   //使用模板字面量建立了一個字串

console.log(message);               // "Hello world!"
console.log(typeof message);        // "string"
console.log(message.length);        // 12
複製程式碼

注意:如果模板字串中使用到了反引號,則應該轉義。但是單雙引號不需要轉義

3.2.2 多行字串

在ES5之前JavaScript是不支援多行字串的。(但是在以前的版本中有一個大家都認為是bug的方式可以寫出多行字串,就是在尾部新增一個反斜槓 \)

    var s = "abc \
    aaaaaa";
    console.log(s); //但是輸出的結果中不包括換行
複製程式碼

但是在ES6中字串的模板字面量輕鬆的解決了多行字串的問題,而且沒有任何新的語法

    var s = `abc
    aaaaa
    dsalfja
    dfadfja`;
    console.log(s);
複製程式碼

但是要注意: 反引號中的所有空格和縮排都是有效字元。

4. 陣列的擴充套件

4.1 陣列推導

陣列推導就是直接通過現有陣列生成新陣列的一種簡化寫法,通過for...of結構,允許多重迴圈。注:新陣列會立即在記憶體中生成,這時如果原陣列是一個很大的陣列,將會非常耗費記憶體。

var a1 = [1, 2, 3, 4];
var a2 = [for (i of a1) i * 2];
a2 // [2, 4, 6, 8]

var years = [ 1954, 1974, 1990, 2006, 2010, 2014 ];
[for (year of years) if (year > 2000 && year < 2010) year];
// [ 2006]
複製程式碼

4.2 陣列處理擴充套件方法

Array.from():用於將兩類物件轉為真正的陣列:類似陣列的物件(array-like object)和可遍歷(iterable)的物件,其中包括ES6新增的Set和Map結構。Array.from()還可以接受第二個引數,作用類似於陣列的map方法,用來對每個元素進行處理。

Array.from({ 0: "a", 1: "b", 2: "c", length: 3 });
// [ "a", "b" , "c" ]

Array.from(arrayLike, x => x * x);
// 等同於
Array.from(arrayLike).map(x => x * x);
複製程式碼

Array.of()方法用於將一組值,轉換為陣列。彌補陣列建構函式Array()的不足。

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]

Array(3) // [undefined, undefined, undefined]
複製程式碼

陣列例項的find()用於找出第一個符合條件的陣列元素;陣列例項的findIndex()用於返回第一個符合條件的陣列元素的位置。這兩個方法都可以發現NaN,彌補了IndexOf()的不足。

陣列例項的fill()使用給定值,填充一個陣列。

陣列例項的entries(),keys()和values()用於遍歷陣列,它們都返回一個遍歷器,可以用for...of迴圈進行遍歷。keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

5. 物件功能的擴充

5.1 建立物件

ES5中建立物件的幾種方法

  1. 字面量法
  2. 工廠模式
  3. 建構函式
  4. 組合方式 建構函式+原型模式

在ES6中可以使用class建立一個物件

class 類名{
    constructor(引數){
        this.屬性 = 引數;        
    }  
    running{//物件中簡寫方法,省略了function
        
    }
}
複製程式碼

注意:

  1. lass 是關鍵字,後面緊跟類名,類名首字母大寫,採取的是大駝峰命名法則。類名之後是{}。
  2. 在{}中,不能直接寫語句,只能寫方法,方法不需要使用關鍵字。
  3. 方法和方法之間沒有逗號。不是鍵值對

5.2 新增的方法

5.2.1 屬性相關的方法

(1) Obect.getOwnProertyDescriptor();

獲取一個物件中某個屬性的詳細資訊。


var a = 1;
console.log(Object.getOwnPropertyDescriptor(window,"a"))

複製程式碼

瀏覽器列印如下:


{value: 1, writable: true, enumerable: true, configurable: false}
    configurable:false
    enumerable:true
    value:1
    writable:true
    __proto__:Object

複製程式碼
(2) Object.defineProperty( )

精細化設定一個物件的屬性的

  1. configurable:是否可刪除
  2. writable:是否可修改
  3. enumerale:是否可以列舉
  4. value:值

var obj = {};
Object.defineProperty(obj,"name",{
    configurable:false,
    writable:true,
    enumerale:false,
    value:"阿毛"
})

複製程式碼

瀏覽器列印如下:

> delete obj.name;
< false
> obj.name = "阿貓"
< "阿貓"
> obj.name
< "阿貓"


複製程式碼
(3) Object.defineProperties()
var obj = {};
Object.defineProperties(obj,{
   "name":{
       configurable:false,
       writable:false,
       enumerable:false,
       value:"阿毛"
   },
   "age":{
       configurable:true,
       writable:true,
       enumerable:true,
       value:"20"
   }
});

複製程式碼

瀏覽器列印如下:

> obj
< {age: "20", name: "阿毛"}
> delete obj.name;
< false
> delete obj.age
< true
> obj
< {name: "阿毛"}

複製程式碼
(4) getOwnPropertyNames()

獲取自的屬性,以陣列的格式返回的。

var obj = { 
   name:"阿毛",
   age:20
};
複製程式碼

瀏覽器列印如下:


> Object.getOwnPropertyNames(obj);
< (2) ["name", "age"]
    0: "name"
    1: "age"
    length: 2
    __proto__: Array(0)

複製程式碼
(5) Object.keys()
var obj = { 
       name:"阿毛",
       age:20
};
複製程式碼

瀏覽器列印如下:


> Object.getOwnPropertyNames(obj);
< (2) ["name", "age"]
   0: "name"
   1: "age"
   length: 2
   __proto__: Array(0)
複製程式碼

使用Object.getOwnPropertyNames()和Object.keys()都可以得到一個物件的屬性名,屬性名是放在一個陣列中的。

對於物件的遍歷目前有三種方式:

  • for in
  • Object.keys()
  • Object.getOwnPropertyNames()

for in : 會輸出自身以及原型鏈上可列舉的屬性。 Object.keys() : 用來獲取物件自身可列舉的屬性鍵 Object.getOwnPropertyNames() : 用來獲取物件自身的全部屬性名

(6) Object.values();

獲取物件的值,放到陣列中。

var obj = { 
       name:"阿毛",
       age:20
};
複製程式碼

瀏覽器列印如下:

> Object.values(obj);
< (2) ["阿毛", 20]
複製程式碼

5.2.2 繼承相關的方法

(1) Object.create()

使用Object.create比較適合對字面量物件的繼承。

(2) getPrototypeOf

getPrtotypeOf 獲取建立這個物件的那個構造器的prototype屬性。


var obj = {
    name : "阿毛",
    age : 20
}
console.log(Object.getPrototypeOf(obj) == Object.prototype);
console.log(Object.getPrototypeOf([]) == Array.prototype);

複製程式碼
true
true
複製程式碼

5.2.3 防篡改方法

可以對物件進行保護,分成三個級別:

(1) 設定不可擴充: preventExtensions()

Object.preventExtensions()方法讓一個物件變的不可擴充套件,也就是永遠不能再新增新的屬性。如下:

var obj = {
    name : "阿貓",
    age : 20
}
Object.preventExtensions(obj);
obj.name = "阿狗";
delete obj.age;
obj.sex = "man";
console.log(obj);
複製程式碼

瀏覽器列印:

{ name: '阿狗' }
複製程式碼
(2) 封閉物件:seal()

Object.seal()方法封閉一個物件,阻止新增新屬性並將所有現有屬性標記為不可配置。當前屬性的值只要可寫就可以改變。

var obj = {
   name : "阿貓",
   age : 20
}
Object.seal(obj);
obj.name = "阿狗";
delete obj.age;
obj.sex = "man";
console.log(obj);
複製程式碼

瀏覽器列印:

{ name: '阿狗', age: 20 }
複製程式碼
(3) 凍結物件: freeze()

Object.freeze() 方法可以凍結一個物件,凍結指的是不能向這個物件新增新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該物件已有屬性的可列舉性、可配置性、可寫性。該方法返回被凍結的物件。如下:

var obj = {
   name : "阿貓",
   age : 20
}
Object.freeze(obj);
obj.name = "阿狗";
delete obj.age;
obj.sex = "man";
console.log(obj);
複製程式碼

瀏覽器列印:

{ name: '阿貓', age: 20 }
複製程式碼

也可以使用isExtensible, isSealed, isFrozen來判斷當處處理如個保護狀態,如下:

1.Object.isExtensible() 方法判斷一個物件是否是可擴充套件的(是否可以在它上面新增新的屬性)。

==只有這個返回值預設為true,因為這個方法是判斷是否可以擴充,而預設的是可以==


var obj = {
    name : "阿貓",
    age : 20
}

console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));
Object.preventExtensions(obj)
console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));

複製程式碼

瀏覽器列印如下:


true
false
false
{ name: '阿狗' }
false
false
false

複製程式碼
2. Object.isSealed() 方法判斷一個物件是否被密封。

var obj = {
    name : "阿貓",
    age : 20
}

Object.seal(obj)
console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));

複製程式碼

瀏覽器列印如下:


{ name: '阿狗', age: 20 }
false
true
false

複製程式碼
3. Object.isFrozen()方法判斷一個物件是否被凍結。

var obj = {
    name : "阿貓",
    age : 20
}

Object.freeze(obj)
console.log(Object.isExtensible(obj));
console.log(Object.isSealed(obj));
console.log(Object.isFrozen(obj));

複製程式碼

瀏覽器列印如下:


{ name: '阿貓', age: 20 }
false
true
true

複製程式碼

5.2.4 Object.assign

Object.assign就去用於物件的合併,將源物件的所有的可列舉屬性,複製到目標物件。

程式碼如下:


var obj1 = {};
var obj2 = {
    name:"阿貓",
    age:20
}
var obj3 = {
    name:"阿狗"
}

Object.assign(obj1,obj2,obj3)//obj1是目標物件,obj2、obj3都是源物件
console.log(obj1);

複製程式碼

瀏覽器列印如下:


{ name: '阿狗', age: 20 }

複製程式碼

==注意細節:==

  1. 第一個引數是目標物件,assign這個方法把其它的引數中的屬性全部加在第一個引數身上。
  2. 第一個引數在屬性複製過程中,可以會被修改,後面的會覆蓋前面的屬性
  3. assign這個方法的返回值就是第一個引數的引用,也就是返回了第一個引數。
  4. aassign這個方法會把原型上面的發展也拷貝了。
  5. assign不能拷貝繼承過來的屬性
  6. assign也不拷貝不可列舉的屬性

如下:


var obj1 = {};
var obj2 = {
    name : "阿貓"
}
Object.defineProperty(obj2,"age",{
    configurable:true,
    writable:true,
    value:20,
    enumerable:false
})
Object.assign(obj1,obj2);
console.log(obj1);

複製程式碼

瀏覽器列印如下:


{ name: '阿貓' }

複製程式碼
  1. assign是淺複製,如下:

var obj1 = {};
var obj2 = {
    name:"阿貓",
    friends:["阿狗","阿郎"]
}
Object.assign(obj1,obj2);
console.log(obj1);
obj2.friends.push("阿毛");
console.log(obj1)


複製程式碼

瀏覽器列印如下:


{ name: '阿貓', friends: [ '阿狗', '阿郎' ] }
{ name: '阿貓', friends: [ '阿狗', '阿郎', '阿毛' ] }

複製程式碼

在對源物件的應用型別資料修改後,目標物件也修改了

5.3 物件擴充套件運算子

陣列中的擴充套件運算子:


var arr1 = [1,2,3];
var arr2 = [...arr1]
console.log(arr2);

複製程式碼

作用:用於取出引數物件的所有可遍歷屬性,拷貝當前物件中。


var obj1 =  {
    name : "阿貓",
    age : 20,
    friends : ["阿狗,阿毛"]
}

var obj2 = {...obj1}
console.log(obj2);
obj1.friends.push("阿郎")
console.log(obj2);

複製程式碼

效果如下


{ name: '阿貓', age: 20, friends: [ '阿狗,阿毛' ] }
{ name: '阿貓', age: 20, friends: [ '阿狗,阿毛', '阿郎' ] }

複製程式碼

還可以這樣用:

var obj1 =  {
    name : "阿貓",
}

var obj2 = {
    age : 20
}
var obj3 = {...obj1,...obj2}
console.log(obj3);
複製程式碼

列印如下:

{ name: '阿貓', age: 20 }
複製程式碼

6. 函式的新增特性

6.1.1 函式引數的預設值

JavaScript函式的最大的一個特點就是在傳遞引數的時候,引數的個數不受限制的。為了健壯性考慮,一般在函式內部需要做一些預設值的處理。

function makeRequest(url, timeout, callback) {
    timeout = timeout || 2000;
    callback = callback || function() {};
}

複製程式碼

其實上面的預設值方法有個bug:當timeout是0的時候也會當做假值來處理,從而給賦值預設值2000.

ES6從語言層面面上增加了 預設值的 支援。看下面的程式碼:

//這個函式如果只傳入第一個引數,後面兩個不傳入,則會使用預設值。如果後面兩個也傳入了引數,則不會使用預設值。
function makeRequest(url, timeout = 2000, callback = function() {}) {

    // 其餘程式碼

複製程式碼

6.1.2 預設引數對 arguments 物件的影響

在非嚴格模式下,arguments總是能反映出命名引數的變化。看下面的程式碼:

    function foo(a, b) {
        //非嚴格模式
        console.log(arguments[0] === a); //true
        console.log(arguments[1] === b); //true
        a = 10;
        b = 20;
        console.log(arguments[0] === a); //true
        console.log(arguments[1] === b); //true
    }
    foo(1, 2);
複製程式碼

在ES5的嚴格模式下,arguments只反映引數的初始值,而不再反映命名引數的變化!

    function foo(a, b) {
        //嚴格模式
        "use strict"
        console.log(arguments[0] === a); //true
        console.log(arguments[1] === b); //true
        a = 10;
        b = 20;
        console.log(arguments[0] === a); //false。  修改a的值不會影響到arguments[0]的值
        console.log(arguments[1] === b); //false
    }
    foo(1, 2);
複製程式碼

當使用ES6引數預設值的時候,不管是否是在嚴格模式下,都和ES5的嚴格模式相同。看下面的程式碼:

    function foo(a, b = 30) {
        console.log(arguments[0] === a); //true
        console.log(arguments[1] === b); //true
        a = 10;
        b = 20;
        console.log(arguments[0]  === a); //false。  由於b使用了預設值。雖然a沒有使用預設值,但是仍然表現的和嚴格模式一樣。
        console.log(arguments[1] === b); //false。  b使用了預設值,所以表現的和嚴格模式一樣。
    }
    foo(1, 2);
複製程式碼

注意:如果這樣呼叫foo(1),則 a == 1, b == 30, arguments[0] == 1, arguments[1] == undefined。也就是說預設值並不會賦值給arguments引數。

6.1.3 預設參數列達式 (Default Parameter Expressions)

引數的預設值,也可以是一個表示式或者函式呼叫等。看下面的程式碼

    function getValue() {
        return 5;
    }

    function add(first, second = getValue()) { //表示使用getValue這個函式的返回值作為second的預設值。
        return first + second;
    }

    console.log(add(1, 1));     // 2.  呼叫add函式的時候,傳入了第二個引數,則以傳入的引數為準。
    console.log(add(1));        // 6。 呼叫add函式的時候,沒有傳入第二個引數,則會呼叫getValue函式。
複製程式碼

有一點需要要注意:getValue()只會在呼叫add且不傳入第二個引數的時候才會去呼叫。不是在解析階段呼叫的。

    let value = 5;
    function getValue() {
        return value++;
    }

    function add(first, second = getValue()) {  //
        return first + second;
    }

    console.log(add(1, 1));     // 2
    console.log(add(1));        // 6。 
    console.log(add(1));        // 7
    console.log(add(1));        // 8
複製程式碼

由於預設值可以表示式,所以我們甚至可以使用前面的引數作為後面引數的預設值。

function add(first, second = first) {  // 使用第一個引數作為第二個引數的預設值
        return first + second;
 }
複製程式碼

注意:可以把前面的引數作為後面引數的預設值,但是不能把後面的引數作為第一個引數的預設值。這可以前面說的let和const的暫存性死區一個意思。

function add(first = second, second)) {  // 這種寫法是錯誤的

        return first + second;
}
複製程式碼

6.2 箭頭函式

ECMAScript 6 最有意思的部分之一就是箭頭函式。正如其名,箭頭函式由 “箭頭”(=>)這種新的語法來定義。

其實在別的語言中早就有了這種語法結構,不過他們叫拉姆達表示式。

6.2.1 箭頭函式語法

基本語法如下:

(形參列表)=>{
  //函式體
}
複製程式碼

箭頭函式可以賦值給變數,也可以像匿名函式一樣直接作為引數傳遞。

示例1:

    var sum = (num1, num2) =>{
        return num1 + num2;
    }
    console.log(sum(3, 4));
    //前面的箭頭函式等同於下面的傳統函式
    var add = function (num1, num2) {
        return num1 + num2;
    }
    console.log(add(2, 4))
複製程式碼

如果函式體內只有一行程式碼,則包裹函式體的 大括號 ({ })完全可以省略。如果有return,return關鍵字也可以省略。 如果函式體內有多條語句,則 {} 不能省略。

示例2:

    var sum = (num1, num2) => num1 + num2;
    console.log(sum(5, 4));
    //前面的箭頭函式等同於下面的傳統函式
    var add = function (num1, num2) {
        return num1 + num2;
    }
    console.log(add(2, 4));

    //如果這一行程式碼是沒有返回值的,則方法的返回自也是undefined
    var foo = (num1, num2) => console.log("aaa");
    console.log(foo(3,4));  //這個地方的返回值就是undefined
複製程式碼

如果箭頭函式只有一個引數,則包裹引數的小括號可以省略。其餘情況下都不可以省略。當然如果不傳入引數也不可以省略

示例3:

    var foo = a=> a+3; //因為只有一個引數,所以()可以省略
    console.log(foo(4)); // 7
複製程式碼

如果沒有需要傳入的引數,而且還不想新增傳統的大括號和return,則必須給整個物件新增一個小括號 ()

示例4:

    var foo = ()=>({name:"lisi", age:30});
    console.log(foo());
    //等同於下面的;
    var foo1 = ()=>{
        return {
            name:"lisi",
            age : 30
        };
    }
複製程式碼

6.2.2 使用箭頭函式實現函式自執行

    var person = (name => {
            return {
                name: name,
                age: 30
            }
        }
    )("zs");
    console.log(person);
複製程式碼

6.2.3 箭頭函式中無this繫結

在ES5之前this的繫結是個比較麻煩的問題,稍不注意就達不到自己想要的效果。因為this的繫結和定義位置無關,只和呼叫方式有關。

在箭頭函式中則沒有這樣的問題,在箭頭函式中,this和定義時的作用域相關,不用考慮呼叫方式

箭頭函式沒有 this 繫結,意味著 this 只能通過查詢作用域鏈來確定。如果箭頭函式被另一個不包含箭頭函式的函式囊括,那麼 this 的值和該函式中的 this 相等,否則 this 的值為 window。

    var PageHandler = {
        id: "123456",
        init: function () {
            document.addEventListener("click",
                event => this.doSomething(event.type), false); // 在此處this的和init函式內的this相同。
        },

        doSomething: function (type) {
            console.log("Handling " + type + " for " + this.id);
        }
    };
    PageHandler.init();
複製程式碼

看下面的一段程式碼:

    var p = {
        foo:()=>console.log(this)   //此處this為window
    }
    p.foo();  //輸出為 window物件。   並不是我想要的。所以在定義物件的方法的時候應該避免使用箭頭函式。
//箭頭函式一般用在傳遞引數,或者在函式內部宣告函式的時候使用。
複製程式碼

說明:

箭頭函式作為一個使用完就扔的函式,不能作為建構函式使用。也就是不能使用new 的方式來使用箭頭函式。 由於箭頭函式中的this與函式的作用域相關,所以不能使用call、apply、bind來重新繫結this。但是雖然this不能重新繫結,但是還是可以使用call和apply方法去執行箭頭函式的。

6.2.4 無arguments繫結

雖然箭頭函式沒有自己的arguments物件,但是在箭頭函式內部還是可以使用它外部函式的arguments物件的。

    function foo() {
        //這裡的arguments是foo函式的arguments物件。箭頭函式自己是沒有 arguments 物件的。
        return ()=>arguments[0]; //箭頭函式的返回值是foo函式的第一個引數
    }
    var arrow = foo(4, 5);
    console.log(arrow()); // 4
複製程式碼

7. 新增資料結構

7.1 Set資料結構

set和資料差不多,也是一種集合,區別在於:它裡面的值都是唯一的,沒有重複的。

建立一個set物件如下

var setList = new Set();
    setList.add("a");
    setList.add("c");
    setList.add("b");
    setList.add("a");  //重複,所以新增失敗。注意這個地方並不會儲存。
    console.log(setList.size); // 長度是3
複製程式碼

set的方法有add()、has()、delete()、clear()等方法,分別用於新增、查詢、刪除、和清空的功能。

var setList = new Set(["a","b","c","d"]);
console.log(setList);//{"a", "b", "c", "d"}
setList.add("e");
console.log(setList);//{"a", "b", "c", "d", "e"}
console.log(setList.has("b"));//true
setList.delete("c");
console.log(setList);//{"a", "b", "d", "e"}
setList.clear();
console.log(setList);//{}

複製程式碼

使用set可以輕鬆的實現將一組資料去重

var arr1 = [1,1,2,2,3,3];
var arr2 = [...(new Set(arr1))]
console.log(arr2);//[1,2,3]
複製程式碼

7.2 Map資料結構

它類似於物件,裡面存放也是鍵值對,區別在於:物件中的鍵名只能是字串,如果使用map,它裡面的鍵可以是任意值。

建立一個map物件如下:

 var map = new Map();
    map.set("a", "lisi");
    map.set("b", "zhangsan");
    map.set("b", "zhangsan222");  // 第二次新增,新的value會替換掉舊的
    console.log(map.get("a"));
    console.log(map.get("b"));   //zhangsan222
    console.log(map.get("c")); //undefined.如果key不存在,則返回undefined
    console.log(map.size); //2
複製程式碼

map中的方法有:

  1. has(key) - 判斷給定的 key 是否在 map 中存在
  2. delete(key) - 移除 map 中的 key 及對應的值
  3. clear() - 移除 map 中所有的鍵值對
var map = new Map();
map.set("a","a");
map.set("b","b");
map.set("c","c");
console.log( map.get("a"))//a
console.log( map.has("a") ) //true
console.log( map.has("d") ) //false
map.clear();
console.log(map);//Map {}
複製程式碼

8.Class

ES6從形式上,向主流的物件導向的語言靠攏,前面我們都是建立構造器,然後去new構造器,構造器就相當於一個類,在ES6中,就可以使用class來建立物件了。

8.1 class建立物件

上文說到,通過class建立物件的格式如下:

class 類名{
    constructor(引數){
        this.屬性 = 引數;        
    }  
    running{//物件中簡寫方法,省略了function
        
    }
}
複製程式碼

不使用Class,去建立一個類,如下:

function persion(name,age){
    this.name = name,
    this.age = age;
}

persion.prototype.say = function(){
    console.log(`我是${this.name},${this.age}歲`)
}

var p1 = new persion("阿毛",20);
複製程式碼

在ES6中,可以使用class來宣告一個類了,如下:


class Persion{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    say(){
        console.log(`${this.name}的年齡是${this.age}`)
    }
}

var p1 = new Persion("abc",12);

p1.say();

複製程式碼

在瀏覽器中訪問之,如下:


abc的年齡是12

複製程式碼

雖然使用class非常給力,但是class是屬性JS中高階的語法,可以轉成低階的語法,如下Babel網站


8.2使用extends實現繼承

格式:

class 子類 extends 父類{
    constructor(引數){
        super(引數);
        this.引數 = 值;
    }
}
複製程式碼

注意:

  1. 使用 extends 關鍵字來實現繼承
  2. 在子類中的構造器 constructor 中,必須要顯式呼叫父類的 super 方法,如果不呼叫,則 this 不可用 在ES5的繼承如下:
function Persion(name,age){
    this.name = name;
    this.age = age;
}
Persion.prototype.say = function(){
    console.log(`I'm ${this.name} and ${this.age}`);
}

function GoodMan(name,age,sex){
    //繼承屬性
    Persion.call(this,name,age);
    this.sex = sex;
}
//繼承方法   利用淺拷貝繼承方法
for (var p in Persion.prototype) {
    GoodMan.prototype[p] = Persion.prototype[p];
}
//GoodMan 自己的方法
GoodMan.prototype.do = function () {
    console.log(`I'm ${this.name},I'm ${this.sex}`);
}

var p1 = new GoodMan("阿毛",20,"男");
p1.do();
p1.say();

複製程式碼

使用ES6中的extends來實現繼承:


class Persion{
    constructor(name ,age){
        this.name = name;
        this.age = age;      
    }
    say(){
        console.log(`I'm ${this.name} and ${this.age}`);
    }
}

class GoodMan extends Persion{
    constructor(name,age,sex){
        super(name,age);
        this.sex = sex;
    }
    do(){
        console.log(`I'm ${this.name},I'm ${this.sex}`);
    }
}

p1 = new GoodMan("阿毛",20,"男");
p1.do();
p1.say();

複製程式碼

在瀏覽器中效果如下:

I'm 阿毛,I'm 男
I'm 阿毛 and 20
複製程式碼

8.3 類的靜態方法 static

直接通過類名來訪問的方法就是靜態方法。如:Math.abs();這裡的 abs()是靜態方法。

class Persion{
   constructor(name,age){
       this.name = name;
       this.age = age;
   }
   say(){
       console.log(`${this.name}的年齡是${this.age}`)
   }
   static run(){
       console.log("running......")
   }
}
Persion.run();
複製程式碼

呼叫時就是Persion.run();

==靜態方法只能用類名呼叫!!!!==

靜態方法也可以繼承

   class Father{
       static foo(){
           console.log("我是父類的靜態方法");
       }
   }
   class Son extends Father{

   }
   Son.foo(); //子類也繼承了父類的靜態方法。  這種方式呼叫和直接通過父類名呼叫時一樣的。

複製程式碼

總結:

整個ES6的新特性和用法學習下來,覺得比較有用的點如下:

  1. 優先使用let,因為相較var來說,let可以形成作用域,不易產生變數汙染的情況。
  2. 不常變動的值使用const,方便閱讀,也防止不小心的串改。
  3. 熟悉新加入的對字串的處理函式,簡化了對於字串處理的過程。
  4. 使用模板字串即 (`` ,不僅僅方便了字串的整合,同時也省去樣式的操作
  5. 使用匿名函式的場合,一律改為使用箭頭函式。
  6. 增加Set和Map的使用率,可以減少僅僅使用Array和Object物件的時的一些複雜操作。

ES6新增加的內容還有很多,前端小白學習不夠到位,還有很大的油田等待開採,文章中的內容有誤之處,還請大家及時指正,小弟感激不盡。如有版權問題,請聯絡我

相關文章