非常詳細的自己收集理解的前端面試題(二)

1.JS原型鏈

JS裏萬物皆對象,對象又分爲普通對象函數對象。每當定義一個對象時,對象中都會包含一些預定義的屬性。其中每個對象都有一個_proto_屬性,這個屬性用來指向創建它的構造函數的原型對象;每個函數對象都有一個prototype 屬性,這個屬性指向的是該函數的原型對象

先說一下函數對象的prototype屬性

function f(){}
f.prototype = { name: 'myfriend '};//f的原型對象

var a = new f();
var b = new f();

a.name  //myfriend 
b.name  //myfriend

原型對象可以實現類似繼承的機制和完成數據的共享。所有實例對象需要共享的屬性和方法,都放在這個原型對象裏面;那些不需要共享的屬性和方法,就放在構造函數裏面。

//接着上面的代碼
f.prototype.name = 'hello';

a.name  //hello
b.name  //hello

原型對象的屬性不是實例對象自身的屬性。當實例對象本身沒有某個屬性或方法的時候,它會到原型對象去尋找該屬性或方法。這就是原型對象的特殊之處。如果實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法。

a.name = "luse"

a.name // 'luse'
b.name // 'hello'
f.prototype.name // 'hello';

其次是所有對象都有_proto_,指向的是其構造函數的原型對象。

//接着上面的代碼
a._proto_ == f.prototype // true
b._proto_ == f.prototype // true

現在再來看一下什麼是原型鏈,所有對象的原型最終都可以上溯到Object.prototype這就是所有對象都有valueIf和toString方法的原因,因爲這是從Object.prototype繼承的。

//接着上面的代碼
// a是一個對象,又_proto_屬性,指向a的構造函數的原型對象
a._proto_ == f.prototype 

// f.prototype是一個對象,也會有_proto_屬性,指向f的構造函數的原型對象
f.prototype._proto_ == Function.prototype

// Function.prototype是一個普通對象,也會有_proto_屬性,指向Function.prototype的構造函數的原型對象
Function.prototype._proto_ == Object.prototype

//直到原型鏈的最頂端爲null,整個爲一個鍊形結構
Object.prototype._proto_ == null

2.call、apply和bind

函數實例的call方法,可以指定函數內部this的指向(即函數執行時所在的作用域),然後在所指定的作用域中,調用該函數。call的第一個參數就是this所要指向的那個對象,後面的參數則是函數調用時所需的參數。

var name = 'hello';

var obj = { name: 'world'};

var f = function (arg1,arg2) {
  return alert(this.name + arg1 + arg2);
}

//function.call(obj,arg1,arg2...argx)
f.call(obj,"Is","Big")// worldisbig

apply方法的作用與call方法類似,也是改變this指向,然後再調用該函數。唯一的區別就是,它接收一個數組作爲函數執行時的參數

//function.apply(obj,[arg1,arg2...argx])
f.apply(obj,["Is","Big"])// worldisbig

bind方法用於將函數體內的this綁定到某個對象,然後返回一個新函數。

var name = 'hello';

var obj = { name: 'world'};

var f = function (arg1,arg2) {
  return alert(this.name + arg1 + arg2);
}

//function.bind(obj,arg1,arg2...argx)
var newf = f.bind(obj,"Is","Big"); 
newf(); // worldIsBig
//或者
var newf = f.bind(obj,"Is"); 
newf("Big"); // worldIsBig

3.css浮動崩塌

當一個元素浮動之後,不會影響到塊級元素的佈局而只會影響內聯元素(通常是文本)的排列,以下舉例:

未設置任何浮動的文檔普通流

給text1框設置向左浮動的時候,text1脫離了文檔流靠左,而text2框和text3框是塊級元素仍然保持原來的文檔流,使得text1框把text2框覆蓋了,並且把text2框內聯的文本擠到了text3文本的位置,導致兩者重疊。

如果三個元素的浮動都設置靠左,父元素只包含浮動元素,且父元素未設置高度和寬度的時候,那麼它的高度就會踏縮爲零。這是因爲浮動元素脫離了文檔流,包圍它們的父塊中沒有內容了,所以就造成了“浮動塌陷”了。

解決辦法:1.在父元素下加一個空div的子元素,並加上css屬性clear:both清除浮動。

                  2.對父元素設置屬性overflow: hidden或overflow: auto,這樣父級的高度就隨子級容器及子級內容的高度而自適應。

4.異步任務、同步任務、任務隊列和事件循環

程序裏面所有的任務,可以分成兩類:同步任務(synchronous)和異步任務。
同步任務:就是在主線程排隊執行的任務,只有前一個任務完成了,纔會接着執行下一個任務。
異步任務:就是不在主線程、而是被掛起放在一個任務隊列的任務。到該異步任務可以執行了(例如ajax從服務器獲取到了結果),該任務纔會到主線程執行。排在異步任務後面的代碼,不用等到異步任務執行結束才執行,它可以立即執行。

任務隊列:放置當前程序處理的異步任務的地方。
事件循環:首先,主線程會去執行所有的同步任務。等到同步任務全部執行完,就會去看任務隊列裏面的異步任務。如果滿足條件,那麼異步任務就重新進入主線程開始執行,這時它就變成同步任務了。等到執行完,下一個異步任務再進入主線程開始執行。一旦任務隊列清空,程序就結束執行。

5.W3C的WEB標準

網頁由三部分組成,結構(html),表現(css)和行爲(js)。Web標準一般是將這三部分獨立分開,使其更具有模塊化。
    W3C對web標準提出規範化要求:
1、  對於結構要求:標籤閉合,標籤字母小寫,標籤不允許隨意嵌套
2、  對於css,js來說盡量使用外鏈css樣式和js腳本。使結構,表現和行爲分爲三塊,符合規範。同時提高頁面渲染速度,提高用戶體驗。樣式儘量少使用行間樣式,使結構與表現分離,標籤id和class等屬性命名要做到見明知意,標籤越少,加載越快,用戶體驗提高,代碼維護簡單,便於改版。

6.js的內存泄露

內存泄漏:由於疏忽或錯誤造成程序未能釋放已經不再使用的內存。

常見js的幾種內存泄露:

1.在函數內使用未聲明的變量
        js對未聲明變量會在全局最高對象上創建它的引用,根據垃圾回收機制,一個變量的生命週期的結束要等到不再使用該變量,而全局變量則要到關閉瀏覽器頁面纔會被回收,所以及其佔用內存。

function leakage() {
    under="我是未聲明就使用的變量"
}
leakage()
console.log(window)

 2.開發時的console.log語句未去掉
        在傳遞給console.log的對象是不能被垃圾回收,因爲在代碼運行之後需要在開發工具能查看對象信息。所以需要養成使用完
console.log後就馬上去掉。

3.閉包
        閉包就是能夠讀取其他函數內部變量的函數,比如下面的f2函數就是閉包
        我之前的面試題(一)有寫過閉包https://blog.csdn.net/m0_37820751/article/details/98224901

function f1(){
    n=999;
    function f2(){
      alert(n);
  }
  return f2;
}
 
var result=f1();
result();

         f2被賦給全局變量result,就處於隨時被調用的狀態,所以不會被回收,需要釋放其內存,可以讓reault=null。閉包會比普通函數佔用更多的內存,所以不能過分使用。

 4.DOM的引用

var main = document.getElementById("main");
var test = document.getElementById("test");
remove.onclick = function () {
        main.removeChild(test);
}

       儘管我們把main父節點下的test子節點remove掉了,test仍然保存着對DOM的引用,導致不會被垃圾回收,造成內存泄露。我們可以在最後使用完之後給test賦值null,使其內存釋放。

5.定時器使用後未關閉
       
這也是很多人在使用完定時器setTimeout或者setInterval後,沒有進行clearTimeout或者clearIterval來關閉對於的計時器,導致計時器一直在執行佔用內存。

7.js的垃圾回收機制

        在js中有引用計數標記清除這兩種垃圾回收機制。首先要知道垃圾回收只作用於對象,因爲原始類型的值是存在於棧中,會自動回收,而引用類型值存在於堆中,需要垃圾回收。

引用計數,即跟蹤記錄每個值被引用的次數。
        1.當聲明瞭一個變量並將一個引用類型的值賦給該變量時,則這個引用類型值的引用次數就是1;
        2.又將這個引用類型的值賦給另一個變量時,則這個引用類型值的引用次數加1;
        3.當一個包含這個引用類型值的變量又取得了另外一個值,那麼這個引用類型值的引用次數就減1;
        4.當引用次數變爲0的時候,則說明沒有辦法再訪問到這個值了,所以,就代表可以將其所佔用的內存空間給收回來。
        5.垃圾收集器下次再運行時,他就會釋放引用次數爲0的值所佔的內存。
引用計數的bug:循環引用,指的是對象A中包含一個指向對象B的指針,而對象B中也包含一個指向對象A的引用。如下例子:

function problem(){     
    var objA = new Object();
    var objB = new Object(); 
 
    objA.someOtherObject = objB;
    objB.anotherObject = objA; 
}

        循環引用實際上是在堆中的兩個引用類型之間的循環引用,即使釋放了objA和objB對這兩個引用類型的引用,他們內部還存在着互相引用,引用次數爲1不會被垃圾回收。假如這個函數被重複多次調用,就會導致大量內存得不到回收。引用計數已經屬於不常見的垃圾回收策略,但是BOM和DOM中的對象是使用C++以COM(Component Ojbect Model,組件對象)對象的形式實現的,而COM對象的垃圾回收機制是引用計數。 因此,即使IE的JavaScript引擎使用的是標記清除的策略,但是JavaScript訪問的COM對象依然是基於引用計數的策略的,只要IE中涉及到了COM對象,就會存在循環引用的問題。

標記清除,JavaScript中最重用的垃圾收集方式。
        當變量進入環境時,就標記這個變量爲“進入環境”,邏輯上說,永遠不能釋放  進入環境的變量所佔用的內存,因爲一旦進入環境就有可能隨時用到他們,當變量離開環境的時候,將其標記爲“離開環境”。

function addTen(num){  
   var sum += num;  //垃圾收集已將這個變量標記爲“進入環境”。
   return sum;      //垃圾收集已將這個變量標記爲“離開環境”。
}
addTen(10);  //輸出20

        標記清除的好處就是可以解決循環引用問題。如上面循環引用,引用計數都是1,所以只用引用計數的話兩個都沒辦法回收。但是用標記清除只要是有標記就可以直接清除,如何標記變量其實並不重要,關鍵在於採取什麼策略。需要更深入瞭解標記清除可以看https://www.ituring.com.cn/book/tupubarticle/10955
        垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記。然後,它會去掉環境中的變量(正在使用的變量)以及被環境中的變量引用的變量的標記。而在此之後再被加上標記的變量將被視爲準備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最後,垃圾收集器完成內存清除工作,銷燬那些帶標記的值並回收它們所佔用的內存空間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章