ES6常用知識學習札記
轉載請註明出處
原文連線 http://blog.huanghanlian.com/article/5c7aa6c7bf3acc0864870f9d
es6 是什麼
首先弄明白ECMA和js的關係。ECMA是標準,Javascript是ECMA的實現。因為js也是一種語言,但凡語言都有一套標準,而ECMA就是javascript的標準。 在2015年正式釋出了ECMAscript6.0,簡稱ES6,又稱為ECMAscript2015。
-
歷史
-
ECMAScript和Javascript
-
ECMA是標準,JS是實現
- 類似於HTML5是標準,IE10,Chrome,FF都是實現
- 換句話說,將來也能有其他XXXScript來實現ECMA
- ECMAScript簡稱 ECMA或ES
-
目前版本
- 低階瀏覽器主要支援ES 3.1
- 高階瀏覽器正在從ES 5過度ES 6
-
-
- 歷史版本
時間 | ECMA | JS | 解釋 |
---|---|---|---|
1996.11 | EC 1.0 | JS穩定 | Netscript將js提交給ECMA組織,ES正式出現 |
1998.06 | ES 2.0 | ES2正式釋出 | |
1999.12 | ES 3.0 | ES3被廣泛接受 | |
2007.10 | ES 4.0 | ES4過於激進,被廢了 | |
2008.07 | ES 3.1 | 4.0退化為嚴重縮水版的3.1<br/>因為吵得太厲害,所以ES3.1代號為Harmony(和諧) | |
2009.12 | ES 5.0 | ES5正式釋出<br/>同時公佈了JavaScript.next也就是後來的ES6.0 | |
2011.06 | ES 5.1 | ES5.1成為了ISO國際標準 | |
2013.03 | ES 6.0 | ES6.0草案定稿 | |
2013.12 | ES 6.0 | ES6.0草案發布 | |
2015.06 | ES 6.0 | ES6.0預計釋出正式版<br/>JavaScript.next開始指向ES 7.0 |
ES6相容性和新特性
es5相容性
http://kangax.github.io/compat-table/es5/
es6相容性
http://kangax.github.io/compat-table/es6/
ES6(ES2015)-- IE10+,Chrome,FireFox,移動端,NodeJS。這些環境基本上都是認得,都能相容
但是有需求相容ie怎麼辦
有兩種辦法
編譯,轉換
-
線上轉換
- 簡單來說就是寫好了ES6瞭然後引用一個js庫進來。我什麼也不用做了。它替我去做了各種各樣的事情
- 缺點,使用者每次開啟頁面都要重新轉換一遍,效能體驗不是很好。
- 提前編譯
ES6的到底有什麼樣的東西?
- 變數(對原有的變數做了修改)
- 函式(對原有的函式也做了修改)
- 陣列(對陣列做了一些改進)
- 字串(改進)
- 面向物件
- Promise(序列化的非同步請求方式)
- yield && generator(generator是專門把同步操作拆成非同步操作,generator是對Promise的一個封裝)
- 模組化
變數-let和const
回顧ES5是怎麼生明變數的,有什麼樣的缺點
var
的缺點
- 可以重複宣告
- 無法限制修改
- 沒有塊級作用域
可以重複宣告
最大的問題
var a=12; var a=5; alert(a);//彈窗5
會發現5能出來,沒有報錯,沒有警告,什麼都沒有
這在其他語言是不可出現的。
無法限制修改
在程式中,有些東西是永遠不變的。
比方說常量
PI=3.1415926
是不會發生改變
在很多語言中都有 常量
的概念。在js中沒有
至少 var
不是一個常量。
換句話說,要不要改,能不能讓別人別動這個值,不要改這個值。全憑自覺。
為什麼java是全世界最流行的一門語言
原因很簡單,因為他非常的嚴謹,他非常的死板。
相信一件事,越是容易的語言,越是簡單的語言。實際上是不嚴謹。就沒法去開發大型專案
反過來他可能讓你覺得很難受的語言java,對你限制很嚴格。但是你掌握了呀之後,開發起大型應用會非常的得心應手。
沒有塊級作用域
es5 只在函式中支援塊級作用域
{ //這就是語法塊 } if(){ 變數=xxxx } //變量出來就用不了了,這就是塊作用域 for(){ }
體現塊級作用域作用
if(true){ var a=12; } alert(a); //在塊級作用域內宣告變數。在外部依然能夠訪問
在ES6中有了兩種新的定義變數的方式
let
和 const
let
- 不能重複宣告,let是變數,可以修改,塊級作用域
- 塊級作用域
- 可修改let變數的值
const
- 不可重複宣告,const常量,不能修改,塊級作用域
- 塊級作用域
- 不可修改const變數的值
let 不能重複宣告例子
let a=12; let a=5; console.log(a); //報錯 //Uncaught SyntaxError: Identifier 'a' has already been declared //不能重複宣告
const 不能重複宣告例子
const a=12; const a=5; console.log(a); //報錯 //Uncaught SyntaxError: Identifier 'a' has already been declared //不能重複宣告
在大型專案中,重複宣告這件事,指不定你定義了什麼東西別人也定義了。還不報錯,到時候定位bug很難找。
變數和常量
變數
let a=12;//宣告變數賦值 a=5;//給變數a賦值 console.log(a);//你會明確的發現它變成了5
常量
const a=12; a=5; console.log(a);
報錯,不能對常量賦值
塊級作用域
var 塊級作用域只在函式中體現,也就是說在函式中 var
宣告的變數不會在全域性作用域中體現
function aa(){ var a=1; console.log(a) } aa(); console.log(a)
let和const只在塊級作用域,或者在語法塊之內起作用
if(true){ let a=12; } console.log(a);//Uncaught ReferenceError: a is not defined
if(true){ const a=12; } console.log(a);//Uncaught ReferenceError: a is not defined
語言推出一個新的版本,一個更好的版本,他一定是要解決一些原來有的問題,ES6也不例外。
塊級作用域有什麼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <input type="button" value="按鈕1"> <input type="button" value="按鈕2"> <input type="button" value="按鈕3"> </body> <script type="text/javascript"> window.onload=function(){ var aBtn=document.getElementsByTagName('input'); for (var i = 0; i < aBtn.length; i++) { aBtn[i].onclick=function(){ alert(i) }; } }; </script> </html>
以上程式碼執行,不管按哪個按鈕彈出都是3
由於 var
宣告變數只在函式作用域中擴散到全域性
在for或者if快級作用域中宣告的變數會在區域性或全域性生效
當 for
迴圈執行完畢, i
這個變數暴露到全域性了,等於3
所以 for
迴圈中執行的事件繫結,是將點選事件回撥函式執行。當點選按鈕時候,會出發繫結回撥函式,此時當前作用域中, i
等於3,所以無論點選哪個按鈕彈出都是3
以前我們是通過閉包解決這個問題
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <input type="button" value="按鈕1"> <input type="button" value="按鈕2"> <input type="button" value="按鈕3"> </body> <script type="text/javascript"> window.onload=function(){ var aBtn=document.getElementsByTagName('input'); for (var i = 0; i < aBtn.length; i++) { ! function(i) { aBtn[i].onclick=function(){ alert(i) }; }(i); } console.log(i) }; </script> </html>
在每一層迴圈的時候,用一個匿名函式而且是立即執行的匿名函式給他包裝起來,然後將每一次遍歷的1.2.3分別的值去傳到這個匿名函式裡,然後匿名函式接到這個引數i再放到點選事件中去引用i當我們每次點選事件輸出的值i就會取每一個閉包環境下的i。所以這樣就能達到效果。
使用 let
來實現
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <input type="button" value="按鈕1"> <input type="button" value="按鈕2"> <input type="button" value="按鈕3"> </body> <script type="text/javascript"> window.onload=function(){ var aBtn=document.getElementsByTagName('input'); for (let i = 0; i < aBtn.length; i++) { aBtn[i].onclick=function(){ alert(i) }; } console.log(i) }; </script> </html>
for
迴圈本身就是一個語法塊,自身就是一個塊
由於 var
只把函式作為作用域
所以以上需要通過立即執行函式來包一層,來實現效果。
而 let
本身是支援塊級作用域的,所以電腦按鈕執行回掉函式,列印i,是當前塊級作用域下的 i
這個 i
在非 for
塊作用域下是未定義的。
函式-箭頭函式
箭頭函式在寫法上對es5做了一些修整,程式碼看起來更顯得簡潔
- 如果只有一個引數,圓括號"()"可以省略
- 函式體如果只有一句return語句,花括號也可以省略
// 定義一個箭頭函式 let a = (arg)=>{ //這裡=>符號就相當於function關鍵字 return arg+=1 } // 也可以簡寫為 let a = arg => arg+=1
箭頭函式的作用跟以前接觸的函式沒什麼本質的區別,更多的是一種寫法上的變化。
function show() { } 同等於 let show =()=>{ }
function () { } 同等於 ()=>{ }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> </body> <script type="text/javascript"> window.onload=function(){ alert('123') }; </script> </html> 同等於 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> </body> <script type="text/javascript"> window.onload=()=>{ alert('123') }; </script> </html>
箭頭函式也對this的指向做了修整es6之前的函式的this指向呼叫函式時所在的物件,而箭頭函式的this指向函式定義時所在的物件
//普通函式 var obj = { say: function () { setTimeout(function() { console.log(this) }); } } obj.say();//Window object
// 箭頭函式 var obj = { say: function () { setTimeout(() => { console.log(this) }); }, test:123 } obj.say(); // obj
函式-引數
- 引數擴充套件/陣列展開
- 預設引數
引數擴充套件
- 收集剩餘引數
ES6 引入 rest 引數(形式為...變數名),用於獲取函式的多餘引數,這樣就不需要使用arguments物件了。rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。
function show (a,b,...args){ console.log(a);//1 console.log(b);//2 console.log(args);//[3,4,5,6] } show(1,2,3,4,5,6);
下面是一個 rest 引數代替arguments變數的例子。
// arguments變數的寫法 function sortNumbers() { return Array.prototype.slice.call(arguments).sort(); } // rest引數的寫法 const sortNumbers = (...numbers) => numbers.sort();
- 展開陣列
展開後的效果,跟直接把陣列內容寫在這一樣
let arr=[1,2,3]; console.log(1,2,3); console.log(...arr); //1,2,3同等於...arr
function show(a,b,c){ console.log(a) console.log(b) console.log(c) } let arr=[1,2,3]; show(1,2,3); show(...arr); //1,2,3同等於...arr
let arr1=[1,2,3]; let arr2=[4,5,6]; let arr=[...arr1,...arr2]; let arrS=[1,2,3,4,5,6]; //...arr1,寫法,相當於將陣列內容掏出來內容
預設引數
function show(a,b=5,c=6){ //我希望b,預設是5 不傳的時候 //我希望c,預設是6 不傳的時候 console.log(a,b,c);//1,2,6 } show(1,2);
解構賦值
允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構。比如:
var [a,b] = [1,2] // a=1b=2
- 左右兩邊結構必須一樣
- 右邊必須是個合法的資料
- 宣告和賦值必須一句話完成,不能把宣告與賦值分開
let [a, b] = [1, 2]// 左右都是陣列,可以解構賦值 let {a, b} = {a:1, b:2}// 左右都是物件,可以解構賦值 let [obj, arr] = [{a:1}, [1, 2]]// 左右都是物件,可以解構賦值 let [a, b] = {a:1, b:2}// err 左右結構不一樣,不可以解構賦值 let {a,b} = {1, 2}// err 右邊不是一個合法的資料,不能解構賦值 let [a, b]; [a, b] = [1, 2]// err 宣告與賦值分開,不能解構賦值
陣列
陣列擴充套件了4個方法:map、reduce、filter、forEach
- map 對映
通過指定函式處理陣列的每個元素,並返回處理後的陣列。
一個對一個
[12,58,99,86,45,91] [不及格,不及格,及格,及格,不及格,及格] //將陣列對映成另一個數組
[45,57,135,28] //將使用者id對映成物件 [ {name:'huang',role:1}, {name:'huang1',role:2}, {name:'huang2',role:3}, {name:'huang4',role:1} ]
map例子
let arr=[12,5,8]; //我想要將陣列內容乘與2的結果 let result=arr.map(function(item){ console.log(item); //需要將你要的內容返回出來 return item*2; }); console.log(arr);//[12, 5, 8] console.log(result);//[24, 10, 16]
簡寫1
let arr=[12,5,8]; //我想要將陣列內容乘與2的結果 let result=arr.map(item=>{ console.log(item); //需要將你要的內容返回出來 return item*2; }); console.log(arr);//[12, 5, 8] console.log(result);//[24, 10, 16]
簡寫2
let arr=[12,5,8]; //我想要將陣列內容乘與2的結果 let result=arr.map(item=>item*2); console.log(arr);//[12, 5, 8] console.log(result);//[24, 10, 16]
let arr=[12,58,99,86,45,91] let result=arr.map(item=>item>=60?'及格':'不及格'); console.log(arr);//[12, 58, 99, 86, 45, 91] console.log(result);//["不及格", "不及格", "及格", "及格", "不及格", "及格"]
- reduce 彙總
將陣列元素計算為一個值(從左到右)。
一堆出一個
算個總數
let arr=[12,58,99,86,45,91] /** * [description] * @param{[type]} (total,currentValue,index,arr [ * 初始值, 或者計算結束後的返回值。 * 當前元素 * 當前元素的索引 * 當前元素所屬的陣列物件。 * ] * @return {[type]}[返回計算結果] */ let result=arr.reduce((total,currentValue,index,arr)=>{ return total+currentValue; }); console.log(result)//391
算個平均數
let arr=[12,58,99,86,45,91] /** * [description] * @param{[type]} (total,currentValue,index,arr [ * 初始值, 或者計算結束後的返回值。 * 當前元素 * 當前元素的索引 * 當前元素所屬的陣列物件。 * ] * @return {[type]}[返回計算結果] */ let result=arr.reduce((total,currentValue,index,arr)=>{ if(index!=arr.length-1){//如果不是最後一次 return total+currentValue;//求和 }else{//最後一次 return (total+currentValue)/arr.length;//求和再除於長度個數 } }); console.log(result)//65.16666666666667
- filter 過濾器
檢測數值元素,並返回符合條件所有元素的陣列。
定義和用法
filter() 方法建立一個新的陣列,新陣列中的元素是通過檢查指定陣列中符合條件的所有元素。
注意: filter() 不會對空陣列進行檢測。
注意: filter() 不會改變原始陣列。
需求,能被3整除的留下,不能的去除
let arr=[12,58,99,86,45,91] //需求,能被3整除的留下,不能的去除 /** * [description] * @param{[type]} (currentValue,index,arr [ * 當前元素的值 * 當前元素的索引值 * 當前元素屬於的陣列物件 * ] * @return {[type]}[返回陣列,包含了符合條件的所有元素。如果沒有符合條件的元素則返回空陣列。] */ let result=arr.filter((currentValue,index,arr)=>{ if(currentValue%3==0){ return true; }else{ return false; } }); console.log(result)//[12, 99, 45]
簡寫
let arr=[12,58,99,86,45,91] //需求,能被3整除的留下,不能的去除 /** * [description] * @param{[type]} (currentValue,index,arr [ * 當前元素的值 * 當前元素的索引值 * 當前元素屬於的陣列物件 * ] * @return {[type]}[返回陣列,包含了符合條件的所有元素。如果沒有符合條件的元素則返回空陣列。] */ let result=arr.filter((currentValue,index,arr)=>{ return currentValue%3==0; }); console.log(result)//[12, 99, 45]
再次簡寫
let arr=[12,58,99,86,45,91] let result=arr.filter(currentValue=>currentValue%3==0); console.log(result)//[12, 99, 45]
- forEach 迴圈(迭代)
陣列每個元素都執行一次回撥函式。
forEach() 方法用於呼叫陣列的每個元素,並將元素傳遞給回撥函式。
注意: forEach() 對於空陣列是不會執行回撥函式的。
字串
- 多了兩個新方法
- 字串模板
多了兩個新方法
- startsWith() 表示引數字串是否在原字串的頭部,返回布林值
startsWith應用
let str='http://blog.huanghanlian.com' if(str.startsWith('http://')){ console.log('普通網址'); }else if(str.startsWith('https://')){ console.log('加密網址'); }else if(str.startsWith('git://')){ console.log('git網址'); }else if(str.startsWith('svn://')){ console.log('svn網址'); }else{ console.log('其他') }
- endsWith() 表示引數字串是否在原字串的尾部,返回布林值
let str='http://blog.huanghanlian.com/sitemap.xml' if(str.endsWith('.xml')){ console.log('網站地圖'); }else if(str.endsWith('.jpg')){ console.log('圖片'); }else if(str.endsWith('.txt')){ console.log('文字檔案'); }else{ console.log('其他') } //網站地圖
- includes() 表示是否在原字串找到了引數字串,返回布林值
字串模板
模板字串有兩個能力
- 能直接把變數塞到字串中去。
- 可以折行
平常寫字串有兩種寫法,
let str="abc"; let str2='efg';
一種是單引號,一種是雙引號。在js裡都能用。沒什麼區別
現在出來一種新的字串
let str=`abc`;
這種符號叫反單引號
簡單使用例子
let a=12; let str=`a${a}bc`; console.log(str);//a12bc
反單引號中的美元符號帶上花括號他的作用就是把變數直接塞進字串裡面去。
例子
以前字串拼接
let title='我是標題'; let content='我是內容'; let str='<div class="wrap">\ <h1>'+title+'</h1>\ <p>'+content+'</p>\ </div>'; document.body.innerHTML=str; console.log(str);
有了字串模板後的寫法
let title='我是標題'; let content='我是內容'; let str='<div class="wrap">\ <h1>'+title+'</h1>\ <p>'+content+'</p>\ </div>'; let str2=`<div class="wrap"> <h1>${title}</h1> <p>${content}</p> </div>`; document.body.innerHTML=str2; console.log(str2);
面向物件-基礎
面向物件
es5面向物件
function User(name,pass){ this.name=name; this.pass=pass; } //給這個類加原型方法 /** * [showName 獲取使用者名稱] * @return {[type]} [返回使用者名稱] */ User.prototype.showName=function(){ console.log(this.name); return this.name; }; /** * [showPass 獲取使用者密碼] * @return {[type]} [返回使用者密碼] */ User.prototype.showPass=function(){ console.log(this.pass); return this.pass; }; var ul=new User('黃繼鵬','abc'); //呼叫類方法 ul.showName();//黃繼鵬
這樣寫的缺點
- 類和建構函式不分,
- 類散開了,先宣告一個建構函式,然後對函式原型新增方法
ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作為物件的模板。通過class關鍵字,可以定義類。 先看如何定義一個class類:
class User { constructor(name) {// 構造器,相當於es5中的建構函式 this.name = name// 例項屬性 } showName(){// 定義類的方法,不能使用function關鍵字,不能使用逗號分隔 console.log(this.name) } } var foo = new User('黃繼鵬') foo.showName();//黃繼鵬
(1)constructor
- es6中class類專用的構造器,相當於之前定義的建構函式,每個類都必須有constructor,如果沒有則自動新增一個空的constructor構造器。
- 建立例項的時候自動執行constructor函式
- constructor中的this指向例項,並且預設返回this(例項)
(2)class類的prototype
其實class的基本型別就是函式(typeof User = "function"),既然是函式,那麼就會有prototype屬性。
類的所有方法都是定義在prototype上
class User { constructor() { // ... } toString() { // ... } toValue() { // ... } } User.toValue()// err User.toValue is not a function User.prototype.toValue()// 可以呼叫toValue方法 // 等同於 User.prototype = { constructor() {}, toString() {}, toValue() {}, };
(3)類的例項
- 類的例項只能通過new來建立
- 除了靜態方法,定義在類上的所有的方法都會被例項繼承
- 除非定義在類的this物件上才是例項屬性,否則都是定義在類的原型(prototype)上
//定義類 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3); point.toString() // (2, 3) point.hasOwnProperty('x') // true point.hasOwnProperty('y') // true point.hasOwnProperty('toString') // false point.proto.hasOwnProperty('toString') // true
(4)靜態方法
如果在類中定義一個方法的前面加上static關鍵字,就表示定義一個靜態方法,靜態方法不會被例項繼承,但會被子類繼承,所以不能通過例項使用靜態方法,而是通過類直接呼叫
class User { constructor(name){ this.name = name } static show(){ console.log('123') } } class VipUser extends User{} VipUser.show()// 123 User.show()// 123 var foo = new User('foo') foo.show()// foo.show is not a function
(5)靜態屬性
- class的靜態屬性指的是 Class 本身的屬性,目前只能通過Class.propName定義靜態屬性
- 靜態屬性可以被子類繼承,不會被例項繼承
class User{} User.name = 'foo' // 為class定義一個靜態屬性 class VipUser extends User{} console.log(VipUser.name)// foo var foo = new User() console.log(foo.name)// undefined
(6)私有屬性和私有方法
es6是不支援私有屬性和私有方法,但是日常需求可能會用到私有屬性和私有方法,所以目前有一些提案,不過只是提案,尚未支援。
類的繼承
ES5寫法
function User(name,pass){ this.name=name; this.pass=pass; } User.prototype.showName=function(){ console.log(this.name); }; /** * [showPass 獲取使用者密碼] * @return {[type]} [返回使用者密碼] */ User.prototype.showPass=function(){ console.log(this.pass); }; //繼承user類 function aUser(name, pass, type) { User.call(this, name, pass); this.type = type; }; aUser.prototype.showType = function() { console.log(this.type); }; var ul=new User('黃繼鵬','abc'); ul.showName()//黃繼鵬 var ull=new aUser('繼小鵬','ccc','男'); ul.showName();//繼小鵬 ull.showType();//男 //aUser繼承類User類,並且有自己的方法
class通過extends關鍵字實現繼承:
class User { constructor(name){ this.name = name } show(){...} } class VipUser extends User{ constructor(vipName){// 子類的構造器 super(vipName)// 呼叫父類的constructor。相當於User.prototype.constructor.call(this,vipName) } showVip(){...} } var v = new VipUser('foo')// 建立例項 v instanceof VipUser// v是子類VipUser的例項 v instanceof User// v還是父類User的例項
(1)super
super可以當做函式使用,也可以當做物件使用。
當做函式使用
super作為函式呼叫時,代表父類的建構函式,就是在子類的構造器中執行父類的constructor函式以獲取父類的this物件,因為子類沒有自己的this物件,所以ES6規定子類必須在constructor中執行一次super函式。super()函式只能在子類的constructor中執行,不能在其他地方執行。
雖然super代表父類的構造器,但是super()在執行時內部的this指向子類,所以super()就相當於User.prototype.constructor.call(this)。
當做物件使用
super可以作為物件呼叫父類的屬性和方法,在子類的普通方法中,指向父類的原型物件(即User.prototype);在子類的靜態方法中,指向父類。
class User { constructor(){ this.x = 'hello' } show() { return 2; } } class VipUser extends User { constructor() { super(); console.log(super.show()); // 2此時super指向User.prototype,相當於User.prototype.show() console.log(super.x)// undefined無法訪問例項屬性 } } let vip = new VipUser(); console.log(vip.x);//hello
由於super物件在普通函式中使用super指向User.prototype,所以super只能訪問父類的原型上的方法,沒法訪問父類的例項屬性和例項方法。
ES6規定如果在子類中使用super物件呼叫父類的方法時,方法內部的this指向子類
class User { constructor() { this.x = 1 } show() { return this.x; } } class VipUser extends User { constructor() { super(); this.x = 2 console.log(super.show())// 2此時show()方法內部的this指向子類,所以輸出2,而不是1 } } let vip = new VipUser();
上述程式碼中雖然super.show()呼叫的是User.prototype.show(),但是由於通過super物件呼叫父類方法時,方法內部的this指向子類,所以super.show()相當於 super.show().call(this),也就是User.prototype.show().call(this)
在子類的靜態方法中super物件指向父類,而不是父類的原型(User.prototype)。
class User { constructor() { this.x = 1 } static fn() { console.log('父類靜態方法') } } class VipUser extends User { constructor() { super(); this.x = 2 } static childFn() { super.fn()// 相當於User.fn() } } VipUser.childFn()
(2)類的prototype和proto屬性
在es5中每一個物件都有proto屬性,指向對應的建構函式的prototype屬性。Class 作為建構函式的語法糖,同時有prototype屬性和proto屬性,因此同時存在兩條繼承鏈。
- 子類的proto屬性,表示建構函式的繼承,總是指向父類。
- 子類prototype屬性的proto屬性,表示方法的繼承,總是指向父類的prototype屬性。
class User { } class VipUser extends User { } VipUser.proto === User // true VipUser.prototype.proto === User.prototype // true
(3)例項的proto屬性
子類例項的proto屬性指向子類的原型(子類的prototype),子類例項的proto屬性的proto屬性指向父類的原型(父類的prototype)
class User { } class VipUser extends User { } var vip = new VipUser() console.log(vip.proto === VipUser.prototype)// true console.log(vip.proto.proto === User.prototype)// true
面向物件-應用
面向物件應用---react
react介紹:
- 元件化
在react裡一個元件就是一個class,
- 依賴於JSX
jsx==babel==browser.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> <script type="text/babel"> let oDiv=document.getElementById('div1'); ReactDOM.render( <span>123</span>, oDiv ); </script> </head> <body> <div id="div1"></div> </body> </html>
ReactDOM.render( <span>123</span>, oDiv );
這種語法為什麼會支援呢?
這個就是jsx和普通js最大的差別。
你可以認為jsx是普通js的 擴充套件版本
既然是擴充套件版本,那肯定會多出一些功能來。
如果不寫引號,不是字元串同時長得像html,他就是可以要建立一個標籤
切換搭配重點
react是一個基於元件
元件與class形式存在
我想寫一個元件,作為一個元件是不是應該有一些基本的功能,比如我能被渲染,我有一些狀態,我有生命週期,換句話說我現在不是從零開始寫一個class,我需要很多基礎的類的整合。
class Test extends React.Component{ }
類繼承最大的意義在於一切不用從零開始
一個類需要有建構函式 constructor
.
作為繼承類,在建構函式中需要繼承父級的屬性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> <script type="text/babel"> class Test extends React.Component{ //建構函式接到任何引數,都給繼承父級類 constructor(...args){ super(...args); } render(){ return <div>hello world</div> } } let oDiv=document.getElementById('div1'); ReactDOM.render( <Test>123</Test>, oDiv ); </script> </head> <body> <div id="div1"></div> </body> </html>
元件套元件方法例子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script> <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script> <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> <script type="text/babel"> class Item extends React.Component{ //建構函式接到任何引數,都給繼承父級類 constructor(...args){ super(...args); } render(){ return <div>{this.props.str}</div> } } class List extends React.Component{ //建構函式接到任何引數,都給繼承父級類 constructor(...args){ super(...args); } render(){ /*//寫法1 let aItem=[]; for(let i=0;i<this.props.arr.length;i++){ aItem.push(<Item key={i} str={this.props.arr[i]}></Item>); }*/ /*// 寫法2 let aItem=this.props.arr.map((str,index)=>{ return <Item key={index} str={str}></Item> }) return <div> {aItem} </div>*/ // 寫法3 return <div> { this.props.arr.map((str,index)=>{ return <Item key={index} str={str}></Item> }) } </div> } } let oDiv=document.getElementById('div1'); ReactDOM.render( <List arr={['abc','efg','hij']}></List>, oDiv ); </script> </head> <body> <div id="div1"></div> </body> </html>
Promise
Promise的中文含義是 承諾
瞭解Promise之前。先來了解下同步非同步
非同步:操作之間沒啥管系,同時進行多個操作
同步:同時只能做一件事
同步非同步的優缺點
非同步:程式碼更復雜
同步:程式碼簡單
一個頁面可能會有多個請求
比如淘寶網頁,banner區域,側邊欄,導航欄,右側欄,資訊商品等
都是由鍍鉻介面非同步請求組成
這就回造成程式碼邏輯複雜
按照以往前端ajax請求寫法。一個請求成功後繼續請求巢狀。邏輯會變得異常費勁
非同步
$.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/1', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功') }, error:function(error){ console.log('失敗') }, }) }, error:function(error){ console.log('失敗') }, }) }, error:function(error){ console.log('失敗') }, }) }, error:function(error){ console.log('失敗') }, })
同步
let banner_data=ajax_async('/banner'); let banner_data1=ajax_async('/banner1'); let banner_data2=ajax_async('/banner2'); let banner_data3=ajax_async('/banner3'); let banner_data4=ajax_async('/banner4');
你會發現非同步處理效能好,使用者體驗好,但實際程式碼複雜
要是同步方式頁面使用者體驗不好
這個時候幻想一下,我能不能像同步方式來寫程式碼。也像非同步一樣請求資料。
Promise就能做到這個工作
Promise--消除非同步操作
- 用同步書寫方式,來書寫非同步方法
Promise如何使用
需要使用promise的時候,你需要new一個promise物件。
這個物件接收一個引數,是一個函式。
將非同步的程式碼寫在函式裡
這個函式兩個引數
resolve
決心
reject
拒絕
//封裝Promise ajax let p=new Promise(function(resolve,reject){ //非同步程式碼塊 //resolve--成功了 //reject--失敗了 $.ajax({ type: 'post', dataType:'json', url: '/api/banner', success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); //使用Promise ajax封裝 //當Promise呼叫有結果了就會呼叫then //then有兩個引數,都是函式,第一個是resolve,第二個是reject p.then((result)=>{ console.log(result); },(error)=>{ console.log(error); })
function createPromise(url){ return new Promise(function(resolve,reject){ //非同步程式碼塊 //resolve--成功了 //reject--失敗了 $.ajax({ type: 'post', dataType:'json', url, success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); } createPromise('./aa') .then((res)=>{ console.log(res) },(err)=>{ console.log(err) })
function createPromise(url){ return new Promise(function(resolve,reject){ //非同步程式碼塊 //resolve--成功了 //reject--失敗了 $.ajax({ type: 'post', dataType:'json', url, success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); } Promise.all([ createPromise('./aa'), createPromise('./bb') ]) .then((res)=>{ let [arr1,arr2]=res },(err)=>{ console.log(err) })
generator-認識生成器函式
generator的作用
generator-生成器
生成器是程式裡面的一個概念,可以依靠它生成一堆東西
Generator可以理解為生成器,和普通函式沒多大區別,普通函式是隻要開始執行,就一直執行到底,而Generator函式是中間可以停,搭配使用next函式繼續執行。
生動的比喻
普通函式好比坐飛機,飛機起飛,不到目的地中途是不會降落的
Generator好比於計程車。可以隨叫隨停。停了再走,走了再停
(1)定義一個Generator函式
function * fn(){ alert('a') yield alert('b') } var f = fn() f.next()// a f.next()// b
直接呼叫Generator函式,是什麼都不執行的,呼叫第一個next()才開始執行,一直執行到第一個yield停止,第二次呼叫next(),從第一個yield執行到第二個yield停止,依次類推
現在疑惑,在真實場景中,我為什麼要讓一個函式停呢?
剛才舉個了計程車的例子,說白了,你為什麼要讓出租車司機停車,肯定是你有事情,你要去忙,或者要求拿什麼東西,或者見什麼朋友。
等你事情辦完了,還再回來。
所以Generator特別適合一個場景。
比如說你要請求資料。請求資料不是瞬間就能回來的,這個時候就需要暫停,來等他結果過來。再繼續執行下面的操作。
/** * 普通函式在執行過程中需要請求得到結果再執行對應程式碼,就會出現程式碼巢狀再巢狀 */ function 函式(){ 程式碼... ajax({ 程式碼... }) } /** * Generator函式可以讓程式碼在那一步暫時暫停 拿到資料後再繼續往下走 */ function *函式(){ 程式碼... yiels ajax(xxx) 程式碼... }
Generator是怎麼做到走走停停的?
其實本質是用Generator函式生成了一堆小函式
比方說fn函式
function * fn(){ alert('a') yield alert('b') } var f = fn() f.next()// a f.next()// b
其實他在背後生成了兩個小函式
function fn_1(){ alert('a') } function fn_2(){ alert('b') }
當然這個過程我們是看不見的
相當於把一個大函式切分成了兩個小函式
第一次next的時候他走的是 fn_1
第二次next的時候走的是 fn_2
generator-yield
yield和next
yield代表暫時暫停執行,next代表繼續執行。
yield和next可以傳引數,也可以有返回值
- yield可以傳參
function *show(){ console.log('a') let a=yield; console.log('b') console.log(a) } let gen=show(); gen.next(12) gen.next(5) //a //b //5
第一次執行next的時候執行黃色框程式碼
第二次執行紅色框的程式碼
傳參的時候通過yield來傳參的時候,第一個next是無效的,
如果想給第一個過程傳參需要使用傳統方法,在使用函式時傳參
function *show(num1,num2){ console.log(`${num1},${num2}`) console.log('a') let a=yield; console.log('b') console.log(a) } let gen=show(11,12); gen.next(12);//沒法給yield傳參 gen.next(5) //11,12 //a //b //5
- yield返回
function *show(){ console.log('a') let a=yield 12; console.log('b') } let gen=show(11,12); let res1=gen.next(); console.log(res1) let res2=gen.next() console.log(res2) //a //{value: 12, done: false} //b //{value: undefined, done: true}
value是yield 返回的引數
done程式碼函式是否走完
為什麼第二次執行完value是空
因為第二次next是執行的最後一道程式,最後一道程式就沒有yield 了,如果想返回東西需要使用return
function *show(){ console.log('a') let a=yield 12; console.log('b') return 111; } let gen=show(11,12); let res1=gen.next(); console.log(res1) let res2=gen.next() console.log(res2) //a //{value: 12, done: false} //b //{value: 111, done: true}
yield 到底是個啥
generator-例項:runner
這種Generator函式適用多個非同步請求之間有邏輯分析的情況,比如有一個需求,先請求使用者資料,根據使用者資料的型別判斷使用者是普通使用者還是VIP使用者,然後再根據判斷結果請求普通商品資料或者VIP商品資料
// 藉助runner指令碼,runner指令碼規定Generator函式執行完一個next之後自動執行下一個next runner(function() * (){ let userData = yield $.ajax(...) // 請求使用者資料 if(userData.type === 'vip') { let goods = yield $.ajax(...) // 請求vip商品資料 } else { let goods = yield $.ajax(...) // 請求普通商品資料 } })
第一次yield ajax其實是Promise物件,將Promise物件yield 出去。
yield 給了runner物件
將資料請求完成給data1
這個函式暫停了
使用Generator函式使得程式碼看起來更像同步程式碼,其實使用Promise同樣可以實現這種效果,只不過得需要在then()函式中巢狀請求。
非同步請求的幾種方式
- 回撥寫法
$.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/1', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功'); $.ajax({ type: 'post', url: '/api/banner', success:function(result){ console.log('成功') }, error:function(error){ console.log('失敗') }, }) }, error:function(error){ console.log('失敗') }, }) }, error:function(error){ console.log('失敗') }, }) }, error:function(error){ console.log('失敗') }, })
- Promise寫法
function createPromise(url){ return new Promise(function(resolve,reject){ //非同步程式碼塊 //resolve--成功了 //reject--失敗了 $.ajax({ type: 'post', dataType:'json', url, success:function(result){ resolve(result); }, error:function(error){ reject(error); }, }) }); } Promise.all([ createPromise('./aa'), createPromise('./bb') ]) .then((res)=>{ let [arr1,arr2]=res },(err)=>{ console.log(err) })
- Generator寫法
runner(function() * (){ let userData = yield $.ajax(...) // 請求使用者資料 if(userData.type === 'vip') { let goods = yield $.ajax(...) // 請求vip商品資料 } else { let goods = yield $.ajax(...) // 請求普通商品資料 } })
Promise和Generator相比,Generator並沒有特別的省事
Promise也有它不適用的地方。我如果是寫死要請求介面。那麼Promise和Generator確實沒太大區別,
Generator他的優點在於適合參雜一些邏輯
比方說在請求一個介面拿到使用者資訊,根據資訊判斷他該去請求哪些不同的介面
感覺比普通巢狀還麻煩
帶邏輯-Generator
// 藉助runner指令碼,runner指令碼規定Generator函式執行完一個next之後自動執行下一個next runner(function() * (){ let userData = yield $.ajax(...) // 請求使用者資料 if(userData.type === 'vip') { let goods = yield $.ajax(...) // 請求vip商品資料 } else { let goods = yield $.ajax(...) // 請求普通商品資料 } })
Promise適合一次請求一堆場景
Generator適合邏輯性請求處理
generator-例項:KOA
KOA是nodejs的框架
async await
async其實就是對Generator的封裝,只不過async可以自動執行next()。
async function read () { let data1= await new Promise(resolve => { resolve('100') }) let data2 = await 200 return 300 }
async 返回值
async預設返回一個Promise,如果return不是一個Promise物件,就會被轉為立即resolve的Promise,可以在then函式中獲取返回值。
async必須等到裡面所有的await執行完,async才開始return,返回的Promise狀態才改變。除非遇到return和錯誤。
async function fn () { await 100 await 200 return 300 } fn().then(res => { console.log9(res) // 300 })
await
await也是預設返回Promise物件,如果await後面不是一個Promise物件,就會轉為立即resolve的Promise
如果一個await後面的Promise如果為reject,那麼整個async都會中斷執行,後面的awiat都不會執行,並且丟擲錯誤,可以在async的catch中捕獲錯誤
async function f() { await Promise.reject('error'); await Promise.resolve('hello world'); // 不會執行 } f().then(res =>{ }).catch(err=>{ console.log(err)// error })
如果希望一個await失敗,後面的繼續執行,可以使用try...catch或者在await後面的Promise跟一個catch方法:
// try...catch async function f() { try { await Promise.reject('出錯了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v))// hello world // catch async function f() { await Promise.reject('出錯了') .catch(e => console.log(e));// 出錯了 return await Promise.resolve('hello world'); } f() .then(v => console.log(v))// hello world