從ES6開始,JavaScript就朝著工程化和麵向對象的大步邁進,我們並不知道這對於年輕的JavaScript來說是好還是壞,因為它最開始是做為一款輕量級的腳本語言而風靡全球的。這個問題就留給時間來證明吧! ...
ECMAScript 6 是ECMA於2015.06發佈的版本,作為一個分界點,現在我們通常把這之後的版本統稱為ES6。ES6帶來了許多全新的語法,同時添加了類的概念,可以預見的是,JavaScript正朝著工程化語言邁進,我們並不知道這對於年輕的JavaScript來說是好還是壞,因為它最開始是做為一款輕量級的腳本語言而風靡全球的。
一 新的原始類型和變數申明
1,symbol
在ES6之前,我們知道JavaScript支持6種數據類型:object,string,boolean,number,null,undefined。現在,ES6新增了一種原始數據類型:symbol,表示獨一無二的值,即每個symbol類型的值都不相同。這讓我想起了另一個特殊的值:NaN,想一想,他們是不是有一點類似呢!
1 var sy = Symbol('test');
2 var sy1 = Symbol('test');
3 console.log(tepeof sy);//'symbol'
4 sy == sy1;//false
5 var sy2 = new Symbol('test');//error : Symbol is not a constructor
創建symbol數據類型的值時,需要給Symbol函數傳遞一個字元串,並且有一點特殊的是:不能使用new關鍵字調用它。另外,每個symbol類型值都是獨一無二的,即使傳遞的是相同的字元串。
2,let和const
ES6新增了兩個申明變數的關鍵字:let和const。他們申明的變數僅在let和const關鍵字所在的代碼塊內起作用,即在使用let和const的那一對大括弧{}內起作用,也稱塊級作用域(ES6之前只有函數作用域和全局作用域)。let和const申明變數不會在預編譯過程中有提升行為(在全局申明也不會變成window的屬性),且不能重覆申明。所以要使用這類變數,只能在let和const關鍵字之後使用它們。
1 {
2 let a = 0;
3 console.log(a);//0
4 }
5 console.log(a);//error a is not defined
const用來申明一個常量,申明時必須賦值,且一旦申明就不能改變。
其實說const變數不能更改是不准確的,請看下麵的例子:
1 const obj = {
2 name:'ren',
3 age:12
4 };
5 obj = {};//error
6 obj.sex = male;
7 consol.log(obj);//{name:'ren',age:12;sex:'male'}
const申明的如果是一個原始值,那麼上面的說法是準確的,如果const申明的是一個引用值,那麼更準確的說法應該是一個不能被重新賦值的變數。
3,解構賦值
解構賦值是對賦值運算符的擴展。它是一種針對數組或者對象進行模式匹配,然後對其中的變數進行賦值。
let [a,b,c] = [1,2,3];
console.log(a,b,c);//1,2,3
**************************
let [a,b,c] = [1,,3];
console.log(a,b,c);//1,undefined,3
**************************
let [a,,b] = [1,2,3];
console.log(a,b);//1,3
**************************
let [a,..b] = [1,2,3];//...是剩餘運算符,表示賦值運算符右邊除第一個值外剩餘的都賦值給b
console.log(a,b);//1,[2,3]
事實上所有可枚舉(iterable)的對象都可以使用結構賦值,例如數組,字元串對象,以及ES6新增的Map和Set類型。
1 let arr = 'hello';
2 let [a,b,c,d,e] = arr;
3 console.log(a,b,c,d,e);//'h','e','l','l','o'
對象的解構賦值和數組類似,不過左邊的變數名需要使用對象的屬性名,並且用大括弧{}而非中括弧[]:
1 let obj = {name:'ren',age:12,sex:'male'};
2 let {name,age,sex} = obj;
3 console.log(name,age,sex);//'ren',12,'male';
二 新的對象和方法
1,Map和Set
Map對象用於保存鍵值對,任何值JavaScript支持的值都可以作為一個鍵或者一個值。這聽起來和對象差不多啊?其實它們還是有區別的:
a) object的鍵只能是字元串或ES6的symbol值,而Map可以是任何值。
b) Map對象有一個size屬性,存儲了鍵值對的個數,而object對象沒有類似屬性。
1 let myMap = new Map([['name','ren'],['age',12]]);
2 console.log(myMap);//{'name'=>'ren','age'=>12}
3 myMap.set('sex','male');
4 console.log(myMap);//{'name'=>'ren','age'=>12,'sex'=>'male'}
5 myMap.get('name');//'ren'
6 myMap.has('age');//true
7 myMap.delete('age');//true
8 myMap.has('age');//false
9 myMap.get('age');//undefined
Map構造函數接收一個二維數組來創建一個Map對象。數組元素的第0位表示Map對象的key,第1位表示Map對象的value。
Map對象使用set方法來新增數據,set方法接收兩個參數,第一個表示key,第二個表示value。使用get方法獲取數據,參數是對象的key。
Map對象使用delete方法來刪除數據,接收一個參數,表示需要被刪除的key。
Map對象使用has方法檢測是否已經具有某個屬性,返回boolean值。
Set對象和Map對象類似,但它是用來存儲一組唯一值的,而不是鍵值對。類似數組,但它的每個元素都是唯一的。
1 let mySet = new Set([1,2,3]);
2 console.log(mySet);//{1,2,3}
3 mySet.add(4);
4 console.log(mySet);//{1,2,3,4}
5 mySet.delete(1);//true
6 mySet.has(1);//false
利用Set對象唯一性的特點,可以輕鬆實現數組的去重:
1 let arr = [1,1,2,3,4,4];
2 let mySet = new Set(arr);
3 let newArr = Array.from(mySet);
4 console.log(newArr);//[1,2,3,4]
2,對象新特性
創建對象的字面量方式可以更加簡潔。直接使用變數名作為屬性,函數體作為方法,最終變數值變成屬性值,函數名變成方法名。
1 let name = 'ren';
2 let age = 12;
3 let myself = {
4 name,
5 age,
6 say(){
7 console.log(this.name);
8 }
9 };
10 console.log(myself);//{name:'ren',age:12,say:fn}
11 myself.say();//'ren'
對象的拓展運算符(...)三點。用於拷貝目標對象所有可遍歷的屬性到當前對象。
1 let obj = {name:'ren',age:12};
2 let person = {...obj};
3 console.log(person);//{name:'ren',age:12}
4 obj == person;//false
5 let another = {sex:'male'};
6 let someone = {...person,...another};//合併對象
7 console.log(someone);//{name:'ren',age:12,sex:'male'}
ES6對象新增了兩個方法,assign和is。
assign用於淺拷貝源對象可枚舉屬性到目標對象。
1 let source = {a:{ b: 1},b: 2};
2 let target = {c: 3};
3 Object.assign(target, source);
4 console.log(target);//{c: 3, a: {b:1}, b: 2}
5 source.a.b = 2;
6 console.log(target.a.b);//2
如果有同名屬性,那麼目標對象的屬性值會被源對象的屬性值覆蓋。所以數組的表現就有一點特別了:
1 Object.assign([1,2,3],[11,22,33,44]);//[11,22,33,44]
數組的index就是屬性名,當使用assign方法時,從第0位開始,目標數組的值便開始被源數組的值覆蓋了。
is方法和(===)功能基本類似,用於判斷兩個值是否絕對相等。
1 Object.is(1,1);//true
2 Object.is(1,true);//false
3 Object.is([],[]);//false
4 Object.is(+0,-0);//false
5 Object.is(NaN,NaN);//true
他們僅有的兩點區別是,is方法可以區分+0還是-0,還有就是它認為NaN是相等的。
3,字元串新方法
includes()判斷字元串是否包含參數字元串,返回boolean值。如果想要知道參數字元串出現的位置,還是需要indexOf或lastIndexOf方法。
startsWith()/endsWith(),判斷字元串是否以參數字元串開頭或結尾。返回boolean值。這兩個方法可以有第二個參數,一個數字,表示開始查找的位置。
1 let str = 'blue,red,orange,white';
2 str.includes('blue');//true
3 str.startsWith('blue');//true
4 str.endsWith('blue');//false
repeat()方法按指定次數返回一個新的字元串。如果次數是大於0的小數則向下取整,0到-1之間的小數則向上取整,其他負數將拋出錯誤。
1 console.log('hello'.repeat(2));//'hellohello'
2 console.log('hello'.repeat(1.9));//'hello'
3 console.log('hello'.repeat(-0.9));//''
4 console.log('hello'.repeat(-1.9));//error
padStart()/padEnd(),用參數字元串按給定長度從前面或後面補全字元串,返回新字元串。
1 let arr = 'hell';
2 console.log(arr.padEnd(5,'o'));//'hello'
3 console.log(arr.padEnd(6,'o'));//'helloo'
4 console.log(arr.padEnd(6));//'hell ',如果沒有指定將用空格代替
5 console.log(arr.padStart(5,'o'));//'ohell'
另外,如果字元串加上補全的字元串超出了給定的長度,那麼,超出的部分將被截去。
4,數組的新方法
of()是ES6新增的用於創建數組的方法。of把傳入的參數當做數組元素,形成新的數組。
1 let arr = Array.of(1,'2',[3],{});
2 console.log(arr);//[1,'2',[3],{}]
from()方法可以將可迭代對象轉換為新的數組。函數可接受3個參數:第一個表示將被轉換的可迭代對象,第二個是回調函數,將對每個數組元素應用該回調函數,然後返回新的值到新數組,第三個是回到函數內this的指向。後兩個參數是可選的。
1 let obj = {
2 double(n) {
3 return n * 2;
4 }
5 }
6 let arr = [1, 2, 3];
7 console.log(Array.from(arr, function (n){
8 return this.double(n);
9 }, obj)); // [2, 4, 6]
find()和findIndex(),查找數組中符合條件的元素值或索引。如果有多個符合條件的,將只返回第一個。
1 let arr = [1,2,3,4,5];
2 console.log(arr.find(1));//1
3 console.log(arr.findIndex(5));//4
fill()/copyWithin(),替換數組中部分元素,會修改原數組。
1 let arr = [1,2,3,4,5];
2 console.log(arr.fill(0,0,3));//[0,0,0,4,5]
3 //參數1表示目標值,參數2,3表示替換的始末位置,左閉右開區間。
4 console.log(arr.copyWithin(0,2,4));//[0,4,0,4,5]
5 //參數1表示修改的起始位置,參數2,3表示用來替換的數據的始末位置,左閉右開區間。
fill()用指定的值替換,copyWithin()使用數組中原有的某一部分值替換。
includes()用於檢測數組是否包含某個值,可以指定開始位置。
1 let arr = [1,2,3,4,5];
2 console.log(arr.includes(2));//true
3 console.log(arr.includes(1,1));//false
三 函數
1,參數預設值
ES6首次添加了參數預設值。我們再也不用在函數內部編寫容錯代碼了。
1 function add(a=1,b=2){
2 return a + b;
3 }
4 add();//3
5 add(2);//4
6 add(3,4);//7
和參數預設值一起,ES6還帶來了不定參。它的功能和使用arguments差不多。
1 function add(...num){
2 return num.reduce(function(result,value){
3 return result + value;
4 });
5 }
6 add(1,2,3,4);//10
下麵介紹的箭頭函數沒有arguments屬性,如果箭頭函數內要實現不定參,上述方式就是一個不錯的選擇了。
2,箭頭函數
箭頭函數實現了一種更加簡潔的書寫方式,並且也解決了關鍵字聲明方式的一些麻煩事兒。箭頭函數內部沒有arguments,也沒有prototype屬性,所以不能用new關鍵字調用箭頭函數。
箭頭函數的書寫方式:參數 => 函數體。
1 let add = (a,b) => {
2 return a+b;
3 }
4 let print = () => {
5 console.log('hi');
6 }
7 let fn = a => a * a;
8 //當只有一個參數時,括弧可以省略,函數體只有單行return語句時,大括弧也可以省略,強烈建議不要省略它們,是真的難以閱讀
當函數需要直接返回對象時,建議用變數保存,然後返回變數名,或用小括弧把對象包裹起來。否則將拋出錯誤。
1 var returnObj = () =>{
2 var obj = {name:'ren',age:12};
3 retufn obj;
4 };
5 //var returnObj = () => ({name:'ren',age:12});
箭頭函數和普通函數最大的區別在於其內部this永遠指向其父級AO對象的this。
普通函數在預編譯環節會在AO對象上添加this屬性,保存一個對象(請參照《JavaScript之深入對象(二)》)。每個普通函數在執行時都有一個特定的this對象,而箭頭函數執行時不會在自己的this屬性上添加一個新對象,而是直接引用父級AO對象上this綁定的對象。普通函數的AO對象只有在函數執行時才產生,換言之,普通函數的this是由函數執行時的環境決定。而箭頭函數的特別之處在於,當函數被定義時,就需要引用其父級AO對象的this,即箭頭函數的this由定義時的環境決定。
根據箭頭函數的特點,不難推測:如果定義對象的方法直接使用箭頭函數,那麼函數內的this將直接指向window。
1 var age = 123;
2 let obj = {
3 age:456,
4 say:() => {
5 console.log(this.age);
6 }
7 };
8 obj.say();//123
9 //對象是沒有執行期上下文的(AO對象),定義對象的方法實際上是在全局作用域下,即window
如果你一定要在箭頭函數中讓this指向當前對象,其實也還是有辦法的(但是沒必要這麼麻煩啊,直接使用普通函數不是更好嗎?):
1 var age = 123;
2 let obj = {
3 age:456,
4 say:function(){
5 var fn = () => {
6 console.log(this.age);
7 }
8 return fn();
9 }
10 };
11 obj.say();//456
我們來分析一下這是怎麼做到的:首先,我們使用obj調用say方法時,say內創建了AO對象,並且該AO對象的this屬性指向了obj(這都不明白的請回去往前複習一下我的《JavaScript之深入函數/對象》),然後,say內部又聲明瞭一個箭頭函數。我們說箭頭函數在聲明時就要強行引用父級AO的this屬性,那麼現在該箭頭函數的父級AO是誰呢?當然就是say的AO啦,所以這裡箭頭函數的this直接就綁定了obj,最後箭頭函數在執行時拿到的this,實際上就是say方法的AO.this,即obj本身。
上面是在對象中使用箭頭函數,如果那讓你難於理解,那麼請看下麵這種方式:在普通函數中使用箭頭函數。
1 var obj = {name:'ren'};
2 function test(){
3 var fn = () => {
4 console.log(this);
5 };
6 fn();
7 }
8 test();//window
9 test.call(obj);//{name:'ren'}
test函數在全局執行時,其this指向window,這時也產生了箭頭函數的定義,於是箭頭函數內的this也被指向了window,所以最終列印出window對象。
當我們手動改變test函數執行時this的指向時,箭頭函數定義所綁定的this實際上也被我們修改了。所以最終列印出obj。
四 class(類)
class 作為對象的模板被引入ES6,你可以通過 class 關鍵字定義類。class 的本質依然是一個函數。
1,創建類
1 class Ex {//關鍵字申明方式
2 constructor(name){
3 this.name = name;
4 this.say = () => {
5 console.log(this.name);
6 }
7 }
8 methods(){
9 console.log('hello ' + this.name);
10 }
11 static a = 123;
12 static m = () => {
13 console.log(this.a);
14 };
15 }
16 //let ex = class{} 字面量方式
17 var example = new Ex('ren');
18 example.say();//'ren'
19 Ex.m();//123
20 example.methods();//'hello ren'
constructor是創建類必須的方法,當使用new調用類創建實例時,將自動執行該方法,該方法和構造函數類似,預設返回this對象。實例的方法和屬性都定義在constructor內部。相當於構造函數的this方式。
類保留了prototype屬性,類中的方法不需要使用function關鍵字,並且方法之間不需要逗號隔開。類中定義的方法實際上還是保存在類的prototype屬性上。
使用static關鍵字定義類的靜態屬性和方法。類中不能定義共有屬性,要想定義實例的共有屬性還是需要使用prototype屬性:Ex.prototype.屬性名 = 屬性值。
創建實例依然使用new關鍵字。
2,類的繼承
類的繼承通過extends關鍵字實現。
1 class Person {
2 constructor (name,age){
3 this.name = name;
4 this.age = age;
5 }
6 say(){
7 console.log(this.name + ':' + this.age);
8 }
9 }
10 class Student extends Person{
11 constructor (name,age,sex){
12 super(name,age);
13 this.sex = sex;
14 }
15 }
16 var student = new Student('ren',12,'male');
17 student.name;//'ren'
18 student.sex;//'male'
19 student.say();//'ren:12'
子類繼承自父類,不會隱式的創建自己的this對象,而是通過super()引用父類的this。這個過程和在子構造函數內使用父構造函數call(this)很像,但他們有本質的區別。另外,ES6規定,super()必須在子類的this之前執行。所以一般我們把super()放在子類constructor方法的第一行,這樣準沒錯!
五 非同步機制
ES6新增了兩種實現非同步的新機制,Promise和Generator。文筆有限,怕講的不清楚,誤人子弟,請有興趣的同學去下麵的鏈接繼續學習,廖老師的教程也是受很多人推崇的,當然MDN更官方。(實際上是需要較大篇幅才能講明白,這裡就偷個懶了)
1,Promise
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544
2,Generator
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Generator
https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112