【Step-By-Step】高頻面試題深刻解析 / 週刊01

一週彙總javascript

1.如何正確判斷this的指向?css

2.JS中原始類型有哪幾種?null 是對象嗎?原始數據類型和複雜數據類型有什麼區別?html

3.說一說你對HTML5語義化的理解html5

4.如何讓 (a == 1 && a == 2 && a == 3) 的值爲true?java

5.防抖(debounce)函數的做用是什麼?有哪些應用場景,請實現一個防抖函數。node

更多優質文章可戳: github.com/YvetteLau/B…git

1.如何正確判斷this的指向?(2019-05-20)

若是用一句話說明 this 的指向,那麼便是: 誰調用它,this 就指向誰。github

可是僅經過這句話,咱們不少時候並不能準確判斷 this 的指向。所以咱們須要藉助一些規則去幫助本身:面試

this 的指向能夠按照如下順序判斷:canvas

全局環境中的 this

瀏覽器環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部)this 都指向全局對象 window;

node 環境:不管是否在嚴格模式下,在全局執行環境中(在任何函數體外部),this 都是空對象 {};

是不是 new 綁定

若是是 new 綁定,而且構造函數中沒有返回 function 或者是 object,那麼 this 指向這個新對象。以下:

構造函數返回值不是 function 或 object。

function Super(age) {
    this.age = age;
}

let instance = new Super('26');
console.log(instance.age); //26
複製代碼

構造函數返回值是 function 或 object,這種狀況下 this 指向的是返回的對象。

function Super(age) {
    this.age = age;
    let obj = {a: '2'};
    return obj;
}


let instance = new Super('hello');
console.log(instance.age); //undefined
複製代碼

你能夠想知道爲何會這樣?咱們來看一下 new 的實現原理:

  1. 建立一個新對象。
  2. 這個新對象會被執行 [[原型]] 鏈接。
  3. 屬性和方法被加入到 this 引用的對象中。並執行了構造函數中的方法.
  4. 若是函數沒有返回其餘對象,那麼 this 指向這個新對象,不然 this 指向構造函數中返回的對象。
function new(func) {
    let target = {};
    target.__proto__ = func.prototype;
    let res = func.call(target);
    //排除 null 的狀況
    if (res && typeof(res) == "object" || typeof(res) == "function") {
    	return res;
    }
    return target;
}
複製代碼

函數是否經過 call,apply 調用,或者使用了 bind 綁定,若是是,那麼this綁定的就是指定的對象【歸結爲顯式綁定】。

function info(){
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
var info = person.info;
info.call(person);   //20
info.apply(person);  //20
info.bind(person)(); //20
複製代碼

這裏一樣須要注意一種特殊狀況,若是 call,apply 或者 bind 傳入的第一個參數值是 undefined 或者 null,嚴格模式下 this 的值爲傳入的值 null /undefined。非嚴格模式下,實際應用的默認綁定規則,this 指向全局對象(node環境爲global,瀏覽器環境爲window)

function info(){
    //node環境中:非嚴格模式 global,嚴格模式爲null
    //瀏覽器環境中:非嚴格模式 window,嚴格模式爲null
    console.log(this);
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
var info = person.info;
//嚴格模式拋出錯誤;
//非嚴格模式,node下輸出undefined(由於全局的age不會掛在 global 上)
//非嚴格模式。瀏覽器環境下輸出 28(由於全局的age會掛在 window 上)
info.call(null);
複製代碼

隱式綁定,函數的調用是在某個對象上觸發的,即調用位置上存在上下文對象。典型的隱式調用爲: xxx.fn()

function info(){
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
person.info(); //20;執行的是隱式綁定
複製代碼

默認綁定,在不能應用其它綁定規則時使用的默認規則,一般是獨立函數調用。

非嚴格模式: node環境,執行全局對象 global,瀏覽器環境,執行全局對象 window。

嚴格模式:執行 undefined

function info(){
    console.log(this.age);
}
var age = 28;
//嚴格模式;拋錯
//非嚴格模式,node下輸出 undefined(由於全局的age不會掛在 global 上)
//非嚴格模式。瀏覽器環境下輸出 28(由於全局的age不會掛在 window 上)
//嚴格模式拋出,由於 this 此時是 undefined
info(); 
複製代碼

箭頭函數的狀況:

箭頭函數沒有本身的this,繼承外層上下文綁定的this。

let obj = {
    age: 20,
    info: function() {
        return () => {
            console.log(this.age); //this繼承的是外層上下文綁定的this
        }
    }
}

let person = {age: 28};
let info = obj.info();
info(); //20

let info2 = obj.info.call(person);
info2(); //28
複製代碼

點擊查看更多

2.JS中原始類型有哪幾種?null 是對象嗎?原始數據類型和複雜數據類型有什麼區別?(2019-05-21)

目前,JS原始類型有六種,分別爲:

  • Boolean
  • String
  • Number
  • Undefined
  • Null
  • Symbol(ES6新增)

ES10新增了一種基本數據類型:BigInt

複雜數據類型只有一種: Object

null 不是一個對象,儘管 typeof null 輸出的是 object,這是一個歷史遺留問題,JS 的最第一版本中使用的是 32 位系統,爲了性能考慮使用低位存儲變量的類型信息,000 開頭表明是對象,null 表示爲全零,因此將它錯誤的判斷爲 object

基本數據類型和複雜數據類型的區別爲:

  1. 內存的分配不一樣
  • 基本數據類型存儲在棧中。

  • 複雜數據類型存儲在堆中,棧中存儲的變量,是指向堆中的引用地址。

  1. 訪問機制不一樣
  • 基本數據類型是按值訪問
  • 複雜數據類型按引用訪問,JS不容許直接訪問保存在堆內存中的對象,在訪問一個對象時,首先獲得的是這個對象在棧內存中的地址,而後再按照這個地址去得到這個對象中的值。
  1. 複製變量時不一樣(a=b)
  • 基本數據類型:a=b;是將b中保存的原始值的副本賦值給新變量a,a和b徹底獨立,互不影響
  • 複雜數據類型:a=b;將b保存的對象內存的引用地址賦值給了新變量a;a和b指向了同一個堆內存地址,其中一個值發生了改變,另外一個也會改變。
let b = {
    age: 10
}

let a = b;
a.age = 20;
console.log(b); //{ age: 20 }
複製代碼
  1. 參數傳遞的不一樣(實參/形參)

函數傳參都是按值傳遞(棧中的存儲的內容):基本數據類型,拷貝的是值;複雜數據類型,拷貝的是引用地址

//基本數據類型
let b = 10

function change(info) {
    info=20;
}
//info=b;基本數據類型,拷貝的是值得副本,兩者互不干擾
change(b);
console.log(b);//10
複製代碼
//複雜數據類型
let b = {
    age: 10
}

function change(info) {
    info.age = 20;
}
//info=b;根據第三條差別,能夠看出,拷貝的是地址的引用,修改互相影響。
change(b);
console.log(b);//{ age: 20 }
複製代碼

點擊查看更多

3.說一說你對HTML5語義化的理解(2019-05-22)

語義化意味着顧名思義,HTML5的語義化指的是合理正確的使用語義化的標籤來建立頁面結構,如 header,footer,nav,從標籤上便可以直觀的知道這個標籤的做用,而不是濫用div。

語義化的優勢有:

  • 代碼結構清晰,易於閱讀,利於開發和維護
  • 方便其餘設備解析(如屏幕閱讀器)根據語義渲染網頁。
  • 有利於搜索引擎優化(SEO),搜索引擎爬蟲會根據不一樣的標籤來賦予不一樣的權重

語義化標籤主要有:

  • title:主要用於頁面的頭部的信息介紹
  • header:定義文檔的頁眉
  • nav:主要用於頁面導航
  • main:規定文檔的主要內容。對於文檔來講應當是惟一的。它不該包含在文檔中重複出現的內容,好比側欄、導航欄、版權信息、站點標誌或搜索表單。
  • article:獨立的自包含內容
  • h1~h6:定義標題
  • ul: 用來定義無序列表
  • ol: 用來定義有序列表
  • address:定義文檔或文章的做者/擁有者的聯繫信息。
  • canvas:用於繪製圖像
  • dialog:定義一個對話框、確認框或窗口
  • aside:定義其所處內容以外的內容。<aside> 的內容可用做文章的側欄。
  • section:定義文檔中的節(section、區段)。好比章節、頁眉、頁腳或文檔中的其餘部分。
  • figure:規定獨立的流內容(圖像、圖表、照片、代碼等等)。figure 元素的內容應該與主內容相關,但若是被刪除,則不該對文檔流產生影響。
  • details:描述文檔或者文檔某一部分細節
  • mark:義帶有記號的文本。

語義化應用

例如使用這些可視化標籤,構建下面的頁面結構:

對於早期不支持 HTML5 的瀏覽器,如IE8及更早以前的版本,咱們能夠引入 html5shiv 來支持。

點擊查看更多

4.如何讓 (a == 1 && a == 2 && a == 3) 的值爲true?

利用隱式轉換規則

== 操做符在左右數據類型不一致時,會先進行隱式轉換。

a == 1 && a == 2 && a == 3 的值意味着其不多是基本數據類型。由於若是 a 是 null 或者是 undefined bool類型,都不可能返回true。

所以能夠推測 a 是複雜數據類型,JS 中複雜數據類型只有 object,回憶一下,Object 轉換爲原始類型會調用什麼方法?

  • 若是部署了 [Symbol.toPrimitive] 接口,那麼調用此接口,若返回的不是基本數據類型,拋出錯誤。

  • 若是沒有部署 [Symbol.toPrimitive] 接口,那麼根據要轉換的類型,先調用 valueOf / toString

    1. 非Date類型對象,hintdefault 時,調用順序爲:valueOf >>> toString,即valueOf 返回的不是基本數據類型,纔會繼續調用 toString,若是toString 返回的還不是基本數據類型,那麼拋出錯誤。
    2. 若是 hintstring(Date對象默人的hint是string) ,調用順序爲:toString >>> valueOf,即toString 返回的不是基本數據類型,纔會繼續調用 valueOf,若是valueOf 返回的還不是基本數據類型,那麼拋出錯誤。
    3. 若是 hintnumber,調用順序爲: valueOf >>> toString
var obj = {
    [Symbol.toPrimitive](hint) {
        console.log(hint);
        return 10;
    },
    valueOf() {
        console.log('valueOf');
        return 20;
    },
    toString() {
        console.log('toString');
        return 'hello';
    }
}
console.log(obj + 'yvette'); //default
//若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString`
console.log(obj == 'yvette'); //default
//若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString`
console.log(obj * 10);//number
//若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString`
console.log(Number(obj));//number
//若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`valueOf` >>> `toString`
console.log(String(obj));//string
//若是沒有部署 [Symbol.toPrimitive]接口,調用順序爲`toString` >>> `valueOf`
複製代碼

那麼對於這道題,只要 [Symbol.toPrimitive] 接口,第一次返回的值是 1,而後遞增,即成功成立。

let a = {
    [Symbol.toPrimitive]: (function(hint) {
            let i = 1;
            //閉包的特性之一:i 不會被回收
            return function() {
                return i++;
            }
    })()
}
console.log(a == 1 && a == 2 && a == 3); //true
複製代碼

調用 valueOf 接口的狀況:

let a = {
    valueOf: (function() {
        let i = 1;
        //閉包的特性之一:i 不會被回收
        return function() {
            return i++;
        }
    })()
}
console.log(a == 1 && a == 2 && a == 3); //true
複製代碼

另外,除了i自增的方法外,還能夠利用 正則,以下

let a = {
    reg: /\d/g,
    valueOf () {
        return this.reg.exec(123)[0]
    }
}
console.log(a == 1 && a == 2 && a == 3); //true
複製代碼

調用 toString 接口的狀況,再也不作說明。

利用數據劫持

使用 Object.defineProperty 定義的屬性,在獲取屬性時,會調用 get 方法。利用這個特性,咱們在 window 對象上定義 a 屬性,以下:

let i = 1;
Object.defineProperty(window, 'a', {
    get: function() {
        return i++;
    }
});
console.log(a == 1 && a == 2 && a == 3); //true
複製代碼

ES6 新增了 Proxy ,此處咱們一樣能夠利用 Proxy 去實現,以下:

let a = new Proxy({}, {
    i: 1,
    get: function () {
        return () => this.i++;
    }
});
console.log(a == 1 && a == 2 && a == 3); // true
複製代碼

數組的 toString 接口默認調用數組的 join 方法,重寫數組的 join 方法。

let a = [1, 2, 3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3); //true
複製代碼

利用 with 關鍵字

我本人對 with 向來是敬而遠之的。不過 with 的確也是此題方法之一:

let i = 0;

with ({
    get a() {
        return ++i;
    }
}) {
    console.log(a == 1 && a == 2 && a == 3); //true
}
複製代碼

點擊查看更多

5.防抖(debounce)函數的做用是什麼?有哪些應用場景,請實現一個防抖函數。

防抖函數的做用

防抖函數的做用就是控制函數在必定時間內的執行次數。防抖意味着N秒內函數只會被執行一次,若是N秒內再次被觸發,則從新計算延遲時間。

舉例說明:小思最近在減肥,可是她很是貪吃。爲此,與其男友約定好,若是10天不吃零食,就能夠購買一個包(不要問爲何是包,由於包治百病)。可是若是中間吃了一次零食,那麼就要從新計算時間,直到小思堅持10天沒有吃零食,才能購買一個包。因此,管不住嘴的小思,沒有機會買包(悲傷的故事)...這就是防抖

無論吃沒吃零食,每10天買一個包,中間想買包,忍着,等到第十天的時候再買,這種狀況是節流。如何控制女友的消費,各位攻城獅們,get到了嗎?防抖可比節流有效多了!

防抖應用場景

  1. 搜索框輸入查詢,若是用戶一直在輸入中,沒有必要不停地調用去請求服務端接口,等用戶中止輸入的時候,再調用,設置一個合適的時間間隔,有效減輕服務端壓力。
  2. 表單驗證
  3. 按鈕提交事件。
  4. 瀏覽器窗口縮放,resize事件等。

防抖函數實現

  1. 事件第一次觸發時,timernull,調用 later(),若 immediatetrue,那麼當即調用 func.apply(this, params);若是 immediatefalse,那麼過 wait 以後,調用 func.apply(this, params)
  2. 事件第二次觸發時,若是 timer 已經重置爲 null(即 setTimeout 的倒計時結束),那麼流程與第一次觸發時同樣,若 timer 不爲 null(即 setTimeout 的倒計時未結束),那麼清空定時器,從新開始計時。
function debounce(func, wait, immediate = true) {
    let timer;
    // 延遲執行函數
    const later = (context, args) => setTimeout(() => {
        timer = null;// 倒計時結束
        if (!immediate) {
            func.apply(context, args);
            //執行回調
            context = args = null;
        }
    }, wait);
    let debounced = function (...params) {
        let context = this;
        let args = params;
        if (!timer) {
            timer = later(context, args);
            if (immediate) {
                //當即執行
                func.apply(context, args);
            }
        } else {
            clearTimeout(timer);
            //函數在每一個等待時延的結束被調用
            timer = later(context, args);
        }
    }
    debounced.cancel = function () {
        clearTimeout(timer);
        timer = null;
    };
    return debounced;
};
複製代碼

immediate 爲 true 時,表示函數在每一個等待時延的開始被調用。

immediate 爲 false 時,表示函數在每一個等待時延的結束被調用。

只要高頻事件觸發,那麼回調函數至少被調用一次。

點擊查看更多

參考文章:

[1] www.ecma-international.org/ecma-262/6.…

[2] 嗨,你真的懂this嗎?

[3] 【面試篇】寒冬求職季之你必需要懂的原生JS(上)

[4] 【面試篇】寒冬求職季之你必需要懂的原生JS(中)

[5] digcss.com/throttle-th…

謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。github.com/YvetteLau/B…

關注公衆號,加入技術交流羣

相關文章
相關標籤/搜索