ECMAScript 6.0(如下簡稱 ES6)是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式發佈了。它的目標,是使得 JavaScript 語言能夠用來編寫複雜的大型應用程序,成爲企業級開發語言。javascript
這句話基本涵蓋了爲何會產生ES6此次更新的緣由——編寫複雜的大型應用程序。html
回顧近兩年的前端開發,複雜度確實在快速增長,近期不論從系統複雜度仍是到前端開發人員數量應該達到了一個飽和值,換個方式說,沒有ES6咱們的前端代碼依舊能夠寫不少複雜的應用,而ES6的提出更好的幫咱們解決了不少歷史遺留問題,另外一個角度ES6讓JS更適合開發大型應用,而不用引用太多的庫了。前端
本文,簡單介紹幾個ES6核心概念,我的感受只要掌握如下新特性便能愉快的開始使用ES6作代碼了!java
這裏的文章,請配合着阮老師這裏的教程,一些細節阮老師那邊講的好得多:http://es6.ruanyifeng.com/#docs/class-extendswebpack
除了阮老師的文章還參考:http://www.infoq.com/cn/articles/es6-in-depth-arrow-functionses6
PS:文中只是我的感悟,有誤請在評論提出web
都說了複雜的大型應用了,因此咱們第一個要討論的重要特性就是模塊概念,咱們作一個複雜的項目一定須要兩步走:面試
① 分得開,而且須要分開編程
② 合得起來api
咱們廣泛認爲沒有複雜的應用,只有分不開的應用,再複雜的應用,一旦可使用組件化、模塊化的方式分紅不一樣的小單元,那麼其難度便會大大下降,模塊化是大型、複雜項目的主要攔路虎。爲了解決這個問題,社區制定了一些模塊加載方案,對於瀏覽器開發來講,咱們用的最多的是AMD規範,也就是你們熟知的requireJS,而ES6中在語音標準層面實現了模塊功能,用以取代服務端通訊的CommonJS和AMD規範,成爲了通用的規範,多說無益,咱們這裏上一段代碼說明:
1 /* 2 validate.js 多用於表單驗證 3 */ 4 export function isEmail (text) { 5 var reg = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 6 return reg.test(text); 7 } 8 9 export function isPassword (text) { 10 var reg = /^[a-zA-Z0-9]{6,20}$/; 11 return reg.test(text); 12 }
那麼咱們如今想在頁面裏面使用這個工具類該怎麼作呢:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <!-- 請注意這裏type=module才能運行 --> 9 <script type="module"> 10 import {isEmail} from './validate.js'; 11 var e1 = 'dddd'; 12 var e2 = 'yexiaochai@qq.com' 13 console.log(isEmail(e1)) 14 console.log(isEmail(e2)) 15 </script> 16 </body> 17 </html>
ES6中的Module提出,在我這裏看來是想在官方完成以前requireJS乾的工做,這裏也有一些本質上的不同:
① requireJS是使用加載script標籤的方式載入js,沒有什麼限制
② import命令會被js引擎靜態分析,先於模塊其餘語句執行
以上特性會直接給咱們帶來一些困擾,好比原來咱們項目控制器會有這麼一段代碼:
1 var viewId = ''; //由瀏覽器獲取試圖id,url可能爲?viewId=booking|list|... 2 //若是不存在則須要構建,記住構建時須要使用viewdata繼承源view 3 requirejs(viewId, function(View) { 4 //執行根據url參數動態加載view邏輯 5 })
前面說過了,import命令會被js引擎靜態分析,先於模塊其餘語句執行,因此咱們在根本不能將import執行滯後,或者動態化,作不到的,這種寫法也是報錯的:
if (viewId) { import view from './' + viewId; }
這種設計會有利於提升編譯器效率,可是以前的動態業務邏輯就不知道如何繼續了?而ES6若是提供import的方法,咱們變能夠執行邏輯:
1 import(viewId, function() {
2 //渲染頁面
3 })
事實上他也提供了:
如今看起來,JS中的模塊便十分完美了,至於其中一些細節,即可以用到的時候再說了
咱們對咱們的定位一直是很是清晰的,咱們就是要幹大項目的,咱們是要幹複雜的項目,除了模塊概念,類的概念也很是重要,咱們以前用的這種方式實現一個類,咱們來溫故而知新。
當一個函數被建立時,Function構造函數產生的函數會隱式的被賦予一個prototype屬性,prototype包含一個constructor對象
而constructor即是該新函數對象(constructor意義不大,可是能夠幫咱們找到繼承關係)
每一個函數都會有一個prototype屬性,該屬性指向另外一對象,這個對象包含能夠由特定類型的全部實例共享的屬性和方法
每次實例化後,實例內部都會包含一個[[prototype]](__proto__)的內部屬性,這個屬性指向prototype
① 咱們經過isPrototypeOf來肯定某個對象是否是個人原型 ② hasOwnPrototype 能夠檢測一個屬性是存在實例中仍是原型中,該屬性不是原型屬性才返回true
var Person = function (name, age) {
this.name = name; this.age = age; }; Person.prototype.getName = function () { return this.name; }; var y = new Person('葉小釵', 30);
爲了方便,使用,咱們作了更爲複雜的封裝:
1 var arr = []; 2 var slice = arr.slice; 3 4 function create() { 5 if (arguments.length == 0 || arguments.length > 2) throw '參數錯誤'; 6 7 var parent = null; 8 //將參數轉換爲數組 9 var properties = slice.call(arguments); 10 11 //若是第一個參數爲類(function),那麼就將之取出 12 if (typeof properties[0] === 'function') 13 parent = properties.shift(); 14 properties = properties[0]; 15 16 function klass() { 17 this.initialize.apply(this, arguments); 18 } 19 20 klass.superclass = parent; 21 klass.subclasses = []; 22 23 if (parent) { 24 var subclass = function () { }; 25 subclass.prototype = parent.prototype; 26 klass.prototype = new subclass; 27 parent.subclasses.push(klass); 28 } 29 30 var ancestor = klass.superclass && klass.superclass.prototype; 31 for (var k in properties) { 32 var value = properties[k]; 33 34 //知足條件就重寫 35 if (ancestor && typeof value == 'function') { 36 var argslist = /^\s*function\s*\(([^\(\)]*?)\)\s*?\{/i.exec(value.toString())[1].replace(/\s/i, '').split(','); 37 //只有在第一個參數爲$super狀況下才須要處理(是否具備重複方法須要用戶本身決定) 38 if (argslist[0] === '$super' && ancestor[k]) { 39 value = (function (methodName, fn) { 40 return function () { 41 var scope = this; 42 var args = [function () { 43 return ancestor[methodName].apply(scope, arguments); 44 } ]; 45 return fn.apply(this, args.concat(slice.call(arguments))); 46 }; 47 })(k, value); 48 } 49 } 50 51 klass.prototype[k] = value; 52 } 53 54 if (!klass.prototype.initialize) 55 klass.prototype.initialize = function () { }; 56 57 klass.prototype.constructor = klass; 58 59 return klass; 60 }
這裏寫一個demo:
1 var AbstractView = create({ 2 initialize: function (opts) { 3 opts = opts || {}; 4 this.wrapper = opts.wrapper || $('body'); 5 6 //事件集合 7 this.events = {}; 8 9 this.isCreate = false; 10 11 }, 12 on: function (type, fn) { 13 if (!this.events[type]) this.events[type] = []; 14 this.events[type].push(fn); 15 }, 16 trigger: function (type) { 17 if (!this.events[type]) return; 18 for (var i = 0, len = this.events[type].length; i < len; i++) { 19 this.events[type][i].call(this) 20 } 21 }, 22 createHtml: function () { 23 throw '必須重寫'; 24 }, 25 create: function () { 26 this.root = $(this.createHtml()); 27 this.wrapper.append(this.root); 28 this.trigger('onCreate'); 29 this.isCreate = true; 30 }, 31 show: function () { 32 if (!this.isCreate) this.create(); 33 this.root.show(); 34 this.trigger('onShow'); 35 }, 36 hide: function () { 37 this.root.hide(); 38 } 39 }); 40 41 var Alert = create(AbstractView, { 42 43 createHtml: function () { 44 return '<div class="alert">這裏是alert框</div>'; 45 } 46 }); 47 48 var AlertTitle = create(Alert, { 49 initialize: function ($super) { 50 this.title = ''; 51 $super(); 52 53 }, 54 createHtml: function () { 55 return '<div class="alert"><h2>' + this.title + '</h2>這裏是帶標題alert框</div>'; 56 }, 57 58 setTitle: function (title) { 59 this.title = title; 60 this.root.find('h2').html(title) 61 } 62 63 }); 64 65 var AlertTitleButton = create(AlertTitle, { 66 initialize: function ($super) { 67 this.title = ''; 68 $super(); 69 70 this.on('onShow', function () { 71 var bt = $('<input type="button" value="點擊我" />'); 72 bt.click($.proxy(function () { 73 alert(this.title); 74 }, this)); 75 this.root.append(bt) 76 }); 77 } 78 }); 79 80 var v1 = new Alert(); 81 v1.show(); 82 83 var v2 = new AlertTitle(); 84 v2.show(); 85 v2.setTitle('我是標題'); 86 87 var v3 = new AlertTitleButton(); 88 v3.show(); 89 v3.setTitle('我是標題和按鈕的alert');
ES6中直接從標準層面解決了咱們的問題,他提出了Class關鍵詞讓咱們能夠更好的定義類,咱們這裏用咱們ES6的模塊語法從新實現一次:
1 export class AbstractView { 2 constructor(opts) { 3 opts = opts || {}; 4 this.wrapper = opts.wrapper || $('body'); 5 //事件集合 6 this.events = {}; 7 this.isCreate = false; 8 } 9 on(type, fn) { 10 if (!this.events[type]) this.events[type] = []; 11 this.events[type].push(fn); 12 } 13 trigger(type) { 14 if (!this.events[type]) return; 15 for (var i = 0, len = this.events[type].length; i < len; i++) { 16 this.events[type][i].call(this) 17 } 18 } 19 createHtml() { 20 throw '必須重寫'; 21 } 22 create() { 23 this.root = $(this.createHtml()); 24 this.wrapper.append(this.root); 25 this.trigger('onCreate'); 26 this.isCreate = true; 27 } 28 show() { 29 if (!this.isCreate) this.create(); 30 this.root.show(); 31 this.trigger('onShow'); 32 } 33 hide() { 34 this.root.hide(); 35 } 36 } 37 export class Alert extends AbstractView { 38 createHtml() { 39 return '<div class="alert">這裏是alert框</div>'; 40 } 41 } 42 export class AlertTitle extends Alert { 43 constructor(opts) { 44 super(opts); 45 this.title = ''; 46 } 47 createHtml() { 48 return '<div class="alert"><h2>' + this.title + '</h2>這裏是帶標題alert框</div>'; 49 } 50 setTitle(title) { 51 this.title = title; 52 this.root.find('h2').html(title) 53 } 54 } 55 export class AlertTitleButton extends AlertTitle { 56 constructor(opts) { 57 super(opts); 58 this.on('onShow', function () { 59 var bt = $('<input type="button" value="點擊我" />'); 60 bt.click($.proxy(function () { 61 alert(this.title); 62 }, this)); 63 this.root.append(bt) 64 }); 65 } 66 }
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <script type="text/javascript" src="zepto.js"></script> 9 10 <!-- 請注意這裏type=module才能運行 --> 11 <script type="module"> 12 import {Alert, AlertTitle, AlertTitleButton} from './es6class.js'; 13 var v1 = new Alert(); 14 v1.show(); 15 var v2 = new AlertTitle(); 16 v2.show(); 17 v2.setTitle('我是標題'); 18 var v3 = new AlertTitleButton(); 19 v3.show(); 20 v3.setTitle('我是標題和按鈕的alert'); 21 </script> 22 </body> 23 </html>
這裏的代碼完成了與上面同樣的功能,而代碼更加的清爽了。
咱們這裏學習ES6,由大到小,首先討論模塊,其次討論類,這個時候理所固然到了咱們的函數了,ES6中函數也多了不少新特性或者說語法糖吧,首先咱們來講一下這裏的箭頭函數
//ES5 $('#bt').click(function (e) { //doing something }) //ES6 $('#bt').click(e => { //doing something })
有點語法糖的感受,有一個很大不一樣的是,箭頭函數不具備this屬性,箭頭函數直接使用的是外部的this的做用域,這個想不想用看我的習慣吧。
ES6能夠爲參數提供默認屬性
1 function log(x, y = 'World') { 2 console.log(x, y); 3 } 4 5 log('Hello') // Hello World 6 log('Hello', 'China') // Hello China 7 log('Hello', '') // Hello
至於不定參數撒的,我這裏沒有多過多的使用,等項目遇到再說吧,若是研究的太細碎,反而不適合咱們開展工做。
以前的js世界裏,咱們定義變量都是使用的var,別說還真挺好用的,雖有會有一些問題,可是對於熟悉js特性的小夥伴都能很好的解決,通常記住:變量提高會解決絕大多數問題。
就能解決不少問題,並且真實項目中,咱們會會避免出現變量出現重名的狀況因此有時候你們面試題中看到的場景在實際工做中不多發生,只要不刻意臆想、製造一些難以判斷的場景,其實並不會出現多少BUG,不能由於想考察人家對語言特性的瞭解,就作一些容易容易忘掉的陷阱題。
不管如何,var 聲明的變量受到了必定詬病,事實上在強類型語言看來也確實是設計BUG,可是徹底廢棄var的使用顯然不是js該作的事情,這種狀況下出現了let關鍵詞。
let與var一致用以聲明變量,而且一切用var的地方均可以使用let替換,新的標準也建議你們不要再使用var了,let具備更好的做用域規則,也許這個規則是邊界更加清晰了:
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
這裏是一個經典的閉包問題:
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6](); // 10
由於i在全局範圍有效,共享同一個做用域,因此i就只有10了,爲了解決這個問題,咱們以前會引入閉包,產生新的做用域空間(好像學名是變量對象,我給忘了),可是那裏的i跟這裏的i已經不是一個東西了,但若是將var改爲let,上面的答案是符合預期的。能夠簡單理解爲每一次「{}」,let定義的變量都會產生新的做用域空間,這裏產生了循環,因此每一次都不同,這裏與閉包有點相似是開闢了不一樣的空間。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
這裏由於內部從新聲明瞭i,事實上產生了3個做用域,這裏一共有4個做用域指向,let最大的做用就是js中塊級做用域的存在,而且內部的變量不會被外部所訪問,因此以前爲了防止變量侮辱的當即執行函數,彷佛變得不是那麼必要了。
以前咱們定義一個常量會採用所有大寫的方式:
var NUM = 10;
爲了解決這個問題,ES6引入了const命令,讓咱們定義只讀常量,這裏不對細節作過多研究,直接後續項目實踐吧,項目出真知。
ES6中提出了生成器Generators的概念,這是一種異步編程的解決方案,能夠將其理解爲一種狀態機,封裝了多個內部狀態,這裏來個demo:
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
這個yield(產出)相似於以前的return,直觀的理解就是一個函數能夠返回屢次了,或者說函數具備「順序狀態」,yield提供了暫停功能。這裏我想寫個代碼來驗證下期中的做用域狀態:
function* test(){ let i = 0; setTimeout(function() { i++; }, 1000); yield i; yield i++; return i } let t = test(); console.log(t.next()); setTimeout(function() { console.log(t.next()); }, 2000); console.log(t.next()); //{value: 0, done: false} //{value: 0, done: false} //{value: 2, done: true}
以前咱們寫一個城市級聯的代碼,可能會有些使人蛋疼:
1 $.get('getCity', {id: 0}, function(province) { 2 let pid = province[0]; 3 //根據省id獲取城市數據 4 $.get('getCity', {id: pid}, function(city) { 5 let cityId = city[0]; 6 //根據市級id獲取縣 7 $.get('getCity', {id: cityId}, function(city) { 8 //do smt. 9 }); 10 }); 11 });
這個代碼你們應當比較熟悉了,用promise能從語法層面解決一些問題,這裏簡單介紹下promise。
Promise是一種異步解決方案,有些同事認爲其出現就是爲了咱們代碼變得更好看,解決回調地獄的語法糖,ES6將其寫入了語音標準,提供了原生Promise對象。Promise爲一容器,裏面保存異步事件的結果,他是一個對象具備三個狀態:pending(進行中)、fulfilled(已成功)、rejected(已失敗),這裏仍是來個簡單代碼說明:
1 function timeout(ms) { 2 return new Promise((resolve, reject) => { 3 setTimeout(resolve, ms, 'done'); 4 }); 5 } 6 7 timeout(100).then((value) => { 8 console.log(value); 9 });
實例化Promise時,第一個回調必須提供,是進行轉爲成功時候會執行,第二個也是一個函數失敗時候調用,非必須,這裏來個demo:
1 let timeout = function (ms) { 2 return new Promise(function (resolve) { 3 setTimeout(resolve, ms); 4 }); 5 }; 6 7 timeout(1000).then(function () { 8 return timeout(1000).then(function () { 9 let s = '你們'; 10 console.log(s) 11 return s; 12 }) 13 14 }).then(function (data) { 15 return timeout(1000).then(function () { 16 let s = data + '好,'; 17 console.log(s) 18 return s; 19 }) 20 }).then(function(data) { 21 return timeout(1000).then(function () { 22 let s = data + '我是葉小釵'; 23 console.log(s) 24 return s; 25 }); 26 }).then(function(data) { 27 console.log(data) 28 });
若是咱們請求有依賴的話,第一個請求依賴於第二個請求,代碼就能夠這樣寫:
1 let getData = function(url, param) { 2 return new Promise(function (resolve) { 3 $.get(url, param, resolve ); 4 }); 5 } 6 getData('http://api.kuai.baidu.com/city/getstartcitys?callback=?').then(function (data) { 7 console.log('我獲取了省數據,咱們立刻根據省數據申請市數據', data); 8 return getData('http://api.kuai.baidu.com/city/getstartcitys?callback=?').then(function (data1) { 9 console.log(data1); 10 return '我是市數據'; 11 }) 12 13 }).then(function(data) { 14 //前面的參數傳過來了 15 console.log(data); 16 console.log('我獲取了市數據,咱們立刻根據市數據申請縣數據'); 17 getData('http://api.kuai.baidu.com/city/getstartcitys?callback=?').then(function (data1) { 18 console.log(data1); 19 }); 20 })
如此即可以免多層嵌套了,關於Promise的知識點還不少,咱們遇到複雜的工做場景再拿出來講吧,我對他的定位就是一個語法糖,將異步的方式變成同步的寫法,骨子裏仍是異步,上面咱們用Promise解決回調地獄問題,可是回調地獄問題遇到的很少,卻發現Promise一堆then看見就有點煩,咱們的Generator函數彷佛可讓這個狀況獲得緩解。
可是暫時在實際工做中我沒有找到更好的使用場景,這裏暫時到這裏,後面工做遇到再詳述,對這塊不是很熟悉也不妨礙咱們使用ES6寫代碼。
代理,其實就是你要作什麼我幫你作了就好了,通常代理的緣由都是,我須要作點手腳,或者多點操做,或者作點「賦能」,如咱們經常包裝setTimeout通常:
1 let timeout = function (ms, callback) { 2 setTimeout(callback, ms); 3 }
咱們包裝setTimeout每每是爲了clearTimeout的時候能所有清理掉,其實就是攔截下,ES6提供了Proxy關鍵詞用於設置代理器:
1 var obj = new Proxy({}, { 2 get: function (target, key, receiver) { 3 console.log(`getting ${key}!`); 4 return Reflect.get(target, key, receiver); 5 }, 6 set: function (target, key, value, receiver) { 7 console.log(`setting ${key}!`); 8 return Reflect.set(target, key, value, receiver); 9 } 10 }); 11 obj.count = 1 12 // setting count! 13 ++obj.count 14 // getting count! 15 // setting count! 16 // 2
//target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲 var proxy = new Proxy(target, handler);
咱們這裏繼續寫一個簡單的demo:
1 let person = { 2 constructor: function(name, age = 20) { 3 this.name = name; 4 this.age = age 5 }, 6 addAge: function() { 7 this.age++; 8 }, 9 getAge: function() { 10 return this.age; 11 } 12 } 13 14 var proxy = new Proxy(person, { 15 get: function(target, property) { 16 console.log(arguments); 17 return target[property]; 18 }, 19 set: function(target, property) { 20 console.log(arguments); 21 } 22 }); 23 24 person.constructor('葉小釵', 30); 25 console.log(person.age) 26 console.log(proxy.age)
可是暫時我沒有發現比較好的業務場景,好比說,我如今重寫了一個實例的get方法,便能在一個全局容器中記錄這個被執行了多少次,這裏一個業務場景是:我一次個頁面連續發出了不少次請求,可是我單頁應用作頁面跳轉時候,我須要將全部的請求句柄移除,這個彷佛也不是代理完成的工做,因而要使用ES6寫代碼,彷佛能夠暫時忽略代理。
有了以上知識,基本從程序層面可使用ES6寫代碼了,可是工程層面還須要引入webpack等工具,這些咱們下次介紹吧。