1、let聲明變量javascript
一、基本用法:php
ES6 新增了let命令,用來聲明變量。它的用法相似於var,可是所聲明的變量,只在let命令所在的代碼塊內有效。html
以下代碼:前端
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
上面代碼在代碼塊之中,分別用let和var聲明瞭兩個變量。而後在代碼塊以外調用這兩個變量,結果let聲明的變量報錯,var聲明的變量返回了正確的值。這代表,let聲明的變量只在它所在的代碼塊有效。java
還有咱們常常會遇到的坑:for循環結合定時器的使用,以下代碼:程序員
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
若是換成let,則是:web
for (var i=0; i<5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
} // 5 5 5 5 5
以上兩段代碼出現不一樣結果的緣由就是var聲明的是全局變量,就算setTimeout設置的是0ms,可是也是等for循環內的全局變量i執行到5時,纔會執行,所以每次打印的結果都是5。而let聲明的變量只在其所在代碼塊內有效,也就是說每次for循環都保存了一次i的值,所以最後依次打印出0、一、二、三、4。ajax
一樣的:《JavaScript中閉包結合for循環的使用》也是這個緣由。編程
二、不存在變量提高:json
經過var聲明的變量,會出現變量提高的狀況,這是由於程序是從上到下執行,執行以前先聲明,代碼以下:
可是let聲明的變量不存在變量提高:
想要使用let聲明的變量,在使用以前必須先聲明,不然會報錯。
相關閱讀:《JavaScript閉包+做用域+變量提高》。
三、暫時性死區:
只要塊級做用域內存在let命令,它所聲明的變量就「綁定」(binding)這個區域,再也不受外部的影響,以下代碼:
上面代碼中,存在全局變量tmp,可是塊級做用域內let又聲明瞭一個局部變量tmp,致使後者綁定這個塊級做用域,因此在let聲明變量前,對tmp賦值會報錯。
ES6明確規定,若是區塊中存在let和const命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。
總之,在代碼塊內,使用let命令聲明變量以前,該變量都是不可用的。這在語法上,稱爲「暫時性死區」(temporal dead zone,簡稱 TDZ)。
四、不容許重複聲明同一個變量:
let不容許在相同做用域內,重複聲明同一個變量。以下代碼:
所以,不能在函數內部從新聲明參數。
以前有個小習慣,發生一個事件的時候,有時候要用到事件對象中的屬性,就習慣了在事件函數內重複聲明參數,以下代碼:
1 |
var btn = document.getElementById( 'btn' ); |
2 |
btn.onclick = function (ev) { |
3 |
var ev = ev || window.event; |
4 |
ev.cancelBubble = true ; |
|
2、const聲明常量
const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
1 |
const Work = 'WEB前端開發' ; |
|
上面代碼代表改變常量的值會報錯。
const聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須當即初始化,不能留到之後賦值。
上面代碼表示,對於const來講,只聲明不賦值,就會報錯。
const命令聲明的常量也是不提高,一樣存在暫時性死區,只能在聲明的位置後面使用,以下代碼:
const聲明的常量,也與let同樣不可重複聲明。
const實際上保證的,並非變量的值不得改動,而是變量指向的那個內存地址不得改動。對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址(棧內存),所以等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址(堆內存),保存的只是一個指針,const只能保證這個指針是固定的,至於它指向的數據結構是否是可變的,就徹底不能控制了。所以,將一個對象聲明爲常量必須很是當心。
改變const常量對象的屬性則不會報錯,代碼以下:
3、塊級做用域
一、爲何須要塊級做用域呢?
ES5 只有全局做用域和函數做用域,沒有塊級做用域,這帶來不少不合理的場景。
第一種場景,內層變量可能會覆蓋外層變量,代碼以下:
06 |
var tmp = 'hello world' ; |
|
以上代碼,在函數f內外都聲明瞭tmp變量,所以在函數內部會優先讀取局部變量tmp,可是在if內判斷爲false,不會繼續執行,再加上變量提高的緣由,最後打印結果是undefined。
第二種場景,用來計數的循環變量泄露爲全局變量,代碼以下:
3 |
for ( var i = 0; i < s.length; i++) { |
|
上面代碼中,變量i只用來控制循環,可是循環結束後,它並無消失,泄露成了全局變量。
let實際上爲 JavaScript 新增了塊級做用域。
上面的函數有兩個代碼塊,都聲明瞭變量n,運行後輸出5。這表示外層代碼塊不受內層代碼塊的影響。若是兩次都使用var定義變量n,最後輸出的值纔是10。
ES6 容許塊級做用域的任意嵌套,以下代碼:
1 |
{{{{{let insane = 'Hello World' }}}}}; |
|
二、塊級做用域與函數聲明:
ES5 規定,函數只能在頂層做用域和函數做用域之中聲明,不能在塊級做用域聲明,以下代碼:
上面兩種函數聲明,根據 ES5 的規定都是非法的。
可是,瀏覽器沒有遵照這個規定,爲了兼容之前的舊代碼,仍是支持在塊級做用域之中聲明函數,所以上面兩種狀況實際都能運行,不會報錯。
ES6 引入了塊級做用域,明確容許在塊級做用域之中聲明函數。ES6 規定,塊級做用域之中,函數聲明語句的行爲相似於let,在塊級做用域以外不可引用。
ES6 的塊級做用域容許聲明函數的規則,只在使用大括號的狀況下成立,若是沒有使用大括號,就會報錯,以下代碼:
4、頂層對象的屬性
在全局環境下聲明一個變量,這個變量實際是做爲window全局對象的屬性,代碼以下:
2 |
console.log(window.name); |
|
頂層對象的屬性與全局變量掛鉤,被認爲是JavaScript語言最大的設計敗筆之一。這樣的設計帶來了幾個很大的問題,首先是無法在編譯時就報出變量未聲明的錯誤,只有運行時才能知道(由於全局變量多是頂層對象的屬性創造的,而屬性的創造是動態的);其次,程序員很容易不知不覺地就建立了全局變量(好比打字出錯);最後,頂層對象的屬性是處處能夠讀寫的,這很是不利於模塊化編程。另外一方面,window對象有實體含義,指的是瀏覽器的窗口對象,頂層對象是一個有實體含義的對象,也是不合適的。
ES6爲了改變這一點,一方面規定,爲了保持兼容性,var命令和function命令聲明的全局變量,依舊是頂層對象的屬性;另外一方面規定,let命令、const命令、class命令聲明的全局變量,不屬於頂層對象的屬性。也就是說,從ES6開始,全局變量將逐步與頂層對象的屬性脫鉤,以下代碼:
2 |
console.log(window.name); |
|
5、變量的解構賦值
一、ES6 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。
之前,爲變量賦值,只能直接指定值,代碼以下:
ES6 容許寫成下面這樣:
1 |
let [a, b, c] = [1, 2, 3]; |
|
本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。下面是一些使用嵌套數組進行解構的例子。
1 |
let [a, [b], c] = [1, [2], 3]; |
|
若是解構不成功,變量的值就等於undefined,以下代碼:
以上兩種狀況都屬於解構不成功,foo的值都會等於undefined。
二、解構賦值容許指定默認值:
1 |
let [a=1, b=2] = [3, 4]; |
|
三、對象的解構賦值:
解構不只能夠用於數組,還能夠用於對象。
1 |
let { foo, bar } = { foo: "aaa" , bar: "bbb" }; |
|
對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。
1 |
let { bar, foo } = { foo: "aaa" , bar: "bbb" }; |
5 |
let { baz } = { foo: "aaa" , bar: "bbb" }; |
|
上面代碼的第一個例子,等號左邊的兩個變量的次序,與等號右邊兩個同名屬性的次序不一致,可是對取值徹底沒有影響。第二個例子的變量沒有對應的同名屬性,致使取不到值,最後等於undefined。
若是變量名與屬性名不一致,必須寫成下面這樣:
1 |
let {foo : baz} = {foo : 'aaa' , bar : 'bbb' }; |
|
這實際上說明,對象的解構賦值是下面形式的簡寫:
1 |
let { foo: foo, bar: bar } = { foo: "aaa" , bar: "bbb" }; |
|
四、字符串的解構賦值:
字符串也能夠解構賦值。這是由於此時,字符串被轉換成了一個相似數組的對象,代碼以下:
1 |
const [a, b, c, d, e] = 'hello' ; |
|
相似數組的對象都有一個length屬性,所以還能夠對這個屬性解構賦值:
1 |
let {length : len} = 'hello' ; |
|
五、函數參數的結垢賦值:
函數的參數也可使用解構賦值:
上面代碼中,函數add的參數表面上是一個數組,但在傳入參數的那一刻,數組參數就被解構成變量x和y。對於函數內部的代碼來講,它們能感覺到的參數就是x和y。
六、變量的解構賦值有不少用途,例如:
(1)交換變量的值:
(2)從函數返回多個值:
函數只能返回一個值,若是要返回多個值,只能將它們放在數組或對象裏返回。有了解構賦值,取出這些值就很是方便,代碼以下:
06 |
let [a, b, c] = example(); |
16 |
let { foo, bar } = example(); |
|
(3)函數參數的定義:
解構賦值能夠方便地將一組無序參數與變量名對應起來:
1 |
function fn1 ([a, b, c]) { |
6 |
function fn2 ({a, b}) { |
|
(4)提取JSON數據:
解構賦值對提取JSON對象中的數據,尤爲有用,以下代碼:
04 |
work: [ 'web前端開發' , 'SEO搜索引擎優化' ] |
07 |
let {name, sex, work} = person; |
|
上面代碼能夠快速提取 JSON 數據的值。
(5)輸入模塊的指定方法:
加載模塊時,每每須要指定輸入哪些方法。解構賦值使得輸入語句很是清晰,代碼以下:
1 |
import {mapActions, mapGetters} from Vuex; |
|
6、關於數組的擴展
一、將dom集合(或者是類數組)轉爲數組:
1 |
var div = document.getElementsByTagName( 'div' ); |
2 |
var divArry = Array.from(div); |
|
二、接收一串參數,轉爲數組:
1 |
var arry = Array.of(1, 2, 3, 4); |
|
7、對象Object的擴展
一、合併對象,將obj二、obj3等對象的屬性合併到obj1裏邊,相似於jQuery的$.extend({opt1, opt2, opt3})方法:
1 |
Object.assign(obj1, obj2, obj3); |
|
二、獲取對象的prototype:
1 |
Object.getPrototypeOf(obj) |
|
8、函數的擴展
一、函數能夠設置默認參數值:
1 |
function fn(a=1, b=2){ return a + b;} |
|
二、函數剩餘參數組成的數組:
1 |
function fn (a, b, ...c) { |
4 |
console.log(fn(1, 2, 3, 4, 5, 6)); |
|
上面的代碼,...c表明a和b參數後面全部的參數,是一個數組。
三、箭頭函數:
箭頭函數若是隻有一行,能夠省略大括號:
1 |
let fn = (a=1, b=2) => a + b; |
|
若是隻有一個參數,而且沒有默認值的時候,能夠省略小括號:
須要注意的是:
(1)使用箭頭函數定義的函數(構造函數),不能被new;
(2)箭頭函數不存在arguments;
(3)this永遠指向定義時所在的對象;
04 |
setTimeout( function () { |
|
9、Set:ES6新增的數據結構,相似於數組
SET內元素的值都是惟一的,若是加入了重複的值,它會自動去重。
1 |
let set = new Set([1, 1, '1' , 2, 3, '2' ]); |
|
10、Map:ES6新增的數據結構,相似鍵值對的對象,能夠用object看成key,也能夠用任意類型的數據看成key
01 |
let map = new Map([ [ 'name' , 'zym' ], [ 'age' , 24], [ 'sex' , 'man' ] ]); |
03 |
console.log(map.size); |
04 |
map.set( 'work' , 'WEB前端開發' ); |
07 |
map.set(obj, 'this is a obj' ); |
09 |
console.log(map.has( 'height' )); |
|
11、promise 是一個對象,用來傳遞異步操做的消息。它表明了某個將來纔會知道結果的事件(一般是一個異步操做),而且這個事件提供統一的 API,可供進一步處理
01 |
let p = new Promise((resolve, reject) => { |
11 |
result === 1 ? resolve(result) : reject( 'error' ); |
|
promise.all() 只有當全部Promise對象都爲成功時纔會執行then裏面的方法:
01 |
let p1 = new Promise((resolve, reject) => { |
08 |
let p2 = new Promise((resolve, reject) => { |
15 |
Promise.all([p1, p2]).then((res) => { |
16 |
let a = res[0] > res[1] ? 1 : 0; |
|
12、class類
constructor方法是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。
定義一個空的類Point,JavaScript 引擎會自動爲它添加一個空的constructor方法。
constructor方法默認返回實例對象(即this),徹底能夠指定返回另一個對象。
3 |
return Object.create( null ); |
7 |
console.log( new Foo() instanceof Foo) |
|
例如如下一個完整的類:
03 |
constructor (name, age, sex) { |
17 |
return '(' + this .name + ')' ; |
36 |
let person = new Person( 'zym' , 24, 'man' ); |
37 |
console.log(person.constructor === Person.prototype.constructor); |
39 |
console.log(Object.getOwnPropertyNames(person)); |
40 |
console.log(Object.keys(person)); |
41 |
console.log(Object.getPrototypeOf(person)); |
42 |
console.log(Person.name); |
45 |
console.log(person.work); |
47 |
console.log(Person.run()); |
|
ES6中的class類只是對ES5中的構造函數作了一個封裝,至關於外邊包了一個殼子,看起來和其餘編程語言的類同樣。
類繼承:
03 |
this .name = 'zhangsan' ; |
21 |
console.log(b.showName()); |
|
ES5的繼承,實質是先創造子類的實例對象this,而後再將父類的方法添加到this上面(Parent.apply(this))。
ES6的繼承機制徹底不一樣,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改this。若是子類沒有定義constructor方法,這個方法會被默認添加。
(1)super做爲函數時,指向父類的構造函數。super()只能用在子類的構造函數之中,用在其餘地方就會報錯:super雖然表明了父類的構造函數,可是返回的是子類的實例,即super內部的this指的是子類。所以super()在這裏至關於A.prototype.constructor.call(this)。
(2)super做爲對象時,指向父類的原型對象。
若是子類沒有定義constructor方法, 這個方法會被默認添加, 也就是說, 無論有沒有顯式定義, 任何一個子類都有constructor方法。
在子類的構造函數中, 只有調用super以後, 纔可使用this關鍵字, 不然會報錯。 這是由於子類實例的構建, 是基於對父類實例加工, 只有super方法才能返回父類實例。
十3、Module模塊擴展
歷史上,JavaScript 一直沒有模塊(module)體系,沒法將一個大程序拆分紅互相依賴的小文件,再用簡單的方法拼裝起來。其餘語言都有這項功能,好比 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,可是 JavaScript 任何這方面的支持都沒有,這對開發大型的、複雜的項目造成了巨大障礙。
在 ES6 以前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用於服務器,後者用於瀏覽器。ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
ES6 模塊的設計思想,是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。好比,CommonJS 模塊就是對象,輸入時必須查找對象屬性。
2 |
let { stat, exists, readFile } = require( 'fs' ); |
5 |
let _fs = require( 'fs' ); |
7 |
let exists = _fs.exists; |
8 |
let readfile = _fs.readfile; |
|
上面代碼的實質是總體加載fs模塊(即加載fs的全部方法),生成一個對象(_fs),而後再從這個對象上面讀取3個方法。這種加載稱爲「運行時加載」,由於只有運行時才能獲得這個對象,致使徹底沒辦法在編譯時作「靜態優化」。
ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入。
2 |
import { stat, exists, readFile } from 'fs' ; |
|
上面代碼的實質是從fs模塊加載3個方法,其餘方法不加載。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象