前言:你們好,我叫邵威儒,你們都喜歡喊我小邵,學的金融專業卻憑藉興趣愛好入了程序猿的坑,從大學買的第一本vb和自學vb,我就與編程結下不解之緣,隨後自學易語言寫遊戲輔助、交易軟件,至今進入了前端領域,看到很多朋友都寫文章分享,本身也弄一個玩玩,如下文章純屬我的理解,便於記錄學習,確定有理解錯誤或理解不到位的地方,意在站在前輩的肩膀,分享我的對技術的通俗理解,共同成長!javascript
後續我會陸陸續續更新javascript方面,儘可能把javascript這個學習路徑體系都寫一下
包括前端所經常使用的es六、angular、react、vue、nodejs、koa、express、公衆號等等
都會從淺到深,從入門開始逐步寫,但願能讓你們有所收穫,也但願你們關注我~html
文章列表:juejin.im/user/5a84f8…前端
Author: 邵威儒
Email: 166661688@qq.com
Wechat: 166661688
github: github.com/iamswr/vue
接下來我主要給你們講下我對經常使用的es6的理解,咱們工做當中,其實有不少用不上的,若是想詳細瞭解的話能夠看看阮一峯老師的es6:es6.ruanyifeng.com/java
這篇文章主要讓你學會工做當中經常使用的es6技巧,以及擴展如實現數據雙向綁定,class用es5如何實現、如何給僞數組添加迭代器等等。node
// 1.var存在變量做用域的提高
console.log(a) // 打印輸出 undefined
var a = 1
// 怎麼理解做用域的提高呢?
// var str = 'hello swr'
// function(){
// console.log(str) // 打印輸出 undefined
// var str = 'goodbye swr'
// }
// test()
// 上面這段代碼其實是
var str = 'hello swr'
function(){
var str
console.log(str) // 打印輸出undefined
// 實際上就是var聲明的變量,拿到
// 當前做用域的最頂層,而此時還沒有賦值
// 只是聲明,因此打印出undefined,而非當運行
// 到這段代碼時才聲明,優先聲明,
// 當運行到那行的時候,其實是賦值
// 一樣的,function xx(){}也存在做用域提高
str = 'goodbye swr'
}
test()
// var 不存在塊級做用域的概念
// 個人理解是在es6以前,是沒有塊級做用域的概念,
// 變量只有遇到函數的時候纔會變爲局部變量
{
var str 1 = 'hello swr'
}
console.log(str1) // 打印輸出 hello swr
複製代碼
// 2.let
// 2.1 不存在變量做用域提高,這樣能夠避免了咱們還沒聲明變量就拿變量來用
// 2.2 同一做用域的同一個變量不可以重複聲明,避免咱們重複聲明變量
// 2.3 let聲明的變量不會到全局上
// 2.4 let和代碼塊{}結合使用會造成塊級做用域
// 2.1
// console.log(a) // 報錯,a未聲明
// let a = 'hello swr'
// 2.2
// let a = 'hello swr'
// let a = 'hello swr' // 報錯,變量被重複聲明
// 2.3
// let a = 'hello swr'
// console.log(window.a) // undefined
// 2.4
// 在代碼塊之外調用str2,會報錯
{
let str2 = 'hello swr'
}
console.log(str2) // 報錯,未找到變量
// 上面這種寫法,也有點類型es6以前的當即執行函數
(function(){
var str2 = 'hello swr'
})()
// 一個例子
// 使用var,會發現最終console.log中打印的i都是3
// 由於for循環不是函數,而此時var i是處於全局當中
// for循環是同步代碼,因此會執行完同步代碼後
// 再執行setTimeout的異步代碼,此時i已爲3,因此打印出來都是3
for(var i = 0;i < 3;i++){
setTimeout(function(){
console.log(i)
},1000)
}
// 那麼咱們用let試下
// let和代碼塊結合起來使用會造成塊級做用域
// 那麼當for時,這3個setTimeout會分別在3個不一樣的塊級做用域
// 當執行setTimeout的console.log(i)時,會先尋找最近的塊級做用域中的i
// 因此會依次打印出0 1 2
for(let j = 0;j < 3;j++){
setTimeout(function(){
console.log(i)
},1000)
}
複製代碼
// 3.const
// 3.1 const和let基本上能夠說是徹底一致的,可是const聲明的對象不能更改其指向的引用地址(即堆區)
// 3.1
// 當用普通值賦值給const聲明的變量後,再從新賦值時
// 值引用會被更改,因此會報錯
const STR1 = 'hello swr'
STR1 = 'goodbye swr' // 報錯,Assignment to constant variable
// 當咱們修改這個引用地址裏面的內容時,則不會報錯
// 由於這個變量是指向這個引用地址的
const OBJ = {name:"swr"}
OBJ.name = 'hello swr'
console.log(OBJ) // {name:"hello swr"}
// 可是當咱們把這個變量從新賦值一個引用地址時,則會報錯
OBJ = {} // 報錯
複製代碼
解構賦值主要分爲對象的解構和數組的解構,在沒有解構賦值的時候,咱們賦值是這樣的react
let arr = [0,1,2]
let a = arr[0]
let b = arr[1]
let c = arr[2]
複製代碼
這樣寫很繁瑣,那麼咱們有沒辦法既聲明,又賦值,更優雅的寫法呢?確定是有的,那就是解構賦值,解構賦值,簡單理解就是等號的左邊和右邊相等。git
let arr = [0,1,2]
let [a,b,c] = arr
console.log(a) // 0
console.log(b) // 1
console.log(c) // 2
複製代碼
可是不少時候,數據並不是一一對應的,而且咱們但願獲得一個默認值es6
let arr = [,1,2]
let [a='我是默認值',b,c] = arr
console.log(a) // '我是默認值'
console.log(b) // 1
console.log(c) // 2
// 從這個例子能夠看出,在解構賦值的過程當中,a=undefined時,會使用默認值
// 那麼當a=null時呢?當a=null時,那麼a就不會使用默認值,而是使用null
複製代碼
// 數組的拼接
let a = [0,1,2]
let b = [3,4,5]
let c = a.concat(b)
console.log(c) // [0,1,2,3,4,5]
let d = [...a,...b]
console.log(d) // [0,1,2,3,4,5]
複製代碼
// 數組的克隆
// 假如咱們簡單地把一個數組賦值給另一個變量
let a = [0,1,2,3]
let b = a
b.push(4)
console.log(a) // [0,1,2,3,4]
console.log(b) // [0,1,2,3,4]
// 由於這只是簡單的把引用地址賦值給b,而不是從新開闢一個內存地址,因此
// a和b共享了同一個內存地址,該內存地址的更改,會影響到全部引用該地址的變量
// 那麼用下面的方法,把數組進行克隆一份,互不影響
let a = [0,1,2,3]
let b = [...a]
b.push(4)
console.log(a) // [0,1,2,3]
console.log(b) // [0,1,2,3,4]
複製代碼
對象的解構賦值和數組的解構賦值其實相似,可是數組的數組成員是有序的
而對象的屬性則是無序的,因此對象的解構賦值簡單理解是等號的左邊和右邊的結構相同github
let {name,age} = {name:"swr",age:28}
console.log(name) // 'swr'
console.log(age) // 28
複製代碼
對象的解構賦值是根據key值進行匹配
// 這裏能夠看出,左側的name和右側的name,是互相匹配的key值
// 而左側的name匹配完成後,再賦值給真正須要賦值的Name
let { name:Name,age } = { name:'swr',age:28 }
console.log(Name) // 'swr'
console.log(age) // 28
複製代碼
那麼當變量已經被聲明瞭呢?
let name,age
// 須要用圓括號,包裹起來
({name,age} = {name:"swr",age:28})
console.log(name) // 'swr'
console.log(age) // 28
複製代碼
變量可否也設置默認值?
let {name="swr",age} = {age:28}
console.log(name) // 'swr'
console.log(age) // 28
// 這裏規則和數組的解構賦值同樣,當name = undefined時,則會使用默認值
複製代碼
let [a] = [{name:"swr",age:28}]
console.log(a) // {name:"swr",age:28}
let { length } = "hello swr"
console.log(length) // 9
複製代碼
function ajax({method,url,type='params'}){
console.log(method) // 'get'
console.log(url) // '/'
console.log(type) // 'params'
}
ajax({method:"get",url:"/"})
複製代碼
咱們先看下代碼
// 在以往,咱們給函數傳不肯定參數數量時,是經過arguments來獲取的
function sum() {
console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
// 咱們能夠看出,arguments不是一個數組,而是一個僞數組
let total = 0
let { length } = arguments
for(let i = 0;i < length;i++){
total += arguments[i]
}
return total
}
console.log(sum(1,2,3,4,5,6)) // 21
複製代碼
// 接下來咱們用擴展運算符看看
function sum(...args){ // 使用...擴展運算符
console.log(args) // [ 1, 2, 3, 4, 5, 6 ] args是一個數組
return eval(args.join('+'))
}
console.log(sum(1,2,3,4,5,6)) // 21
複製代碼
獲得的args是一個數組,直接對數組進行操做會比對僞數組進行操做更加方便,還有一些注意點須要注意
// 正確的寫法 擴展運算符只能放在最後一個參數
function sum(a,b,...args){
console.log(a) // 1
console.log(b) // 2
console.log(args) // [ 3, 4, 5, 6 ]
}
sum(1,2,3,4,5,6)
// 錯誤的寫法 擴展運算符只能放在最後一個參數
function sum(...args,a,b){
// 報錯
}
sum(1,2,3,4,5,6)
複製代碼
咱們能夠對比下擴展運算符的方便之處
// 以往咱們是這樣拼接數組的
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = arr1.concat(arr2)
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]
// 如今咱們用擴展運算符看看
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [...arr1,...arr2]
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]
複製代碼
// 以往咱們這樣來取數組中最大的值
function max(...args){
return Math.max.apply(null,args)
}
console.log(max(1,2,3,4,5,6)) // 6
// 如今咱們用擴展運算符看看
function max(...args){
return Math.max(...args) // 把args [1,2,3,4,5,6]展開爲1,2,3,4,5,6
}
console.log(max(1,2,3,4,5,6)) // 6
複製代碼
// 擴展運算符能夠把argument轉爲數組
function max(){
console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
let arr = [...arguments]
console.log(arr) // [1,2,3,4,5,6]
}
max(1,2,3,4,5,6)
// 可是擴展運算符不能把僞數組轉爲數組(除了有迭代器iterator的僞數組,如arguments)
let likeArr = { "0":1,"1":2,"length":2 }
let arr = [...likeArr] // 報錯 TypeError: likeArr is not iterable
// 可是能夠用Array.from把僞數組轉爲數組
let likeArr = { "0":1,"1":2,"length":2 }
let arr = Array.from(likeArr)
console.log(arr) // [1,2]
複製代碼
對象也可使用擴展運算符
// 以往咱們這樣合併對象
let name = { name:"邵威儒" }
let age = { age:28 }
let person = {}
Object.assign(person,name,age)
console.log(person) // { name: '邵威儒', age: 28 }
// 使用擴展運算符
let name = { name:"邵威儒" }
let age = { age:28 }
let person = {...name,...age}
console.log(person) // { name: '邵威儒', age: 28 }
複製代碼
須要注意的是,經過擴展運算符和Object.assign對對象進行合併的行爲,是屬於淺拷貝,那麼咱們在開發當中,常常須要對對象進行深拷貝,接下來咱們看看如何進行深拷貝。
// 方法一:利用JSON.stringify和JSON.parse
let swr = {
name:"邵威儒",
age:28,
pets:['小黃']
}
let swrcopy = JSON.parse(JSON.stringify(swr))
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黃' ] }
// 此時咱們新增swr的屬性
swr.pets.push('旺財')
console.log(swr) // { name: '邵威儒', age: 28, pets: [ '小黃', '旺財' ] }
// 可是swrcopy卻不會受swr影響
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黃' ] }
// 這種方式進行深拷貝,只針對json數據這樣的鍵值對有效
// 對於函數等等反而無效,很差用,接着繼續看方法2、三。
複製代碼
// 方法二:
function deepCopy(fromObj,toObj) { // 深拷貝函數
// 容錯
if(fromObj === null) return null // 當fromObj爲null
if(fromObj instanceof RegExp) return new RegExp(fromObj) // 當fromObj爲正則
if(fromObj instanceof Date) return new Date(fromObj) // 當fromObj爲Date
toObj = toObj || {}
for(let key in fromObj){ // 遍歷
if(typeof fromObj[key] !== 'object'){ // 是否爲對象
toObj[key] = fromObj[key] // 若是爲普通值,則直接賦值
}else{
if(fromObj[key] === null){
toObj[key] = null
}else{
toObj[key] = new fromObj[key].constructor // 若是爲object,則new這個object指向的構造函數
deepCopy(fromObj[key],toObj[key]) // 遞歸
}
}
}
return toObj
}
let dog = {
name:"小白",
sex:"公",
firends:[
{
name:"小黃",
sex:"母"
}
]
}
let dogcopy = deepCopy(dog)
// 此時咱們把dog的屬性進行增長
dog.firends.push({name:"小紅",sex:"母"})
console.log(dog) // { name: '小白',
sex: '公',
firends: [ { name: '小黃', sex: '母' }, { name: '小紅', sex: '母' } ] }
// 當咱們打印dogcopy,會發現dogcopy不會受dog的影響
console.log(dogcopy) // { name: '小白',
sex: '公',
firends: [ { name: '小黃', sex: '母' } ] }
複製代碼
// 方法三:
let dog = {
name:"小白",
sex:"公",
firends:[
{
name:"小黃",
sex:"母"
}
]
}
function deepCopy(obj) {
if(obj === null) return null
if(typeof obj !== 'object') return obj
if(obj instanceof RegExp) return new RegExp(obj)
if(obj instanceof Date) return new Date(obj)
let newObj = new obj.constructor
for(let key in obj){
newObj[key] = deepCopy(obj[key])
}
return newObj
}
let dogcopy = deepCopy(dog)
dog.firends.push({name:"小紅",sex:"母"})
console.log(dogcopy)
複製代碼
Object.defineProperty這個並非es6的語法,這個是給一個對象,添加屬性,可是目前框架不少實用這個方法,來實現數據劫持,也就是數據雙向綁定
// 平時咱們這樣給一個對象添加屬性
let obj = {str:"hello swr"}
obj.str = 'goodbye swr'
console.log(obj.str) // 'goodbye swr'
複製代碼
那麼當咱們想在給一個對象,讀取值或寫入值時,進行別的操做,該怎麼作呢?
// 使用Object.defineProperty()
// 接收的第一個參數爲對象,第二個參數爲屬性名,第三個參數爲配置對象
let obj = {}
Object.defineProperty(obj,'name',{
enumerable:true,// 是否可枚舉,默認值 false
// 若是爲false的話,打印這個obj對象,是看不到name這個屬性
writable:true, // 是否可寫,默認值 false
// 若是爲false的話,給name賦值,不會生效
configurable:true, // 是否可配置(是否可刪除),默認值 false
// 若是爲true,delete obj.name,再打印obj,則顯示{}
// 若是爲false,delete obj.name,再打印obj,則顯示{name:undefined}
value:'swr', // name對應的值
})
// 上面的寫法其實和下面的寫法是同樣的
let obj = {}
obj.name = 'swr'
複製代碼
那麼既然同樣,咱們有必要寫這麼大串的代碼嗎?
其實核心是get和set,咱們繼續往下看
// 須要注意的是,當使用get set時,則不能使用value和writable
let obj = {}
let str
Object.defineProperty(obj,'name',{
enumerable:true,
configurable:true,
get(){ // 讀,當咱們讀取時,則會執行到get,好比obj.name
// return 'swr' // 當咱們obj.name進行讀取時,會返回'swr'
return str
},
set(newValue){ // 寫,當咱們寫入時,則會執行到set,好比obj.name = 'swr'
// 而且會把newValue做爲參數傳進去
str = newValue
}
})
obj.name = 'swr' // 寫入
console.log(obj.name) // 'swr' // 讀取
複製代碼
這樣一來,咱們能夠在get set函數中,寫出對應的業務邏輯,
包括不少框架底層,例如
// 通常再也不選擇這樣的寫法
Fn.prototype.xxx = xxx
// 更多的是選擇這樣的寫法
// 這樣的好處就是當讀取值的時候,能夠作一系列咱們想作的事情
Object.defineProperty(Fn.prototype,'xxx',{...})
複製代碼
這個問題在面試當中,會常常問這個問題,可是面試官更但願聽到的是具體底層的實現方式,那麼接下來咱們也實現一下吧~ ( 簡陋版的……(#^.^#)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>對象的數據雙向綁定</title>
</head>
<body>
<input id='input' type="" name="" value="">
<script>
let el = document.getElementById('input') // 1. 獲取輸入框的dom節點
let obj = { // 2. 建立一個對象
name: ""
}
function oberseve(obj) { // 3. 對對象進行觀察
if (typeof obj !== 'object') return // 3.1 判斷參數是否爲對象
for (let key in obj) { // 3.2 對對象進行遍歷,目的是爲了把每一個屬性都設置get/set
defineReactive(obj, key, obj[key])
oberseve(obj[key]) // 3.3 obj[key] 有可能仍是一個函數,須要遞歸,給obj[key]裏的屬性進行設置get/set
}
}
function defineReactive(target, property, value) { // 4. 使用Object.defineProperty
Object.defineProperty(target, property, {
get() {
el.value = value // 4.1 當讀取時,把值賦值給input框
return value
},
set(newVal) {
el.value = newVal // 4.1 當設置時,把賦值給input框
value = newVal
}
})
}
oberseve(obj) // 5.執行該函數,對obj對象裏的屬性進行設置get/set
el.addEventListener('input', function () { // 6.給輸入框綁定input事件
obj.name = this.value // 7.當輸入框輸入內容時,咱們會把輸入框的
// 內容賦值給obj.name,觸發obj.name的set方法
})
</script>
</body>
</html>
複製代碼
當咱們在輸入框輸入內容時,再到控制檯輸入obj.name查看這個值時,會發現打印出"hello swr"
當咱們在控制檯,給obj.name賦值時,會發現輸入框的內容也會做出相應更改
這樣咱們就實現了一個簡陋版的數據雙向綁定了,可是這也是有缺點的,這個只是針對對象進行了數據雙向綁定,而尤大大的Vuejs就是基於Object.defineProperty實現的。
除了Object.defineProperty能夠實現數據雙向綁定以外,還有其餘方式嗎?
確定是有其餘方式能夠實現的,利用es6的proxy代理也能夠實現數據雙向綁定,可是目前的框架仍是比較少使用這種方式。
Proxy代理也能夠進行數據劫持,可是和Object.defineProperty不一樣的是,Proxy是在數據外層套了個殼,而後經過這層殼訪問內部的數據,目前Proxy支持13種方式。
Proxy,個人理解是在數據外層套了個殼,而後經過這層殼訪問內部的數據,就像下面的圖
let dog = {
name:"小黃",
firends:[{
name:"小紅"
}]
}
// 1.首先new一個Proxy對象
let proxy = new Proxy(dog,{ // 2.參數一爲須要代理的數據,參數二爲上圖能夠代理的13種的配置對象
get(target,property){ // 3.參數1爲上面dog對象,參數2爲dog的屬性
console.log('get被監控到了')
return target[property]
},
set(target,property,value){ // 4.參數1爲上面dog對象,參數2爲dog的屬性,參數3爲設置的新值
// 有點相似Object.defineProperty
console.log('set被監控到了')
target[property] = value
}
})
// 那麼接下來咱們設置一下這個屬性
// dog.name = '小紅' // set值時,發現不會打印 'set被監控到了'
// dog.name // get值時,發現不會打印 'get被監控到了'
// 思考:爲何在set/get值的時候不會打印出來咱們須要的東西呢?
// 上面說得很明白了,proxy至關因而一個殼,代理咱們須要監控的數據,也就是咱們要經過proxy來訪問內部數據纔會被監控到
proxy.name = '小紅' // 打印輸出 'set被監控到了'
proxy.name // 打印輸出 'get被監控到了'
複製代碼
// Reflect常常和Proxy搭配使用
// 好比咱們上面的例子中
let proxy = new Proxy(dog,{
get(target,property){
console.log('get被監控到了')
return target[property]
},
set(target,property,value){
console.log('set被監控到了')
// target[property] = value
// 這裏的target[property] = value 能夠用下面的寫法
Reflect.set(target,property,value)
}
})
複製代碼
// 那麼咱們該怎樣實現深度的數據劫持呢?
let dog = {
name:"小黃",
firend:{
name:"小紅"
}
}
// 咱們首先寫一個set方法,但願是經過這樣來調用
set(dog.firend,funtion(obj){
console.log(obj) // { name:"小紅" } 回調函數中的obj表明的是dog.firend的對象
})
複製代碼
// 實現
let dog = {
name:"小黃",
firend:{
name:"小紅"
}
}
function set(obj,callback){
let proxy = new Proxy(obj,{
set(target,property,value){
target[property] = value
}
})
// 最後把proxy傳給咱們的回調函數
callback(proxy)
}
set(dog.firend,function(obj){
console.log(obj) // { name:"小紅" } 實際就是從set函數中傳出來的proxy對象
})
複製代碼
在js中,常見的數據類型有undefined null string number boolean object,而es6中,則新增了第七種數據類型symbol。
symbol會生成一個獨一無二的值,爲常量
let s1 = Symbol()
let s2 = Symbol()
console.log(s1 === s2) // false
// 由於Symbol生成的是一個獨一無二的值,爲常量,通常是做爲對象的屬性
let obj = {
[s1]:1,
[s2]:2
}
console.log(obj) // { [Symbol()]: 1, [Symbol()]: 2 }
複製代碼
Symbol.for與Symbol差很少,可是Symbol.for會生成一個惟一的標識
let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
console.log(s1 === s2) // true
// 也能夠經過Symbol.keyFor把標識找出來
console.log(Symbol.keyFor(s1)) // foo
複製代碼
Array的經常使用方法有from reduce map forEach findIndex find every some filter includes等等
用法也很簡單,我主要講一下from和reduce。
把僞數組(包括不含有迭代器的僞數組)轉化爲數組
// 聲明一個僞數組
let likeArr = { 0:1,1:2,2:3,length:3 }
// 轉換爲數組
Array.from(likeArr) // [1,2,3]
複製代碼
那麼咱們用前面所說的擴展運算符,可以把僞數組轉爲數組嗎?
// 聲明一個僞數組
let likeArr = { 0:1,1:2,2:3,length:3 }
// 用擴展運算符轉換爲數組
let arr = [...likeArr] // 報錯 likeArr is not iterable
複製代碼
likeArr is not iterable意思是,likeArr這個僞數組沒有迭代器,
那麼能夠看出,Array.from和...擴展運算符的區別了,
Array.from能夠將僞數組(包含沒有迭代器的僞數組)轉爲數組,
而...擴展運算符只能把擁有迭代器的僞數組轉爲數組,如arguments、map、set,
那麼咱們若是想用...擴展運算符轉爲數組,該怎麼辦呢?
// 既然擴展運算符只能把有迭代器的僞數組轉爲數組,
// 那麼咱們就給僞數組添加一個迭代器
// 迭代器iterator須要一個generator生成器生成
// 咱們給這個僞數組新增一個[Symbol.iterator]的迭代器
let likeArr = { 0:1,1:2,2:3,length:3,[Symbol.iterator]:function *() {
for(let i = 0;i < this.length;i++){
yield this[i]
}
} }
console.log([...likeArr]) // [1,2,3]
複製代碼
let arr = [1,2,3,4,5]
// 參數一:前一個值
// 參數二:下一個值(當前值)
// 參數三:當前的索引
// 參數四:arr數組
let total = arr.reduce(function(prev,next,currentIndex,arr){
return prev + next
})
console.log(total) // 15
複製代碼
// 那麼reduce是怎樣一個運行流程呢?
// 咱們一步步拆解出來看
let arr = [1,2,3,4,5]
// arr會一直是[1,2,3,4,5]
// 第一步:此時的prev爲1,next爲2,currentIndex爲1
let total = arr.reduce(function(prev,next,currentIndex,arr){
return prev + next // 1+2=3 而且把3當作下一次的prev
})
// 第二步:此時的prev爲3,next爲3,currentIndex爲2
let total = arr.reduce(function(prev,next,currentIndex,arr){
return prev + next // 3+3=6 而且把6當作下一次的prev
})
// 第三步:此時的prev爲6,next爲4,currentIndex爲3
let total = arr.reduce(function(prev,next,currentIndex,arr){
return prev + next // 6+4=10 而且把10當作下一次的prev
})
// 第四步:此時的prev爲10,next爲5,currentIndex爲4
let total = arr.reduce(function(prev,next,currentIndex,arr){
return prev + next // 10+5=15 最終結果會做爲返回值返回
})
複製代碼
那咱們本身實現一個reduce,看看是如何實現的
Array.prototype.myReduce = function (callback) {
let prev = this[0]
for(let i = 0;i < this.length-1;i++){
prev = callback(prev,this[i+1],i+1,this)
}
return prev
}
let arr = [1,2,3,4,5]
let total = arr.myReduce(function(prev,next,currentIndex,arr){
console.log(prev,next)
return prev + next
})
console.log(total) // 15
複製代碼
能夠把數組返回成一個映射後的數組
let arr = [1,2,3].map(item => item+1)
console.log(arr) // [2,3,4]
複製代碼
查找,查找到後再也不繼續查找,查找不到則返回undefined,內部返回true的話,則返回當前item,
let arr = [1,2,3,4]
let val = arr.find(item=>item === 3)
console.log(val) // 3
複製代碼
每一個值是否知足條件,若是是則返回true,若是不是則返回false
let arr = [1,2,3,4]
let isTrue = arr.every(item => {
return item > 0
})
console.log(isTrue) // true
let isTrue2 = arr.every(item => {
return item > 2
})
console.log(isTrue2) // false
複製代碼
是否有其中一個值知足條件,若是是則返回true,若是不是則返回false
let arr = [1,2,3,4]
let isTrue = arr.some(item => {
return item > 2
})
console.log(isTrue) // true
let isTrue2 = arr.some(item => {
return item > 4
})
console.log(isTrue2) // false
複製代碼
過濾,在回調函數中返回的爲false的話,至關於過濾掉當前項,返回一個過濾後的數組
let arr = [1,2,3,4]
let newArr = arr.filter(item=>{
return item > 2
})
console.log(newArr) // [3,4]
複製代碼
基本和some同樣
set是放不重複的項,也就是去重
let set = new Set([1,2,3,4,3,2,1])
console.log(set) // Set { 1, 2, 3, 4 }
複製代碼
Set有幾個經常使用的方法,add clear delete entries
// add
let set = new Set([1,2,3,4,3,2,1])
set.add(5)
console.log(set) // Set { 1, 2, 3, 4, 5 }
// 添加一個已有的值,則不會添加進去
set.add(1)
console.log(set) // Set { 1, 2, 3, 4, 5 }
// delete
set.delete(3)
console.log(set) // Set { 1, 2, 4, 5 }
// entries
console.log(set.entries()) // SetIterator { [ 1, 1 ],
[ 2, 2 ],
[ 4, 4 ],
[ 5, 5 ] }
// clear
set.clear()
console.log(set) // Set {}
複製代碼
Set經常使用於去重(並集)
function distinct(arr1,arr2){
return [...new Set([...arr1,...arr2])]
}
let arr = distinct([1,2,3],[2,3,4,5])
console.log(arr) // [1,2,3,4,5]
複製代碼
求交集
function intersect(arr1,arr2) {
// 利用Set裏的方法has,來判斷new Set(arr2)中是否含有item,
// 若是含有,那麼則是true,當爲true時,filter函數則會保留該項
// 若是沒有,則是false,當爲false時,filter函數則不會保留該項
return arr1.filter(item => new Set(arr2).has(item))
}
console.log(intersect([1,2,3],[2,3,4,5])) // [2,3]
複製代碼
求差集
function difference(arr1,arr2){
return arr1.filter(item => !new Set(arr2).has(item))
}
console.log(difference([1,2,3],[2,3,4,5])) // [1]
複製代碼
也是集合,主要格式是 key => value,一樣是不能放重複的key
// 若是放重複的key會怎樣呢?會被覆蓋
let map = new Map()
map.set('name','邵威儒')
map.set('name','swr')
console.log(map) // Map { 'name' => 'swr' }
// 取的話用get
map.get('name') // 'swr'
// 刪的話用delete
map.delete('name')
console.log(map) // Map {}
// 不少方法和set差很少
複製代碼
let map = new Map()
map.set('name','邵威儒')
map.set('age',28)
// 通常使用for ... of ... 遍歷
for(let [key,value] of map.entries()){
console.log(key,value) // name 邵威儒
// age 28
}
// 也能夠用forEach
map.forEach(item => {
console.log(item) // 邵威儒
// 28
})
複製代碼
Set我用得最多的就是去重了,實際上Set Map我在開發中仍是比較少會用到
核心仍是繼承,而Class我認爲是es5面向對象的語法糖。
在看Class以前建議看一下js的面向對象 juejin.im/post/5b8a87…
看完後,咱們開始進入es6的class
// 語法
// 聲明一個類
Class Person{
// 在constructor中寫實例屬性、方法
constructor(){
this.name = "邵威儒" // 實例屬性
this.say = function(){ // 實例方法
console.log("我是實例方法上的say")
}
}
// 原型方法
eat(){
console.log("我是原型方法上的eat")
}
// 靜態方法 也會被繼承
static myName(){
return "我是靜態方法的myName"
}
// 在es6中靜態屬性不能這樣寫 static name = "邵威儒" 這樣會報錯
// 在es7中能夠這樣寫static name = "邵威儒"
}
let p = new Person() // new一個對象
console.log(p.name) // 邵威儒
p.eat() // 我是原型方法上的eat
console.log(Person.myName()) // 我是靜態方法的myName
複製代碼
那麼子類怎麼繼承父類呢?
// 父類
class Person{
constructor(){
this.name = "swr"
}
static myName(){
return "Person"
}
eat(){
console.log('eat')
}
}
// 子類
// 子類Child繼承父類Person
// class Child extends Person實際上至關於
// Child.prototype = Object.create(Person.prototype)
// 打印出來能夠看到
// console.log(Child.prototype === Person.prototype) // false
// console.log(Child.prototype.__proto__ === Person.prototype) // true
class Child extends Person{
constructor(){
super() // 此處的super至關於Person.call(this)
}
}
複製代碼
前面我說了Class就類型es5面向對象的語法糖,爲何這樣說呢?
接下來咱們看一下經過es5怎麼模擬實現一個Class(能夠用babel轉一下,看看轉爲es5的代碼是怎樣的)
let Child = (function(){
// 這種閉包的寫法,好處能夠把做用域封閉起來
// 在Child構造函數外寫一系列變量
// 如 let name = "邵威儒";let age = 28 等等…
function Child(){
console.log(this) // 打印內部this,看看指向哪裏
}
return Child
})()
// 經過直接調用函數,看看什麼狀況
console.log(Child()) // 此時裏面的this是指向全局的
// 經過new來生成對象
console.log(new Child()) // 此時裏面的this是指向這個new出來的新對象
複製代碼
在es6中,不使用new來調用類,會報錯 Class constructor Child cannot be invoked without 'new'
class Child {
}
Child() // TypeError: Class constructor Child cannot be invoked without 'new'
複製代碼
也就是說,想在es5中,模擬類,那麼沒使用new來調用構造函數時,也要拋出一個錯誤,那麼咱們會想到類的校驗方法
// * 1.聲明一個類的校驗方法
// * 參數一:指向的構造函數
// * 參數二:被調用時,this的指向
function _classCallCheck(constructor,instance) {
// * 2.若是這個instance指向的不是constructor的話,意味着不是經過new來調用構造函數
if(!(instance instanceof constructor)){
// * 3.不知足時,則拋出異常
throw TypeError("Class constructor Child cannot be invoked without 'new'")
}
}
let Child = (function(){
function Child(){
// * 4.在調用該構造函數的時候,先執行如下類的校驗方法
_classCallCheck(Child,this)
}
return Child
})()
// 不經過new調用時,會報錯
Child() // 報錯 Class constructor Child cannot be invoked without 'new'
複製代碼
那麼咱們類上,有實例屬性方法、原型屬性方法、靜態屬性方法
function _classCallCheck(constructor,instance) {
if(!(instance instanceof constructor)){
throw TypeError("Class constructor Child cannot be invoked without 'new'")
}
}
// * 4.描述器 descriptor
// 參數一:構造函數
// 參數二:描述原型屬性方法數組
// 參數三:描述靜態屬性方法數組
function _createClass(constructor,protoProperties,staticProperties) {
// * 5.若是protoProperties數組有數組成員
if(protoProperties.length){
// * 6.遍歷
for(let i = 0;i < protoProperties.length;i++){
// * 7.經過Object.defineProperty把屬性方法添加到constructor的原型對象上
Object.defineProperty(constructor.prototype,protoProperties[i].key,{
// * 8.利用擴展運算符,把{key:"say",value:function(){console.log("hello swr")}}展開
...protoProperties[i]
})
}
}
}
// * 1.實例屬性方法、原型屬性方法、靜態屬性方法
// 在es6中,原型屬性方法不是經過prototype實現的
// 而是經過一個叫描述器的東西實現的
let Child = (function(){
function Child(){
_classCallCheck(Child,this)
// * 2.實例屬性方法仍是寫在構造函數內
this.name = '邵威儒'
}
// * 3.描述器 descriptor
// 參數一:構造函數
// 參數二:描述原型屬性方法
// 參數三:描述靜態屬性方法
_createClass(Child,
[
{key:"say",value:function(){console.log("hello swr")}},
{key:"myname",value:"iamswr"}
],
[
{key:"total",value:function(){return 100}}
])
return Child
})()
// * 9.最後咱們new一個對象出來,而且調用原型屬性方法,看可否調用成功
let c = new Child()
c.say() // 'hello swr' 調用成功
複製代碼
接下來,咱們把靜態方法,staticProperties也處理一下,
此時會發現,protoProperties和staticProperties都會遍歷而後使用Object.defineProperty
那麼咱們封裝一個方法進行處理
function _classCallCheck(constructor,instance) {
if(!(instance instanceof constructor)){
throw TypeError("Class constructor Child cannot be invoked without 'new'")
}
}
// * 1.封裝一個方法,處理遍歷和Object.defineProperty
function _defineProperty(target,properties) {
for (let i = 0; i < properties.length; i++) {
Object.defineProperty(target, properties[i].key, {
...properties[i]
})
}
}
function _createClass(constructor,protoProperties,staticProperties) {
if(protoProperties.length){
_defineProperty(constructor.prototype, protoProperties)
}
// * 2.若是staticProperties數組有數組成員
if(staticProperties.length){
// * 3.靜態方法須要添加在constructor
_defineProperty(constructor, staticProperties)
}
}
let Child = (function(){
function Child(){
_classCallCheck(Child,this)
this.name = '邵威儒'
}
_createClass(Child,
[
{key:"say",value:function(){console.log("hello swr")}},
{key:"myname",value:"iamswr"}
],
[
{key:"total",value:function(){return 100}}
])
return Child
})()
let c = new Child()
c.say()
// * 4.最後咱們經過Child來調用靜態方法
console.log(Child.total()) // 100
複製代碼
這樣完成了一個雛形,可是還有最重要的繼承還沒實現,接下來咱們實現繼承。
function _classCallCheck(constructor,instance) {
if(!(instance instanceof constructor)){
throw TypeError("Class constructor Parent cannot be invoked without 'new'")
}
}
function defineProperty(target,properties) {
for (let i = 0; i < properties.length; i++) {
Object.defineProperty(constructor.prototype, properties[i].key, {
...properties[i]
})
}
}
function _createClass(constructor,protoProperties,staticProperties) {
if(protoProperties.length){
defineProperty(constructor.prototype, protoProperties)
}
if(staticProperties.length){
defineProperty(constructor, staticProperties)
}
}
// * 6.繼承方法
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
// * 7.把子類的原型對象指向新的原型對象 組合寄生式繼承 繼承原型屬性方法
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass, // 把constructor指向子類
enumerable: false,
writable: true,
configurable: true
}
});
// * 8.繼承父類的靜態方法
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
// * 1.父類
let Parent = (function(){
function Parent(){
_classCallCheck(Parent,this)
this.name = '父類實例屬性'
}
_createClass(Parent,
[
{key:"say",value:function(){console.log("父類原型方法say")}},
{key:"myname",value:"父類原型屬性myname"}
],
[
{key:"total",value:function(){return 100}}
])
return Parent
})()
// * 2.子類
let Child = (function (Parent) { // * 4.這裏接收傳進的參數 父類
// * 5.寫一個繼承方法,繼承原型屬性方法和靜態方法
_inherits(Child, Parent);
function Child() {
_classCallCheck(Child, this)
// * 9.繼承實例屬性方法
return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
return Child
})(Parent) // * 3.在這裏經過傳參,把父類傳進去
let c = new Child()
console.log(c.name) // '父類實例屬性'
複製代碼
這樣就能夠用es5模擬es6的class了,會發現其實es6的class是es5面向對象的一個語法糖,通過這樣解剖一下源碼實現,會對class有更深入的理解。
還有個問題,咱們在react中,會這樣寫class
class Parent{
name = "邵威儒"
}
// 在正常狀況下會報錯,可是由於平時項目是使用了babel插件
// 會幫咱們自動編譯語法,這種寫法目前還處於草案階段
// 上面的寫法實際等價於下面的寫法
class Parent{
constructor(){
this.name = "邵威儒"
}
}
複製代碼
裝飾器是用來裝飾類的
class Person {
}
function myFunction(target) {
target['myName'] = "邵威儒"
}
myFunction(Person)
console.log(Person['myName']) // 邵威儒
複製代碼
這種寫法,至關於給Person這個類添加了myName的屬性
那麼換成decorator該怎麼寫呢?
// 在類前面寫@myFunction
@myFunction
class Person {
}
function myFunction(target) {
target['myName'] = "邵威儒"
}
// myFunction(Person) 這一步能夠不寫
console.log(Person['myName']) // 邵威儒
複製代碼
那麼咱們該怎麼給myName傳參呢?
@myFunction('邵威儒')
class Person {
}
function myFunction(value) {
return function(target){ // target表明的是類
target['myName'] = value
}
}
console.log(Person['myName']) // 邵威儒
複製代碼
修飾符也能夠修飾類的方法
class Person {
@myFunction
say(){}
}
// 若是修飾的是方法
// 參數一:是Person.prototype
// 參數二:是say
// 參數三:是描述器
function myFunction(target,key,descriptor){
// 給這個類添加一個原型屬性
Object.assign(target,{name:"邵威儒"})
}
let p = new Person()
console.log(p.name) // 邵威儒
複製代碼
修飾符也能夠修飾類的屬性,好比咱們有個不可修改的屬性
class Person {
@onlyRead
name = '邵威儒'
}
function onlyRead(target,key,descriptor){
descriptor.writable = false
}
let p = new Person()
p.name = 'swr' // 報錯,不能賦值
複製代碼
decorator的用處不少,包括重寫函數
function myFunction(target,key,descriptor){
// 拿出本來的函數
let fn = descriptor.value
// 而且在原有的fn上加上本身的業務邏輯,好比console.log('哈哈哈')
descriptor.value = function(){
// 這裏寫咱們須要加入的內容
console.log('哈哈哈')
// 這裏執行原來的fn
fn()
}
}
複製代碼
裝飾器常常在react中使用~其實decorator是簡寫,逼格高一些。