2019秋招知識盲點總結

開始

儘管秋招尚未拿到offer(好難過),可是一些知識點仍是要總結的,既然本身選了這條路,那就必定要堅決不移的走下去......javascript

注意 new 運算符的優先級

function Foo() {
    return this;
}
Foo.getName = function () {
    console.log('1');
};
Foo.prototype.getName = function () {
    console.log('2');
};

new Foo.getName();   // -> 1
new Foo().getName(); // -> 2

經過這段代碼能夠看出:new Foo() 的優先級高於 new Foo.css

對於代碼1 來講:是將 Foo.getName 當成一個構造函數來執行,執行構造函數因此輸出爲1.html

對於代碼2來講:經過 new Foo() 建立了一個Foo的實例,經過實例訪問其原型鏈上的 方法因此輸出爲2.前端

注意非匿名的當即執行函數

var foo = 1;
 
// 有名當即執行函數
(function foo() {
    foo = 10;
    console.log(foo);
})();

// 執行這段代碼會輸出什麼呢?

// -> ƒ foo() { foo = 10 ; console.log(foo) }

// 再去訪問 foo 的值
foo
// -> 1

當JS執行器遇到非匿名的當即執行函數時,會建立一個輔助的特定對象,而後將函數名稱做爲這個對象的屬性,所以行數內部才能夠訪問到 foo ,但這個值是隻讀的,因此對其從新賦值不會生效,因此打印結果仍是這個函數,而且外部的值也沒有發生改變。java

關於對象的深拷貝

  • 可使用 JSON.stringifyJSON.parse 這個兩個方法node

    優勢:簡單web

    缺點:會忽略掉 undefined ; 不能序列化函數 ; 不能解決循環引用的對象面試

    function clone(obj) {
        return JSON.parse(JSON.stringify(obj));
    }
  • 使用遞歸循環賦值的方式ajax

    優勢:能夠處理 undefined、函數等各類狀況算法

    缺點:實現相對麻煩,效率不高

    function clone(obj) {
        if(!obj || typeof obj !== 'object') {
            return;
        }
        var _obj = obj.constructor === Object ? {} : [];
        for(let key in obj) {
            if(typeof obj[key] === 'object') {
                _obj[key] = clone(obj[key]);
            } else {
                _obj[key]  = obj[key];
            }
        }
        return _obj;
    }
    
    // 或者
    
    function clone(obj) {
        if(!obj || typeof obj !== 'object')
            throw new TypeError('params typeError');
        let _obj = obj.constructor === Object ? {} : [];
        Object.getOwnPropertyNames(obj).forEach(name => {
            if(typeof obj[name] === 'object') {
                _obj[name] = clone(obj[name]);
            } else {
                _obj[name] = obj[name];
            }
        });
        return _obj;
    }
  • 使用內置 MessageChannel 對象

    優勢:是內置函數中處理深拷貝性能最快的

    缺點:不能處理函數(會報錯)

    function clone(obj) {
        return new Promise(resolve => {
            let {port1, port2} = new MessageChannel();
            port2.onmessage = ev => resolve(ev.data);
            port1.postMessage(obj);
        });    
    }
    
    clone(obj).then(console.log);

關於async/await , promise 異步執行順序

想解決這個問題,就必須知道 `await` 作了什麼?
剛開始覺得 await 會一直等待表達執行的執行結果,以後纔會執行後面的代碼。 實際上 await 是一個讓出線程的標誌(遇到 await 會當即返回一個 pending 狀態的promise)。await後面的函數會先執行一遍,而後就會跳出整個 async 函數來執行後面js代碼。等本輪事件循環執行完又跳回到 async 函數中等待await後面表達式的返回值,若是返回值爲非 promise 則繼續執行async後面的代碼,不然將 promse 加入隊列。

且看一道面試題(分析代碼執行 順序):

async function async1() {
    console.log("async1 start");
    await async2();
    console.log("async1 end");
}

async function async2() {
   console.log("async2");
}

console.log("script start");

setTimeout(function () {
    console.log("settimeout");
},0);

async1();

new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
});

console.log("script end");

OK,那接下來具體分析執行過程:

首先輸出 "script start" ,而後當即將定時器加入異步事件隊列。執行 async1() ,輸出 "async1 start" ,進入 async2() ,輸出 "async2" ,跳出整個 async1() 函數來執行後面js代碼,執行promise執行器中的內容,輸出 "promise1" ,執行resolve()回調,將then函數中的內容加入異步事件隊列,接着輸出 "script end" 。回到 async1() 的await等待 async2() 函數的返回值,由於返回值是一個promise實例,將promise加入異步事件隊列。此時的同步代碼執行完畢,輪詢並從隊列拿出代碼放入主線程執行,因此輸出 "promise2" ,繼續執行 async1() 中的後續內容,輸出 "async1 end" ,最後取出定時器中的內容並執行,輸出 "settimeout"

綜上所述:

script start
async1 start
async2
promise1
script end
promise2
async1 end
settimeout

那麼再看一個例子應該會簡單不少:

function testFunc() {
    console.log("testFunc..."); // 2
    return "testFunc";
}

async function testAsync() {
    console.log("testAsync...");  // 7
    return Promise.resolve("hello testAsync");
}

async function foo() {
    console.log("test start..."); // 1
    const v1 = await testFunc();
    connsole.log('hello world.'); // 5
    console.log(v1);              // 6 testFunc
    const v2 = await testAsync();
    console.log(v2);              // 9 hello testAsync
}

foo();

var promise = new Promise(resolve => { 
    console.log("promise start..");  // 3
    resolve("promise"); 
});
promise.then(val => console.log(val)); // 8 promise

console.log("test end..."); // 4

防抖和節流

  • 防抖:若是用戶屢次調用且間隔小於wait值,那麼就會被轉化爲一次調用。
  • 節流:屢次執行函數轉化爲,每隔必定時間(wait)調用函數 。

一個簡單的防抖函數:

function debounce(func, wait) {
    let timer = null;
    return function(...params) {
        // 若是定時器存在則清除
        if(timer){
            clearTimeout(timer);
        }
        // 從新開始定時執行
        timer = setTimeout(() => {
            func.apply(this, params);
        }, wait);
    }
}

缺點:只能在最後執行,不能當即被執行,在某些狀況下不適用。

改進...

function debounce(func, wait, immediate) {
    
    let timer, context, args;
    
    // 定時器
    let later = function() {
        return setTimeout(() => {
            timer = null;
            if(!immediate) {
                func.apply(context, args);
            }
        }, wait);
    }
    
    return function(...params) {
        if(!timer) {
            timer = later();
            
            // immediate 爲 true,則當即執行
            // 不然 緩存上下文 和 參數
            if(immediate) {
                func.apply(this, params);
            } else {
                context = this;
                args = params;
            }
        } else {
            clearTimeout(timer);
            timer = later();
        }           
    }
}

一個簡單的節流函數:

// 節流函數
// 快速的屢次執行,轉化爲等待wait時間再去執行
function throttle(func, wait) {
    var timer = null;
    var context = null;
    return function(...args) {
        context = this;
        if(!timer) {
            timer = setTimeout(function() {
                timer = null;
                func.apply(context, args);
            }, wait);
        }
    }
}

// 若是想讓第一次調用當即執行也很是簡單
僅須要將  func.apply(context, args) 提到定時器外邊便可。

節流函數除了可使用定時器實現之外,固然也能夠有其餘方式:

// 第一次調用會被當即執行
function throttle(func, wait) {
    var prev = 0;
    var context = null;
    return function(...args) {
        var now = +new Date();
        context = this;
        if(now -prev > wait) {
            func.apply(context,args);
            prev = now;
        }    
    }
}

call、apply和bind

怎麼去模擬一個call函數呢?

思路:call一個很是重要的做用就是改變上下文環境也就是this,咱們能夠給用戶傳入的上下文對象上添加一個函數,經過這個上下文對象去執行函數,而後將這個函數刪除,返回結果就能夠了。

Function.prototype.myCall = function(context, ...args) {
    context = context || window;
    // 給上下文對象上添加這個函數
    context.fn = this;
    // 經過這個上下文對象去執行函數
    let result = context.fn(...args);
    // 將這個函數刪除
    delete  context.fn;
    return result;
}

call既然都實現了,那麼apply也是相似的,只不過傳入的參數是一個數組而已。

Function.prototype.myApply = function(context, arr) {
    context = context || window;
    arr = arr || [];
    let type = {}.toString.call(arr).slice(8,-1);
    if(type !== 'Array')
        throw new TypeError('CreateListFromArrayLike called on non-object');
    context.fn = this;
    let result = context.fn(...arr);
    delete context.fn;
    return result;
}

模擬bind函數,bind函數應該返回一個新的函數。

Function.prototype.myBind  = function(context, ...args) {        
    // 保存當前的函數
    let func = this;
    return function F(...params) {
        if(this instanceof F) {
            return new func(...args, ...params);
        }
        return func.apply(context,[...args,...params]);
    }    
}

數組降維

function flattenDeep(arr) {
    if(!Array.isArray(arr))
        return [arr];
    return arr.reduce((prev,cur) => {        
        return [...prev, ...flattenDeep(cur)];
    },[]);
}

flattenDeep([1, [[2], [3, [4]], 5]]);

棧的壓入和彈出

輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能爲該棧的彈出順序。假設壓入棧的全部數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不多是該壓棧序列的彈出序列。

function IsPopOrder(pushV,popV){
    if(pushV.length === 0) return false;
    var stack = []; // 模擬棧
    for(var i = 0, j = 0; i < pushV.length;){
        stack.push(pushV[i]);
        i += 1;
        // 壓入棧的值須要被彈出
        while(j < popV.length && stack[stack.length-1] === popV[j]){
            stack.pop();
            j++;
            if(stack.length === 0) break;
        }
    }
    return stack.length === 0;
}

利用棧模擬隊列

思路:

  • 對棧A添加數據。
  • 若是棧B爲空,循環將棧A中內容彈出放入棧B,並彈出棧B最後一項
  • 若是棧B不爲空,則直接彈出棧B的最後一項
var stackA = [];
var stackB = [];

function push(node){
    stackA.push(node);
}
function pop(){
    if(!stackB.length){
        while(stackA.length){
            stackB.push(stackA.pop());
        }
    }
    return stackB.pop();
}

Fetch和ajax之間的區別

fetch

  • Fetch API是基於Promise設計的
  • 容易同構(先後端運行同一套代碼)
  • 語法簡潔,更加語義化
  • 原生支持率不高,能夠用polyfill兼容IE8+瀏覽器
fetch(url).then(function(response){
    return response.json();
}).then(function(data){
    console.log(data);
}).catch(function(err){
    console.log(err);
});

ajax

  • 設計粗糙,不關注分離原則
  • 基於事件的異步模型,不夠友好
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';

xhr.onload = function(){
    console.log(xhr.response);
}
xhr.onerror = function(){
    console.log('error');
}

xhr.send();

Fetch常見坑

  • fetch請求默認是不帶cookie的,須要設置 fetch(url, {credentials: 'include'})
  • 服務器返回400,500錯誤碼時不會reject,只有網絡錯誤致使不能完成時,纔會reject。
  • IE8, 9 的 XHR 不支持 CORS 跨域。

歸併排序

將一個完整的數組分紅兩部分,分別對其排序,而後將兩部分merge在一塊兒便可。

function merge(left, right) {
    var temp = [];
    while(left.length && right.length) {        
        if(left[0] < right[0])
            temp.push(left.shift());
        else
            temp.push(right.shift());        
    }
    return temp.concat(left,right);
}

function mergeSort(arr) {
    if(arr.length === 1)
        return arr;
    var mid = (arr.length/2)|0;
    var left = arr.slice(0,mid);
    var right = arr.slice(mid);    
    return merge(mergeSort(left), mergeSort(right));
}

箭頭函數的this原理

this 指向固定化,並非由於箭頭函數內部有綁定 this 的機制,實際緣由是箭頭函數沒有本身的 this ,致使內部的this就是外層代碼的 this ,也正是由於沒有 this ,因此箭頭函數不能用做構造函數。

js相關尺寸

iCtsdx.png

BFC原理

  • 在BFC垂直方向元素邊距會發生重疊
  • 不會與浮動元素的box重合
  • 獨立的容器,裏外互不影響
  • 浮動元素參與計算

    自定義事件

var content = document.querySelector('.content');
    // 自定義事件
    var evt = new Event('custom');
    var customEvt = new CustomEvent('customEvt', {
        // 經過這個屬性傳遞參數
        detail: {
            name: 'tom',
            age: 12
        }
    });
    content.addEventListener('custom', (e) => {
        console.log('自定義事件被觸發,無參數...');
        console.log(e);
    });
    content.addEventListener('customEvt', (e) => {
        console.log('自定義事件被觸發,有參數...');
        console.log(e);
        console.log(e.detail);
    });
    // 點擊時觸發這個自定義事件
    content.addEventListener('click', (e) => {
        content.dispatchEvent(evt);
        content.dispatchEvent(customEvt);
    });

變量提高

var foo = 3; // 不在同一個做用域

function hoistVariable() {
    // 內部變量提高致使 foo 的初始值爲undefined
    // 因此 foo = 5;
    var foo = foo || 5;
    console.log(foo); // 5
}

hoistVariable();

上邊的比較簡單,看一個函數和變量同名,關於變量提高的小問題。

var a = 6;
function b(){
    console.log(a); // @1
    var a = 4;
    function a(){
        alert(4);
    }
    console.log(a); //@2
}
b();
  • 由於JavaScript中的函數是一等公民,函數聲明的優先級 最高(高於變量提高),會被提高至當前做用域最頂端,因此在 @1 輸出的是 function a(){alert(4);}
  • 接下來執行 a=4; 這一句,從新對 a 進行賦值。
  • 函數已被提高,因此不考慮,因此在 @2 這裏天然會輸出 4

若是還不能理解?且看預編譯後的代碼:

var a;
a = 6;
function b(){
    var a; 
    a = function a(){ // 函數先提高
        alert(4);
    }
    console.log(a); // @1
    a = 4;
    console.log(a); // @2    
}
b(); // 結果已經很是明瞭了

POST和GET的區別

  • POST對請求參數的長度沒有限制,而GET若是請求參數過長,會被瀏覽器截斷。
  • GET請求參數會直接暴露在URL上,因此不適合用來傳遞敏感信息。
  • GET請求能夠被瀏覽器主動緩存,而POST請求不能夠,除非手動設置。
  • GET請求在瀏覽器回退時是無害的,而POST會再次提交請求。
  • GET請求產生的URL能夠被瀏覽器緩存,而POST不能夠。

通訊類

1. 先後端如何通訊?

  • ajax
  • WebSocket
  • CORS

2. 如何建立ajax?

// 建立xhr對象
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');

// GET 請求
xhr.open('GET',url,true);
xhr.send();

// POST 請求
xhr.open('POST',url,true);
// 表單數據 , 也能夠提交json數據,相應的content-Type: application/json
xhr.setRequestHeader('content-Type', 'application/x-www-from-urlencoded');
xhr.send(dataArr.join('&'));

xhr.onload = function() {
    if(xhr.status === 200 || xhr.status === 304) {    
        var data = xhr.responseText;
        // 拿到數據
    } else {
        // 出問題了
    }
}

3. 跨域通訊的幾種方式?

  • JSONP:利用 script 標籤的跨域能力,服務返回一個js函數調用,數據做爲函數的一個參數來傳遞。

    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url; // 跨域地址
    document.head.appendChild(script);
    
    //有能耐把我這輩子都安排了,否則少他媽扯淡。
    setTimeout(function() {
        document.head.removeChild(script);
        script = null;
    });
    
    // 接收數據
    function jsonpCallback(data) {
        console.log(data);
    }
  • WebSocket:不受同源政策限制。

    var ws = new WebSocket('wss://echo.websocket.org');
    ws.onopen = function(e) {
        ws.send('hello...');
    }
    ws.onmessage = function(e) {
        var data = e.data;
    }
    ws.onclose = function() {
        console.log('close...');
    }
  • Hash:利用 location.hash 來傳值。 缺點:數據直接暴露在url中,大小、類型都有限制。

    一、父窗體能夠把信息寫在子窗體的href的hash上,子窗口經過監聽hashchange事件獲取信息。

    二、子窗體改變父窗體的hash值,那麼就要藉助第三個子窗體,第三個子窗體是第二個子窗體的子窗體。(第三個子窗體要與父窗體同源)

    三、第二個子窗體把信息設置在第三個子窗體的hash值上,而後第三個子窗體改變父窗體的hash值,從而實現跨域。

    iPQsr6.png

    // 父窗體
    var son = document.getElementByTagName('iframe');
    son.src = son.src + '#' + data;
    
    // 子窗體
    window.onhashchange = function() {
        var data = window.location.hash;
    }
  • postMessage :語法:window.postMessage(msg,targetOrigin)

    // 窗口A 發送
    BWindow.postMessage('發送的數據', 'http://B.com');
    
    // 窗口B 接收
    window.addEventListener('message', (event) => {
       event.origin: // http://A.com
       event.source; // AWindow
       event.data;   // '發送的數據' 
    });
  • CORS: 跨域資源共享。

    fetch(url, {
        method: 'get',
        // 頭信息配置
    }).then(() => {});

安全類

  • CSRF,跨站請求僞造(Cross-site request forgery)

    iP1PXt.png

    防護措施:

    Token驗證(請求必須攜帶Token)

    Referer 驗證(驗證請求的來源是否可信)

  • XSS(cross-site scripting 跨站腳本攻擊)

    原理:注入腳本

    防護措施:對用戶的輸入作驗證

渲染機制類

1. 什麼是DOCTYPE及做用?

用來聲明文檔類型和DTD規範的。

DTD(document type definition)文檔類型定義,是一系列的語法規則,用來聲明XML或(X)HTML的文件類型,瀏覽器會使用它來決定文檔類型,決定使用何種協議來解析,以及切換瀏覽器模式。

2. 常見的doctype有哪些?

  • HTML5 <!DOCTYPE html>
  • HTML4.01 Strict 嚴格模式 (不包含展現性或棄用的元素)
  • HTML4.01 Transitional 傳統(寬鬆)模式(包含展現性或棄用的元素)

頁面性能類

提高頁面性能的方式?

  • 資源壓縮合並,減小HTTP請求
  • 非核心代碼異步加載

    異步加載方式:

    1. 動態腳本加載
    2. defer (HTML解析完順序執行)
    3. async (加載完當即執行)
  • 利用瀏覽器緩存
  • 使用CDN
  • 預解析DNS

    <meta http-equiv="x-dnns-prefetch-control" content="on">
    <link rel="dns-prefetch" href="//host_name_to_prefetch.com">

錯誤監控

  • 前端錯誤分類:代碼(運行)錯誤 資源加載錯誤
  • 錯誤捕獲方式

    運行錯誤捕獲:(1)try...catch (2)window.onerror

    資源加載錯誤 :(1)object.onerror(資源錯誤不冒泡) (2)performance.getEntries() (3)Error事件捕獲(在事件流捕獲階段處理錯誤)

  • 跨域js運行錯誤也是能夠捕獲的,捕獲的錯誤:script error
  • 上報錯誤原理

    利用Image對象上報

    // 利用Image標籤上報錯(簡單、不須要藉助其餘庫)
    (new Image()).src = 'http://www.baidu.com/test?error=xxx';

二分法查找

function binarySearch(arr,val,leftIndex,rightIndex) {
    if(leftIndex > rightIndex){ return; }
    var midIndex = (leftIndex + rightIndex) / 2 | 0;
    var midVal = arr[midIndex];
    if(val > midVal) {
        return binarySearch(arr,val,midIndex+1,rightIndex);
    }else if(val < midVal) {
        return binarySearch(arr,val,leftIndex,midIndex-1);
    }else{
        return midIndex;
    }
}

連續最長不重複字符串

在一個字符串中找出連續的不重複的最大長度的字符串,解決這類問題的思路:

  • 利用循環疊加字符串,直到出現重複爲止
  • 每一次疊加,記錄下來最大長度的字符串
// 連續最長不重複字符串
function getMaxLenStr(str) {
    var cur = [];
    var maxLenStr = '';
    for(var i = 0; i < str.length; i++) {
        if(!cur.includes(str[i])) {
            cur.push(str[i]);
        } else {
            cur = []; // 置爲空
            cur.push(str[i]);
        }
        
        // 存儲最大長度的字符串
        if(maxLenStr.length < cur.length) {
            maxLenStr = cur.join('');
        }        
    }
    return maxLenStr;
}

getMaxLenStr('ababcabcde'); // abcde

和上面這道題有同樣思路的是:求一個數組當中,連續子向量的最大和。

無非是將 對比字符串的長度 改成 對比值大小

function FindGreatestSumOfSubArray(arr) {
    let sum = arr[0];
    let max = arr[0];
    for(let i = 1; i < arr.length; i++) {
        if(sum < 0) {
            sum = arr[i];
        }else{
            sum += arr[i];
        }
        // 記錄最大值
        if(max < sum) {
            max = sum;
        }
    }
    return max;
}

面試題:解碼字符串並輸出

阿里的一道面試題:給定一個編碼字符,按編碼規則進行解碼,輸出字符串

編碼規則:coount[letter] ,將letter的內容count次輸出,count是0或正整數,letter是區分大小寫的純字母。

實例:

  • const s= 3[a]2[bc]; decodeString(s); // 返回 ‘aaabcbc’
  • const s= 3[a2[c]]; decodeString(s); // 返回 ‘accaccacc’
  • const s= 2[ab]3[cd]ef; decodeString(s); // 返回 ‘ababcdcdcdef’

解題過程...

  • 思路:

    使用棧這種數據結構,若是push的內容爲‘]’,則循環pop字符,直到碰到’[‘,而後將pop

出來的字符串按規則整理後,從新push進棧中,最後將棧內的內容拼接成字符串輸出便可。

  • 代碼:

    const s = '2[a2[c]]ef';
    function decodeString(str) {
        let stack = []; // 存儲字符串的棧
        for (let i = 0; i < str.length; i++) {
            let cur = str[i];
            if (cur !== ']') {
                stack.push(cur);
            } else { // 彈出
                let count = 0;
                let loopStr = [];
                let popStr = '';
                while ((popStr = stack.pop()) !== '[') {
                    loopStr.unshift(popStr);
                }
                count = stack.pop();
                // 添加結果
                let item = '';
                for (let i = 0; i < count; i++) {
                    item += loopStr.join('');
                }
                stack.push(...(item.split('')));
            }
        }
        return stack.join('');
    }
    console.log(decodeString(s)); // accaccef

排序算法時間複雜度

  • 元素的移動次數與關鍵字的初始排列次序無關的是:基數排列。
  • 元素的比較次數與初始序列無關是:選擇排序。
  • 算法的時間複雜度與初始序列無關的是:選擇排序。

BOM和DOM

BOM 即瀏覽器對象模型,BOM沒有相關標準,BOM的核心對象是window對象。

DOM即文檔對象模型,DOM是W3C標準,DOM的最根本對象是document(window.document), 這個對象其實是window對象的屬性,這個對象的獨特之處是惟一一個既屬於BOM有屬於DOM的對象。

http和WebSocket的區別:

  • 相同點:

都是創建於tcp鏈接之上,經過tcp協議來傳輸數據。

  • 不一樣點:

HTTP是一種單向的協議,即客戶端只能向服務器端請求信息。request永遠等於response,而且這個response是被動的,不能主動發起。一旦有一個任務超時,就會阻塞後續的任務(線頭阻塞)。

HTTP協議是無狀態的,如使用輪詢、long poll都須要將身份鑑別信息上傳。

WebSocket真正的全雙工通訊,只須要一次鏈接,這樣就避免了HTTP的無狀態性,服務器端能夠主動推送消息到達客戶端。

http2.0

  • 二進制分幀
  • 多路複用,這樣就避免了線頭阻塞
  • 服務器端推送,客戶端請求HTML,服務器能夠主動推送js、css那些客戶端可能會用到的東西,這樣就避免了重複發送請求
  • 頭壓縮

O(n)複雜度去重

function unique(arr) {
    let obj = {};
    let ret = [];
    for(let i = 0; i < arr.length; i++) {
        let cur = `${typeof arr[i]}-${arr[i]}`;
        if(!obj[cur]) {
            ret.push(arr[i]);
            obj[cur] = true;
        }
    }        
    return ret;
}

var arr = [1, '1', 3, 3, 4];
unique(arr);
// [1, '1', 3, 4]

判斷是否相同

function isSame(a, b) {
    if(a === b) return true;
    if(typeof a !== typeof b) return false;
    
    // 若是均爲object類型,判斷屬性個數是否相同
    if(typeof a === 'object' && typeof b === 'object') {
        let aLen = Object.getOwnPropertyNames(a).length;
        let bLen = Object.getOwnPropertyNames(b).length;
        if(aLen !== bLen) return false;
    }
    return Object.getOwnPropertyNames(a).every(key => {
        if(typeof a[key] === 'object') {
            return isSame(a[key], b[key]);
        }
        return a[key] === b[key];
    });
}


後記一波:

付出了總歸是有回報的。

  • 電話面試拿到了百度雲事業部實習生的offer。
  • 南京現場面試得到了上海微盟的offer。
  • 參加北京的面試獲得了迅雷的offer。簽約那天恰好是中秋節(2018/9/24),多是一份最棒的中秋大禮了吧😁。決定去迅雷了,因此,秋招之路到此結束......

但,學習是永無止境的。(先從作好總結開始)

相關文章
相關標籤/搜索