天天看點

前端面試題目——JS系列文章目錄前言

系列文章目錄

  1. HTML篇
  2. CSS篇
  3. JS篇
  4. VUE篇
  5. webpack篇

文章目錄

  • 系列文章目錄
  • 前言
    • 一、基礎知識
    • 二、ES6+
    • 三、其他

前言

彙總常見面試題

一、基礎知識

(一)資料處理

  1. 判斷數組(類型判定)

    Object.prototype.tostring.call()【最佳】 | instanceOf | Array.isArray() 分析優劣

  2. 數組有哪些方法,哪些能改變原值

    改變(7個):push pop shift unshift sort reverse splice

    不改變:concat reduce join slice filter forEach…

  3. 多元數組扁平化處理(flat)

    将二維甚至多元數組轉化為一維數組,例如

    var arrList = [[1,2,3],4,5,[6,7,[8,9]]]

    轉化為

    [1,2,3,4,5,6,7,8,9]

  • 第一種:周遊 + 遞歸
let arrList = [[1, 2, 3], 4, 5, [6, 7, [8, 9]]];
function flat(arr) {
    let result = []
    for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (Array.isArray(item)) {
            result = result.concat(flat(item))
        } else {
            result.push(item)
        }
    }
    return result
}
console.log(flat(arrList))
           
  • 第二種:reduce + 遞歸
function flat2(arr) {
    return arr.reduce((res, item) => {
        return res.concat(Array.isArray(item) ? flat2(item) : item)
    }, [])
}
console.log(flat2(arrList))
           
  1. 數組去重
  • 第一種:ES6 Set
function unique(arr){
	return Array.from(new Set(arr))     // 或者 return [...new Set(arr)]
}
           
  • 第二種:利用includes 或者 indexOf方法
function unique2(arr) {
    let list = [];
    for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (!list.includes(item)) {  // 或者 list.indexOf(item)===-1
           list.push(item)
        }
    }
    return list
}
           
  • 第三種:sort 排序 + 相鄰元素對比
function unique3(arr) {
    arr = arr.sort();
    let list = [arr[0]];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i - 1]) {
            list.push(arr[i])
        }
    }
    return list
}
           
  • 第四種: filter 隻保留資料在數組中第一次出現的位置
function unique4(arr) {
    return arr.filter((item, index) => {
        return arr.indexOf(item, 0) === index     // 隻要item第一次出現的位置值
    })
}
           
  • 第五種:利用對象的key,還可統計出現次數
function unique5(arr) {
    let obj = {};
    for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if (!obj[item]) {
            obj[item] = 1;
        } else {
            obj[item]++
        }
    };
    return Object.keys(obj);
}
           
  1. 僞數組轉化為數組

僞數組:不能調用數組的方法

Array.prototype.slice.call(arguments)  // 利用數組的slice
Array.from(arguments)   // ES6方法
           
  1. 實作對象深拷貝
  • JSON.parse(JSON.stringify(obj)) 耗時較長,函數、正則、時間對象等無法拷貝。
  • Object.assign({}, obj) 适用于一層深拷貝
  • lodash cloneDeep(obj)方法
  • 周遊+遞歸拷貝 相對完美
  1. 取得URL後面query的參數值
  • window.location.search + split("&")分割
function getQueryVariable(variable)
{
       var query = window.location.search.substring(1); // 擷取?号後面的str
       var vars = query.split("&");
       for (var i=0;i<vars.length;i++) {
               var pair = vars[i].split("=");
               if(pair[0] == variable){return pair[1];}
       }
       return(false);
}
           
  • 正則比對
function getQueryString(name) {
    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
    var r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return unescape(r[2]);
    }
    return null;
}
           

(二)原理

  1. 手寫call apply bind實作??
Function.prototype.myBind = function () {
    const args = Array.prototype.slice.call(arguments);
    const ctx = args.shift();  // 把數組的第一個元素從其中删除,并傳回第一個元素的值
    const self = this;
    return function () {
        self.apply(ctx, args)   // apply第二個參數是個數組形式
    }
}
           
  1. new 的實作原理,若構造函數中有傳回值怎麼辦

構造函數正常傳回一個執行個體對象

// 構造函數 Person
function Person() {
	this.name = 'haha';
	this.age = 12;
}
let xiaoming = new Person();
console.log(xiaoming);   // {name: "haha", age: 12}
           

當構造函數中有傳回值時:(傳回基本類型時,不影響結果;傳回引用類型時,覆寫了原來的執行個體對象。)

function Person() {
	this.name = 'haha';
	this.age = 12;
	let a =1;
	let b = {id: 89};
	let c = true;
	let d = [4,5,6]
	// return a   ⇒ 不變,{name: "haha", age: 12}
	// return b   ⇒ 傳回了b: {id: 89}
	// return c   ⇒ 不變,{name: "haha", age: 12}
	// return d   ⇒ 傳回了d,[4,5,6]
}
let xiaoming = new Person();
console.log(xiaoming); 
           

new 實作原理

根據MDN描述:new關鍵字會做如下操作:

1、建立一個空的簡單JavaScript對象(即{});

2、連結該對象(設定該對象的constructor)到另一個對象 ;

3、将步驟1新建立的對象作為this的上下文 ;

4、如果該函數沒有傳回對象,則傳回this。

function myNew(F) {
	let obj = {};  // 第一步:建立空對象執行個體
    obj.__proto__ = F.prototype;  // 第二步:保證obj能通路到構造函數F的屬性
    F.apply(obj, Array.prototype.slice(arguments, 1));  // 第三步:綁定this
    return obj instanceof Object ? obj : {};   // 第四步:傳回執行個體對象
}
// 驗證
console.log(myNew(Person))  // {name: "haha", age: 12}
           
  1. this指向方面,demo題 。this指向的幾種情況,立即執行函數的this

    函數直接調用(指向window) 對象方法(對象) 構造函數(執行個體化出來的對象) 計時器調用(全局) 匿名函數(window) 立即執行函數(window)

var fullname = 'John Doe'; 
var obj = { 
	fullname: 'Colin Ihrig', 
	prop: { 
		fullname: 'Aurelio De Rosa', 
		getFullname: function() { return this.fullname; }
	}
}; 		
console.log(obj.prop.getFullname());  // Aurelio De Rosa
var test = obj.prop.getFullname;
console.log(test());  // John Doe
           
var myobject={
	foo:"bar",
	func:function(){
		var self=this;
		console.log(this.foo); // bar
		console.log(self.foo); // bar 
		(function(){
			console.log(this.foo);// undefined
			console.log(self.foo);// bar
		})();
	}
};
myobject.func();
           
  1. object.defineProperty可以同時定義get和value 嗎?

不可以同時定義get 和value .使用Object.defineProperty() 定義對象屬性時,如已設定

set

get

, 就不能設定

writable

value

中的任何一個了,不然會報如下

TypeError

錯誤:

var i =0;
Object.defineProperty(user, 'age',{
    // value: 13,
    writable: true,
    get: function() {
        return i++
    }
})
           
Object.defineProperty(user, 'age',{
       ^
TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
           
對象裡目前存在的屬性描述符有兩種主要形式:資料描述符和存取描述符。資料描述符(value writable)是一個具有值的屬性,該值可能是可寫的,也可能不是可寫的。存取描述符是由getter-setter函數對描述的屬性。描述符必須是這兩種形式之一;不能同時是兩者。
  1. Object.defineProperty對數組哪裡支援不好?

對象數組監聽:

function observe(obj) {
    Object.keys(obj).forEach((key) => {
        defineReactive(obj, key, obj[key])
    })
}
function defineReactive(obj, key, value) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: false,
        get: () => {
            console.log('get');
            return value;
        },
        set: (newValue) => {
            console.log('set')
            return newValue;
        }
    })
}
var arr = [1, 2, 3];
observe(arr);
arr.push(4)     // push方法無法通過set監聽
arr.length = 8;   // 更改length無法觸發set監聽
console.log(arr[0], arr)
// 輸出結果:
get
1 [
  [Getter/Setter],
  [Getter/Setter],
  [Getter/Setter],
  4,
  <4 empty items>
]
           
  1. eventloop、宏任務、微任務執行順序。demo例題

知識點:JS事件循環

題目一:setTimeout + Promise

console.log('start')
setTimeout(function () {
    console.log('setTimeout')
}, 0);

new Promise(function (resolve) {
    resolve();
}).then(function () {
    console.log('promise1')
}).then(function () {
    console.log('promise2')
})
console.log('end')
// 結果:start end promise1 promise2 setTimeout
           

題目二:setTimeout + Promise

setTimeout(function () {
  console.log(2)
}, 0)
new Promise(function (resolve) {
    console.log('3')
    resolve()
    console.log(4)
}).then(function () {
    console.log('5')
});
console.log(8)
// 結果:3,4,8,5,2
           

題目三:setTimeout + Promise + process.nextTick

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
// 第一輪(script宏任務): 1 7 6 8
// 第二輪(第一個setTimeout宏任務):2 4 3 5
// 第三輪(第二個setTimeout宏任務):9 11 10 12
           

題目四:setTimeout + Promise + async

console.log('script start')
async function async1() {
  //async 文法糖 async2()執行完畢 才執行下面 會加入在微觀任務裡面
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

setTimeout(function () {
  console.log('setTimeout')
}, 0)

new Promise((resolve) => {
  console.log('promise')
  resolve()
})
  .then(function () {
    console.log('promise1')
  })
  .then(function () {
    console.log('promise2')
  })

console.log('script end')
// 第一輪:'script start' 'async2 end' 'promise' 'script end' 'async1 end' 'promise1' 'promise2' 
// 第二輪:'setTimeout'
           

題目五:setTimeout + Promise + async

async function async1() {
        console.log(1);
        const result = await async2();
        console.log(3);
    }

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

    Promise.resolve().then(() => {
        console.log(4);
    });

    setTimeout(() => {
        console.log(5);
    });

    async1();
    console.log(6);
  // 結果: 1 2 6 4 3 5 ?
           

二、ES6+

  1. es6中的箭頭函數用ES5的方式實作

例1

// ES6
const func = (a, b) => a+b;

// ES5
var func = function func(a, b) {
  return a + b;
};
           

例2

// ES6 f1定義時所處函數this是指的obj2, setTimeout中的箭頭函數this繼承自f1, 是以不管有多層嵌套,this都是obj2
var obj2 = {
	say: function () {
  		var f1 = () => {
    		console.log(this); // obj2
    		setTimeout(() => {
      			console.log(this); // obj2
    		})
  		}
  		f1();
  	}
}
obj2.say()

// ES5
var obj2 = {
  say: function say() {
    var _this = this;   // 關鍵步驟

    var f1 = function f1() {
      console.log(_this); // obj2

      setTimeout(function () {
        console.log(_this); // obj2
      });
    };
    f1();
  }
};
           
  1. promise()原理,手寫實作。鍊式調用時傳回的對象是一個對象嗎?
const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected';
class myPromise {
    constructor(executor) {   // 傳入的executor
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;

        this.onFulfilledCallbacks = [];  // 存儲所有的成功回調
        this.onRejectedCallbacks = [];  // 存儲所有的失敗回調

        // 應該在 構造函數中添加resolve reject ,寫在外面的方法會挂在prototype上
        const resolve = (value) => {
            if (this.status === PENDING) {   // 隻有pending狀态下才能更改為fulfilled狀态
                this.status = FULFILLED; 
                this.value = value;
                // 釋出:通知所有的成功回調函數執行
                this.onFulfilledCallbacks.forEach(fn => fn());
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                // 釋出:通知所有的失敗回調函數執行
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        // 捕捉執行器抛出的異常
        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
        
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
        onRejected = typeof onRejected === 'function' ? onRejected : ()=>{throw this.reason}
        let promise2 = new myPromise((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout(() => {  // 将onFulfilled變成異步,resolvePromise能拿到promise2
                    try {
                        let x = onFulfilled(this.value);  // 這裡有可能是一個普通值也可能是一個promise
                        resolvePromise(promise2, x, resolve, reject);  // 外界拿不到promise2 resolve reject
                    } catch (e) {
                        reject(e);
                    }
                },0)
            }
            if (this.status === REJECTED) {
                setTimeout(() => {  // 将onFulfilled變成異步,resolvePromise能拿到promise2
                    try {
                        let x = onRejected(this.reason);;  // 這裡有可能是一個普通值也可能是一個promise
                        resolvePromise(promise2, x, resolve, reject);  // 外界拿不到promise2 resolve reject
                    } catch (e) {
                        reject(e);
                    }
                },0)
            }
            if (this.status === PENDING) {  // pending還沒有結果的狀态,主要考慮的是executor為異步情況。要使用釋出訂閱模式,收集回調
                // 訂閱的過程, 使用了釋出訂閱的開發模式
                this.onFulfilledCallbacks.push(() => {   // 等釋出的時候,就周遊這個數組執行即可。
                    try {
                        let x = onFulfilled(this.value);  // 這裡有可能是一個普通值也可能是一個promise
                        resolvePromise(promise2, x, resolve, reject);  // 外界拿不到promise2 resolve reject
                    } catch (e) {
                        reject(e);
                    }
                })
                this.onRejectedCallbacks.push(() => {   // 等釋出的時候,就周遊這個數組執行即可。
                    try {
                        let x = onRejected(this.reason);  // 這裡有可能是一個普通值也可能是一個promise
                        resolvePromise(promise2, x, resolve, reject);  // 外界拿不到promise2 resolve reject
                    } catch (e) {
                        reject(e);
                    }
                })
                
            }
        });
        
        return promise2;
    }

    catch(errCallback) {
        return this.then(null, errCallback);
    }
}
// 2.3 promise resolution procedure 
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {  // 防止循環引用
        return reject(new TypeError('chaning cycle detected for promise #<myPromise>'));
    }
    let isCalled = false;
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') { 
        try { // x.then可能會抛出一個異常,為了捕獲異常并reject
            // x 為一個object or function
            let then = x.then;
            if (typeof then === 'function') {   // 認為x是promise對象
                then.call(x, (y) => {
                    if (isCalled) return;  // 避免重複調用成功or失敗的回調
                    isCalled = true;
                    resolvePromise(promise2, y, resolve, reject);  // 遞歸,promise多層嵌套問題
                }, (r) => {
                    if (isCalled) return;  // 避免重複調用成功or失敗的回調
                    isCalled = true;
                    reject(r);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (isCalled) return;  // 避免重複調用成功or失敗的回調
            isCalled = true;
            reject(e)
        }
        
    } else {    // 普通值
        resolve(x);
    }
}
module.exports = myPromise
           

三、其他

  1. commonjs和es6 import的差別,哪種可以條件引入if
  • CommonJS 子產品輸出的是一個值的拷貝,ES6 子產品輸出的是值的引用

    CommonJS 子產品輸出的是值的拷貝,也就是說,一旦輸出一個值,子產品内部的變化就影響不到這個值。ES6 子產品不會緩存運作結果,而是動态地去被加載的子產品取值,并且變量總是綁定其所在的子產品 。

  • CommonJS 子產品是運作時加載,ES6 子產品是編譯時輸出接口。 ES6 子產品在引入時并不會立即執行,核心隻是對其進行了引用,隻有在真正用到時才會被執行,這就是“編譯時”加載。

    是以CommonJS可以和If 配合引入。

繼續閱讀