ES5沒有塊級做用域,只有全局做用域和函數做用域,因爲這一點,變量的做用域甚廣,因此一進入函數就要立刻將它建立出來。這就形成了所謂的變量提高。javascript
ES5的「變量提高」這一特性每每一不當心就會形成一下錯誤:html
內層變量覆蓋外層變量java
var tmp = new Date(); function f() { console.log(tmp); if (false) { //執行則undefined var tmp = "hello world"; } }
變量泄露,成爲全局變量閉包
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5
往常咱們每每是使用閉包
來解決這一問題的(好比自執行函數)。如今,基於這一問題,ES6增長了塊級做用域
,因此再也不須要自執行函數了。app
ES6是是向後兼容的,而保持向後兼容性意味着永不改變JS代碼在Web平臺上的行爲,因此var
建立的變量其做用域依舊將會是全局做用域和函數做用域。這樣以來,即便擁有了塊級做用域,也沒法解決ES5的「變量提高」問題。因此,這裏ES6新增了倆個新關鍵詞:let
和const
。模塊化
let函數
「let是更完美的var」,它有着更好的做用域規則。this
const
const聲明一個只讀的常量。一旦聲明,常量的值就不能改變,但const聲明的對象能夠有屬性變化(對象凍結Object.freeze)prototype
const a = []; a.push('Hello'); // 可執行 a = ['Dave']; // 報錯
也可使用Object.freeze將對象凍結設計
const foo = Object.freeze({}); // 常規模式時,下面一行不起做用; // 嚴格模式時,該行會報錯 foo.prop = 123;//
使用let和const:
變量只在聲明所在的塊級做用域內有效
變量聲明後方可以使用(暫時性死區)
不能重複定義變量
聲明的全局變量,不屬於全局對象的屬性
var a = 1; window.a // 1 let b = 1; window.b // undefined
咱們知道,ES5函數中的this指向的是運行時所在的做用域。好比
function foo() { setTimeout(function(){ console.log('id:', this.id); }, 100); } var id = 21; foo.call({id:42});//id: 21
在這裏,我聲明瞭一個函數foo,其內部爲一個延遲函數setTimeout,每隔100ms打印一個this.id。咱們經過foo.call({id:42})
來調用它,而且爲這個函數設定做用域。它真正執行要等到100毫秒後,因爲this指向的是運行時所在的做用域,因此這裏的this就指向了全局對象window,而不是函數foo。這裏:
使用call來改變foo的執行上下文,使函數的執行上下文再也不是window,從而來辨別setTimeout中的this指向
setTimeout方法掛在window對象下,因此其this指向執行時所在的做用域——window對象。
超時調用的代碼都是在全局做用域中執行的,所以函數中this 的值在非嚴格模式下指向window 對象,在嚴格模式下是undefined --《javascript高級程序設計》
爲了解決這一問題,咱們往常的作法每每是將this賦值給其餘變量:
function foo() { var that = this; setTimeout(function(){ console.log('id:', that.id); }, 100); } var id = 21; foo.call({id:42});//id: 42
而如今ES6推出了箭頭函數解決了這一問題。
標識符=> 表達式
var sum = (num1, num2) => { return num1 + num2; } // 等同於 var sum = function(num1, num2) { return num1 + num2; };
若是函數只有一個參數,則能夠省略圓括號
若是函數只有一條返回語句,則能夠省略大括號
和return
若是函數直接返回一個對象,必須在對象外面加上括號。(由於一個空對象{}和一個空的塊 {} 看起來徹底同樣。因此須要用小括號包裹對象字面量。)
針對this關鍵字的問題,ES6規定箭頭函數中的this綁定定義時所在的做用域,而不是指向運行時所在的做用域。這一以來,this指向固定化了,從而有利於封裝回調函數。
function foo() {var that = this; setTimeout(()=>{ console.log('id:', that.id); }, 100); } var id = 21; foo.call({id:42});//id: 42
注意:箭頭函數this指向的固定化,並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this。而箭頭函數根本沒有本身的this,其內部的this也就是外層代碼塊的this。這就致使了其:
不能用做構造函數
不能用call()、apply()、bind()這些方法去改變this的指向
傳統ECMAScript沒類的概念,它描述了原型鏈的概念,並將原型鏈做爲實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。而實現這一行爲的傳統方法即是經過構造函數:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2);
在這裏,構造函數Point會有一個原型對象(prototype),這個原型對象包含一個指向Point的指針(constructor),而實例p包含一個指向原型對象的內部指針(prop)。因此整個的繼承是經過原型鏈來實現的。詳情可見個人這篇文章:javascript中的prototype和constructor
ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念,做爲對象的模板。經過class關鍵字,能夠定義類。可是類只是基於原型的面向對象模式的語法糖。對於class
的引入,褒貶不一,不少人認爲它反而是一大缺陷,但對我來講,這是一個好的語法糖,由於往常的原型鏈繼承的方式每每能把我繞那麼一下子。
//定義類 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var p = new Point(1, 2);
類裏面有一個constructor方法,它是類的默認方法,經過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,若是沒有顯式定義,一個空的constructor方法會被默認添加。
constructor方法中的this關鍵字表明實例對象,
定義「類」的方法(如上例的toString)的時候,前面不須要加上function這個關鍵字,直接把函數定義放進去了就能夠了。另外,方法之間不須要逗號分隔,加了會報錯。
使用的時候,也是直接對類使用new命令,跟構造函數的用法徹底一致
類的全部方法都定義在類的prototype屬性上面
Class之間能夠經過extends關鍵字實現繼承,這比ES5的經過修改原型鏈實現繼承,要清晰和方便不少。
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 調用父類的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 調用父類的toString() } }
歷史上,JavaScript一直沒有模塊(module)體系,沒法將一個大程序拆分紅互相依賴的小文件,再用簡單的方法拼裝起來,這對開發大型的、複雜的項目造成了巨大障礙。爲了適應大型模塊的開發,社區制定了一些模塊加載方案,好比CMD和AMD。
ES6的模塊化寫法:
import { stat, exists, readFile } from 'fs';
上面代碼的實質是從fs模塊加載3個方法,其餘方法不加載。這種加載稱爲「編譯時加載」,即ES6能夠在編譯時就完成模塊加載,效率要比CommonJS模塊的加載方式高。固然,這也致使了無法引用ES6模塊自己,由於它不是對象。
模塊功能主要由兩個命令構成:
export
用於規定模塊的對外接口,對外的接口,必須與模塊內部的變量創建一一對應關係。
// 寫法一 export var m = 1; //錯誤 export 1; // 寫法二 var m = 1; export {m}; //錯誤 export m; // 寫法三 重命名 var n = 1; export {n as m};
import
用於輸入其餘模塊提供的功能,它接受一個對象(用大括號表示),裏面指定要從其餘模塊導入的變量名(也可使用*號總體加載)
在javascript的開發中,咱們經常須要這樣來輸出模板:
function sayHello(name){ return "hello,my name is "+name+" I am "+getAge(18); } function getAge(age){ return age; } sayHello("brand") //"hello,my name is brand I am 18"
咱們須要使用+來鏈接字符串和變量(或者表達式)。例子比較簡單,因此看上去無傷大雅,可是一旦在比較複雜的狀況下,就會顯得至關繁瑣不方便,這一用法也讓咱們不厭其煩。對此,ES6引入了模板字符串
,能夠方便優雅地將 JS 的值插入到字符串中。
對於模板字符串,它:
\
對於上面的例子,模板字符串的寫法是:
function sayHello(name){ return `hello,my name is ${name} I am ${getAge(18)}`; } function getAge(age){ return age; } sayHello("brand") //"hello,my name is brandI am 18"
嚴格模式的目標之一是容許更快地調試錯誤。幫助開發者調試的最佳途徑是當肯定的問題發生時拋出相應的錯誤(throw errors when certain patterns occur),而不是悄無聲息地失敗或者表現出奇怪的行爲(非嚴格模式下常常發生)。嚴格模式下的代碼會拋出更多的錯誤信息,能幫助開發者很快注意到一些必須當即解決的問題。在 ES5 中, 嚴格模式是可選項,可是在 ES6 中,許多特性要求必須使用嚴格模式,這個習慣有助於咱們書寫更好的 JavaScript。