ES6中類的繼承&promise
這篇筆記是對ES6中的數組、函數、對象、類、promise和async/await的學習心得。
塊級作用域
- var 可以重復定義、不能限制修改、沒有塊級作用域
- let 不能重復定義, 變量,塊級作用域
- const 不能重復定義, 常量,塊級作用域
// 重復申明 變量提升
var命令會發生”變量提升“現象
let不允許在相同作用域內,重復聲明同一個變量; 變量不提升
const 的變量不可以被修改,也不能重復申明
使用const的對象可以修改,因為const內部是依靠指針來判斷一個對象是否被修改
數組
數組實例的 find() 和 findIndex()
數組實例的find方法,用於找出第一個符合條件的數組成員。它的參數是一個回調函數,
所有數組成員依次執行該回調函數,直到找出第一個返回值為true的成員,然後返回該成員。
如果沒有符合條件的成員,則返回undefined。
[1, 4, -5, 10].find((n) => n < 0)
// -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
[1,2,-3,-4].findIndex(function(n){
return n<0
})// 2
- fill()方法
Fill方法用給一個給定的值來填充數組,通常用來初始化一個新建的array對象
var a = new Array(5) console.log(a) // [ <5 empty items> ] a.fill(0) console.log(a) //[ 0, 0, 0, 0, 0 ] // ES5方法實現 // 需要借助apply和map方法 var array = Array.apply(null, new Array(5)) .map(function(){ return 0 }) console.log(array) // [ 0, 0, 0, 0, 0 ] // ES6方式實現 const array = new Array(2).fill(0)
from方法
Array.from([array-like])=>[x,x,x]
用於將一個array-like object轉換為數組
什麽是array-like?
例子:js中的參數對象arguments就是一個array-like object我們可以通過[]來訪問其中的元素,
也可以使用length。但是不能使用array對象的方法
// 新建一個array-like object var a = {} var i = 0 while(i<10) { a[i]=i*i i++ } a.length = i // console.log(a) /* { ‘0‘: 0, ‘1‘: 1, ‘2‘: 4, ‘3‘: 9, ‘4‘: 16, ‘5‘: 25, ‘6‘: 36, ‘7‘: 49, ‘8‘: 64, ‘9‘: 81, length: 10 } */ // 在es5中可以使用array.prototype.slice方法來將array-like對象轉換成真正的數組, // 不便的是如果要轉換一個現有的object // 通常還要調用call()方法,否則返回的是一個空數組 // ES5 var al = Array.prototype.slice.call(a); // 需要增加call()方法 al.push(10) console.log(al) // [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 10 ] // ES6 var a2= Array.from(a); // form方法不是定義在prototype上的 a2.push(10) console.log(a2) // [ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 10 ]
數組的遍歷
ES6提供三個新的方法 entries, keys 和values用於遍歷數組。
// 區別
// keys是對鍵名的遍歷 values是對鍵值的遍歷 entries是對鍵值對的遍歷
var a = [‘a‘, ‘b‘, ‘c‘]
for(let i of a.keys()) {
console.log(i) // 0 1 2
}
for(let i of a.entries()) {
console.log(i)
}
// [ 0, ‘a‘ ]
// [ 1, ‘b‘ ]
// [ 2, ‘c‘ ]
函數
參數的默認值
// 參數的默認值
// 如果在參數重使用了默認值,那麽就不能再方法體內再使用let關鍵字申明同名的變量
function greed(x=‘Hello‘, y=‘Node‘) {
console.log(x, y)
}
greed() // Hello Node
// spread運算符
// 合並數組,在ES5中通常調用concat方法來實現
var arr =[1,2,3]
var arr2=[4,5]
console.log([...arr, ...arr2]) // [ 1, 2, 3, 4, 5 ]
// 將字符串轉換為數組
var name=[...‘learing‘]
console.log(name) // [ ‘l‘, ‘e‘, ‘a‘, ‘r‘, ‘i‘, ‘n‘, ‘g‘ ]
// ...也可以作為函數的參數,表示該函數有多個參數,也可以在函數調用時使用
function func(x,y,z){
return x+y+z
}
var args = [1,2,3]
func(...args) // 6
箭頭函數
優點
箭頭函數 可以修復 this作用域
setTimeout中匿名函數的this的指向,node和瀏覽器是不同的
// 匿名函數中的this
/* var obj={}
obj.func = function() {
console.log(this)
} */
function foo() {
setTimeout( function(){
console.log(this)
}, 1000);
}
foo()
// 瀏覽器中是Windows 在node中輸出的是一個timeout對象
// 使用箭頭函數後,this的指向和foo函數內部相同
// 箭頭函數自動綁定this, 實際上匿名函數函數內部沒有定義this,僅是引用外面一層的this而已。
// 弄清匿名函數內部的this指向
function foo() {
this.name=‘lear‘
setTimeout(() => {
console.log(this)
console.log(‘name--‘, this.name) // lear
}, 100);
}
// foo()
// 箭頭函數本身沒有定義this,在箭頭函數內部使用this關鍵字時,
// 它開始在代碼定義的位置向上找,直到遇見第一個this,
// 這帶來了很大的便利,但是在構造函數中會出現問題
function Person() {
this.name =‘lear‘
}
Person.prototype.greet = ()=> {
console.log(this.name) // undefined
}
var person =new Person()
person.greet()
// 出現undefined是因為 箭頭函數綁定了上一層的環境,就是全局環境中的this
// 這段代碼在瀏覽器中運行時this是Windows
// 不建議在構造函數中使用箭頭函數
Set&Map
Set
Set 的實現類似於數組,和普通數組的不同在於Set中不能包含重復的數據
/* var set= new Set([1,2,3,4,5]) // 使用夠構造函數初始化一個set
set.add(6) //添加
set.delete(5) // 刪除
console.log(set.has(6)) // true
console.log(set)
for(var i of set) {
console.log(i)
}
set.clear() // 清除所有元素
console.log(set) // {}
*/
- set的遍歷
let set = new Set([1,2,3])
for(let i of set.keys()) {
console.log(i) // 1 2 3
}
for(let i of set.values()) {
console.log(i) // 123
}
for(let i of set.entries()) {
console.log(i)
}
// [ 1, 1 ]
// [ 2, 2 ]
// [ 3, 3 ]
Map
Map 表示鍵值對組成的有序集合
有序表現在map的遍歷順序即為插入順序
var obj = {‘c‘:3}
var map= new Map([[‘a‘,1], [‘b‘,2],[obj,3]])
console.log(map.size) // 3 map的大小
console.log(map.has(‘a‘)) // true 判斷是否存在鍵值對
console.log(map.get(‘a‘)) // 獲取摸個鍵值對的值
map.set(‘d‘, 4) // 如果鍵值不存在,則增加新的鍵值對,否者覆蓋原有的值
map.delete(‘d‘) // 刪除 返回布爾值
// 遍歷map
for(let key of map.keys()) {
console.log(key)
}
// a
// b
// { c: 3 }]
對象
// 淺拷貝
var obj1 = {a: {b:1}}
var obj2 = Object.assign({}, obj1)
obj1.a.b=2
// console.log(obj2.a.b) // 2
// 對象的遍歷
var obj={
‘name‘: ‘lear‘,
‘age‘: 10,
‘sex‘: ‘male‘
}
// 1.使用for in 遍歷
for(var key in obj) {
// console.log(‘key‘, key, obj[key] )
}
// 2.object.keys() 遍歷
console.log(Object.keys(obj)) // [ ‘name‘, ‘age‘, ‘sex‘ ]
類 class
ES5
// ES5
// 在js中,類的所有實例對象都從同一個原型對象上繼承屬性
/* function Person (sex, age) {
this.sex =sex
this.age= age
}
Person.prototype.getInfo =function(){
return this.sex+‘,‘+this.age
}
var person =new Person(‘man‘, ‘10‘)
console.log(person.getInfo()) // man,10
*/
ES6 語法糖
// ES6 語法糖
class Person {
constructor(sex, age){
this.sex=sex
this.age=age
}
getInfo(){
return this.sex+‘,‘+this.age
}
}
var person=new Person(‘female‘, ‘20‘)
console.log(person.getInfo()) // female,20
屬性和構造函數
// 屬性和構造函數
// class中的屬性定義在constructor函數(構造函數)中,構造函數負責類的初始化,
// 包括初始化屬性和調用其他類方法等
// 構造函數同樣支持默認值參數
// 如果申明一個類的時候沒有聲明構造函數,那麽會默認添加一個空的構造函數。
// 構造函數只有在使用關鍵字new實例化一個對象的時候才會被調用
class Student{
constructor(name="lear", sex=‘male‘){
this.age=‘lear‘
this.sex=‘male‘
}
}
類方法
// 類方法
class Student{
constructor(name=‘lear‘, sex=‘male‘){
this.name=name
this.sex=sex
}
getInfo(){
console.log(‘name :‘, this.name, ‘sex :‘,this.sex)
}
}
var student=new Student()
// console.log(student.getInfo()) // name : lear sex : male
// 類方法也可以作為屬性定義在構造函數中,
class Student{
constructor(name=‘lear‘, sex=‘male‘){
this.name=name
this.sex=sex
this.getInfo=()=>{ // 類方法也可以是箭頭函數
console.log(‘name :‘, this.name, ‘sex :‘,this.sex)
}
}
}
proto
ES5中,類的實現通過__proto__屬性來指向構造函數的prototype對象
es6中,getinfo方法和constructor方法雖然看似是定義在類的內部,但實際上還是定義在prototype上,
側面可以看出ES6是對class的實現依舊是基於prototype
person.constructor=Person.prototype.constructor // true
對象的__proto__屬性指向類的原型
類的本質是構造函數
靜態方法
// 在定義類時如果定義了方法,那麽該類的每個實例在初始化時都會有一份該方法的備份
// 有時候我們不希望一些方法被繼承,而是希望作為父類的屬性來使用,可以直接通過調用類名調用的方法,即靜態方法
class Person{
static getName() {
return ‘lear‘
}
}
Person.getName() // lear
var person=new Person()
person.getName() // error
// super 關鍵字調用父類的靜態方法
class Person{
static getName() {
return ‘lear‘
}
}
class Student extends Person{
static getName2() {
return super.getName() + ‘, Hi‘
}
}
console.log(Student.getName2()) // lear, Hi
類的繼承
// ES5中的繼承
// 我們有一個父類Person,並且在類的內部和原型鏈上各定義了一個方法:
function Person(name, age){
this.name=name
this.age=age
this.greed=function(){
console.log(‘Hello, i am‘, this.name)
}
}
Person.prototype.getInfo=function(){
return this.name+‘,‘+this.age
}
// 修改原型鏈
// 這是最普通的繼承方法, 通過將子類的prototype指向父類的實例來實現
function Student(){
}
Student.prototype=new Person()
Student.prototype.name=‘lear‘
Student.prototype.age=10
var stud=new Student()
stud.getInfo() // lear,10
// 缺點:在子類構造函數中無法通過傳遞參數對父類繼承的屬性值進行修改,只能通過修改prototype的方式進行修改。
// 構造函數繼承
function Person(name, age){
this.name=name
this.age=age
this.greed=function(){
console.log(‘Hello, i am‘, this.name)
}
}
Person.prototype.getInfo=function(){
return this.name+‘,‘+this.age
}
function Student(name,age,sex) {
Person.call(this)
this.name=name
this.age=age
this.sex=sex
}
var stud=new Student(‘lear‘,10,‘male‘)
stud.greed() // Hello, i am lear
stud.getInfo() // error
promise
promise是什麽
官網解釋 promise 表示一個一步操作的最終結果。
翻譯 可以將promise理解為一個狀態機,它存在三種不同的狀態,並在某一時刻只能有一種狀態
- pending 表示還在執行
- resolved 執行成功
- rejected 執行失敗
一個promise是對一個異步操作的封裝,異步操作有等待完成、成功和失敗三種可能的結果,對應了promise的三種狀態。
promise的狀態只能有pending轉換位resolved或者pending轉換為rejected,一旦狀態轉化完成就無法再改變。
假設我們用promise封了一個異步操作,那麽當它被創建的時候就處於pending狀態,當異步操作成功完成時,
我們將狀態轉換為resolved,如果執行中出現錯誤,將狀態轉換為rejected。
var promise=new Promise(function(resolve,reject){
// code
if(){
/*異步操作成功 */
resolve(value)
}else{
reject(error)
}
})
使用then方法獲取結果
var fs=require(‘fs‘)
function readFile_promise(path){
return new Promise(function(resolve,reject){
fs.readFile(path, ‘utf-8‘,function(err,data){
if(data){
resolve(data)
}else{
reject(err)
}
})
})
}
var result=readFile_promise(‘./1.txt‘)
result.then(function(value){
//success
console.log(‘success‘, value)
},function(error){
//failure
console.log(‘failure‘,error)
})
// 將一個異步函數封裝成promise,只要在回調函數中針對不同的返回結果調用resolve或者reject方法。
// resolve函數會在異步操作成功完成時被調用,並將異步操作的返回值作為參數傳遞到外部。
// reject是在異步操作出現異常時被調用,會將錯誤信息作為參數傳遞出去。
then方法的返回值
then方法總是返回一個新的promise對象,多次調用then方法,默認返回一個一個空的promise對象
使用return來來返回。
var promise=readFile_promise(‘./foo.txt‘)
promise.then(function(value){
//success
console.log(‘success‘, value) // foo
return readFile_promise(‘./bar.txt‘)
},function(error){
//failure
console.log(‘failure‘,error)
}).then(function(value){
console.log(‘then‘, value) // bar
})
promise的執行
var promise=new Promise((resolve, reject)=>{
console.log(‘begin‘)
resolve()
})
setTimeout(()=>{
promise.then(()=>{
console.log(‘end‘)
})
},5000)
// 雖然我們是通過then方法來獲取promise的結果,但是promise是當then方法調用之後才執行嗎?
// 開始begin 5s後end
// 運行順序是,當promise從被創建的那一刻起就開始執行了,then方法只是提供了訪問promise狀態的接口,與promise的執行無關。
promise 常用的api
- resolved
- rejected
- all
// 如果有多個promise需要執行,可以使用promise.all()
// 方法統一聲明,改方法可以將多個promise對象包裝成一個promise
// 該方法接收一個數組作為參數,數據的元素如果不是promise對象,則回先調用resolve方法轉換。
// 如果中間有一個promise狀態是reject,那麽轉換後的promise也會變成reject,並且將錯誤信息傳給catch方法
var promises=[‘foo.txt‘,‘bar.txt‘,‘baz.txt‘]
promises.map(function(path){
// console.log(path)
return readFile_promise(path)
})
Promise.all(promises).then(function(results){
console.log(results) // [ ‘foo.txt‘, ‘bar.txt‘, ‘baz.txt‘ ] 順序排列的
}).catch(function(err){
//
})
使用promise組織異步代碼
// 例子; 有三個文本文件需要順序讀取
var lists=[‘foo.txt‘,‘bar.txt‘,‘baz.txt‘]
var count=0;
readFile_promise(‘foo.txt‘).then(readCB).then(readCB).then(readCB);
function readCB(data){
console.log(data) // foo bar baz
if(++count>2){
return
}
return readFile_promise(lists[count])
}
async/await
await關鍵字後面往往是一個promise,如果不是就隱式調用promise.resolve來轉換成一個promise。
await 等待後面的promise執行完成再進行下一步操作。
var asyncReadFile=async function(){
var result1=await readFile_promise(‘./foo.txt‘)
console.log(result1.toString()) // foo
}
asyncReadFile()
async返回值
async函數總是會返回一個promise對象,如果return關鍵字後面不是一個promise,那麽默認
調用promise。resolve方法進行轉換。
async function asyncFunc(){
return ‘hello Node‘
}
asyncFunc().then(function(data){
console.log(data) // hello Node
})
async函數的執行過程
- 在async函數開始執行的時候回自動生成一個promise對象。
- 當方法體開始執行後,如果遇到return關鍵字或者throw關鍵字,執行會立刻退出,
如果遇到await關鍵字則回暫停執行 await後面的異步操作結束後會恢復執行 - 執行完畢,返回一個promise
async function asyncFunc(){
console.log(‘begin‘)
return ‘hello Node‘
}
asyncFunc().then(function(data){
console.log(data) // hello Node
console.log(‘end‘)
})
// begin
// hello
// end
await
await 操作符的結果是由其後面promise對象的操作結果來決定的,如果後面promise對象變為resolved,
await操作符發返回的值就是resolve的值;如果promise對象的狀態變成rejected,那麽await也會拋出reject的值。
async function readFile(){
var result=await readFile_promise(‘./foo.txt‘)
console.log(result) // foo
}
readFile()
// 等價於
readFile_promise(‘foo.txt‘).then(function(data){
console.log(data) // foo
})
await於並行
await會等待後面的promise完成後再采取下一步動作,這意味著當多個await操作時,程序會便成完全的
串行操作。
當異步操作之間不存在依賴關系時,可以使用promise.all來實現並行。
async function readFile(){
const [result1, result2]=await Promise.all([
readFile_promise(‘./foo.txt‘),
readFile_promise(‘./bar.txt‘)
])
console.log(result1, result2) // foo bar
}
readFile()
// 等價於
function readFile(){
return Promise.all([
readFile_promise(‘./foo.txt‘),
readFile_promise(‘./baz.txt‘)
]).then((result)=>{
console.log(result) // [ ‘foo‘, ‘baz‘ ]
})
}
readFile()
ES6中類的繼承&promise