前端面試之手寫代碼

數組去重

(一維)數組去重最原始的方法就是使用雙層循環,分別循環原始數組和新建數組;或者咱們可使用indexOf來簡化內層的循環;或者能夠將原始數組排序完再來去重,這樣會減小一個循環,只須要比較先後兩個數便可;固然咱們可使用ES5,ES6的方法來簡化去重的寫法,好比咱們可使用filter來簡化內層循環,或者使用SetMap、擴展運算符這些用起來更簡單的方法,可是效率上應該不會比原始方法好。二維數組的去重能夠在上面方法的基礎上再判斷元素是否是數組,若是是的話,就進行遞歸處理。javascript

雙層循環

java

var array = [1, 1, '1', '1'];

function unique(array) { var res = []; for (var i = 0, arrayLen = array.length; i < arrayLen; i++) { for (var j = 0, resLen = res.length; j < resLen; j++ ) { if (array[i] === res[j]) { break; } } if (j === resLen) { res.push(array[i]) } } return res; }前端

複製代碼console.log(unique(array)); // [1, "1"] 複製代碼複製代碼

利用indexOf

var array = [1, 1, '1'];

function unique(array) {
    var res = [];
    for (var i = 0, len = array.length; i < len; i++) {
        var current = array[i];
        if (res.indexOf(current) === -1) {
            res.push(current)
        }
    }
    return res;
}

console.log(unique(array));
複製代碼複製代碼

排序後去重

var array = [1, 1, '1'];

function unique(array) {
    var res = [];
    var sortedArray = array.concat().sort();
    var seen;
    for (var i = 0, len = sortedArray.length; i < len; i++) {
        // 若是是第一個元素或者相鄰的元素不相同
        if (!i || seen !== sortedArray[i]) {
            res.push(sortedArray[i])
        }
        seen = sortedArray[i];
    }
    return res;
}

console.log(unique(array));
複製代碼複製代碼

filter

filter能夠用來簡化外層循環node

使用indexOf:jquery

github

var array = [1, 2, 1, 1, '1'];

function unique(array) { var res = array.filter(function(item, index, array){ return array.indexOf(item) === index; }) return res; }git

複製代碼console.log(unique(array)); 複製代碼複製代碼

排序去重:面試

var array = [1, 2, 1, 1, '1'];

function unique(array) {
    return array.concat().sort().filter(function(item, index, array){
        return !index || item !== array[index - 1]
    })
}

console.log(unique(array));
複製代碼複製代碼

ES6方法

Set:json

var array = [1, 2, 1, 1, '1'];

function unique(array) { return Array.from(new Set(array)); }c#

複製代碼console.log(unique(array)); // [1, 2, "1"] 複製代碼複製代碼

再簡化下

function unique(array) {
    return [...new Set(array)];
}

//或者
var unique = (a) => [...new Set(a)]
複製代碼複製代碼

Map:

function unique (arr) {
    const seen = new Map()
    return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}
複製代碼複製代碼

類型判斷

類型判斷須要注意如下幾點

  • typeof對六個基本數據類型UndefinedNullBooleanNumberStringObject(大寫)返回的結果是

    undefinedobjectbooleannumberstringobject(小寫),能夠看到NullObject 類型都返回了 object 字符串;typeof卻能檢測出函數類型;綜上,typeof能檢測出六種類型,可是不能檢測出null類型和Object下細分的類型,如ArrayFunctionDateRegExp,Error

  • Object.prototype.toString的做用很是強大,它能檢測出基本數據類型以及Object下的細分類型,甚至像 Math,JSON,arguments它都能檢測出它們的具體類型,它返回結果形式例如[object Number](注意最後的數據類型是大寫).因此,Object.prototype.toString基本上能檢測出全部的類型了,只不過有時須要考慮到兼容性低版本瀏覽器的問題。

通用API

// 該類型判斷函數能夠判斷六種基本數據類型以及Boolean Number String Function Array Date RegExp Object Error, // 其餘類型由於遇到類型判斷的狀況較少因此都會返回object,不在進行詳細的判斷 // 好比ES6新增的Symbol,Map,Set等類型 var classtype = {};

"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item) { classtype["[object " + item + "]"] = item.toLowerCase(); })

function type(obj) { // 解決IE6中null和undefined會被Object.prototype.toString識別成[object Object] if (obj == null) { return obj + ""; }

<span class="hljs-comment">//若是是typeof後類型爲object下的細分類型(Array,Function,Date,RegExp,Error)或者是Object類型,則要利用Object.prototype.toString</span>
<span class="hljs-comment">//因爲ES6新增的Symbol,Map,Set等類型不在classtype列表中,因此使用type函數,返回的結果會是object</span>
<span class="hljs-keyword">return</span> <span class="hljs-keyword">typeof</span> obj === <span class="hljs-string">"object"</span> || <span class="hljs-keyword">typeof</span> obj === <span class="hljs-string">"function"</span> ?
    classtype[<span class="hljs-built_in">Object</span>.prototype.toString.call(obj)] || <span class="hljs-string">"object"</span> :
    <span class="hljs-keyword">typeof</span> obj;
複製代碼
複製代碼<span class="hljs-comment">//若是是typeof後類型爲object下的細分類型(Array,Function,Date,RegExp,Error)或者是Object類型,則要利用Object.prototype.toString</span> <span class="hljs-comment">//因爲ES6新增的Symbol,Map,Set等類型不在classtype列表中,因此使用type函數,返回的結果會是object</span> <span class="hljs-keyword">return</span> <span class="hljs-keyword">typeof</span> obj === <span class="hljs-string">"object"</span> || <span class="hljs-keyword">typeof</span> obj === <span class="hljs-string">"function"</span> ? classtype[<span class="hljs-built_in">Object</span>.prototype.toString.call(obj)] || <span class="hljs-string">"object"</span> : <span class="hljs-keyword">typeof</span> obj; 複製代碼} 複製代碼複製代碼

判斷空對象

判斷是否有屬性,for循環一旦執行,就說明有屬性,此時返回false

function isEmptyObject( obj ) { var name; for ( name in obj ) { return false; } return true; } 複製代碼console.log(isEmptyObject({})); // true console.log(isEmptyObject([])); // true console.log(isEmptyObject(null)); // true console.log(isEmptyObject(undefined)); // true console.log(isEmptyObject(1)); // true console.log(isEmptyObject('')); // true console.log(isEmptyObject(true)); // true 複製代碼複製代碼

咱們能夠看出isEmptyObject實際上判斷的並不只僅是空對象。可是既然jQuery是這樣寫,多是由於考慮到實際開發中 isEmptyObject用來判斷 {} 和 {a: 1} 是足夠的吧。若是真的是隻判斷 {},徹底能夠結合上篇寫的 type函數篩選掉不適合的狀況。

判斷Window對象

Window對象有一個window屬性指向自身,能夠利用這個特性來判斷是不是Window對象

function isWindow( obj ) {
    return obj != null && obj === obj.window;
}
複製代碼複製代碼

判斷數組

isArray是數組類型內置的數據類型判斷函數,可是會有兼容性問題,一個polyfill以下

isArray = Array.isArray || function(array){
  return Object.prototype.toString.call(array) === '[object Array]';
}
複製代碼複製代碼

判斷類數組

jquery實現的isArrayLike,數組和類數組都會返回true。所若是isArrayLike返回true,至少要知足三個條件之一:

  1. 是數組

  2. 長度爲 0 好比下面狀況,若是咱們去掉length === 0 這個判斷,就會打印 false,然而咱們都知道 arguments 是一個類數組對象,這裏是應該返回 true

    function a(){
        console.log(isArrayLike(arguments))
    }
    a();
    複製代碼複製代碼
  3. lengths 屬性是大於 0 的數字類型,而且obj[length - 1]必須存在(考慮到arr = [,,3]的狀況)

function isArrayLike(obj) {
<span class="hljs-comment">// obj 必須有 length屬性</span>
<span class="hljs-keyword">var</span> length = !!obj &amp;&amp; <span class="hljs-string">"length"</span> <span class="hljs-keyword">in</span> obj &amp;&amp; obj.length;
<span class="hljs-keyword">var</span> typeRes = type(obj);

<span class="hljs-comment">// 排除掉函數和 Window 對象</span>
<span class="hljs-keyword">if</span> (typeRes === <span class="hljs-string">"function"</span> || isWindow(obj)) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}

<span class="hljs-keyword">return</span> typeRes === <span class="hljs-string">"array"</span> || length === <span class="hljs-number">0</span> ||
    <span class="hljs-keyword">typeof</span> length === <span class="hljs-string">"number"</span> &amp;&amp; length &gt; <span class="hljs-number">0</span> &amp;&amp; (length - <span class="hljs-number">1</span>) <span class="hljs-keyword">in</span> obj;
複製代碼
複製代碼<span class="hljs-comment">// obj 必須有 length屬性</span> <span class="hljs-keyword">var</span> length = !!obj &amp;&amp; <span class="hljs-string">"length"</span> <span class="hljs-keyword">in</span> obj &amp;&amp; obj.length; <span class="hljs-keyword">var</span> typeRes = type(obj); <span class="hljs-comment">// 排除掉函數和 Window 對象</span> <span class="hljs-keyword">if</span> (typeRes === <span class="hljs-string">"function"</span> || isWindow(obj)) { <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; } <span class="hljs-keyword">return</span> typeRes === <span class="hljs-string">"array"</span> || length === <span class="hljs-number">0</span> || <span class="hljs-keyword">typeof</span> length === <span class="hljs-string">"number"</span> &amp;&amp; length &gt; <span class="hljs-number">0</span> &amp;&amp; (length - <span class="hljs-number">1</span>) <span class="hljs-keyword">in</span> obj; 複製代碼} 複製代碼複製代碼

判斷NaN

判斷一個數是否是NaN不能單純地使用 === 這樣來判斷, 由於NaN不與任何數相等, 包括自身,注意在ES6isNaN中只有值爲數字類型使用NaN纔會返回true

isNaN: function(value){
  return isNumber(value) && isNaN(value);
}
複製代碼複製代碼

判斷DOM元素

利用DOM對象特有的nodeType屬性(

isElement: function(obj){
  return !!(obj && obj.nodeType === 1);
    // 兩次感嘆號將值轉化爲布爾值
}
複製代碼複製代碼

判斷arguments對象

低版本的瀏覽器中argument對象經過Object.prototype.toString判斷後返回的是[object Object],因此須要兼容

isArguments: function(obj){
  return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee'));
}
複製代碼複製代碼

深淺拷貝

若是是數組,實現淺拷貝,比能夠sliceconcat``返回一個新數組的特性來實現;實現深拷貝,能夠利用JSON.parseJSON.stringify來實現,可是有一個問題,不能拷貝函數(此時拷貝後返回的數組爲null)。上面的方法都屬於技巧,下面考慮怎麼實現一個對象或者數組的深淺拷貝

淺拷貝

思路很簡單,遍歷對象,而後把屬性和屬性值都放在一個新的對象就OK了

var shallowCopy = function(obj) {
    // 只拷貝對象
    if (typeof obj !== 'object') return;
    // 根據obj的類型判斷是新建一個數組仍是對象
    var newObj = obj instanceof Array ? [] : {};
    // 遍歷obj,而且判斷是obj的屬性才拷貝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}
複製代碼複製代碼

深拷貝

思路也很簡單,就是在拷貝的時候判斷一下屬性值的類型,若是是對象,就遞歸調用深淺拷貝函數就ok了

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}
複製代碼複製代碼

扁平化

遞歸

循環數組元素,若是仍是一個數組,就遞歸調用該方法

// 方法 1 var arr = [1, [2, [3, 4]]];

function flatten(arr) { var result = []; for (var i = 0, len = arr.length; i < len; i++) { if (Array.isArray(arr[i])) { result = result.concat(flatten(arr[i])) } else { result.push(arr[i]) } } return result; }

複製代碼console.log(flatten(arr)) 複製代碼複製代碼

toString()

若是數組的元素都是數字,可使用該方法

// 方法2 var arr = [1, [2, [3, 4]]];

function flatten(arr) { return arr.toString().split(',').map(function(item){ return +item // +會使字符串發生類型轉換 }) }

複製代碼console.log(flatten(arr)) 複製代碼複製代碼

reduce()

// 方法3
var arr = [1, [2, [3, 4]]];

function flatten(arr) {
    return arr.reduce(function(prev, next){
        return prev.concat(Array.isArray(next) ? flatten(next) : next)
    }, [])
}

console.log(flatten(arr))
複製代碼複製代碼

...

// 扁平化一維數組
var arr = [1, [2, [3, 4]]];
console.log([].concat(...arr)); // [1, 2, [3, 4]]

// 能夠扁平化多維數組
var arr = [1, [2, [3, 4]]];

function flatten(arr) {

    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }

    return arr;
}

console.log(flatten(arr))
複製代碼複製代碼

防抖與節流

防抖

function debounce(fn, wait) {
    var timeout = null;
    return function() {
        if(timeout !== null) 
        {
                clearTimeout(timeout);
        }
        timeout = setTimeout(fn, wait);
    }
}
// 處理函數
function handle() {
    console.log(Math.random()); 
}
// 滾動事件
window.addEventListener('scroll', debounce(handle, 1000));
複製代碼複製代碼

節流

利用時間戳實現

var throttle = function(func, delay) {
            var prev = Date.now();
            return function() {
                var context = this;
                var args = arguments;
                var now = Date.now();
                if (now - prev >= delay) {
                    func.apply(context, args);
                    prev = Date.now();
                }
            }
        }
        function handle() {
            console.log(Math.random());
        }
        window.addEventListener('scroll', throttle(handle, 1000));
複製代碼複製代碼

利用定時器實現

var throttle = function(func, delay) {
            var timer = null;
            return function() {
                var context = this;
                var args = arguments;
                if (!timer) {
                    timer = setTimeout(function() {
                        func.apply(context, args);
                        timer = null;
                    }, delay);
                }
            }
        }
        function handle() {
            console.log(Math.random());
        }
        window.addEventListener('scroll', throttle(handle, 1000));
複製代碼複製代碼

模擬new

  • new產生的實例能夠訪問Constructor裏的屬性,也能夠訪問到Constructor.prototype中的屬性,前者能夠經過apply來實現,後者能夠經過將實例的proto屬性指向構造函數的prototype來實現
  • 咱們還須要判斷返回的值是否是一個對象,若是是一個對象,咱們就返回這個對象,若是沒有,咱們該返回什麼就返回什麼
function New(){
    var obj=new Object();
    //取出第一個參數,就是咱們要傳入的構造函數;此外由於shift會修改原數組,因此arguments會被去除第一個參數
    Constructor=[].shift.call(arguments);
    //將obj的原型指向構造函數,這樣obj就能夠訪問到構造函數原型中的屬性
    obj._proto_=Constructor.prototype;
    //使用apply改變構造函數this的指向到新建的對象,這樣obj就能夠訪問到構造函數中的屬性
    var ret=Constructor.apply(obj,arguments);
    //要返回obj
    return typeof ret === 'object' ? ret:obj;
}
複製代碼複製代碼

function Otaku(name,age){ this.name=name; this.age=age; this.habit='Games' }

Otaku.prototype.sayYourName=function(){ console.log("I am" + this.name); }

var person=objectFactory(Otaku,'Kevin','18')

複製代碼console.log(person.name)//Kevin console.log(person.habit)//Games console.log(person.strength)//60 複製代碼複製代碼

模擬Call

  • call()方法在使用一個指定的this值和若干個指定的參數值的前提下調用某個函數或方法
  • 模擬的步驟是:將函數設爲對象的屬性—>執行該函數—>刪除該函數
  • this參數能夠傳null,當爲null的時候,視爲指向window
  • 函數是能夠有返回值的

簡單版

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
}
foo.bar() // 1
複製代碼複製代碼

完善版

Function.prototype.call2 = function(context) {
    var context=context||window
    context.fn = this;
    let args = [...arguments].slice(1);
    let result = context.fn(...args);
    delete context.fn;
    return result;
}
let foo = {
    value: 1
}
function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}
//表示bar函數的執行環境是foo,即bar函數裏面的this表明foo,this.value至關於foo.value,而後給bar函數傳遞兩個參數
bar.call2(foo, 'black', '18') // black 18 1
複製代碼複製代碼

模擬apply

  • apply()的實現和call()相似,只是參數形式不一樣
Function.prototype.apply2 = function(context = window) {
    context.fn = this
    let result;
    // 判斷是否有第二個參數
    if(arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}
複製代碼複製代碼

模擬bind

Function.prototype.bind2=function(context){ var self=thisl var args=Array.prototype.slice.call(arguments,1);
<span class="hljs-keyword">var</span> fNOP=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{};
<span class="hljs-keyword">var</span> fBound=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">var</span> bindArgs=<span class="hljs-built_in">Array</span>.prototype.slice.call(<span class="hljs-built_in">arguments</span>);
    <span class="hljs-keyword">return</span> self.apply(<span class="hljs-keyword">this</span> <span class="hljs-keyword">instanceof</span> fNOP ? <span class="hljs-keyword">this</span> : context, args.concat(bindAt))
}
複製代碼
複製代碼<span class="hljs-keyword">var</span> fNOP=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{}; <span class="hljs-keyword">var</span> fBound=<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{ <span class="hljs-keyword">var</span> bindArgs=<span class="hljs-built_in">Array</span>.prototype.slice.call(<span class="hljs-built_in">arguments</span>); <span class="hljs-keyword">return</span> self.apply(<span class="hljs-keyword">this</span> <span class="hljs-keyword">instanceof</span> fNOP ? <span class="hljs-keyword">this</span> : context, args.concat(bindAt)) } 複製代碼} 複製代碼複製代碼

模擬instanceof

function instanceOf(left,right) {

    let proto = left.__proto__;
    let prototype = right.prototype
    while(true) {
        if(proto === null) return false
        if(proto === prototype) return true
        proto = proto.__proto__;
    }
}
複製代碼複製代碼

模擬JSON.stringify

JSON.stringify(value[, replacer [, space]])

  • Boolean | Number| String 類型會自動轉換成對應的原始值。

  • undefined、任意函數以及symbol,會被忽略(出如今非數組對象的屬性值中時),或者被轉換成 null(出如今數組中時)。

  • 不可枚舉的屬性會被忽略

  • 若是一個對象的屬性值經過某種間接的方式指回該對象自己,即循環引用,屬性也會被忽略。

function jsonStringify(obj) {
    let type = typeof obj;
    if (type !== "object") {
        if (/string|undefined|function/.test(type)) {
            obj = '"' + obj + '"';
        }
        return String(obj);
    } else {
        let json = []
        let arr = Array.isArray(obj)
        for (let k in obj) {
            let v = obj[k];
            let type = typeof v;
            if (/string|undefined|function/.test(type)) {
                v = '"' + v + '"';
            } else if (type === "object") {
                v = jsonStringify(v);
            }
            json.push((arr ? "" : '"' + k + '":') + String(v));
        }
        return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
    }
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"
複製代碼複製代碼

參考資料:

轉自前端面試之手寫代碼

相關文章
相關標籤/搜索