從引用聊到深淺拷貝,從深拷貝過渡到ES6新數據結構Map及Set,再到另外一個map即
Array.map()
和與其相似的Array.flatMap()
,中間會有其餘相關話題,例如Object.freeze()
與Object.assign()
等等。es6
一邊複習一邊學習,分清引用與深淺拷貝的區別,並實現淺拷貝與深拷貝,以後經過對深拷貝的瞭解,拓展到ES6新數據結構Map及Set的介紹,再引入對另外一個數組的map方法的使用與相似數組遍歷方法的使用。經過一條隱式鏈將一長串知識點串聯介紹,可能會有點雜,但也會有對各知識點不一樣之處有明顯區分,達到更好的記憶與理解。
一般在介紹深拷貝以前,做爲引子咱們會看見相似如下例子:算法
var testObj = {
name: 'currName'
}
var secObj = testObj
secObj.name = 'changedName'
console.log(testObj) // { name: 'changedName' }
複製代碼
這其實就是一種引用,對於複雜數據結構,爲了節省存儲資源,符號 「=」 其實並非將值賦給新建的變量,而是作了一個地址引用,使其指向原來存儲在堆中的數據的地址,此時testObj與secObj都指向同一個地址,所以在修改secObj的數據內容時,便是對其指向的原有數據進行修改。數組
對於數組有類似的引用狀況,代碼以下:數據結構
var testArr = [0, [1, 2]]
var secArr = testArr
secArr[0] = 'x'
console.log(testArr) // [ 'x', [ 1, 2 ] ]
複製代碼
對於淺拷貝,其與引用的區別,咱們一邊實現淺拷貝,以後進行對比再解釋,實現以下:函數
function shallowCopy (obj) {
var retObj = {}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
retObj[key] = obj[key];
}
}
return retObj
}
var testObj = {
'name': 'currName',
'nums': [1, [2, 3]],
'objs': {
'innerobj': 'content'
}
}
var secObj = shallowCopy(testObj)
secObj.name = 'changedName'
secObj.nums[0] = '一'
secObj.nums[1] = ['二', '三']
console.log(testObj) // { name: 'currName',
// nums: [ '一', [ '二', '三' ] ],
// objs: { innerObj: 'changedContent' } }
console.log(secObj) // { name: 'changedName',
// nums: [ '一', [ '二', '三' ] ],
// objs: { innerObj: 'changedContent' } }
複製代碼
從上例能夠看出通過淺拷貝後獲得的對象,對於第一層數據其修改後已經不能影響以前的數據,但對於內部還存在迭代器的數據屬性,仍是有引用狀況的存在,因此後者對這些屬性的修改,依舊會影響前者中這些屬性的內容。post
引用與淺拷貝的區別就在於: 對第一層數據是否依舊修改後互相影響。性能
assign方法效果相似於在數組中的concat拼接方法,其能夠將源對象中可枚舉屬性進行復制到目標對象上,並返回目標對象,該方法中第一個參數便就是目標對象,其餘參數爲源對象。所以該方法咱們定義源對象爲空對象時即可以在對拷貝的實現中使用,但須要注意的是Object.assign()其方法自身實行的即是淺拷貝,而不是深拷貝,所以經過該方法實現的拷貝只能是淺拷貝。學習
實現淺拷貝代碼以下:ui
var testObj = {
'name': 'currName',
'nums': [1, [2, 3]],
'objs': {
'innerObj': 'content'
}
}
var secObj = Object.assign({}, testObj)
secObj.name = 'changedName'
secObj.nums[0] = '一'
secObj.nums[1] = ['二', '三']
secObj.objs['innerObj'] = 'changedContent'
console.log(testObj) // { name: 'currName',
// nums: [ '一', [ '二', '三' ] ],
// objs: { innerObj: 'changedContent' } }
console.log(secObj) // { name: 'changedName',
// nums: [ '一', [ '二', '三' ] ],
// objs: { innerObj: 'changedContent' } }
複製代碼
freeze方法其效果在有必定程度與淺拷貝相同,但效果上還要比拷貝多上一層,即freeze凍結,但由於該方法自身 內部屬性,該方法的名稱又能夠稱爲「淺凍結」,對於第一層數據,如淺拷貝通常,不可被新對象改變,但被freeze方法凍結過的對象,其自身也沒法添加、刪除或修改其第一層數據,但由於「淺凍結」這名稱中淺的這一明顯屬性,freeze方法對於內部若是存在更深層的數據,是能夠被自身修改,且也會被「=」號所引用給新的變量。this
簡單使用以下:
var testObj = {
'name': 'currName',
'nums': [1, [2, 3]],
'objs': {
'innerObj': 'content'
}
}
var secObj = Object.freeze(testObj)
secObj.name = 'changedName'
secObj.nums[0] = '一'
secObj.nums[1] = ['二', '三']
secObj.objs['innerObj'] = 'changedContent'
secObj.age = 18
delete secObj.name
console.log(testObj) // { name: 'currName',
// nums: [ '一', [ '二', '三' ] ],
// objs: { innerObj: 'changedContent' } }
console.log(secObj) // { name: 'currName',
// nums: [ '一', [ '二', '三' ] ],
// objs: { innerObj: 'changedContent' } }
複製代碼
接上面對淺拷貝的介紹,很容易就能夠想到深拷貝即是在淺拷貝的基礎上,讓內部存在更深層數據的對象,不止第一層不能改變原有數據,內部更深層次數據修改時也不能使原有數據改變,即消除了數據中全部存在引用的狀況。經過對淺拷貝的實現,咱們很容易就想到經過遞歸的方法對深拷貝進行實現。
如下就是經過遞歸實現深拷貝的過程:
function deepCopy(content) {
var retObj = {}
for (const key in content) {
if (content.hasOwnProperty(key)) {
retObj[key] = typeof content[key] === 'object'
? deepCopy(content[key])
: content[key];
}
}
return retObj
}
var testObj = {
'name': 'currName',
'nums': [1, [2, 3]],
'objs': {
'innerObj': 'content'
}
}
var secObj = deepCopy(testObj)
secObj.name = 'changedName'
secObj.nums[0] = '一'
secObj.nums[1] = ['二', '三']
secObj.objs['innerObj'] = 'changedContent'
secObj.age = 18
console.log(testObj) // { name: 'currName',
// nums: [ 1, [ 2, 3 ] ],
// objs: { innerObj: 'content' } }
console.log(secObj) // { name: 'changedName',
// nums: { '0': '一', '1': [ '二', '三' ] },
// objs: { innerObj: 'changedContent' },
// age: 18 }
複製代碼
此時對於內部存在的數組來講,會被轉化爲對象,鍵爲數組的下標,值爲數組的值,被存儲在新的對象中,所以有了咱們完善的第二版。
function deepCopy (obj) {
var tempTool = Array.isArray(obj) ? [] : {}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
tempTool[key] = typeof obj[key] === 'object'
? deepCopy(obj[key])
: Array.isArray(obj) ? Array.prototype.concat(obj[key]) : obj[key];
}
}
return tempTool
}
var testObj = {
'name': 'currName',
'nums': [1, [2, 3]],
'objs': {
'innerObj': 'content'
}
}
var secObj = deepCopy(testObj)
secObj.name = 'changedName'
secObj.nums[0] = '一'
secObj.nums[1] = ['二', '三']
secObj.objs['innerObj'] = 'changedContent'
secObj.age = 18
console.log(testObj) // { name: 'currName',
// nums: [ 1, [ 2, 3 ] ],
// objs: { innerObj: 'content' } }
console.log(secObj) // { name: 'changedName',
// nums: [ '一', [ '二', '三' ] ],
// objs: { innerObj: 'changedContent' },
// age: 18 }
複製代碼
對於Hash結構 即 鍵值對的集合,Object對象只能用字符串做爲key值,在使用上有很大的限制,ES6提供的新的數據結構Map相對於Object對象,其「鍵」的範圍不限於字符串類型,實現了「值-值」的對應,使用上能夠有更普遍的運用。但Map在賦值時,只能接受如數組通常有lterator接口且每一個成員都是雙元素的數組的數據結構做爲參數,該數組成員是一個個表示鍵值對的數組,以外就只能經過Map自身set方法添加成員。
因此如下咱們先介紹將對象轉爲Map的方法,再對Map自身方法作一個簡單介紹,本節最後介紹一個Map的運用場景
function objToMap (object) {
let map = new Map()
for (const key in object) {
if (object.hasOwnProperty(key)) {
map.set(key, object[key])
}
}
return map
}
var testObj = {
'name': 'currName',
'nums': [1, [2, 3]],
'objs': {
'innerObj': 'content'
}
}
let map = objToMap(testObj)
map.set('name', 'changedName')
console.log(testObj) // { name: 'currName',
// nums: [ 1, [ 2, 3 ] ],
// objs: { innerObj: 'content' } }
console.log(map) // Map {
// 'name' => 'changedName',
// 'nums' => [ 1, [ 2, 3 ] ],
// 'objs' => { innerObj: 'content' } }
複製代碼
含增刪改查方法:set、get、has、delete;
遍歷方法:keys、values、entries、forEach;
其餘方法:size、clear。
須要注意的是forEach方法還能夠接受第二個參數,改變第一個參數即回調函數的內部this指向。
let map = new Map([
['name', 'currName'],
['nums', [1, [2, 3]]],
['objs', {'innerObj': 'content'}]
])
// 增 刪 改 查
map.set('test', 'testContent')
map.delete('objs')
map.set('name', 'changedName')
console.log(map.get('nums')) // [ 1, [ 2, 3 ] ]
console.log(map.has('nums')) // true
console.log(map) // Map {
// 'name' => 'changedName',
// 'nums' => [ 1, [ 2, 3 ] ],
// 'test' => 'testContent' }
// 遍歷方法
console.log(map.keys()) // [Map Iterator] { 'name', 'nums', 'test' }
console.log(map.values()) // [Map Iterator] { 'changedName', [ 1, [ 2, 3 ] ], 'testContent' }
console.log(map.entries()) // [Map Iterator] {
// [ 'name', 'changedName' ],
// [ 'nums', [ 1, [ 2, 3 ] ] ],
// [ 'test', 'testContent' ] }
const testObj = {
objName: 'objName'
}
map.forEach(function (value, key) {
console.log(key, value, this.objName) // name changedName objName
// nums [ 1, [ 2, 3 ] ] objName
// test testContent objName
}, testObj)
// 其餘方法
console.log(map.size) // 3
console.log(map) // Map {
// 'name' => 'changedName',
// 'nums' => [ 1, [ 2, 3 ] ],
// 'test' => 'testContent' }
map.clear()
console.log(map) // Map {}
複製代碼
對於經典算法問題中 上樓梯問題:共n層樓梯,一次僅能跨1或2步,總共有多少種走法?
這一類問題都有一個遞歸過程當中內存溢出的bug存在,此時就能夠運用Map減小遞歸過程當中重複運算的部分,解決內存溢出的問題。
let n = 100
let map = new Map()
function upStairs (n) {
if (n === 1) return 1
if (n === 2) return 2
if (map.has(n)) return map.get(n)
let ret = upStairs(n - 1) + upStairs(n - 2)
map.set(n, ret)
return ret
}
console.log(upStairs(n)) // 573147844013817200000
複製代碼
本節介紹在ES6中,與Map相關且一同發佈的WeakMap數據結構。
WeakMap與Map主要有下圖三個區別:
區別 | Map | WeakMap |
---|---|---|
「鍵」類型: | 任何類型 | Object對象 |
自身方法: | 基本方法:set、get、has、delete; 遍歷方法:keys、values、entries、forEach; 其餘方法:size、clear。 |
基本方法:set、get、has、delete。 |
鍵引用類型: | 強引用 | 弱引用 |
此處咱們對強弱引用進行簡單介紹:弱引用在回收機制上比強引用好,在「適當」的狀況將會被回收,減小內存資源浪費,但因爲不是強引用,WeakMap不能進行遍歷與size方法取得內部值數量。
含增刪改查方法:set、get、has、delete。
let wMap = new WeakMap()
let key = {}
let obj = {name: 'objName'}
wMap.set(key, obj)
console.log(wMap.get(key)) // { name: 'objName' }
console.log(wMap.has(key)) // true
wMap.delete(key)
console.log(wMap.has(key)) // false
複製代碼
WeakMap由於鍵必須爲對象,且在回收機制上的優越性,其能夠用在如下兩個場景:
1. 對特定DOM節點添加狀態時。當DOM節點被刪除,將DOM節點做爲「鍵」的WeakMap也會自動被回收。
2. 對類或構造函數中私有屬性綁定定義。當實例被刪除,被做爲「鍵」的this消失,WeakMap自動回收。
示例代碼以下:
<!--示例一-->
let element = document.getElementById('box')
let wMap = new WeakMap()
wMap.set(element, {clickCount: 0})
element.addEventListener('click', () => {
let countObj = wMap.get(element)
countObj.clickCount++
console.log(wMap.get(element).clickCount) // click -> n+=1
})
<!--示例二-->
const _age = new WeakMap()
const _fn = new WeakMap()
class Girl {
constructor (age, fn) {
_age.set(this, age)
_fn.set(this, fn)
}
changeAge () {
let age = _age.get(this)
age = age >= 18 ? 18 : null
_age.set(this, age)
_age.get(this) === 18
? _fn.get(this)()
: console.log('error')
}
}
const girl = new Girl(25, () => console.log('forever 18 !'))
girl.changeAge() // forever 18 !
複製代碼
介紹完ES6新增的Map與WeakMap數據結構,咱們繼續介紹一同新增的Set數據結構。
Set之於Array,其實有點像Map之於Object,Set是在數組的數據結構基礎上作了一些改變,新出的一種相似於數組的數據結構,Set的成員的值惟一,不存在重複的值。如下將對Set數據結構做一些簡單的介紹。
Set能夠將具備Iterable接口的其餘數據結構做爲參數用於初始化,此處不止有數組,但僅以數組做爲例子,單獨講述一下。
// Set -> Array
let arr = [1, 2, 3, 3]
let set = new Set(arr)
console.log(set) // Set { 1, 2, 3 }
// Array -> Set
const arrFromSet1 = Array.from(set)
const arrFromSet2 = [...set]
console.log(arrFromSet1) // [ 1, 2, 3 ]
console.log(arrFromSet2) // [ 1, 2, 3 ]
複製代碼
Set內置的方法與Map相似
含增刪查方法:add、has、delete;
遍歷方法:keys、values、entries、forEach;
其餘方法:size、clear。
let arr = [1, 2, 3, 3]
let set = new Set(arr)
// 增刪改查
set.add(4)
console.log(set) // Set { 1, 2, 3, 4 }
set.delete(3)
console.log(set) // Set { 1, 2, 4 }
console.log(set.has(4)) // true
// 遍歷方法 由於在Set結構中沒有鍵名只有健值,因此keys方法和values方法徹底一致
console.log(set.keys()) // [Set Iterator] { 1, 2, 4 }
console.log(set.values()) // [Set Iterator] { 1, 2, 4 }
for (const item of set.entries()) {
console.log(item) //[ 1, 1 ]
// [ 2, 2 ]
// [ 4, 4 ]
}
const obj = {
name: 'objName'
}
set.forEach(function (key, value) {
console.log(key, value, this.name) // 1 1 'objName'
// 2 2 'objName'
// 4 4 'objName'
}, obj)
// 其餘方法
console.log(set.size) // 3
set.clear()
console.log(set) // Set {}
複製代碼
由於擴展運算符...對Set做用,再經過Array遍歷方法,很容易求得並集、交集及差集,也能夠經過間接使用Array方法,構造新的數據賦給Set結構變量。
let a = new Set([1, 2, 3])
let b = new Set([2, 3, 4])
// 並集
let union = new Set([...a, ...b])
console.log(union) // Set { 1, 2, 3, 4 }
// 交集
let intersect = new Set([...a].filter(x => b.has(x)))
console.log(intersect) // Set { 2, 3 }
// 差集
let difference = new Set([...[...a].filter(x => !b.has(x)), ...[...b].filter(x => !a.has(x))])
console.log(difference) // Set { 1, 4 }
// 賦新值
let aDouble = new Set([...a].map(x => x * 2))
console.log(aDouble) // Set { 2, 4, 6 }
let bDouble = new Set(Array.from(b, x => x * 2))
console.log(bDouble) // Set { 4, 6, 8 }
複製代碼
WeakSet之於Set,依舊至關於WeakMap之於Map。
WeakSet與Set之間不一樣之處,依然是:
1. WeakSet內的值只能爲對象;
2. WeakSet依舊是弱引用。
由於弱引用的關係,WeakSet只有簡單的增刪查方法:add、delete、has
let obj1 = {'name': 1}
let obj2 = {'name': 2}
let wSet = new WeakSet()
wSet.add(obj1).add(obj2)
console.log(wSet.has(obj2)) // true
wSet.delete(obj2)
console.log(wSet.has(obj2)) // false
複製代碼
對於WeakSet的應用場景,其與WeakMap相似,由於弱引用的優良回收機制,WeakSet依舊能夠存放DOM節點,避免刪除這些節點後引起的內存泄漏的狀況;也能夠在構造函數和類中存放實例this,一樣避免刪除實例的時候產生的內存泄漏的狀況。
// 1
let wSet = new WeakSet()
wSet.add(document.getElementById('box'))
const _boy = new WeakSet()
// 2
class Boy {
constructor () {
_boy.add(this)
}
method () {
if (!_boy.has(this)) {
throw new TypeError('Boy.prototype.method 只能在Boy的實例上調用!')
}
}
}
複製代碼
講完大Map,此時咱們繼續瞭解完小map,map即爲Array.map(),是數組中一個遍歷方法。並將map做爲一個引子,咱們對比多介紹幾個Array中遍歷相關的方法。
Array.map() —— 能夠有三個參數,item、index、arr,此時當作forEach使用;經常使用方法是經過第一個參數遍歷修改後返回一個新數組。
Array.flatMap() —— 前置知識:Array方法中有一個ES6中新加入的數組展開嵌套的方法Array.flat(),其中能夠有一個參數表示展開層數,默認只展開一層。而Array.flatMap() 爲 Array.map()與Array.flat()方法的疊加。
例子以下:
// flat
const testArr = [1, 2, [3, [4]]]
const flatArr = testArr.flat()
console.log(flatArr) // [1, 2, 3, Array(1)] -> 0: 1
// 1: 2
// 2: 3
// 3: [4]
const arr = [1, 2, 3]
// map
const mapArr = arr.map(x => x * 2)
console.log(mapArr) // [2, 4, 6]
arr.map((item, index, arr) => {
console.log(item, index, arr) // 1 0 [1, 2, 3]
// 2 1 [1, 2, 3]
// 3 2 [1, 2, 3]
})
// flatMap
// arr.flatMap(x => [x * 2]) === arr.map(x => x * 2)
const flatMapArr = arr.flatMap(x => [x * 2])
console.log(flatMapArr) // [2, 4, 6]
複製代碼
Array.reduce() —— reduce方法與map最大的不一樣是不返回新的數組,其返回的是一個計算值,參數爲回調函數與回調函數參數pre初始值,回調函數中參數爲pre與next,當在默認狀況時,pre爲數組中第一個值,next爲數組中第二個值,回調函數返回值能夠滾雪球般更改pre值;而當index設置數值後,pre初始值爲參數值,next從數組中第一個值一直取到數組最後一位。
例子以下:
const arr = [1, 2, 3, 4, 5]
const result = arr.reduce((pre, next) => {
console.log(pre, next) // 1 2
// 3 3
// 6 4
// 10 5
return pre + next
})
console.log(result) // 15
arr.reduce((pre, next) => {
console.log(pre, next) // 9 1
// 9bala 2
// 9balabala 3
// 9balabalabala 4
// 9balabalabalabala 5
return pre += 'bala'
}, 9)
複製代碼
Array.filter() —— 返回值是一個數組,第一個參數爲回調函數,第二個參數爲回調函數中this指向。回調函數的參數有value,index及arr。知足回調函數的中過濾條件的,會被push到返回值中新的數組中。
Array.find() —— 返回值是數組內的一個值,該方法返回數組內知足條件的第一個值,第一個參數爲回調函數,第二個參數爲回調函數中this指向。回調函數的參數有查找到的符合條件前的value,index及arr。當查找的是數組中不可重複的值時,建議使用find方法,會比filter更優越。
Array.findIndex() —— 返回值爲Number,該方法返回數組內知足條件的第一個值在數組中的index,第一個參數爲回調函數,第二個參數爲回調函數中this指向。回調函數中的參數與find方法相似。
例子以下:
const arr = [1, 2, 3, 4, 5]
const obj = {num: 3}
// filter
const filterArr = arr.filter(function (value, index, arr) {
console.log(index, arr) // 0 [1, 2, 3, 4, 5]
// 1 [1, 2, 3, 4, 5]
// 2 [1, 2, 3, 4, 5]
// 3 [1, 2, 3, 4, 5]
// 4 [1, 2, 3, 4, 5]
return value > this.num
}, obj)
console.log(filterArr) // [4, 5]
// find
const findResult = arr.find(function (value, index, arr) {
console.log(index, arr) // 0 [1, 2, 3, 4, 5]
// 1 [1, 2, 3, 4, 5]
// 2 [1, 2, 3, 4, 5]
// 3 [1, 2, 3, 4, 5]
return value > this.num
}, obj)
console.log(findResult) // 4
// findIndex
const findIndexResult = arr.findIndex(function (value) { return value > this.num }, obj)
console.log(findIndexResult) // 3
複製代碼
Array.includes() —— 返回值爲Boolean值,其能夠簡單快捷的判斷數組中是否含有某個值。其第一個參數爲須要查找的值,第二個參數爲開始遍歷的位置,遍歷位置起始點默認爲0。相比於indexOf、filter、find及findIndex方法,includes方法更簡單快捷返回Boolean值進行判斷,其二對於數組中NaN值,includes能夠識別到NaN。
const arr = [1, 2, 3, NaN]
console.log(arr.includes(NaN)) // true
console.log(arr.includes(2, 2)) // false
複製代碼
Array.every() —— 返回值爲Boolean類型,相似於if判斷中的 && 條件符,當數組中每一個值都知足條件時返回true。其第一個參數爲回調函數,第二個參數爲回調函數的this指向。回調函數的參數爲對比結果爲true的value,index及arr,到碰到false中止。
Array.some() —— 返回值爲Boolean類型,相似於if判斷中的 || 條件符,當數組中存在任意一個值知足條件時返回true。其參數與every方法相同,但回調函數的參數,some方法爲對比結果爲false的value,index及arr,到碰到true中止。
例子以下:
// every
const arr = [1, 2, 3, 4, 5]
const obj = { num: 3 }
const everyResult = arr.every(function(value, index, arr) {
console.log(index, arr) // 0 [1, 2, 3, 4, 5]
// 1 [1, 2, 3, 4, 5]
// 2 [1, 2, 3, 4, 5]
return value < this.num
}, obj)
console.log(everyResult) // false
// some
const someResult = arr.some(function(value, index, arr) {
console.log(index, arr) // 0 [1, 2, 3, 4, 5]
// 1 [1, 2, 3, 4, 5]
// 2 [1, 2, 3, 4, 5]
// 3 [1, 2, 3, 4, 5]
return value > this.num
}, obj)
console.log(someResult) // true
複製代碼
感謝以上做者大大 ღ( ´・ᴗ・` )比心
第二篇文章,完結撒花 (✧◡✧)