1. 程式人生 > >ES6中類的繼承&promise

ES6中類的繼承&promise

獲取 構造 填充 參數傳遞 sync 插入 row throw this指向

這篇筆記是對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函數的執行過程
  1. 在async函數開始執行的時候回自動生成一個promise對象。
  2. 當方法體開始執行後,如果遇到return關鍵字或者throw關鍵字,執行會立刻退出,
    如果遇到await關鍵字則回暫停執行 await後面的異步操作結束後會恢復執行
  3. 執行完畢,返回一個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