es6 初步深入学习
es6初步深入學習
es6前言(只作了解就好)
ECMAScript和JavaScript的關系
ECMAScript和JavaScript的關系是,前者是后者的規格,后者是前者的一種實現(另外的ECMAScript方言還有Jscript和ActionScript)。
檢測node環境對ES6的支持情況
阮一峰寫了一個ES-Checker模塊,用來檢查各種運行環境對ES6的支持情況。運行下面的命令,可以查看你正在使用的Node環境對ES6的支持程度。
sudo npm i -g es-checkeres-checkerbabel轉碼器
ES6轉碼器,可以將ES6代碼轉為ES5代碼,從而在現有環境執行。es6在線編輯器/在線轉碼器。
babel stage
在es6標準出來之前,大家都會參與討論,把各自覺得好的語法加進去,便有了很多階段(準標準、草案、提案..)。stage-0是一個提案,里面基本包括大家討論的所有內容,stage-1 -- stage-3以此類推,stage-3基本確定要進入es6了。所以使用webpack的同學會看到,安裝的依賴基本都是babel-preset-stage-0。
babel-polyfill
本人之前開發中遇到過一個問題,array的includes方法(string也有),當時運行在chrome47環境下,整個頁面打不開,報錯(babel includes can not a function),在chrome52環境下支持。解決辦法--安裝babel-polyfill。最后在你的文件中import 'babel-polyfill'。
Babel默認只轉換新的JavaScript句法(syntax),而不轉換新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局對象,以及一些定義在全局對象上的方法(比如Object.assign)都不會轉碼。舉例來說,ES6在Array對象上新增了Array.from方法。Babel就不會轉碼這個方法。如果想讓這個方法運行,必須使用babel-polyfill,為當前環境提供一個墊片。
let和const
let用于聲明變量,const用于聲明常量。但是聊這個之前,我們還是簡單提提老生常談的問題吧---scope。
塊級作用域
在es5中是不存在塊級作用域的,一般我們聽得比較多的是函數作用域(定義在函數中的參數和變量在函數外部是不可見的)。
function getVal(boo) {if (boo) {var val = 'red'// ...return val} else {// 這里可以訪問 valreturn null}// 這里也可以訪問 val}那么在es5中如何使用塊級作用域呢?js中在一個函數中定義的變量,當這個函數調用完后,變量會被銷毀,我們可以利用這種特性(閉包,the most important feature!!)。
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)的語句集都屬于一個塊,在這之中定義的所有變量在代碼塊外都是不可見的,我們稱之為塊級作用域。
function getVal(boo) {if (boo) {let val = 'red'// ...return val} else {// 這里訪問不到 valreturn null}// 這里也訪問不到 val}有了es6,上面閉包的寫法我們用es6可以不需要用到立即執行函數。
for(let i = 0; i < 10; i++){}let
let聲明的變量只在他的代碼塊內有效
{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) //undefinedconsole.log(ii) //ReferenceError: ii is not definedvar 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聲明,常量的值就不能改變。
const一旦聲明變量,就必須立即初始化,不能留到以后賦值。
只所在的塊級作用域內有效。(見let)
不能變量提升,存在暫時性死區,只能在聲明的位置后面使用。(見let)
在一個scope中不可重復declare。
對于復合類型的變量,變量名不指向數據,而是指向數據所在的地址。const命令只是保證變量名指向的地址不變,并不保證該地址的數據不變,所以將一個對象聲明為常量必須非常小心。
小結
ES5只有兩種聲明變量的方法:var命令和function命令。ES6除了添加let和const命令,另外兩種聲明變量的方法:import命令和class命令。所以,ES6一共有6種聲明變量的方法。
Iterator(簡單說下,下次再仔細補充)
有三類數據結構原生具備Iterator接口:數組、某些類似數組的對象、Set和Map結構。Iterator是一種機制,為不同的數據結構提供可遍歷操作。可遍歷結構,我的理解是可以執行for...of。
Iterator作用:
為各種數據結構,提供一個統一的、簡便的訪問接口
使得數據結構的成員能夠按某種次序排列
ES6創造了一種新的遍歷命令for...of循環,Iterator接口主要供for...of消費。
Iterator的遍歷過程
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};}};}for...of
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"]) //4let 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"}Iterator的使用場合
解構賦值(下一章)
擴展運算符(spread, 方便,使用頻率很高。。。spread arguments in function)。
yield*
其他(for...of, Array.form(), Map(), Set())
變量的解構賦值(Destructuring)
解構賦值——我的字面理解是先分解結構,再匹配等號兩邊相對應的結構,如果解構成功給每個相對應地變量賦值,一般情況下如若不成功,就給對應的變量賦值undefined。
模式匹配
只要等號兩邊的模式相同,解構完全匹配。左邊的變量就會被賦予對應的值。
let [a, ...c] = [3, 5, 6, 7, 7, 9]console.log(a) //3console.log(c) //[5, 6, 7, 7, 9]let [x, y, ...z] = ['a'];x // "a"y // undefinedz // []不完全解構
等號右邊的解構匹配左邊的,但是還有多余的值。如果右邊不具備Iterator接口,解構的過程中會報錯。
let [a, [b], d] = [1, [2, 3], 4];a // 1b // 2d // 4let [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
function
變量不能提升
對象解構
這個灰常常用。一般用法就行,不需要很怪異的寫法,可讀性差。總結幾點注意事項:
如果要將一個已經聲明的變量用于解構賦值,必須非常小心。
默認值生效的條件是,對象的屬性值嚴格等于undefined。
對象的擴展
屬性的簡潔表示
這個比較常用,之前寫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.dispatchvuex: {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) {...}Object.assign
deep clone and shallow clone
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,你用上面的方式會丟掉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 changeJavascript 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)
Object.keys && Object.value
ES5引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的key。Object.values返回value。
Class
不得不說,這是我從沒仔細看過的一部分,寫react的時候都是三板斧套路。。。
特意留意一下super()。ES5的繼承,實質是先創造子類的實例對象this,然后再將父類的方法添加到this上面(Parent.apply(this))。es6中先創造父類的實例對象this(所以必須先調用super方法),然后再用子類的構造函數修改this。
搭配Object.assign()
class Point {constructor(){// ...}}Object.assign(Point.prototype, {toString(){},toValue(){}});不存在變量提升
new Foo(); // ReferenceErrorclass Foo {}class表達式
Class表達式,可以寫出立即執行的Class。
let person = new class {constructor(name) {this.name = name;}sayName() {console.log(this.name);}}('張三');person.sayName(); // "張三"this指向和箭頭函數
箭頭函數很方便,解決了之前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函數。
Module
js歷史上沒有標準的模塊化。在ES6之前,社區制定了一些模塊加載方案,最主要的有CommonJS和AMD兩種。前者用于服務器,后者用于瀏覽器。但是這些都只能在運行時起作用,es6的模塊化可以在編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。
模塊功能主要由兩個命令構成:export和import。先拋出一個問題:以下兩行代碼有什么區別?
import {message, menu} from 'antd' import message from 'antd/lib/message'export
對外的接口名與模塊內部變量的關系
export對外的接口名與模塊內部變量之間,建立了一一對應的關系。當export一個function時,語句輸出的接口,與其對應的值是動態綁定關系,即通過該接口,可以取到模塊內部實時的值。
export 1; //error export var m = 1 //rightvar m = 1; export m//error export {m} //rightimport
注意 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));總結
以上是生活随笔為你收集整理的es6 初步深入学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 监控Spark应用方法简介
- 下一篇: 模拟简单计算器及比较大小