因爲js的歷史緣由。javascript裏的es6規範以前,只要函數做用域。當寫一些大型的項目代碼中,很容易產生全局變量,例如:javascript
<body> <button>ok</button> <button>ok</button> <button>ok</button> <button>ok</button> <script> var btn=document.querySelectorAll("button"); for(var i=0;i<btn.length;i++){ btn[i].onclick=function(){ alert(i) } } </script> </body>
點擊每一個按鈕,都結果都是4;java
<body> <button>ok</button> <button>ok</button> <button>ok</button> <button>ok</button> <script> var btn=document.querySelectorAll("button"); for(let i=0;i<btn.length;i++){ btn[i].onclick=function(){ alert(i) } } </script> </body>
0,1,2,3
當使用let定義變量的時候。點擊每一個按鈕,都結果顯示的都是對應的i值。git
如今咱們可使用let 定義塊級變量。 只在代碼塊{}內生效;
如:es6
<script> { var a=1 } alert(a) </script>
彈1數組
<script> { let a=1 } alert(a) </script>
報錯 app
使用塊級應該注意:
1.塊級做用域函數
for(var i = 0; i < 10; i++){} console.log(i); //10 for(let j = 0; j < 10; j++){} console.log(j); //"ReferenceError: j is not defined
2.不存在變量提高
會報錯,ReferenceErrorthis
console.log(a); // 輸出undefined console.log(b); // 報錯ReferenceError console.log(c); // 報錯ReferenceError var a = 2; let b = 2;
3.暫時性死區(TDZ)
只要進入當前塊級做用域,所使用的變量已經存在了,但在聲明以前都屬於死區,不可進行操做。es5
typeof x; // ReferenceError typeof y // undefined let x;
4.不容許重複聲明spa
let x = 1; let x; // "SyntaxError: Identifier 'x' has already been declared var y = 2; var y = 3; // y = 3
5.聲明的全局變量再也不是window的屬性
"use strict"; var a = 1; console.log(window.a) // 1 let b = 1; console.log(window.b) // undefined
const聲明一個只讀的常量。一旦聲明,常量的值就不能改變
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
上面代碼代表改變常量的值會報錯。
const聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須當即初始化,不能留到之後賦值。
const foo; // SyntaxError: Missing initializer in const declaration 使用const定義常量應該注意:
1,const的做用域與let命令相同:只在聲明所在的塊級做用域內有效。
2,const命令聲明的常量也是不提高,一樣存在暫時性死區,只能在聲明的位置後面使用。
3,const聲明的常量,也與let同樣不可重複聲明。
const本質:
const實際上保證的並非變量值不得改動,而是變量指向的那個內存地址不得改變。對於簡單類型的數據(數值,字符串,布爾值),值就是指向的那個內存地址,所以等同於常量。可是對於複合類型的數據(注意是對象和數組),變量指向的是內存地址,保存的只是一個指針,const只能保證這個指針是固定的,至於他指向的數據機構是否是可變的,就不能控制了,所以,將一個對象聲明爲常量是很是當心的。
const foo = {}; // 爲 foo 添加一個屬性,能夠成功 foo.prop = 123; foo.prop // 123 // 將 foo 指向另外一個對象,就會報錯 foo = {}; // TypeError: "foo" is read-only
上面代碼中,常量foo儲存的是一個地址,這個地址指向一個對象。不可變的只是這個地址,即不能把foo指向另外一個地址,但對象自己是可變的,因此依然能夠爲其添加新屬性。
下面是另外一個例子。
const a = []; a.push('Hello'); // 可執行 a.length = 0; // 可執行 a = ['Dave']; // 報錯 上面代碼中,常量a是一個數組,這個數組自己是可寫的,可是若是將另外一個數組賦值給a,就會報錯。
ES6 聲明變量的六種方法 ES5 只有兩種聲明變量的方法:var命令和function命令。ES6 除了添加let和const命令,後面章節還會提到,另外兩種聲明變量的方法:import命令和class命令。因此,ES6 一共有 6 種聲明變量的方法。
rest 參數
ES6 引入 rest 參數(形式爲...變量名),用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。
<script>
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } console.log(add(2, 5, 3)) // 10
</script>
注意在es6中rest參數只能夠是形參的最後一位參數。否則會報錯
<script>
function add(...values,a) { let sum = 0; for (var val of values) { sum += val; } return sum; } console.log(add(2, 5, 3))
</script>
以前咱們定義一個函數是這樣的:
let add=function () { alert(123) } add()
如今咱們能夠改爲箭頭函數
let ale=()=>{alert(234)}
ale()
改爲箭頭函數無非就是把代碼簡寫了,應該注意一下三點:
1,把普通函數的function換成=>.
2,若是隻有一個參數,那麼()可省略不寫。
2,若是隻有一個return 那麼{}可省略不寫。
例如:
<script>
// 普通函數:
let add=function (a) { return a*2 } console.log(add('hellow'))
// 改爲es6箭頭函數
let add= a => a*2 console.log(add('3'))
</script>
1,數組的解構賦值
基本用法
ES6 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構。
之前,爲變量賦值,只能直接指定值。
let a=1 let b=2 let c=3
ES6 容許寫成下面這樣。
let [a, b, c] = [1, 2, 3];
上面代碼表示,能夠從數組中提取值,按照對應位置,對變量賦值。
本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。下面是一些使用嵌套數組進行解構的例子
let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 let [ , , third] = ["foo", "bar", "baz"]; third // "baz" let [x, , y] = [1, 2, 3]; x // 1 y // 3 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4] let [x, y, ...z] = ['a']; x // "a" y // undefined z // []
若是解構不成功,變量的值就等於undefined。如下兩種狀況都屬於解構不成功,foo的值都會等於undefined。
let [foo] = []; let [bar, foo] = [1];
es6中數組有經常使用的四個方法依次是:
map ----映射 一個對一個
reduce -----彙總 一堆出來一個
filter -----過濾器
forEach -----循環(迭代)
1.map用法
<script>
let arr=[45 ,53,96,42,86]
let arr2=[]
arr2=arr.map(function(item){
if(item>=60){ return "及格" }else{ return "不及格" }
})
console.log(arr2)
</script>
也可改爲箭頭函數
<script> let arr=[84 ,53,96,42,86] let arr2=[] arr2=arr.map(item=>item>=60?"及格":"不及格") console.log(arr2) </script>
循環數組,每次都乘2倍,並返回新數組
<script> let arr=[84 ,53,96,42,86] let arr2=[] arr2=arr.map(item=>item*2) console.log(arr2) </script>
2.reduce
let arr=[2,14,31,1,4,3] arr.reduce(function(a,b,c){ alert(a+","+b+","+c) })
先看彈什麼結果,依次是
先看結果。明顯 c參數是下標index,可是有不是0開始。
b是當前的數。 其實a就是最終結果,把上一次算好的數當成了此次的中間結果。 let arr=[2,14,31,1,4,3] let result=arr.reduce(function(tmp,item,index){ return tmp+item }) alert(result)
這就是求和的方式
reduce 流程圖
求平均數
let arr=[2,14,31,1,4,3] let result=arr.reduce(function(tmp,item,index){ if(index !=arr.length-1){ //不是最後一項 return tmp+item }else{ //最後一項 return (tmp+item)/arr.length } }) alert(result)
3.filter 過濾器 留下符合條件的一部分;
經過filter裏面的function裏返回的true和false判斷留或者不留。
let arr=[2,14,31,1,4,3] let arr2=arr.filter(()=>{ return true }) alert(arr2) //[2,14,31,1,4,3]
由於返回的true因此都留下了
let arr=[2,14,31,1,4,3] let arr2=arr.filter(()=>{ return false }) alert(arr2) // [ ]
由於返回的false因此都不留下
下面的取能夠被3整除的數
let arr=[2,14,33,1,4,3,66] let arr2=arr.filter((item)=>{ if(item%3==0){ return true }else{ return false } }) alert(arr2) //33,3,66
因爲item%3==0自己就是布爾值,因此直接能夠return這個,即就能夠返回符合條件的數。
因此能夠寫成,
let arr=[2,14,33,1,4,3,66] let arr2=arr.filter((item)=>{ return item%3==0 }) alert(arr2) //33,3,66
還能夠簡寫成箭頭函數
let arr=[2,14,33,1,4,3,66] let arr2=arr.filter(item=>item%3==0) alert(arr2) //33,3,66
4.forEach 就是普通的循環,沒法就是寫法簡單一些,
let arr=[2,14,33,1,4,3,66] arr.forEach(item=>{ alert(item) })
它第二個參數是可選的,是index
let arr=[2,14,33] arr.forEach((item,index)=>{ alert(index+":"+item) }) //0:2 //1:14 //2:33
(一):
1.includes():返回布爾值,表示是否找到了參數字符串。
2.startsWith():返回布爾值,表示參數字符串是否在原字符串的頭部。
3.endsWith():返回布爾值,表示參數字符串是否在原字符串的尾部。
includes()用法
let s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
startsWith()用法
let str="git.baidu.com" switch(true) { case str.startsWith('www.'): console.log('www網址') break; case str.startsWith('git.'): console.log('git網址') break; default: console.log('其餘網址') }
endsWith()用法
let str="1.jpg" switch(true) { case str.endsWith('txt'): console.log('txt文件') break; case str.endsWith('jpg'): console.log('jpg文件') break; default: console.log('其餘網址') }
(二):模板字符串 (返單引號`)
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' );
大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。
let x = 1; let y = 2; `${x} + ${y} = ${x + y}` // "1 + 2 = 3" `${x} + ${y * 2} = ${x + y * 2}` // "1 + 4 = 5" let obj = {x: 1, y: 2}; `${obj.x + obj.y}` // "3"
模板字符串之中還能調用函數。
function fn() { return "Hello World"; } `foo ${fn()} bar` // foo Hello World bar
JavaScript 語言中,生成實例對象的傳統方法是經過構造函數。下面是一個例子。
function Point(x, y) { this.x = x; this.y = y; console.log(this,12) } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2); console.log(typeof p.toString())
其很差的地方就是,構造函數和類混爲一談了,別的語言(java)中構造函數就是構造函數,類就是類。
並且代碼寫了兩塊,方法經過prototype添加。還有就是這個是es6以前的方式生成對象。es6以前的js裏中是沒有類的概念的,這些方式都是東拼西湊的。
寫到這裏,能夠暫時回顧一下es6以前的創造對象的時候 new 關鍵字都作了什麼。
要建立 Person 的新實例,必須使用 new 操做符。以這種方式調用構造函數實際上會經歷如下 4
個步驟:
(1) 建立一個新對象;
(2) 將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象) ;
(3) 執行構造函數中的代碼(爲這個新對象添加屬性) ;
(4) 返回新對象。
new 操做符
在有上面的基礎概念的介紹以後,在加上new操做符,咱們就能完成傳統面向對象的class + new的方式建立對象,在JavaScript中,咱們將這類方式成爲Pseudoclassical。
基於上面的例子,咱們執行以下代碼
var obj = new Base();
new操做符具體幹了什麼呢?其實很簡單,就幹了三件事情。
var obj = {}; obj.__proto__ = Base.prototype; Base.call(obj);
第一行,咱們建立了一個空對象obj
第二行,咱們將這個空對象的__proto__成員指向了Base函數對象prototype成員對象
第三行,咱們將Base函數對象的this指針替換成obj,而後再調用Base函數,因而咱們就給obj對象賦值了一個id成員變量,這個成員變量的值是」base」,關於call函數的用法。
若是咱們給Base.prototype的對象添加一些函數會有什麼效果呢?
例如代碼以下:
Base.prototype.toString = function() { return this.id; }
那麼當咱們使用new建立一個新對象的時候,根據__proto__的特性,toString這個方法也能夠作新對象的方法被訪問到。因而咱們看到了構造子中,咱們來設置‘類’的成員變量(例如:例子中的id),構造子對象prototype中咱們來設置‘類’的公共方法。因而經過函數對象和Javascript特有的__proto__與prototype成員及new操做符,模擬出類和類實例化的效果。
如今的es6中創做對象的方式就及其簡單。
class Point { constructor (x,y){ this.x=x; this.y=y } tostring (){ return '(' + this.x + ', ' + this.y + ')'; } } let a=new Point(2,3) alert(typeof a.tostring()) //string
上面代碼定義了一個「類」,能夠看到裏面有一個constructor方法,這就是構造方法,並且this關鍵字則表明實例對象,也就是說,es5的構造函數Point,對應es6的point類的構造方法。
point類除了構造方法,還定義了一個tostring方法。注意,定義’類‘的方法時候,前面不須要加上function這個關鍵字,直接把函數定義進去級能夠了。類外,方法之間不須要逗號分隔,加上會報錯。
ES6 的類,徹底能夠看做構造函數的另外一種寫法。
class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true
上面代碼代表,類的數據類型就是函數,類自己就指向構造函數。
使用的時候,也是直接對類使用new命令,跟構造函數的用法徹底一致。
class Bar { doStuff() { console.log('stuff'); } } var b = new Bar(); b.doStuff() // "stuff"
構造函數的prototype屬性,在 ES6 的「類」上面繼續存在。事實上,類的全部方法都定義在類的prototype屬性上面。
class Point { constructor() { // ... } toString() { // ... } toValue() { // ... } } // 等同於 Point.prototype = { constructor() {}, toString() {}, toValue() {}, };
在類的實例上面調用方法,其實就是調用原型上的方法。
class B {} let b = new B(); b.constructor === B.prototype.constructor // true
上面代碼中,b是B類的實例,它的constructor方法就是B類原型的constructor方法。
因爲類的方法都定義在prototype對象上面,因此類的新方法能夠添加在prototype對象上面。Object.assign方法能夠很方便地一次向類添加多個方法。
class Point { constructor(){ // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
prototype對象的constructor屬性,直接指向「類」的自己,這與 ES5 的行爲是一致的。
Point.prototype.constructor === Point // true
另外,類的內部全部定義的方法,都是不可枚舉的(non-enumerable)。
七,面相對象
//es5中經過構造函數建立對象 function User (name,password){ this.name=name; this.password=password; console.log(this) } User.prototype.showname = function(){ alert(this.name) } User.prototype.showpass = function(){ alert(this.password) } var user=new User('lee','123') user.showname() user.showpass() // 以前的面相對象缺點: // 1,類和構造函數混爲一談了。 // 2.方法添加到類(構造函數)外面。
es6中建立對象
// es6 class User { //User是類 constructor(name,password){ //constructor是構造函數 this.name=name; this.password=password } //方法寫在類裏面,並且必須不能有function,多個方法之間不能有‘,’ showname(){ alert(this.name) }, showpass(){ alert(this.password) } } var user = new User ('wang','1234') user.showname() user.showpass()
繼承:
es5中的繼承:
// 繼承es5 function User (name,password){ this.name=name; this.password=password } User.prototype.showname=function(){ alert(this.name) } User.prototype.showpass=function(){ alert(this.password) } function Vipuser(name,password,level){ User.call(this,name,password) this.level=level } Vipuser.prototype=new User() //先new一下 Vipuser.prototype.constructor=Vipuser;//在補一下constructor,由於constructor亂了 Vipuser.prototype.showlevel=function(){ alert(this.level) } var vipuser=new Vipuser('LV','2323',8) vipuser.showname() vipuser.showpass() vipuser.showlevel()
es6中的繼承
// 繼承es6 class User{ constructor(name,password){ this.name=name; this.password=password; } showname(){ alert(this.name) } showpass(){ alert(this.password) } } class Vipuser extends User { constructor(name,password,level){ super(name,password)//super,超類,父類。執行父級的構造函數。繼承父級的屬性,至關於es5繼承中的User.call(this,name,password) this.level=level } showlevel(){ alert(this.level) } } var vipuser=new Vipuser('dawang','123',9) vipuser.showname() vipuser.showpass() vipuser.showlevel()