ECMAScript和JavaScript的關係是,前者是後者的規格,後者是前者的一種實現(另外的ECMAScript方言還有Jscript和ActionScript)。vue
阮一峯寫了一個ES-Checker模塊,用來檢查各類運行環境對ES6的支持狀況。運行下面的命令,能夠查看你正在使用的Node環境對ES6的支持程度。node
sudo npm i -g es-checker es-checker
ES6轉碼器,能夠將ES6代碼轉爲ES5代碼,從而在現有環境執行。es6在線編輯器/在線轉碼器。react
在es6標準出來以前,你們都會參與討論,把各自以爲好的語法加進去,便有了不少階段(準標準、草案、提案..)。stage-0是一個提案,裏面基本包括你們討論的全部內容,stage-1 -- stage-3以此類推,stage-3基本肯定要進入es6了。因此使用webpack的同窗會看到,安裝的依賴基本都是babel-preset-stage-0。webpack
本人以前開發中遇到過一個問題,array的includes方法(string也有),當時運行在chrome47環境下,整個頁面打不開,報錯(babel includes can not a function),在chrome52環境下支持。解決辦法--安裝babel-polyfill。最後在你的文件中import 'babel-polyfill'。es6
Babel默認只轉換新的JavaScript句法(syntax),而不轉換新的API,好比Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(好比Object.assign)都不會轉碼。舉例來講,ES6在Array對象上新增了Array.from方法。Babel就不會轉碼這個方法。若是想讓這個方法運行,必須使用babel-polyfill,爲當前環境提供一個墊片。web
let用於聲明變量,const用於聲明常量。可是聊這個以前,咱們仍是簡單提提老生常談的問題吧---scope。vuex
在es5中是不存在塊級做用域的,通常咱們聽得比較多的是函數做用域(定義在函數中的參數和變量在函數外部是不可見的)。chrome
function getVal(boo) { if (boo) { var val = 'red' // ... return val } else { // 這裏能夠訪問 val return null } // 這裏也能夠訪問 val }
那麼在es5中如何使用塊級做用域呢?js中在一個函數中定義的變量,當這個函數調用完後,變量會被銷燬,咱們能夠利用這種特性(閉包,the most important feature!!)。npm
function caniuse() { for(var i=0;i<10;i++){} console.log(i); //10 } caniuse(); function caniuse2() { (function() { for(var i=0;i<10;i++){} })() console.log(i) //i is not defined } caniuse2()
在es6中,任何一對花括號中(for,if)的語句集都屬於一個塊,在這之中定義的全部變量在代碼塊外都是不可見的,咱們稱之爲塊級做用域。redux
function getVal(boo) { if (boo) { let val = 'red' // ... return val } else { // 這裏訪問不到 val return null } // 這裏也訪問不到 val }
有了es6,上面閉包的寫法咱們用es6能夠不須要用到當即執行函數。
for(let i = 0; i < 10; i++){}
{ let i = 9; console.log(i) //9 } console.log(i) //ReferenceError: i is not defined
function test1() { for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i) //10次10 }, 1000) } } function test2() { for(let i = 0; i < 10; i++) { setTimeout(function() { console.log(i) //0-9 }, 1000) } } function test3() { for(var i = 0; i < 10; i++) { (function(i) { setTimeout(function() { console.log(i) //0-9 }, 100) })(i) } } test1() test2() test3()
腳本開始運行時,變量i已經存在了,可是沒有值,因此會輸出undefined。變量ii用let命令聲明,不會發生變量提高。這表示在聲明它以前,變量ii是不存在的,這時若是用到它,就會拋出一個錯誤。
console.log(i) //undefined console.log(ii) //ReferenceError: ii is not defined var i; let ii;
還有一些比較不容易發現的暫時性死區
function test1(y = 1, x = y) { console.log(x, y) // 1 1 } function test2(x = y, y = 1) { console.log(x, y) //error } test1() test2()
// 報錯 function () { let a = 10; var a = 1; }
用const聲明,常量的值就不能改變。
const一旦聲明變量,就必須當即初始化,不能留到之後賦值。
只所在的塊級做用域內有效。(見let)
不能變量提高,存在暫時性死區,只能在聲明的位置後面使用。(見let)
在一個scope中不可重複declare。
const foo; // SyntaxError: Missing initializer in const declaration
對於複合類型的變量,變量名不指向數據,而是指向數據所在的地址。const命令只是保證變量名指向的地址不變,並不保證該地址的數據不變,因此將一個對象聲明爲常量必須很是當心。
const foo = {}; foo.prop = 123; foo.prop // 123 foo = {}; // TypeError: "foo" is read-only const a = []; a.push('Hello'); // 可執行 a.length = 0; // 可執行 a = ['Dave']; // 報錯 const foo = Object.freeze({}); // 常規模式時,下面一行不起做用; // 嚴格模式時,該行會報錯 foo.prop = 123;
ES5只有兩種聲明變量的方法:var命令和function命令。ES6除了添加let和const命令,另外兩種聲明變量的方法:import命令和class命令。因此,ES6一共有6種聲明變量的方法。
有三類數據結構原生具有Iterator接口:數組、某些相似數組的對象、Set和Map結構。Iterator是一種機制,爲不一樣的數據結構提供可遍歷操做。可遍歷結構,個人理解是能夠執行for...of。
爲各類數據結構,提供一個統一的、簡便的訪問接口
使得數據結構的成員可以按某種次序排列
ES6創造了一種新的遍歷命令for...of循環,Iterator接口主要供for...of消費。
var it = makeIterator(['a', 'b']); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true } function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++]} : {done: true}; } }; }
let arr = [2,3,4,,7,8] for(let val of arr) { console.log(val) //2,3,4,undefined,7,8 } for(let k in arr) { console.log(k) //"0" "1" "2" "4" "5",下標都是字符串哦 } console.log(arr["2"]) //4 let arr2 = {0: 'a', 1: 'b', 3: 'c'} for(let key of arr2) { console.log(key, arr2[key]) //arr2[Symbol.iterator] is not a function } for(let key in arr2) { console.log(key, arr2[key]) //"0" "a" //"1" "b" //"3" "c" }
解構賦值(下一章)
擴展運算符(spread, 方便,使用頻率很高。。。spread arguments in function)。
let [a, ...c] = [3, 5, 6, 7, 7, 9] console.log(a) //3 console.log(c) //[5, 6, 7, 7, 9] let [e, ...f, g] = [3, 5, 6, 7, 7, 9] console.log(e) //3 console.log(f) //[5, 6, 7, 7, 9] console.log(g) //error var str = 'hello'; [...str] // ['h','e','l','l','o'] let arr = ['b', 'c']; ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
yield*
let generator = function* () { yield 1; yield* [2, 4]; yield 5 } let iterator = generator() console.log(iterator.next()) //{"done": false, "value": 1} console.log(iterator.next()) //{"done": false, "value": 2} console.log(iterator.next()) //{"done": false, "value": 4} console.log(iterator.next()) //{"done": true, "value": 5}
其餘(for...of, Array.form(), Map(), Set())
解構賦值——個人字面理解是先分解結構,再匹配等號兩邊相對應的結構,若是解構成功給每一個相對應地變量賦值,通常狀況下如若不成功,就給對應的變量賦值undefined。
只要等號兩邊的模式相同,解構徹底匹配。左邊的變量就會被賦予對應的值。
let [a, ...c] = [3, 5, 6, 7, 7, 9] console.log(a) //3 console.log(c) //[5, 6, 7, 7, 9] let [x, y, ...z] = ['a']; x // "a" y // undefined z // []
等號右邊的解構匹配左邊的,可是還有多餘的值。若是右邊不具有Iterator接口,解構的過程當中會報錯。
let [a, [b], d] = [1, [2, 3], 4]; a // 1 b // 2 d // 4 let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {};
以前寫redux的時候,常常會用到默認值。
export function receiveCustomers({source=null, origin=null, start_at=null, end_at=null, keyword=null}) { return { type: types.CUSTOMERS_QUERY_CHANGE, source, origin, start_at, end_at, keyword } }
null/undefined
var [x = 1] = [undefined]; x // 1 var [x = 1] = [null]; x // null
function
function aaa() {console.log(1)} let [a = aaa()] = [1] //a=1
變量不能提高
let [x = y, y = 1] = []; // ReferenceError
這個灰經常用。通常用法就行,不須要很怪異的寫法,可讀性差。總結幾點注意事項:
若是要將一個已經聲明的變量用於解構賦值,必須很是當心。
默認值生效的條件是,對象的屬性值嚴格等於undefined。
// 錯誤的寫法 var x; {x} = {x: 1}; // SyntaxError: syntax error var {x = 3} = {x: undefined}; x // 3 var {x = 3} = {x: null}; x // null
這個比較經常使用,以前寫vuex的時候有使用到。用過vuex/redux的同窗應該不會陌生
import {fn1, fn2} from 'action' //action.js裏面有兩個方法fn1,fn2,最後 export {fn1,fn2} //等同於export{fn1: fn1, fn2: fn2} const { dispatch } = this.props //等同於const dispatch = this.props.dispatch vuex: { actions: { fn1, fn2 } } //等同於 vuex: { actions: { fn1: fn1, fn2: fn2 } }
function f(x, y) { return {x, y}; } // 等同於 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2}
方法的簡寫
handleText = e => { this.setState({ inputValue: e.target.value }) }; //等同於 handleText: function(e) { ... }
JavaScript存儲對象都是存地址的,因此淺複製會致使 obj 和 obj1 指向同一塊內存地址,大概的示意圖以下。而深複製通常都是開闢一塊新的內存地址,將原對象的各個屬性逐個複製出去。
let obj = {0: "a", 1: "b", 2: "c"} let deepObj = JSON.parse(JSON.stringify(obj))//is not a really deep clone, but it works. let shallowObj = obj obj[3] = 'd' console.log(deepObj) //{0: "a", 1: "b", 2: "c"} console.log(shallowObj) //{0: "a", 1: "b", 2: "c", 3: "d"} console.log(obj) //{0: "a", 1: "b", 2: "c", 3: "d"}
JSON.parse(JSON.stringify(obj))這樣的使用方式並非真正的深拷貝,由於它會丟失一些東西,一些obj的內在property之類的,
好比
obj_test1 = { a: 1, b: 2, get_a() { return this.a } }
這樣的一個obj,你用上面的方式會丟掉get_a,若是你定義一些prototype,也會丟失掉,或者若是包含一些dom元素之類的。可是咱們多數時候只是用它來複制數據,不關心對象的方法之類的東西,這樣的話是夠用的。
對象拷貝(copy)
Object.assign方法實行的是淺拷貝,而不是深拷貝,若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。以前本身的一個疑惑
let obj = {0: "a", 1: "b", 2: "c"} let objClone = Object.assign({}, obj) obj[3] = 'd' console.log(objClone) // {0: "a", 1: "b", 2: "c"} why the same, but not change
Javascript has five primitive data types:
Number
String
Boolean
Undefined
Null
Anything that doesn’t belong to any of these five primitive types is considered an object.
primitive types are passed by value, while objects passed by reference.
a = {a: 1, b: 2, c: 3, d: {aa: 11, bb: 22}}
a1 = Object.assign({}, a, {a: 2})
a1.d.aa = 'i am shllow copy'
第二行是淺拷貝,a拷貝到a1了,而且把property a的值改爲2了 (你以爲a.a會變成2麼,爲何?)
第三行把a1.d這個object理得aa這個property改了 (你以爲a.d.aa也會改麼,爲何?)
合併對象 (merge)
ES5引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的key。Object.values返回value。
不得不說,這是我從沒仔細看過的一部分,寫react的時候都是三板斧套路。。。
特地留意一下super()。ES5的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。es6中先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this。
class Teachers extends React.Component { constructor(props, context) { super(props, context) } componentDidMount(){ const { siteId, dispatch } = this.props if (siteId) { dispatch(fetchTeachersList(siteId)) } } componentWillReceiveProps(nextProps) { const { siteId, dispatch } = this.props if (nextProps.siteId !== siteId) { dispatch(fetchTeachersList(nextProps.siteId)) } } } export default Teachers
搭配Object.assign()
class Point { constructor(){ // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
new Foo(); // ReferenceError class Foo {}
Class表達式,能夠寫出當即執行的Class。
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }('張三'); person.sayName(); // "張三"
箭頭函數很方便,解決了以前es5遺留下來的問題(繼承,this做用域)。箭頭函數沒有它本身的this值,箭頭函數內的this值繼承自外圍做用域。
//es5中, var self = this;這樣的代碼你確定常見,es6箭頭函數中不用鳥 var obj = { field: 'hello', getField: () => { console.log(this.field) } } obj.getField() // undefined,這時this是window, var obj = { field: 'hello', getField(){ console.log(this.field) } } obj.getField() // hello,
let arr = [1,2,3,4,5,6] let arr2 = arr.reduce( (init, prev, index) => {init.push(prev*prev); return init}, [] ) console.log(arr2) //[1,4,9,16,25,36]
想起以前寫的數組轉對象的方法
警告 yield 關鍵字一般不能在箭頭函數中使用(except when permitted within functions further nested within it)。所以,箭頭函數不能用做Generator函數。
js歷史上沒有標準的模塊化。在ES6以前,社區制定了一些模塊加載方案,最主要的有CommonJS和AMD兩種。前者用於服務器,後者用於瀏覽器。可是這些都只能在運行時起做用,es6的模塊化能夠在編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。
模塊功能主要由兩個命令構成:export和import。先拋出一個問題:如下兩行代碼有什麼區別?
import {message, menu} from 'antd' import message from 'antd/lib/message'
export對外的接口名與模塊內部變量之間,創建了一一對應的關係。當export一個function時,語句輸出的接口,與其對應的值是動態綁定關係,即經過該接口,能夠取到模塊內部實時的值。
export 1; //error export var m = 1 //right var m = 1; export m//error export {m} //right
注意 import命令具備提高效果,會提高到整個模塊的頭部,首先執行。因此在文件中的任意位置,exort也同樣,不過利於可讀性,仍是import寫在文件開頭,export寫在文件結尾好點。
注意 import命令接受一個對象(用大括號表示),裏面指定要從其餘模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。
import {message, menu} from 'antd' import message from 'antd/lib/message'
因此這之間的區別就是前者中括號裏的變量名要與export出的接口名一致,後者message是隨意起的名字,能夠不與export出來的變量名保持一致,它表明export default出來的接口。export default命令用於指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,所以export deault命令只能使用一次。因此,import命令後面纔不用加大括號,由於只可能對應一個方法。本質上,export default就是輸出一個叫作default的變量或方法,而後系統容許你爲它取任意名字。
import * as circle from './circle'; console.log('圓面積:' + circle.area(4)); console.log('圓周長:' + circle.circumference(14));