天天看點

JavaScript 進階面試題1

作者:夜碼者

1、typeof和 instanceof差別(必會)

在 javascript 中,判斷一個變量的類型可以用 typeof

  • 1、數字類型、typeof 傳回的值是 number。比如說:typeof(1),傳回值是 number
  • 2、字元串類型,typeof 傳回的值是 string。比如 typeof(“123”傳回值是 string
  • 3、布爾類型,typeof 傳回的值是 boolean。比如 typeof(true)傳回值是 boolean
  • 4、對象、數組、null 傳回的值是 object。比如 typeof(window),typeof(document),typeof(null)傳回的值都是 object
  • 5、函數類型,傳回的值是 function。比如:typeof(eval),typeof(Date)傳回的值都是 function。
  • 6、不存在的變量、函數或者 undefined,将傳回 undefined。比如:typeof(abc)、typeof(undefined)都傳回 undefined

在 javascript 中,instanceof 用于判斷某個對象是否被另一個函數構造使用 typeof 運算符時采用引用類型存儲值會出現一個問題,無論引用的是什麼類型的對象,它都傳回”object”。ECMAScript 引入了另一個 Java 運算符 instanceof 來解決這個問題。

Instanceof 運算符與 typeof 運算符相似,用于識别正在處理的對象的類型。與 typeof 方法不同的是,instanceof 方法要求開發者明确地确認對象為某特定類型

2、js使用 typeof能得到的哪些類型?(必會)

typeof 隻能區分值類型

typeof undefined // undefined
typeof null // object
typeof console.log // function
typeof NaN // number           

3、解釋一下什麼是回調函數,并提供一個簡單的例子?(必會)

軟體子產品之間總是存在着一定的接口,從調用方式上,可以把他們分為三類:同步調用 、回調 和 異步調用

同步調用 是一種阻塞式調用,調用方要等待對方執行完畢才 傳回,它是一種單向調用;

回調 是一種雙向調用模式,也就是說,被調用方在接口被調用時也會調用對方的接口;

異步調用 是一種類似消息或事件的機制,不過它的 調用方向剛好相反,接口的服務在收到某種訊息或發生某種事件時,會主動通知客戶方(即調用客戶方的接口)。回調和異步調用的關系非常緊密,通常我們使用回 調來實作異步消息的注冊,通過異步調用來實作消息的通知。同步調用是三者當中最簡單的,而回調又常常是異步調用的基礎,是以,下面我們着重讨論回調機制在不同軟體架構中的實作

回調函數 就是一個通過函數指針調用的函數。如果你把函數的指針(位址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實作方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應

案例:

#include<stdio.h>

//callbackTest.c
//1.定義函數 onHeight(回調函數)
//@onHeight 函數名
//@height 參數
//@contex 上下文
void onHeight(double height, void *contex)
{
    printf("current height is %lf", height);
}

//2.定義 onHeight 函數的原型
//@CallbackFun 指向函數的指針類型
//@height 回調參數,當有多個參數時,可以定義一個結構體
//@contex 回調上下文,在 C 中一般傳入 nullptr,在 C++中可傳入對象指針
typedef void (*CallbackFun)(double height, void *contex);

//定義全局指針變量
CallbackFun m_pCallback;

//定義注冊回調函數
void registHeightCallback(CallbackFun callback, void *contex)
{
    m_pCallback = callback;
}

//定義調用函數
void printHeightFun(double height)
{
    m_pCallback(height, NULL);
}

//main 函數
int main()
{
    //注冊回調函數 onHeight
    registHeightCallback(onHeight, NULL);

    //列印 height
    double h = 99;
    printHeightFun(99);
}           

4、什麼是閉包?(必會)

“閉包就是能夠讀取其他函數内部變量的函數。例如在 javascript 中,隻有函數内部的子函數才能讀取局部變量,是以閉包可以了解成“定義在一個函數内部的函數“。在本質上,閉包是将函數内部和函數外部連接配接起來的橋梁。”

舉例:建立閉包最常見方式,就是在一個函數内部建立另一個函數。下面例子中的 closure 就是一個閉包

function func(){ 
    var a =1 ,b = 2;

    funciton closure(){ 
        return a + b; 
    } 
    return closure;
}           

5、什麼是記憶體洩漏(必會)

記憶體洩漏指任何對象在您不再擁有或需要它之後仍然存在

6、哪些操作會造成記憶體洩漏?(必會)

  • 1、垃圾回收器定期掃描對象,并計算引用了每個對象的其他對象的數量。如果一個對象的引用數量為 0(沒有其他對象引用過該對象),或對該對象的惟一引用是循環的,那麼該對象的記憶體即可回收
  • 2、setTimeout 的第一個參數使用字元串而非函數的話,會引發記憶體洩漏
  • 3、閉包、控制台日志、循環(在兩個對象彼此引用且彼此保留時,就會産生一個循環)

7、JS記憶體洩漏的解決方式(必會)

7.1 global variables:對未聲明的變量的引用在全局對象内建立一個新變量。在浏覽器中,全局對象就是 window。

function foo(arg) {
    bar = 'some text'; // 等同于 window.bar = 'some text';
}           
  • 解決 :
  • 建立意外的全局變量
function foo() {
    this.var1 = 'potential accident'
}           
  • 可以在 JavaScript 檔案開頭添加 “use strict”,使用嚴格模式。這樣在嚴格模式下解析 JavaScript 可以防止意外的全局變量
  • 在使用完之後,對其指派為 null 或者重新配置設定

7.2 被忘記的 Timers或者 callbacks

在 JavaScript 中使用 setInterval 非常常見

大多數庫都會提供觀察者或者其它工具來處理回調函數,在他們自己的執行個體變為不可達時,會讓回調函數也變為不可達的。對于 setInterval,下面這樣的代碼是非常常見的:

var serverData = loadData();

setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); //This will be executed every ~5 seconds.           

這個例子闡述着timers 可能發生的情況:計時器會引用不再需要的節點或資料

7.3 閉包:一個可以通路外部(封閉)函數變量的内部函數

JavaScript 開發的一個關鍵方面就是閉包:一個可以通路外部(封閉)函數變量的内部函數。由于 JavaScript 運作時的實作細節,可以通過以下方式洩漏記憶體:

var theThing = null;

var replaceThing = function () {

    var originalThing = theThing;

    var unused = function () {
        if (originalThing) // a reference to 'originalThing'
        console.log("hi");
    };

    theThing = {
        longStr: new Array(1000000).join('*'),
        someMethod: function () {
            console.log("message");
        }
    };
};

setInterval(replaceThing, 1000);
           

7.4 DOM引用

有時候,在資料結構中存儲 DOM 結構是有用的。假設要快速更新表中的幾行内容。将每行DOM 的引用存儲在字典或數組中可能是有意義的。當這種情況發生時,就會保留同一 DOM 元素的兩份引用:一個在 DOM 樹種,另一個在字典中。如果将來某個時候你決定要删除這些行,則需要讓兩個引用都不可達。

var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image')
};

function doStuff() {
    elements.image.src = 'http://example.com/image_name.png';
}

function removeImage() {
    // The image is a direct child of the body element.
    document.body.removeChild(document.getElementById('image'));
    // At this point, we still have a reference to #button in the
    //global elements object. In other words, the button element is
    //still in memory and cannot be collected by the GC.
}           

8、說說你對原型(prototype)了解(必會)

JavaScript 是一種通過原型實作繼承的語言與别的進階語言是有差別的,像 java,C#是通過類型決定繼承關系的,JavaScript 是的動态的弱類型語言,總之可以認為 JavaScript 中所有都是對象,在 JavaScript 中,原型也是一個對象,通過原型可以實作對象的屬性繼承,JavaScript 的對象中都包含了一個” prototype”内部屬性,這個屬性所對應的就是該對象的原型

“prototype”作為對象的内部屬性,是不能被直接通路的。是以為了友善檢視一個對象的原型,Firefox 和 Chrome 核心的 JavaScript 引擎中提供了”proto“這個非标準的通路器(ECMA 新标準中引入了标準對象原型通路器”Object.getPrototype(object)”)

原型的主要作用就是為了實作繼承與擴充對象

9、介紹下原型鍊(解決的是繼承問題嗎)(必會)

JavaScript 原型: 每個對象都會在其内部初始化一個屬性,就是 prototype(原型)

原型鍊:當我們通路一個對象的屬性時,如果這個對象内部不存在這個屬性,那麼他就會去 prototype 裡找這個屬性,這個 prototype 又會有自己的 prototype,于是就這樣一直找 下去,也就是我們平時所說的原型鍊的概念

特點:JavaScript 對象是通過引用來傳遞的,我們建立的每個新對象實體中并沒有一份屬于自己的原型副本。當我們修改原型時,與之相關的對象也會繼承這一改變

10、簡單說說 js中的繼承(必會)

繼承有以下六種方法:

  • 1、原型鍊繼承 JavaScript 實作繼承的基本思想:通過原型将一個引用類型繼承另一個引用 類型的屬性和方法
  • 2、借用構造函數繼承(僞造對象或經典繼承) JavaScript 實作繼承的基本思想:在子類構造函數内部調用超類型構造函數。 通過使用 apply() 和 call() 方法可以在新建立的子類對- 象上執行構造函數
  • 3、組合繼承(原型+借用構造)(僞經典繼承) JavaScript 實作繼承的基本思想:将原型鍊和借用構造函數的技術組合在一塊,進而發揮兩者之長的一種繼承模式将原型鍊和借用構造函- 數的技術組合到一起,進而取長補短發揮兩者長處的一種繼承模式
  • 4、型式繼承 JavaScript 實作繼承的基本思想:借助原型可以基于已有的對象建立新對象,同時還不必須是以建立自定義的類型
  • 5、寄生式繼承 JavaScript 實作繼承的基本思想:建立一個僅用于封裝繼承過程的函數,該 函數在内部以某種方式來增強對象,最後再像真正是它做了所有工作一樣傳回對象。寄生- 式繼承是原型式繼承的加強版
  • 6、寄生組合式繼承 JavaScript 實作繼承的基本思想:通過借用函數來繼承屬性,通過原型 鍊的混成形式來繼承方法

繼續閱讀