1. 程式人生 > >撩課-Web大前端每天5道面試題-Day4

撩課-Web大前端每天5道面試題-Day4

1. 如何實現瀑布流?

瀑布流佈局的原理:

1) 瀑布流佈局要求要進行佈置的元素等寬,
    然後計算元素的寬度, 
    與瀏覽器寬度之比,得到需要佈置的列數;

2) 建立一個數組,長度為列數,
    裡面的值為已佈置元素的總高度(最開始為0);

3) 然後將未佈置的元素依次佈置到高度最小的那一列,
   就得到了瀑布流佈局;

4) 滾動載入, scroll事件得到scrollTop,  
    與最後盒子的offsetTop對比,
    符合條件就不斷滾動載入。

瀑布流佈局核心程式碼:

/**
 * 實現瀑布流的佈局
 * @param {string}parentBox
 * @param {string}childBox
 
*/ function waterFull(parentBox, childBox) { // 1. 求出父盒子的寬度 // 1.1 獲取所有的子盒子 var allBox = $(parentBox). getElementsByClassName(childBox); // console.log(allBox); // 1.2 求出子盒子的寬度 var boxWidth = allBox[0].offsetWidth; // console.log(boxWidth); // 1.3 獲取視窗的寬度 var clientW = document. documentElement.clientWidth;
// console.log(clientW); // 1.4 求出總列數 var cols = Math.floor(clientW / boxWidth); // console.log(cols); // 1.5 父盒子居中 $(parentBox).style.width = cols * boxWidth + 'px'; $(parentBox).style.margin = '0 auto'; // 2. 子盒子定位 // 2.1 定義變數 var heightArr = [], boxHeight = 0, minBoxHeight
= 0, minBoxIndex = 0; // 2.2 遍歷所有的子盒子 for (var i = 0; i < allBox.length; i++) { // 2.2.1 求出每一個子盒子的高度 boxHeight = allBox[i].offsetHeight; // console.log(boxHeight); // 2.2.2 取出第一行盒子的高度放入高度陣列中 if (i < cols) { // 第一行 heightArr.push(boxHeight); } else { // 剩餘行的盒子 // 2.2.3 取出陣列中最矮的高度 minBoxHeight = _.min(heightArr); // 2.2.4 求出最矮高度對應的索引 minBoxIndex = getMinBoxIndex(heightArr, minBoxHeight); // 2.2.5 盒子定位 allBox[i].style.position = 'absolute'; allBox[i].style.left = minBoxIndex * boxWidth + 'px'; allBox[i].style.top = minBoxHeight + 'px'; // 2.2.6 更新最矮的高度 heightArr[minBoxIndex] += boxHeight; } } } /** * 根據內容取出在陣列中對應的索引 * @param {object}arr * @param {number}val * @returns {boolean} */ function getMinBoxIndex(arr, val) { for (var i = 0; i < arr.length; i++) { if (arr[i] === val) return i; } } /** * 判斷是否具備載入子盒子的條件 * @returns {boolean} */ function checkWillLoadImage() { // 1. 獲取最後一個盒子 var allBox = $('main').getElementsByClassName('box'); var lastBox = allBox[allBox.length - 1]; // 2. 求出高度 var lastBoxDis = lastBox.offsetHeight * 0.5 + lastBox.offsetTop; // 3. 求出視窗的高度 var clientH = document.documentElement.clientHeight; // 4. 求出頁面滾動產生的高度 var scrollTopH = scroll().top; // 5. 對比 return lastBoxDis <= clientH + scrollTopH; }

 

2. 原生JS都有哪些方式可以實現兩個頁面間的通訊?

1)  通過url位址列傳遞引數;
    例如:點選列表頁中的每一條資料,
    我們跳轉到同一個詳細頁面,
    但是根據點選的不一樣可以看到
    不同的內容,這樣的話我們就可以
    在URL中傳遞不同的值來區分了;

2)  通過本地儲存 cookie、localeStorage、
    sessionStroage...,例如:京東的登入,
    我們在登入頁登入完成後,
    把使用者資訊儲存到本地,
    然後在其它頁面中如果需要使用的話,
    我們直接從本地的儲存資料中拿
    出來用即可;

3)  使用iframe在A頁面中嵌入B頁面,
    這樣的話,在A中可以通過一些屬性
    和方法實現和B頁面的通訊;

4)  利用postMessage實現頁面間通訊, 
    父視窗往子視窗傳遞資訊, 
    子視窗往父視窗傳遞資訊。

 

3. 原生JS動態向一個div中插入1000個div標籤,如何實現?

此題主要考效能!

1)  可以用JS中的createElement建立div,
    每當建立一個就把它新增到div中, 
    但會造成引發迴流的次數太多;

2)  使用字串拼接的方式,
    把1000個div都拼接完成後,
    統一的新增到頁面中, 
    但會對div原有的元素標籤產生影響:
    原來標籤繫結的事件都消失了

3)  綜合1和2可以使用文件碎片方式來處理。

追問:如果是建立1000萬個呢?

可採用的方案: 資料分批非同步載入
1)  首先把前兩螢幕的資料量
    (例如:300條)先獲取到,
    然後使用字串拼接或者文件碎片
    的方式繫結到頁面中; 

2)  當瀏覽器滾動到指定的區域的
    時候在載入300條...以此類推。

 

4. 程式出現bug了,你是如何除錯的?

1)  在控制檯加斷點,
    F10->逐過程  F11->逐語句;
2) 在程式碼重點的位置加入
    console.log輸出對應的值來進行除錯;
3) debugger除錯;
4) 程式碼分割還原除錯;
5) 異常捕獲機制, 記錄執行日誌;
6) 單元測試。

 

5. 開發中是如何進行效能優化的?

現在框架(vue, react,...)、構建工具(webpack, ...)
已經給我們解決掉大部分的效能優化問題, 
面試時, 可以就你瞭解的框架來深入剖析, 
但此題應該是考原生JS的範疇,
參考答案如下:

1) 雅虎35條效能優化黃金定律;
2) JS程式碼優化:
   a. 專案中的JS/CSS檔案最好一個頁面只用一個, 
      需要把JS/CSS進行合併壓縮, 
      並且減少頁面中的垃圾冗餘程式碼。
      專案的資原始檔在伺服器上最好
      做一下GZIP壓縮。
   
  b. 解除檔案快取; 
     我們修改程式碼並上傳, 
     如果之前頁面訪問過該網站,
     很有可能不能立即見效; 
     我們在引入CSS/JS檔案的時候, 
     在檔名的後面加上版本號(加時間戳), 
     比如:
     <script src='itlike.com.js?_=202001...'></script>; 
     當我們上傳新的檔案後
     把時間戳改一下就可以清除快取了。

  c. 移動端儘量少用圖片:
      icon能用svg畫的不用圖片;
     靜態資源圖:做佈局的時候就能確定下來的圖片,
      比如:
     1)  css sprite圖片合併
         (針對於小圖片)
     2)  做圖片延遲載入
         (針對於大圖片 頭部的長條圖片、背景大圖...),
         開始給一張預設的小的圖片
         (最好維持在10kb以內)
     3) base64
        (存在問題: 頁面的程式碼太臃腫了,以後維護不好操作);  
        如果專案中由於圖片太大實在解決不了, 
        改成base64就解決了
   
  d. 動態資料圖:
     通過ajax從後臺讀取回來的圖片 , 圖片懶載入;

  e. 音視訊檔案的優化: 
     載入頁面的時候,儘量不要載入音視訊檔案,
     當頁面中的其他資源載入完成後,
     再開始載入音視訊檔案; 
     目前移動端經常給音視訊做的優化是:
     走直播流檔案(音訊字尾名是m3u8格式);

  f. 減少頁面資源請求的次數:
     如果當前只是一個宣傳頁,
     或者是一個簡單的頁面, 
     使用的css和js可以採用內嵌式開發;

  g. ajax資料請求分批請求, 
     例如:一次要請求10000條資料的話,
     我們每一次只請求100條,第一螢幕肯定能看全了,
     當頁面滾動到對應的其它螢幕的時候,
     在載入下一個100條...

  h. 做資料的二次快取,  
     能用CSS3做動畫的絕對不用JS,
     能使用transform儘量使用,
     能用animation的進行不用transition...
     儘量減少同步操作,多用非同步操作;
     能使用原生JS自己編寫的,
     絕對不用外掛或者框架;  

 

 

6. 如何實現電商網站中的樓層效果?

1) 封裝緩動動畫函式;
2) 點選切換, 滾動切換, 聯動處理;

核心程式碼如下:

// 3. 監聽GPS上的li的點選
for (var j = 0; j < olLis.length; j++) {
    (function (index) {
        var olLi = olLis[index];
        olLi.onmousedown = function () {

            isClick = true;

            // 3.1 排他
            for (var m = 0; m < olLis.length; m++) {
                olLis[m].className = ''
            }
            addClass(this, 'current');

            // 3.2 讓樓層滾動起來
            buffer(
                document.documentElement, 
                {'scrollTop': index * client().height},
                function () {
                isClick = false;
            })
        }
    })(j)
}

// 4. 監聽文件的滾動
window.onscroll = function (ev1) {
    // 4.1 沒有點選產生的滾動
    if (!isClick) {
        // 4.2 獲取頁面產出的頭部滾動的高度
        var roll = Math.ceil(scroll().top);
        console.log(roll);
        // 4.3 遍歷
        for (var i = 0; i < olLis.length; i++) {
            // 4.4 判斷
            if (roll >= ulLis[i].offsetTop) {
                for (var m = 0; m < olLis.length; m++) {
                    olLis[m].className = ''
                }
                addClass(olLis[i], 'current');
            }
        }
    }

}

緩動動畫函式:

/**
 * 緩動動畫(撩課學院)
 * @param {object}obj
 * @param {object}json
 * @param {function}fn
 */
function buffer(obj, json, fn) {
    // 1.1 清除定時器
    clearInterval(obj.timer);
    // 1.2 設定定時器
    var begin = 0, target = 0, speed = 0;
    obj.timer = setInterval(function () {
        // 1.3.0 旗幟
        var flag = true;
        for (var k in json) {
            // 1.3 獲取初始值
            if ("opacity" === k) { // 透明度
                begin = parseInt(parseFloat(getCSSAttrValue(obj, k)) * 100);
                target = parseInt(parseFloat(json[k]) * 100);
            } else if ("scrollTop" === k) {
                begin = Math.ceil(obj.scrollTop);
                target = parseInt(json[k]);
            } else { // 其他情況
                begin = parseInt(getCSSAttrValue(obj, k)) || 0;
                target = parseInt(json[k]);
            }
            // 1.4 求出步長
            speed = (target - begin) * 0.2;
            // 1.5 判斷是否向上取整
            speed = (target > begin)
                         ? Math.ceil(speed)
                         : Math.floor(speed);
            // 1.6 動起來
            if ("opacity" === k) { // 透明度
                // w3c的瀏覽器
                obj.style.opacity = (begin + speed) / 100;
                // ie 瀏覽器
                obj.style.filter = 'alpha(opacity:' + (begin + speed) + ')';
            } else if ("scrollTop" === k) {
                obj.scrollTop = begin + speed;
            } else if ("zIndex" === k) {
                obj.style[k] = json[k];
            } else {
                obj.style[k] = begin + speed + "px";
            }

            // 1.5 判斷
            if (begin !== target) {
                flag = false;
            }
        }

        // 1.3 清除定時器
        if (flag) {
            clearInterval(obj.timer);
            // 判斷有沒有回撥函式
            if (fn) {
                fn();
            }
        }
    }, 20);
}

 

高清視訊講解(第192節)--->點我