1. 程式人生 > >前端面試題(親身面試經驗)

前端面試題(親身面試經驗)

  最近面試了一些公司,趁著疫情期間,總結一波。大家可以看看  會有用的。

 

webpack

1、webpack中entry和output的作用

webpack中的entry標記入口檔案,它可以是一個字串(單入口)或者一個數組(多入口),output用來標記輸出,主要有兩個屬性 path和 filename。其次就是publicPath 和chunkFileName

2、webpack中loader和plugin的作用

loader 用於載入某些資原始檔。 因為webpack 本身只能打包commonjs規範的js檔案,對於其他資源例如 css,圖片,或者其他的語法集,比如 jsx, coffee,是沒有辦法載入的。 這就需要對應的loader將資源轉化,載入進來。loader是用於載入的,它作用於一個個檔案上。
plugin 用於擴充套件webpack的功能。它直接作用於 webpack,擴充套件了它的功能。當然loader也是變相的擴充套件了 webpack ,但是它只專注於轉化檔案。而plugin的功能更加的豐富,而不僅侷限於資源的載入。

3、webpack中可以有哪些優化

1、優化Loader的檔案搜尋範圍,指定include、exclude
2、把Babel編譯過的檔案快取起來  loader: 'babel-loader?cacheDirectory=ture'
3、懶載入、按需載入、分包、壓縮
4、提取公共程式碼與第三方程式碼
5、使用HappyPack外掛開啟多程序Loader轉換
6、區分dev、product環境,減少不必要
7、減少不必要的編譯,提升開發時構建速度(devServer.watchOptions.aggregateTimeout)

4、你是依據什麼來確定你要優化哪個模組

我會使用一個webpack-bundle-analyzer的外掛,這個外掛(plugin常規使用,需要在package.json 中寫npm_config_report=true)可以檢視打包後文件包的大小,進而可以找出比較大的包,對他進行優化

5、webpack構建流程

1、初始化引數,從配置檔案和shell語句中讀到的引數合併,得到最後的引數
2、開始編譯:用合併得到的引數初始化complier物件,載入是所有配置的外掛,執行run方法開始編譯
3、確定入口,通過entry找到入口檔案
4、編譯模組,從入口檔案出發,呼叫所有配置的loader對模組進行解析翻譯,在找到該模組依賴的模組進行處理
5、 完成模組編譯,得到每個模組被翻譯之後的最終的內容和依賴關係
6、輸出資源,根據入口和模組之間的依賴關係,組裝成一個個包含多個模組的chunk,在把每個chunk轉換成一個單獨的檔案載入到輸出列表、
7、輸出完成,確定輸出的路徑和檔名,把內容寫到檔案系統中

簡單版
初始化引數 -> 編譯開始 -> 找到入口 -> 編譯入口及其依賴 -> 完成編譯確定關係 -> 輸出資源 -> 輸出完成

6、webpack熱更新原理

首先可以這麼理解 webpack是一個基於node的小伺服器,這麼就好理解了,簡單來說:
1、watch 編譯過程、devServer 推送更新訊息到瀏覽器
2、瀏覽器接收到服務端訊息做出響應
3、對模組進行熱更新或重新整理頁面

7、webpack-dev-server和http伺服器如nginx有什麼區別?

webpack-dev-server使用記憶體來儲存webpack開發環境下的打包檔案,並且可以使用模組熱更新,他比傳統的http服務對開發更加簡單高效。

8、happypack使用方法

module:{
    rules:[{
            test:/\.js$/,
            use:['happypack/loader?id=babel']
            exclude:path.resolve(__dirname, 'node_modules')
        },{
            test:/\.css/,
            use:['happypack/loader?id=css']
        }],
    plugins:[
        new HappyPack({
            id:'babel',
            loaders:['babel-loader?cacheDirectory']
        }),
        new HappyPack({
            id:'css',
            loaders:['css-loader']
        })
    ]
}

 

js

1、array map和foreach的區別

map有返回值而且必須return返回一個數組才行,而forEach沒有返回值可直接列印結果。

2、array some和filter的區別

some方法返回的是boolean值,可用於檢察陣列中是否有某物件
filter方法返回的是一個新陣列,可用於過濾陣列中的物件

3、new一個物件過程發生了什麼

1、建立一個新物件,如:var person = {};
2、新物件的_proto_屬性指向建構函式的原型物件。
3、將建構函式的作用域賦值給新物件。(也所以this物件指向新物件)
4、執行建構函式內部的程式碼,將屬性新增給person中的this物件。
5、返回新物件person。

4、陣列排序的方式

主要有像快速排序、插入排序、氣泡排序

插入排序

方法:將第一待排序序列第一個元素看做一個有序序列,把第二個元素到最後一個元素當成是未排序序列。
從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置。

程式碼:
var insertSort = function(arr) {
    var len = arr.length;
    var preIndex, current;
    for(var i = 1; i < len; i++){
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current){
            arr[preIndex + 1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex + 1] = current;
    }
    return arr;
}

快速排序

方法 :基本原理是將陣列內的數分成三組,取陣列中間的數為基準,將較小數放在左邊,較大數放在右邊,分別將三類數存放在一個數組內,最後遞迴進行排序。

程式碼:

function quickSort(arr) {

    if(arr.length<=1) {
        return arr;
    }

    let leftArr = [];
    let rightArr = [];
    let q = arr[0];
    for(let i = 1,l=arr.length; i<l; i++) {
        if(arr[i]>q) {
            rightArr.push(arr[i]);
        }else{
            leftArr.push(arr[i]);
        }
    }
    return [].concat(quickSort(leftArr),[q],quickSort(rightArr));
}

氣泡排序

方法:比較兩個相鄰的元素,如果後一個比前一個大,則交換位置,然後類推,每次比較完成後可以少比較最後一個

程式碼:
function bubbleSort(arr) {  
    for(let i = 0,l=arr.length;i<l-1;i++) {
        for(let j = i+1;j<l;j++) { 
          if(arr[i]>arr[j]) {
                let tem = arr[i];
                arr[i] = arr[j];
                arr[j] = tem;
            }
        }
    }
    return arr;
}

5、陣列去重的幾種方式

1、利用ES6 Set去重
程式碼:
Array.from(new Set(arr))或者[...new Set(arr)]
2、健值去重法
原理:
利用物件的屬性不能相同的特點進行去重

function unique(arr) {
    var arrry= [];
     var  obj = {};
    for (var i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            arrry.push(arr[i])
            obj[arr[i]] = 1
        } else {
            obj[arr[i]]++
        }
    }
    return arrry;
}
3、includes


function unique(arr) {
    var array =[];
    for(var i = 0; i < arr.length; i++) {
            if( !array.includes( arr[i]) ) {
                    array.push(arr[i]);
              }
    }
    return array
}
高大上寫法:(reduce+includes)
function unique(arr){
    return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}
4、排序後相鄰去除法
原理:sort之後會把所有元素排序,只需要比較當前和下一個元素是否完全一致,不完全一致則增加
function uniq(array){
    array.sort();
    var temp=[array[0]];
    for(var i = 1; i < array.length; i++){
        if( array[i] !== temp[temp.length-1]){
            temp.push(array[i]);
        }
    }
    return temp;
}
5、indexof
原理:如果當前陣列的第i項在當前陣列中第一次出現的位置不是i,那麼表示第i項是重複的,忽略掉。否則存入結果陣列。
function uniq(array){
    var temp = [];
    for(var i = 0; i < array.length; i++) {
        //如果當前陣列的第i項在當前陣列中第一次出現的位置是i,才存入陣列;否則代表是重複的
        if(array.indexOf(array[i]) == i){
            temp.push(array[i])
        }
    }
    return temp;
}

6、字串出現最多的次數

1、物件法
原理:利用js健值對唯一的特性,如果包含key則value++
function countStr(str){
    var obj = {};
    for(var i = 0, l = str.length,k; i < l ;i++){
        k = str.charAt(i);
        if(obj[k]){
            obj[k]++;
        }else{
            obj[k] = 1;
        }
    }
    var m = 0,i=null;
    for(var k in obj){
            if(obj[k] > m){
                m = obj[k];
                i = k;
            }
    }
return i + ':' + m;
}

7、reduce兩個引數分別是什麼?

第一個引數是一個函式,函式包含4個引數分別是:total(本次總和、初始值)、currentValue(當前元素)、currentIndex(當前index)、arr(原陣列)
第二個引數是一個初始值

8、怎麼可以將一個包含id的陣列變成一個用id做key的json

arr.reduce((total,currentItem) => {
    total[currentItem.id] = currentItem;
    return total
},{})

 

9、下面這個程式碼段輸出什麼

 console.log(1);
        setTimeout(function(){
            console.log(2)
        })
        new Promise(function(res,rej){
            console.log(3)
            res()
        })
        .then(function(){
            console.log(4)
        })
        .catch(function(){
            console.log(5)
        })
        async function a(){
            console.log(6);
            await console.log(7);
            console.log(8)
        }
        a();
        console.log(9)

1,3,6,7,9,4,8,2
本題花式比較多,遵循 同步>非同步 微任務>巨集任務

10、promise有幾種狀態?

三種,分別是pending、fulfilled、rejected(未決定,履行,拒絕),resolve時候會由padding->fulfilled,reject會由padding->rejected

11、聊聊原型鏈

這個。。。不知道怎麼描述,也不給個題目,大致說下我是怎麼說的吧
js是一個萬物皆物件的語言,每一個物件都會包含一個原型物件,物件以原型為模版,從他的原型繼承方法和屬性。當然物件也是可以由原型的,一層一層類似於一個鏈條,我們叫做原型鏈。比如舉個例子,man->person->object

12、判斷是不是array

1、instanceof (檢測建構函式的 prototype 屬性是否出現在某個例項物件的原型鏈)
a instanceof Array
2、原型prototype + toString +  call()
Object.prototype.toString.call(a) === "[object Array]"
Object.getPrototypeOf(a) ===  "[object Array]"
3、原型prototype + isPrototypeOf()
Array.prototype.isPrototypeOf(variable)
4、 Array.isArray

13、判斷是不是物件

1、原型prototype + toString +  call()
Object.prototype.toString.call(obj) === '[object Object]'
Object.getPrototypeOf(b).toString() === "[object Object]"
2、instanceof(需要處理array)
a instanceof Object && !(a instanceof Array)

14、閉包是什麼?怎麼寫?

主要是為了從函式外部訪問函式內部的變數,且這個變數永遠不會被記憶體回收(會回收閉包內函式的變數不會回收閉包的變數),寫法為函式內reuturn一個function

15、null和undefined的區別?

1、null是一個表示”無”的物件,轉為數值時為0;undefined是一個表示”無”的原始值,轉為數值時為NaN。
2、undefined表示”缺少值”,就是此處應該有一個值,但是還沒有定義。
3、null表示”沒有物件”,即該處不應該有值。

16、前端幾種資料儲存方式

localstorage、sessionstorage、cookie、websql等

17、localstorage和sessionStorage以及cookie的區別

首先是cookie他的儲存大小對比local和session會小很多,local他會一直存在於瀏覽器記憶體,即使視窗關閉或者是瀏覽器關閉哪怕是新開一個頁面local都是共享的。而session他是屬於會話級的,我們哪怕開啟相同的頁面,session也不會共享過來。

18、解決跨域的方式

1、通過jsonp跨域(callback)
2、CORS (服務端配置Access-Control-Allow-Origin)
3、domain+iframe(做法:頁面寫一個iframe,將sec設定為要跨域的域名,通過document.getElementById('iframe').contentWindow.$,來取到對應的$,再通過$.ajax呼叫即可,不過僅限於主域名相同)
4、nginx
5、iframe

19、eventloop介紹

js是一個單執行緒的東西(為什麼單執行緒自己百度),
在JavaScript中,存在一個執行棧,當我們呼叫一個函式時,JavaScript引擎會將函式push到執行棧(Stack)中,
執行完畢之後pop出去(先進先出),當遇到非同步任務時,就將任務放進任務佇列中。非同步任務有兩種,一種叫做巨集任務一種叫做微任務,
當主執行緒執行完畢,也就是執行棧為空時,會先確認微任務佇列是否為空,不為空就取出微任務佇列中所有微任務然後順序執行,微任務執行完畢後
會在巨集任務佇列中取出下一個任務新增到執行棧,如果這時候再遇到微任務繼續加到微任務佇列,如此迴圈。

20、物件深拷貝方式

1、最簡單的方式 JSON.parse(JSON.stringify(Obj)) 這種方法使用較為簡單,可以滿足基本的深拷貝需求,而且能夠處理JSON格式能表示的所有資料型別,但是對於正則表示式型別、函式型別等無法進行深拷貝(而且會直接丟失相應的值)。
2、jQuery深拷貝 var copiedObject = $.extend(true, {}, originalObject)
3、手動寫遞迴方式
function deepClone (obj) {
        var newobj = obj.constructor === Array ? [] : {};
        if(typeof obj !== 'object'){
            return;
        }
        for(var i in obj){
           newobj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i];
        }
        return newobj
}

21、object.assign 是深拷貝還是淺拷貝

Object.assign()拷貝的是屬性值。假如源物件的屬性值是一個物件的引用,那麼它也只指向那個引用。
也就是說,如果物件的屬性值為簡單型別(如string, number),通過Object.assign({},srcObj);
得到的新物件為深拷貝;如果屬性值為物件或其它引用型別,那對於這個物件而言其實是淺拷貝的。

22、聊聊平時你都會做哪些優化

這個比較多了,我之前總結過。可以移步https://www.cnblogs.com/jinzhenzong/p/11777065.html,可以整理一下剪短的話語。
我們可以分為幾塊去說,首先是快取方面,我們可以藉助如:localstorage、sessionStorage、cookie、瀏覽器自帶快取、h5 minifest等對資料、ajax介面、頁面等進行快取,
在請求時,我們應該儘量減少重定向,做一些dns預解析、提高瀏覽器併發數等。之後就是頁面解析和渲染時候,可以做關鍵路徑的渲染,避免一下複雜的佈局,用flex替換原始的佈局模型
還有一些像長列表的尾優化啊、圖片懶載入啊,儘量的減少js的佔用時間,比如使用jq時候我們經常會遇到一些操作樣式或者設定內容等,這個時候我們可以對dom進行快取和批量處理,來減少操作dom和迴流重繪等。比如我們
需要監聽使用者輸入時候都要做一下事件的防抖節流,還有一些如減少閉包(為了變數能夠被記憶體回收機制回收)、使用效能好的api如requestanimationframe代替settimeout、setinterval等。還有一塊比較大的就是
資源的處理,更多還是藉助webpack,比如像分包、程式碼合併、檔案的快取、小圖片base64等,還有像綜合考慮是否使用框架、圖片選質量比較低的,視訊的預載入等。
ps:強烈建議去看我原文章至少吧xmind下下來註釋都看一編,不然深入問一下你就廢了

23、前端開啟頁面的過程

同樣之前總結過https://www.cnblogs.com/jinzhenzong/p/11753559.html
大概過程如下:
1、輸入網址
2、瀏覽器解析ip
3、通過ip發起連結
4、伺服器接受請求
5、處理請求並返回
6、頁面渲染 -> 
    解析HTML檔案,建立DOM樹(解析執行JS指令碼時,會停止解析後續HTML) 
    解析CSS,形成CSS物件模型
    將CSS與DOM合併,構建渲染樹
    佈局渲染樹
    繪製渲染樹(可能觸發迴流和重繪)
7、渲染完畢

經過幾個公司問題總結出,一般多會問第六步。前面就是聽聽而已。

24、promise,async函式區別與理解

promise 將原來的用 回撥函式 的 非同步程式設計 方法轉成用relsove和reject觸發事件, 用 then 和 catch 捕獲成功或者失敗的狀態
async函式就相當於自執行的Generator函式,async函式就相當於自執行的Generator函式,較於Promise,async 的優越性就是把每次非同步返回的結果從 then 中拿到最外層的方法中,不需要鏈式呼叫,只要用同步的寫法就可以了。
比 promise 直觀,但是 async 必須以一個 Promise 物件開始 ,所以 async 通常是和Promise 結合使用的。

js的比較多,我只是選了一部分比較常問的,其他還有像設計模式、mvc、mvvm、jq元件、還有各種陣列物件的方法的作用引數等就不總結了

vue篇

1、watch和計算屬性區別

watch適合處理的場景是,偵聽一個數的變化,當該資料變化,來處理其他與之相關資料的變化(該資料影響別的多個數據)
computed適合處理的場景是,獲得一個值或者結果,該結果受其他的依賴的影響。(一個數據受多個數據影響)

2、watch也可以實現監聽為什麼還要計算屬性這個?

1、首先vue官方推薦的是如果可以用計算屬性就不要用watch,為什麼呢?因為計算屬性是有快取的,對效能來說是更好的。
2、其次兩種功能是不一樣的,computed針對於一個值依賴於多個,watch雖然也可以,但是程式碼會很臃腫

3、data為什麼是一個函式?

元件中的data寫成一個函式,資料以函式返回值形式定義,這樣每複用一次元件,就會返回一份新的data,類似於給每個元件例項建立一個私有的資料空間,讓各個元件例項維護各自的資料。而單純的寫成物件形式,就使得所有元件例項共用了一份data,就會造成一個變了全都會變的結果。

4、雙向資料繫結原理?

利用了 Object.defineProperty() ,讓資料變得可觀測,結合訂閱者釋出者模式,當資料變化時候會由釋出者來通知訂閱者更新資料和view
vue3利用proxy來讓資料可觀測。

5、元件怎麼實現雙向資料繫結

元件 內部props寫上value,watch value進行資料的初始化,data中宣告一個newvalue,監聽這個newvalue,如果頁面發生變化使用emit/on向上丟擲input事件

6、生命週期(8+3)官網去看

7、extend和混合

extend用於建立vue例項
    mixins可以混入多個mixin,extends只能繼承一個
    mixins類似於面向切面的程式設計(AOP),extends類似於面向物件的程式設計

8、什麼是單向資料流

資料流向是單向的,即父能給子,子不能修改父,寫元件時我都會宣告一個類似box的東西,當我子元件發生變化我都會通知到box再由box進行分發,這麼做可以保證出現bug時候的除錯

9、事件通訊

父子:props
子父:emit/on
同級:新宣告一個vue作為中間,來轉發。或者原生寫一個eventbus
跨層級:provide 和 inject(不建議日常開發使用,主要在開發高階外掛/元件庫時使用。)、同級的在這裡也可以使用 

10、vue中 key 值的作用

1、不加key的話,更新機制的進行diff的時候是會全部比較的,對效能不好。
2、不加可能會出現資料變化而沒有渲染檢視
3、根據diff演算法,同一層級的一組節點,他們需要通過唯一的id進行區分。

11、vue中如果資料更新了檢視沒有渲染怎麼辦

一般這種都會出現在json\jsonArray中,需要使用$set(目標,要修改的key,值),如果是層級特別多可能set還是不會生效,需要this.$forceUpdate();

12、如果我需要在檢視更新後進行一些操作可以怎麼做

$nextTick

13、vue v-show/v-if

v-show控制display,適用於切換頻繁的場景
v-if如果為false,頁面都不會渲染這個dom,適用於不經常切換的場景。

14、vue-router有哪幾種導航鉤子

1、全域性的 beforeEach、afterEach
2、單個路由獨享的beforeEnter
3、頁面級的 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

15、hash和history路由

hash 值變化不會導致瀏覽器向伺服器發出請求
hash改變會觸發 hashchange 事件(hashchange只能改變 # 後面的url片段)
hash發生變化的url都會被瀏覽器記錄下來,從而你會發現瀏覽器的前進後退都可以用了

history 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。但是history路由需要後端配置
如果路由找不到,就始終返回一個html,不然會由重新整理404的問題

更多的會慢慢完善,我過濾掉了很多比如根據自己理解去解答的,程式碼題也過濾了不少,因為最近電話面試居多,程式題幾乎沒有。過濾了很多基礎的,基本用過的人都會的。只取其中最經典,最常問,最有誤區的,可能會由一些疏漏我之後想起來或者遇到再補上

&n