(含答案)這份js基礎試題,你能答對幾道?

1.javascript 有幾種數據類型?

javascript 有八種數據類型javascript

其中基本數據類型有java

  • undefined
  • null
  • boolean
  • string
  • number
  • symbol (es6)
  • bigint (es10)

引用數據類型有 Object (Function,Array,...)es6


2.請簡單介紹一下基本數據類型和引用數據類型的區別?以及講下如何判斷數據的類型?

基本數據類型是指存放在棧中的簡單數據段,數據大小肯定,內存空間大小能夠分配,它們是直接按值存放的,因此能夠直接按值訪問web

引用類型是存放在堆內存中的對象,變量實際上是保存的在棧內存中的一個指針,這個指針指向堆內存。ajax

因此如下代碼是 OK 的數組

const arr = []
arr.push(1)
console.log(arr) //[1]
複製代碼
  • typeof 只能判斷除了 null 之外的基本數據類型以及函數
typeof 'a'// string 有效
typeof 1// number 有效
typeof true//boolean 有效
typeof Symbol(); // symbol 有效  ---注意Symbol是基本數據類型,不能使用new關鍵字
typeof undefined//undefined 有效
複製代碼

typeof null的問題瀏覽器

在 JavaScript 最初的實現中,JavaScript 中的值是由一個表示類型的標籤和實際數據值表示的。對象的類型標籤是 0。因爲 null 表明的是空指針(大多數平臺下值爲 0x00),所以,null 的類型標籤是 0,typeof null 也所以返回 "object"。閉包

  • instanceof 判斷原理:判斷該對象是否在指定類型的原型鏈上

手寫一個 instanceofapp

function myinstanceOf(son, father{
    if (typeof son !== "object") {
        return false
    }
    let sonPro = Reflect.getPrototypeOf(son);
    let fatherPro = father.prototype;
    while (sonPro) {
        if (sonPro === fatherPro) {
            return true
        }
        sonPro = Reflect.getPrototypeOf(sonPro);
    }
    return false
}

//測試
function Car(make, model, year{
    this.make = make;
    this.model = model;
    this.year = year;
}
const auto = new Car('Honda''Accord'1998);

console.log(auto instanceof Car);  //true
console.log(auto instanceof Object); //true
console.log(myinstanceOf(auto, Car)) //true
console.log(myinstanceOf(auto, Object))  //true

複製代碼

通常用於判斷引用類型異步

[] instanceof Array// true
new Date() instanceof Date;// true
複製代碼
  • toString (建議使用)

通常使用 String 對象原型上的 toString 方法,緣由是對象可能會重寫 toString

封裝本身的 type 方法

let type = function (o{
    let s = Object.prototype.toString.call(o);
    return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
['Null',
    'Undefined',
    'Object',
    'Array',
    'String',
    'Number',
    'Boolean',
    'Function',
    'RegExp'
].forEach(function (t{
    type['is' + t] = function (o{
        return type(o) === t.toLowerCase();
    };
});
複製代碼

3.new 一個對象時,都作了什麼?

作了如下四件事

1.建立一個空對象,做爲將要返回的對象實例。

2.將這個空對象的原型,指向構造函數的 prototype 屬性。

3.將這個空對象賦值給函數內部的 this 關鍵字。

4.開始執行構造函數內部的代碼。

按照以上說明,手動實現一個 new 方法

若是構造函數內部有 return 語句,並且 return 後面跟着一個對象,new 命令會返回 return 語句指定的對象;不然,就會無論 return 語句,返回 this 對象

function New(...arg{
    const obj = {};  //建立一個空對象  --對應1
    const Constructor = arg.shift();  //獲取第一個參數,即構造函數,同時刪除第一個參數
    obj.__proto__ = Constructor.prototype;   //將這個空對象的原型,指向構造函數的prototype屬性 ----對應2
    const res = Constructor.apply(obj, arg);   //執行構造函數方法,將方法裏的this指向建立的obj對象  ---- 對應 3,4
    return res instanceof Object ? res : obj //方法有返回值則返回,沒有則返回新建立的對象
}

//測試
function Car(make, model, year{
    this.make = make;
    this.model = model;
    this.year = year;
}
const auto = new Car('Honda''Accord'1998);
console.log(auto)  //Car{make: "Honda", model: "Accord", year: 1998}
const auto2 = New(Car, 'Honda''Accord'1998)
console.log(auto2)  //Car{make: "Honda", model: "Accord", year: 1998}
複製代碼

4.說一說 js 中的對象繼承?

js 中的繼承指得是

1.繼承目標對象的實例方法

2.繼承原型上的方法

//es5
function Parent(name{
    this.name = name;
    this.sayHi = function ({
        console.log("Hi" + this.name + ".");
    }
}
function Children(name{
    Parent.apply(this, name)   // 實現----1
    this.getName = function ({
        console.log(this.name);
    }
}
 Children.prototype = Object.create(Parent.prototype)  //實現-----2
Children.prototype.constructor = Children

//Object.create原理解析
function objectcreate(prototype{
    function F({ }
    F.prototype = prototype
    return new F()
}

//es6
class Parent {
    constructor(name) {
    this.name = name;
    }
    sayHi() {
  console.log("Hi" + this.name + ".");
    }
};
class Children extends Parent {  //extends關鍵字  實現---2
    constructor(name) {
   super(name)  //super關鍵字實現---1,注意super必須放在構造函數頂部調用
    }
}
複製代碼

5.聊一下 js 中的 this,並講一下改變 this 指向的方式有哪些?箭頭函數有本身的 this 嗎?

面嚮對象語言中 this 表示當前對象的一個引用

但在 JavaScript 中 this 不是固定不變的,它會隨着執行環境的改變而改變。

我的理解是 this 指向使用 this 關鍵字所在做用域的那個對象,也是你們常說的誰調用,就指向誰

以下:

let obj = {
    say() {
    console.log(this)
    }
}
obj.say()  //輸出obj   此時的this在obj環境下
let fn = obj.say
fn()  //輸出window
let obj2 = {
    obj2obj2: {
        say: obj.say
    },
    say: obj.say
}
obj2.obj2obj2.say()  //輸出obj2obj2
obj2.say()  //輸出obj2
複製代碼

構造函數中的 this 有所區別,緣由是 new 關鍵字改變了 this 指向,能夠參考上面

js 中提供了三種方法能夠改變 this 的指向

  • call 方法

function.call(thisArg, arg1, arg2, ...) 第一個參數爲函數方法 this 所指向的對象,剩餘參數爲函數的參數

調用此方法後,函數會當即執行

實現一個 call 方法

Function.prototype.myCall = function (context, ...arg{
    context = (context === null || context === undefined) ? window : Object(context)
    let fnKey = Symbol()
    context[fnKey] = this  //this表示要運行的函數,賦值給鍵名
    let res = context[fnKey](...arg) //調用方法
   // delete context[fnKey]  //刪除鍵名
    Reflect.defineProperty(fnKey)  //es6規範
    return res
}
複製代碼
  • apply 方法

func.apply(thisArg, [argsArray]) 與 call 方法不一樣的是,此方法的第二個參數爲一個數組

實現一個 apply 方法

Function.prototype.myApply = function (context, arg{
    context = (context === null || context === undefined) ? window : Object(context)
    let fnKey = Symbol()
    context[fnKey] = this
    let res;
    if (arg) {
        res = context[fnKey](...arg)
    } else {
        res = context[fnKey]()
    }
    Reflect.defineProperty(fnKey)
    return res
}
複製代碼
  • bind 方法

function.bind(thisArg[, arg1[, arg2[, ...]]]),第二個參數爲參數列表 此方法不會當即執行,而是返回一個方法

實現一個 bind 方法

Function.prototype.mybind = function (context, ...arg{
    context = (context === null || context === undefined) ? window : Object(context)
    var thatFunc = this
    return function ({
    return thatFunc.apply(context, arg.concat(...arguments));
    }
}
複製代碼

箭頭函數()=>{}

箭頭函數沒有本身的 this,而是繼承父級做用域的 this,

沒有本身的 this,固然也不可使用 call,apply,bind 以及 new 方法

6.講一下防抖節流,以及適用場景?

1.節流函數

聽名字咱們也大概知曉什麼意思,意思是建立並返回一個像節流閥同樣的函數,當重複調用函數的時候,最多每隔 wait 毫秒調用一次該函數

聽了上面的解析,發現有一個問題,規定時間內執行,究竟是最開始就執行一次,間隔 wait 秒在執行,仍是說 wait 秒中以後執行,再 wait 秒中以後執行

1.1 在函數調用前先執行

思路以下:

利用閉包,建立一個變量,用來保存上次執行的時間,而後在函數中判斷,若是時間間隔大於 wait 秒,執行函數,不然不做爲

/**
 * @param func 執行函數
 * @param wait 時間間隔
 * @returns {Function}
 */

 let throttle = function (func, delay{
            let prev = 0;
            return function ({
                let context = this;
                let args = arguments;
                let now = Date.now();
                if (now - prev >= delay) {
                    func.apply(context, args);
                    prev = Date.now();
                }
            }
        }
複製代碼

適用場景:防止用戶重複提交,如表單提交,發送短信,輸入次數過多等

1.2 在時間間隔以後執行方法

思路以下:

使用定時器,若是定時器不存在,則建立定時器,定時器中執行方法,同時清除定時器 id

let throttle = function (func, delay{
            let timer = null;
            return function ({
                let context = this;
                let args = arguments;
                if (!timer) {
                    timer = setTimeout(function ({
                        func.apply(context, args);
                        timer = null;
                    }, delay);
                }
            }
        }
複製代碼

適用場景:滾動條事件,搜索框輸入發送 ajax,輸入框判斷是否合法等

1.3 讓函數前時間間隔先後都執行

採用時間戳加定時器的形式

 let throttle = function (func, delay{
            let timer = null;
            let startTime = Date.now();  //設置開始時間
            return function ({
                let curTime = Date.now();
                let remaining = delay - (curTime - startTime);  //剩餘時間
                let context = this;
                let args = arguments;
                if (remaining <= 0) {      // 間隔大於或者等於delay秒
                    func.apply(context, args);
                    startTime = Date.now();
                } else {  // 間隔小於或者等於delay秒
                    clearTimeout(timer);
                    timer = setTimeout(func, remaining);   //取消當前計數器並計算新的remaining
                }
            }
        }

複製代碼

remaining 爲兩次方法執行間隔與 delay 的差距,爲負數則表示,間隔時間大於 delay 秒,反之小於 delay 秒

1.4 擴展,結合上面三個方法,動態使用

使用第三個參數 options,以對象形式,能夠傳入兩個參數 leading,trailing

/**
 * 建立並返回一個像節流閥同樣的函數,當重複調用函數的時候,最多每隔 wait毫秒調用一次該函數
 * @param func 執行函數
 * @param wait 時間間隔
 * @param options 若是你想禁用第一次首先執行的話,傳遞{leading: false},
 *                若是你想禁用最後一次執行的話,傳遞{trailing: false}
 * @returns {Function}
 */

let throttle = function (func, wait, options{
    let context, args, result;  //this上下文,參數,返回值
    let timeout = null;  //定時器id
    let previous = 0;   //上次執行的時間
    if (!options) options = {}; //禁用首次或者末次執行

    let later = function (//remaining秒以後執行方法
        //初始化函數
        previous = options.leading === false ? 0 : new Date().getTime(); //沒有禁用第一次執行,則previous=0
        timeout = null;  //初始化定時器id
        result = func.apply(context, args); //執行方法
        context = args = null;
    };
    return function ({
        let now = new Date().getTime();   //獲取當前時間

        if (!previous && options.leading === false) {
            //當第一次執行時,若是禁用第一次執行(options.leading),將本次執行時間賦值給上次執行
            previous = now
        }

        let remaining = wait - (now - previous);   //用時間間隔wait減去(本次運行的時間now-上次運行的時間previous),使時間間隔爲wait
        context = this;   //獲取當前環境this
        args = arguments//獲取函數參數

        if (remaining <= 0 || remaining > wait) { //若是兩次方法執行時間間隔恰好爲wait,或者大於wait,
            previous = now;  //將本次時間賦值給上次
            result = func.apply(context, args);   //執行方法
            context = args = null

        } else if (!timeout && options.trailing !== false) {
            // 時間間隔小於wait,且定時器不存在,且最後一次執行
            timeout = setTimeout(later, remaining);  //在remaining時間後執行方法,remaining爲距離wait所剩餘的時間
        }
        return result;
    };
}

複製代碼

原理是根據變量初始化 previous 變量,和判斷是否生成定時器

2.防抖函數

當持續觸發事件時,必定時間段內沒有再觸發事件,事件處理函數纔會執行一次,若是設定的時間到來以前,又一次觸發了事件,就從新開始延時。

這個和節流不一樣的地方是,這個函數若是一直觸發,則一直不會執行,只會在不觸發的 wait 秒後執行一次

在函數中止執行的 wait 秒後執行

 let debounce = function (fn, wait{
            let timeout = null;
            return function ({
                if (timeout !== null) clearTimeout(timeout);
                timeout = setTimeout(fn, wait);
            }
        }

複製代碼

原理:使用定時器,每次運行時判判定時器是否存在,存在則清除定時器,在生成一個 wait 秒後執行的定時器

擴展,自定義函數首次執行仍是末次執行

/**
 * 防反跳。func函數在最後一次調用時刻的wait毫秒以後執行!
 * @param func 執行函數
 * @param wait 時間間隔
  * @param options 若是你想第一次首先執行的話,傳遞{leading: true},
 *                若是你想最後一次執行的話,傳遞{trailing: true}
 * @returns {Function}
 */


let debounce = function (func, wait, options{
    let timeout, args, context, timestamp, result;
    let later = function ({
        let last = new Date().getTime() - timestamp; // timestamp會實時更新
        if (last < wait && last >= 0) {  //若是定時器執行時間距離上次函數調用時間,大於0,小於wait,則從新生成定時器,時間間隔爲距離wait所剩餘的時間
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;  //清除定時器
            if (options.trailing) {  //若是定義時間間隔後執行
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };
    return function ({
        context = this;   //綁定函數上下文
        args = arguments;
        timestamp = new Date().getTime();  //當前執行方法時間
        let callNow = options.leading && !timeout;
        if (!timeout) {
            //生成定時器
            timeout = setTimeout(later, wait);
        }
        if (callNow) { //若是定義首次執行
            //當即執行
            result = func.apply(context, args);
            context = args = null;
        }
        return result;
    };
}

複製代碼

原理:執行時判判定時器,若是定時器存在,則不做爲,而後在定時器執行方法中判斷時間間隔,大於 0 小於 wait,則生成新的定時器,每次執行方法動態更新 timestamp 的值。

7.講一下 js 中的做用域鏈,原型鏈?

js 中的做用域分爲兩種,函數做用域和全局做用域,es6 新增了塊級做用域,使用 let 和 const 關鍵字

每一個函數都有本身的做用域,函數中嵌套有函數,以此造成了做用域鏈,做用域鏈最頂端爲全局做用域


var a = 20;
function test({
    var b = a + 10;
    function innerTest({
        var c = 10;
        return b + c;
    }
    return innerTest();  //40
}
複製代碼

上面例子的做用域鏈爲 innerTest()--->test()----->window 環境(瀏覽器模式下)

在做用域中找不到的變量,就會沿着做用域鏈往上找,一直找到頂端

以下 innerTest 函數中的變量 b,是取得 test()函數做用域中的變量

原型鏈

原型鏈是 js 中對象的一種特殊指針,能夠解決一些方法公用和繼承的問題

每一個對象都有本身的原型,而原型對象又有本身的原型,所以造成一種鏈式結構,原型鏈的最頂端爲 Object 對象

8.講一下 js 中的事件循環?

javascript 是單線程的語言,也就是說,同一個時間只能作一件事。

而這個單線程的特性,與它的用途有關,做爲瀏覽器腳本語言,JavaScript 的主要用途是與用戶互動,以及操做 DOM。

這決定了它只能是單線程,不然會帶來很複雜的同步問題。

好比,假定 JavaScript 同時有兩個線程,一個線程在某個 DOM 節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?

提及事件循環,得說一下 js 中的同步任務和異步任務

異步任務中又分宏任務和微任務

除了廣義的同步任務和異步任務,JavaScript 單線程中的任務能夠細分爲宏任務和微任務。

宏任務包括:script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。

微任務包括:process.nextTick, Promises.then, Object.observe, MutationObserver。

js 執行機制爲先執行同步任務,發現其中有異步任務,講任務掛起,添加到下一輪任務隊列中,當本輪的同步任務執行完成後,講異步隊列中的任務轉爲同步任務,重複這個過程,可是要注意的是,異步隊列轉爲同步任務要先執行微任務,再執行宏任務

以上是我的的理解,若有不對的地方,歡迎指出!

相關文章
相關標籤/搜索