系列文章目錄
- HTML篇
- CSS篇
- JS篇
- VUE篇
- webpack篇
文章目錄
- 系列文章目錄
- 前言
-
- 一、基礎知識
- 二、ES6+
- 三、其他
前言
彙總常見面試題
一、基礎知識
(一)資料處理
-
判斷數組(類型判定)
Object.prototype.tostring.call()【最佳】 | instanceOf | Array.isArray() 分析優劣
-
數組有哪些方法,哪些能改變原值
改變(7個):push pop shift unshift sort reverse splice
不改變:concat reduce join slice filter forEach…
-
多元數組扁平化處理(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))
- 數組去重
- 第一種: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);
}
- 僞數組轉化為數組
僞數組:不能調用數組的方法
Array.prototype.slice.call(arguments) // 利用數組的slice
Array.from(arguments) // ES6方法
- 實作對象深拷貝
- JSON.parse(JSON.stringify(obj)) 耗時較長,函數、正則、時間對象等無法拷貝。
- Object.assign({}, obj) 适用于一層深拷貝
- lodash cloneDeep(obj)方法
- 周遊+遞歸拷貝 相對完美
- 取得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;
}
(二)原理
- 手寫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第二個參數是個數組形式
}
}
- 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}
-
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();
- 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函數對描述的屬性。描述符必須是這兩種形式之一;不能同時是兩者。
- 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>
]
- 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+
- 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();
}
};
- 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
三、其他
- commonjs和es6 import的差別,哪種可以條件引入if
-
CommonJS 子產品輸出的是一個值的拷貝,ES6 子產品輸出的是值的引用
CommonJS 子產品輸出的是值的拷貝,也就是說,一旦輸出一個值,子產品内部的變化就影響不到這個值。ES6 子產品不會緩存運作結果,而是動态地去被加載的子產品取值,并且變量總是綁定其所在的子產品 。
-
CommonJS 子產品是運作時加載,ES6 子產品是編譯時輸出接口。 ES6 子產品在引入時并不會立即執行,核心隻是對其進行了引用,隻有在真正用到時才會被執行,這就是“編譯時”加載。
是以CommonJS可以和If 配合引入。