JS-每週3道面試題(第二週)

第二週,慢慢積累,作好準備,迎接蛻變node

1. 介紹下 Set、Map、WeakSet 和 WeakMap 的區別?

  • set

    • Set自己是一個構造函數,用來生成 Set 數據結構。
    • 成員惟1、無序且不重複。
    • [value, value],鍵值與鍵名是一致的(或者說只有鍵值,沒有鍵名)。
    • 能夠遍歷,方法有:add、delete、has等等。
const set = new Set([1,2,2,3,3,3])
[...set]    // [1,2,3]
複製代碼

    set實例的屬性和方法

  • 屬性數組

    • Set.prototype.constructor: 構造函數,默認就是Set函數。
    • Set.prototype.size: 返回Set實例的成員總數。
  • 操做方法bash

    • add(val): 添加某個值,返回Set結構自己。
    • delete(val): 刪除某個值,返回一個布爾值,表示刪除是否成功。
    • has(val): 返回一個布爾值,表示該值是否爲Set的成員。
    • clear(): 清除全部成員,沒有返回值。
  • 遍歷方法數據結構

    • keys(): 返回鍵名的遍歷器。
    • values(): 返回鍵值的遍歷器。
    • entries(): 返回鍵值對的遍歷器。
    • forEach(): 使用回調函數遍歷每一個成員。
  • WeakSet

    • WeakSet 結構與 Set 相似,也是不重複的值的集合。
    • 成員只能是對象,而不能是其餘類型的值。
    • WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用
    • WeakSet 的成員是不適合引用的,由於它會隨時消失,因此不可遍歷
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}
複製代碼
  • 方法函數

    • WeakSet.prototype.add(value): 向 WeakSet 實例添加一個新成員。
    • WeakSet.prototype.delete(value):清除 WeakSet 實例的指定成員。
    • WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在 WeakSet 實例之中。
  • Map

ES6 提供了 Map 數據結構。它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。能夠遍歷,方法不少,能夠和各類數據格式轉換ui

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

const map = new Map([
  ['name', '張三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "張三"
map.has('title') // true
map.get('title') // "Author"
複製代碼

任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構均可以看成Map構造函數的參數。這就是說,Set和Map均可以用來生成新的 Map。spa

const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1

const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
複製代碼
  • 實例的屬性和方法
    • size屬性:返回 Map 結構的成員總數。
    • set(key, value)方法:設置鍵名key對應的鍵值爲value,而後返回整個 Map 結構。
    • get(key)方法:讀取key對應的鍵值,若是找不到key,返回undefined。
    • has(key)方法:返回一個布爾值,表示某個鍵是否在當前 Map 對象之中。
    • delete(key)方法:刪除某個鍵,返回true。若是刪除失敗,返回false。
    • clear()方法:清除全部成員,沒有返回值。
  • 遍歷方法
    • keys(): 返回鍵名的遍歷器。
    • values():返回鍵值的遍歷器。
    • entries():返回全部成員的遍歷器。
    • forEach():遍歷 Map 的全部成員。
const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同於使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"
複製代碼

Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...)。例如prototype

const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map.keys()] // [1,2,3]
複製代碼
  • WeakMap

    • WeakMap結構與Map結構相似,也是用於生成鍵值對的集合。
    • WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名。
    • WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
    • 不能遍歷,方法同get,set,has,delete

WeakMap的專用場合就是,它的鍵所對應的對象,可能會在未來消失。WeakMap結構有助於防止內存泄漏。指針

2. 介紹下深度優先遍歷和廣度優先遍歷,如何實現?

這是一個須要遍歷的DOM樹code

<div id="parent">

    <div id="child-1">
      <div id="child-1-1">
        <div id="child-1-1-1"></div>
        <div id="child-1-1-2"></div>
        <div id="child-1-1-3"></div>
      </div>

      <div id="child-1-2">
        <div id="child-1-2-1"></div>
        <div id="child-1-2-2"></div>
      </div>

      <div id="child-1-3"></div>
    </div>

    <div id="child-2"></div>

    <div id="child-3"></div>

  </div>
複製代碼
  • 深度優先遍歷DFS
let deepTraversal = (node) => {
        let nodes = []
        if (node !== null) {
          nodes.push(node)
          let children = node.children
          for (let i = 0; i < children.length; i++) {
            nodes = nodes.concat(deepTraversal(children[i]))
          }
        }
        return nodes
      }
    
    let res = document.querySelector('#parent')
    
    console.log(deepTraversal(res))
複製代碼

結果

圖片

假設初始狀態是圖中全部頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點而後依次從它的各個未被訪問的鄰接點出發深度優先搜索遍歷圖,直至圖中全部和v有路徑相通的頂點都被訪問到。若此時尚有其餘頂點未被訪問到,則另選一個未被訪問的頂點做起始點,重複上述過程,直至圖中全部頂點都被訪問到爲止。

  • 廣度優先遍歷BFS
let widthTraversal = (node) => {
      let nodes = []
      let stack = []
      if (node) {
        stack.push(node)
        while (stack.length) {
          let item = stack.shift()
          let children = item.children
          nodes.push(item)
            // 隊列,先進先出
            // nodes = [] stack = [parent]
            // nodes = [parent] stack = [child1,child2,child3]
            // nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
            // nodes = [parent,child1,child2]
          for (let i = 0; i < children.length; i++) {
            stack.push(children[i])
          }
        }
      }
      return nodes
    }
複製代碼

結果

圖片

從圖中某頂點v出發,在訪問了v以後依次訪問v的各個不曾訪問過的鄰接點,而後分別從這些鄰接點出發依次訪問它們的鄰接點,並使得「先被訪問的頂點的鄰接點先於後被訪問的頂點的鄰接點被訪問,直至圖中全部已被訪問的頂點的鄰接點都被訪問到。 若是此時圖中尚有頂點未被訪問,則須要另選一個不曾被訪問過的頂點做爲新的起始點,重複上述過程,直至圖中全部頂點都被訪問到爲止。

3. 淺拷貝和深拷貝

引用網上一張圖片說明一下數據類型和淺拷貝和深拷貝的關係

JS中的值有兩種類型:

  1. 基本類型:string、number、boolean、undefined、null
  2. 複雜基本類型:object

存放位置:

  1. 基本類型的值存放在棧(stock)中,大小肯定,而且是不能夠改變的。棧(stack)爲自動分配的內存空間,它由系統自動釋放
  2. 複雜基本類型的值存放在堆中,大小不肯定,能夠改變,在棧中保存的是指向堆中真正值的地址指針。堆(heap)則是動態分配的內存,大小不定也不會自動釋放。

賦值操做、淺拷貝與深拷貝的關係

  • 基本數據類型的賦值(=)是在內存中新開闢一段棧內存,是兩個徹底無關的對象。
var a = 1
var b = a
a++
console.log(a)  // 2
console.log(b)  // 1
複製代碼
  • 引用類型的賦值是傳址,也就是說引用類型的賦值是對象保存在棧中的地址的賦值,這樣的話兩個變量就指向同一個對象,所以二者之間操做互相有影響。
var a = {num:1}
var b = a

a.num = 2
console.log(a.num)  // 2
console.log(b.num)  // 2
複製代碼
  • 將源對象拷貝到目標對象中,但不包括源對象的子對象

  • 將源對象拷貝到目標對象中,包括源對象的子對象

對於複雜基本類型的數據,三種操做的對比

操做 和原數據是否指向同一對象 第一層數據爲基本數據類型 原數據中包含子對象
賦值 改變會使原數據改變 改變會使原數據改變
淺拷貝 改變不會使原數據改變 改變會使原數據改變
深拷貝 改變不會使原數據改變 改變不會使原數據改變

實現淺拷貝

  1. Object.assign(target, source, source...)
function shallowCopy(src) {
        var dst = {};
        for (var prop in src) {
            if (src.hasOwnProperty(prop)) {
                dst[prop] = src[prop];
            }
        }
        return dst;
    }
複製代碼

實現深拷貝

JSON.parse(JSON.stringify(obj))實現深拷貝是有侷限性的,當數據中有function或undefined等數據時,是不能實現的。

function deepClone(obj) {
        // 由於typeof null == "object" 但 null並非對象,是基本類型
        if (obj == null) return
        var res = Array.isArray(obj) ? [] : {}
        
        for (key in obj) {
            if (obj.hasOwnProperty(key)) {
                if ( typeof obj[key] == "object" ) {
                    res[key] = deepClone(obj[key])
                } else {
                    res[key] = obj[key]
                }
            }
        }
        return res
    }
複製代碼

驗證一下

var source = {
        name: 'source',
        do: function (n) {
          console.log(n)
        },
        obj: {
          name: '子對象obj'
        }
      }
    
      var target = deepClone(source)
    
      target.name = 'target'
      target.do = function (n) {
        console.log(n * 2)
      }
      target.obj.name = '拷貝子對象成功'
    
      console.log(source)
      console.log(target)
複製代碼

深拷貝

能夠看到修改了拷貝對象的子對象並無對源對象產生影響,拷貝成功。

歡迎指正和交流

相關文章
相關標籤/搜索