拿到大廠前端offer的前端開發是怎麼回答面試題的

腹黑的可樂發表於2022-11-23

強型別語言和弱型別語言的區別

  • 強型別語言:強型別語言也稱為強型別定義語言,是一種總是強制型別定義的語言,要求變數的使用要嚴格符合定義,所有變數都必須先定義後使用。Java和C++等語言都是強制型別定義的,也就是說,一旦一個變數被指定了某個資料型別,如果不經過強制轉換,那麼它就永遠是這個資料型別了。例如你有一個整數,如果不顯式地進行轉換,你不能將其視為一個字串。
  • 弱型別語言:弱型別語言也稱為弱型別定義語言,與強型別定義相反。JavaScript語言就屬於弱型別語言。簡單理解就是一種變數型別可以被忽略的語言。比如JavaScript是弱型別定義的,在JavaScript中就可以將字串'12'和整數3進行連線得到字串'123',在相加的時候會進行強制型別轉換。

兩者對比:強型別語言在速度上可能略遜色於弱型別語言,但是強型別語言帶來的嚴謹性可以有效地幫助避免許多錯誤。

瀏覽器的主要組成部分

  • ⽤戶界⾯ 包括位址列、前進/後退按鈕、書籤選單等。除了瀏覽器主窗⼝顯示的您請求的⻚⾯外,其他顯示的各個部分都屬於⽤戶界⾯。
  • 瀏覽器引擎 在⽤戶界⾯和呈現引擎之間傳送指令。
  • 呈現引擎 負責顯示請求的內容。如果請求的內容是 HTML,它就負責解析 HTML 和 CSS 內容,並將解析後的內容顯示在螢幕上。
  • ⽹絡 ⽤於⽹絡調⽤,⽐如 HTTP 請求。其接⼝與平臺⽆關,併為所有平臺提供底層實現。
  • ⽤戶界⾯後端 ⽤於繪製基本的窗⼝⼩部件,⽐如組合框和窗⼝。其公開了與平臺⽆關的通⽤接⼝,⽽在底層使⽤作業系統的⽤戶界⾯⽅法。
  • JavaScript 直譯器。⽤於解析和執⾏ JavaScript 程式碼。
  • 資料儲存 這是持久層。瀏覽器需要在硬碟上儲存各種資料,例如 Cookie。新的 HTML 規範 (HTML5) 定義了“⽹絡資料庫”,這是⼀個完整(但是輕便)的瀏覽器內資料庫。

值得注意的是,和⼤多數瀏覽器不同,Chrome 瀏覽器的每個標籤⻚都分別對應⼀個呈現引擎例項。每個標籤⻚都是⼀個獨⽴的程式。

程式之前的通訊方式

(1)管道通訊

管道是一種最基本的程式間通訊機制。管道就是作業系統在核心中開闢的一段緩衝區,程式1可以將需要互動的資料複製到這段緩衝區,程式2就可以讀取了。

管道的特點:

  • 只能單向通訊
  • 只能血緣關係的程式進行通訊
  • 依賴於檔案系統
  • 生命週期隨程式
  • 面向位元組流的服務
  • 管道內部提供了同步機制

(2)訊息佇列通訊

訊息佇列就是一個訊息的列表。使用者可以在訊息佇列中新增訊息、讀取訊息等。訊息佇列提供了一種從一個程式向另一個程式傳送一個資料塊的方法。 每個資料塊都被認為含有一個型別,接收程式可以獨立地接收含有不同型別的資料結構。可以透過傳送訊息來避免命名管道的同步和阻塞問題。但是訊息佇列與命名管道一樣,每個資料塊都有一個最大長度的限制。

使用訊息佇列進行程式間通訊,可能會收到資料塊最大長度的限制約束等,這也是這種通訊方式的缺點。如果頻繁的發生程式間的通訊行為,那麼程式需要頻繁地讀取佇列中的資料到記憶體,相當於間接地從一個程式複製到另一個程式,這需要花費時間。

(3)訊號量通訊

共享記憶體最大的問題就是多程式競爭記憶體的問題,就像類似於執行緒安全問題。我們可以使用訊號量來解決這個問題。訊號量的本質就是一個計數器,用來實現程式之間的互斥與同步。例如訊號量的初始值是 1,然後 a 程式來訪問記憶體1的時候,我們就把訊號量的值設為 0,然後程式b 也要來訪問記憶體1的時候,看到訊號量的值為 0 就知道已經有程式在訪問記憶體1了,這個時候程式 b 就會訪問不了記憶體1。所以說,訊號量也是程式之間的一種通訊方式。

(4)訊號通訊

訊號(Signals )是Unix系統中使用的最古老的程式間通訊的方法之一。作業系統透過訊號來通知程式系統中發生了某種預先規定好的事件(一組事件中的一個),它也是使用者程式之間通訊和同步的一種原始機制。

(5)共享記憶體通訊

共享記憶體就是對映一段能被其他程式所訪問的記憶體,這段共享記憶體由一個程式建立,但多個程式都可以訪問(使多個程式可以訪問同一塊記憶體空間)。共享記憶體是最快的 IPC 方式,它是針對其他程式間通訊方式執行效率低而專門設計的。它往往與其他通訊機制,如訊號量,配合使用,來實現程式間的同步和通訊。

(6)套接字通訊

上面說的共享記憶體、管道、訊號量、訊息佇列,他們都是多個程式在一臺主機之間的通訊,那兩個相隔幾千裡的程式能夠進行通訊嗎?答是必須的,這個時候 Socket 這傢伙就派上用場了,例如我們平時透過瀏覽器發起一個 http 請求,然後伺服器給你返回對應的資料,這種就是採用 Socket 的通訊方式了。

如何防禦 CSRF 攻擊?

CSRF 攻擊可以使用以下方法來防護:

  • 進行同源檢測,伺服器根據 http 請求頭中 origin 或者 referer 資訊來判斷請求是否為允許訪問的站點,從而對請求進行過濾。當 origin 或者 referer 資訊都不存在的時候,直接阻止請求。這種方式的缺點是有些情況下 referer 可以被偽造,同時還會把搜尋引擎的連結也給遮蔽了。所以一般網站會允許搜尋引擎的頁面請求,但是相應的頁面請求這種請求方式也可能被攻擊者給利用。(Referer 欄位會告訴伺服器該網頁是從哪個頁面連結過來的)
  • 使用 CSRF Token 進行驗證,伺服器向使用者返回一個隨機數 Token ,當網站再次發起請求時,在請求引數中加入伺服器端返回的 token ,然後伺服器對這個 token 進行驗證。這種方法解決了使用 cookie 單一驗證方式時,可能會被冒用的問題,但是這種方法存在一個缺點就是,我們需要給網站中的所有請求都新增上這個 token,操作比較繁瑣。還有一個問題是一般不會只有一臺網站伺服器,如果請求經過負載平衡轉移到了其他的伺服器,但是這個伺服器的 session 中沒有保留這個 token 的話,就沒有辦法驗證了。這種情況可以透過改變 token 的構建方式來解決。
  • 對 Cookie 進行雙重驗證,伺服器在使用者訪問網站頁面時,向請求域名注入一個Cookie,內容為隨機字串,然後當使用者再次向伺服器傳送請求的時候,從 cookie 中取出這個字串,新增到 URL 引數中,然後伺服器透過對 cookie 中的資料和引數中的資料進行比較,來進行驗證。使用這種方式是利用了攻擊者只能利用 cookie,但是不能訪問獲取 cookie 的特點。並且這種方法比 CSRF Token 的方法更加方便,並且不涉及到分散式訪問的問題。這種方法的缺點是如果網站存在 XSS 漏洞的,那麼這種方式會失效。同時這種方式不能做到子域名的隔離。
  • 在設定 cookie 屬性的時候設定 Samesite ,限制 cookie 不能作為被第三方使用,從而可以避免被攻擊者利用。Samesite 一共有兩種模式,一種是嚴格模式,在嚴格模式下 cookie 在任何情況下都不可能作為第三方 Cookie 使用,在寬鬆模式下,cookie 可以被請求是 GET 請求,且會發生頁面跳轉的請求所使用。

webpack配置入口出口

module.exports={
    //入口檔案的配置項
    entry:{},
    //出口檔案的配置項
    output:{},
    //模組:例如解讀CSS,圖片如何轉換,壓縮
    module:{},
    //外掛,用於生產模版和各項功能
    plugins:[],
    //配置webpack開發服務功能
    devServer:{}
}
簡單描述了一下這幾個屬性是幹什麼的。
描述一下npm run dev / npm run build執行的是哪些檔案
透過配置proxyTable來達到開發環境跨域的問題,然後又可以擴充套件和他聊聊跨域的產生,如何跨域
最後可以在聊聊webpack的最佳化,例如babel-loader的最佳化,gzip壓縮等等

深/淺複製

首先判斷資料型別是否為物件,如果是物件(陣列|物件),則遞迴(深/淺複製),否則直接複製。

function isObject(obj) {
    return typeof obj === "object" && obj !== null;
}

這個函式只能判斷 obj 是否是物件,無法判斷其具體是陣列還是物件。

localStorage sessionStorage cookies 有什麼區別?

localStorage:以鍵值對的方式儲存 儲存時間沒有限制 永久生效 除非自己刪除記錄
sessionStorage:當頁面關閉後被清理與其他相比不能同源視窗共享 是會話級別的儲存方式
cookies 資料不能超過4k 同時因為每次http請求都會攜帶cookie 所有cookie只適合儲存很小的資料 如會話標識

AJAX

const getJSON = function(url) {
    return new Promise((resolve, reject) => {
        const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() {
            if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {
                resolve(xhr.responseText);
            } else {
                reject(new Error(xhr.responseText));
            }
        }
        xhr.send();
    })
}

實現陣列原型方法

forEach

Array.prototype.forEach2 = function(callback, thisArg) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)  // this 就是當前的陣列
    const len = O.length >>> 0  // 後面有解釋
    let k = 0
    while (k < len) {
        if (k in O) {
            callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}

O.length >>> 0 是什麼操作?就是無符號右移 0 位,那有什麼意義嘛?就是為了保證轉換後的值為正整數。其實底層做了 2 層轉換,第一是非 number 轉成 number 型別,第二是將 number 轉成 Uint32 型別

map

基於 forEach 的實現能夠很容易寫出 map 的實現:

- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.map2 = function(callback, thisArg) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {
        if (k in O) {
-           callback.call(thisArg, O[k], k, O);
+           res[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
+   return res
}

filter

同樣,基於 forEach 的實現能夠很容易寫出 filter 的實現:

- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.filter2 = function(callback, thisArg) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {
        if (k in O) {
-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {
+               res.push(O[k])                
+           }
        }
        k++;
    }
+   return res
}

some

同樣,基於 forEach 的實現能夠很容易寫出 some 的實現:

- Array.prototype.forEach2 = function(callback, thisArg) {
+ Array.prototype.some2 = function(callback, thisArg) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0
    while (k < len) {
        if (k in O) {
-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {
+               return true
+           }
        }
        k++;
    }
+   return false
}

reduce

Array.prototype.reduce2 = function(callback, initialValue) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0, acc

    if (arguments.length > 1) {
        acc = initialValue
    } else {
        // 沒傳入初始值的時候,取陣列中第一個非 empty 的值為初始值
        while (k < len && !(k in O)) {
            k++
        }
        if (k > len) {
            throw new TypeError( 'Reduce of empty array with no initial value' );
        }
        acc = O[k++]
    }
    while (k < len) {
        if (k in O) {
            acc = callback(acc, O[k], k, O)
        }
        k++
    }
    return acc
}

哪些情況會導致記憶體洩漏

1、意外的全域性變數:由於使用未宣告的變數,而意外的建立了一個全域性變數,而使這個變數一直留在記憶體中無法被回收
2、被遺忘的計時器或回撥函式:設定了 setInterval 定時器,而忘記取消它,如果迴圈函式有對外部變數的引用的話,那麼這個變數會被一直留在記憶體中,而無法被回收。
3、脫離 DOM 的引用:獲取一個 DOM 元素的引用,而後面這個元素被刪除,由於一直保留了對這個元素的引用,所以它也無法被回收。
4、閉包:不合理的使用閉包,從而導致某些變數一直被留在記憶體當中。

CDN的使用場景

  • 使用第三方的CDN服務:如果想要開源一些專案,可以使用第三方的CDN服務
  • 使用CDN進行靜態資源的快取:將自己網站的靜態資源放在CDN上,比如js、css、圖片等。可以將整個專案放在CDN上,完成一鍵部署。
  • 直播傳送:直播本質上是使用流媒體進行傳送,CDN也是支援流媒體傳送的,所以直播完全可以使用CDN來提高訪問速度。CDN在處理流媒體的時候與處理普通靜態檔案有所不同,普通檔案如果在邊緣節點沒有找到的話,就會去上一層接著尋找,但是流媒體本身資料量就非常大,如果使用回源的方式,必然會帶來效能問題,所以流媒體一般採用的都是主動推送的方式來進行。

程式碼輸出結果

(function(){
   var x = y = 1;
})();
var z;

console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined

這段程式碼的關鍵在於:var x = y = 1; 實際上這裡是從右往左執行的,首先執行y = 1, 因為y沒有使用var宣告,所以它是一個全域性變數,然後第二步是將y賦值給x,講一個全域性變數賦值給了一個區域性變數,最終,x是一個區域性變數,y是一個全域性變數,所以列印x是報錯。

程式碼輸出結果

function a() {
    var temp = 10;
    function b() {
        console.log(temp); // 10
    }
    b();
}
a();

function a() {
    var temp = 10;
    b();
}
function b() {
    console.log(temp); // 報錯 Uncaught ReferenceError: temp is not defined
}
a();

在上面的兩段程式碼中,第一段是可以正常輸出,這個應該沒啥問題,關鍵在於第二段程式碼,它會報錯Uncaught ReferenceError: temp is not defined。這時因為在b方法執行時,temp 的值為undefined。

Promise.all

描述:所有 promise 的狀態都變成 fulfilled,就會返回一個狀態為 fulfilled 的陣列(所有promisevalue)。只要有一個失敗,就返回第一個狀態為 rejectedpromise 例項的 reason

實現

Promise.all = function(promises) {
    return new Promise((resolve, reject) => {
        if(Array.isArray(promises)) {
            if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {
                Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = value;
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

說一下怎麼取出陣列最多的一項?

// 我這裡只是一個示例
const d = {};
let ary = ['趙', '錢', '孫', '孫', '李', '周', '李', '周', '周', '李'];
ary.forEach(k => !d[k] ? d[k] = 1 : d[k]++);
const result = Object.keys(d).sort((a, b) => d[b] - d[a]).filter((k, i, l) => d[k] === d[l[0]]);
console.log(result)

程式碼輸出問題

function Parent() {
    this.a = 1;
    this.b = [1, 2, this.a];
    this.c = { demo: 5 };
    this.show = function () {
        console.log(this.a , this.b , this.c.demo );
    }
}

function Child() {
    this.a = 2;
    this.change = function () {
        this.b.push(this.a);
        this.a = this.b.length;
        this.c.demo = this.a++;
    }
}

Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();

輸出結果:

parent.show(); // 1  [1,2,1] 5

child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5

parent.show(); // 1 [1,2,1] 5

child1.show(); // 5 [1,2,1,11,12] 5

child2.show(); // 6 [1,2,1,11,12] 5

這道題目值得神帝,他涉及到的知識點很多,例如this的指向、原型、原型鏈、類的繼承、資料型別等。

解析:

  1. parent.show(),可以直接獲得所需的值,沒啥好說的;
  2. child1.show(),Child的建構函式原本是指向Child的,題目顯式將Child類的原型物件指向了Parent類的一個例項,需要注意Child.prototype指向的是Parent的例項parent,而不是指向Parent這個類。
  3. child2.show(),這個也沒啥好說的;
  4. parent.show(),parent是一個Parent類的例項,Child.prorotype指向的是Parent類的另一個例項,兩者在堆記憶體中互不影響,所以上述操作不影響parent例項,所以輸出結果不變;
  5. child1.show(),child1執行了change()方法後,發生了怎樣的變化呢?
  6. this.b.push(this.a),由於this的動態指向特性,this.b會指向Child.prototype上的b陣列,this.a會指向child1a屬性,所以Child.prototype.b變成了[1,2,1,11];
  7. this.a = this.b.length,這條語句中this.athis.b的指向與上一句一致,故結果為child1.a變為4;
  8. this.c.demo = this.a++,由於child1自身屬性並沒有c這個屬性,所以此處的this.c會指向Child.prototype.cthis.a值為4,為原始型別,故賦值操作時會直接賦值,Child.prototype.c.demo的結果為4,而this.a隨後自增為5(4 + 1 = 5)。
  9. child2執行了change()方法, 而child2child1均是Child類的例項,所以他們的原型鏈指向同一個原型物件Child.prototype,也就是同一個parent例項,所以child2.change()中所有影響到原型物件的語句都會影響child1的最終輸出結果。
  10. this.b.push(this.a),由於this的動態指向特性,this.b會指向Child.prototype上的b陣列,this.a會指向child2a屬性,所以Child.prototype.b變成了[1,2,1,11,12];
  11. this.a = this.b.length,這條語句中this.athis.b的指向與上一句一致,故結果為child2.a變為5;
  12. this.c.demo = this.a++,由於child2自身屬性並沒有c這個屬性,所以此處的this.c會指向Child.prototype.c,故執行結果為Child.prototype.c.demo的值變為child2.a的值5,而child2.a最終自增為6(5 + 1 = 6)。

Vue的父子元件生命週期鉤子函式執行順序?

<!-- 載入渲染過程 -->
    <!-- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created ->
    子beforeMount -> 子mounted -> 父mounted -->
    <!-- 子元件更新過程 -->
    <!-- 父beforeUpdate -> 子beforeUpdate -> 子updaed -> 父updated -->
    <!-- 父元件跟新過程 -->
    <!-- 父beforeUpdate -> 父updated -->
    <!-- 銷燬過程 -->
    <!-- 父beforeDestroy -> 子beforeDestroy -> 子destroyed ->父destroyed -->

說一下slice splice split 的區別?

// slice(start,[end])
// slice(start,[end])方法:該方法是對陣列進行部分擷取,該方法返回一個新陣列
// 引數start是擷取的開始陣列索引,end引數等於你要取的最後一個字元的位置值加上1(可選)。
// 包含了源函式從start到 end 所指定的元素,但是不包括end元素,比如a.slice(0,3);
// 如果出現負數就把負數與長度相加後再劃分。
// slice中的負數的絕對值若大於陣列長度就會顯示所有陣列
// 若引數只有一個,並且引數大於length,則為空。
// 如果結束位置小於起始位置,則返回空陣列
// 返回的個數是end-start的個數
// 不會改變原陣列
var arr = [1,2,3,4,5,6]
/*console.log(arr.slice(3))//[4,5,6] 從下標為0的到3,擷取3之後的數console.log(arr.slice(0,3))//[1,2,3] 從下標為0的地方擷取到下標為3之前的數console.log(arr.slice(0,-2))//[1,2,3,4]console.log(arr.slice(-4,4))//[3,4]console.log(arr.slice(-7))//[1,2,3,4,5,6]console.log(arr.slice(-3,-3))// []console.log(arr.slice(8))//[]*/
// 個人總結:slice的引數如果是正數就從左往右數,如果是負數的話就從右往左邊數,
// 擷取的陣列與數的方向一致,如果是2個引數則擷取的是數的交集,沒有交集則返回空陣列 
// ps:slice也可以切割字串,用法和陣列一樣,但要注意空格也算字元

// splice(start,deletecount,item)
// start:起始位置
// deletecount:刪除位數
// item:替換的item
// 返回值為被刪除的字串
// 如果有額外的引數,那麼item會插入到被移除元素的位置上。
// splice:移除,splice方法從array中移除一個或多個陣列,並用新的item替換它們。
//舉一個簡單的例子 
var a=['a','b','c']; 
var b=a.splice(1,1,'e','f'); 
 console.log(a) //['a', 'e', 'f', 'c']
 console.log(b) //['b']

 var a = [1, 2, 3, 4, 5, 6];
//console.log("被刪除的為:",a.splice(1, 1, 8, 9)); //被刪除的為:2
// console.log("a陣列元素:",a); //1,8,9,3,4,5,6

// console.log("被刪除的為:", a.splice(0, 2)); //被刪除的為:1,2
// console.log("a陣列元素:", a) //3,4,5,6
console.log("被刪除的為:", a.splice(1, 0, 2, 2)) //插入 第二個數為0,表示刪除0個  
console.log("a陣列元素:", a) //1,2,2,2,3,4,5,6

// split(字串)
// string.split(separator,limit):split方法把這個string分割成片段來建立一個字串陣列。
// 可選引數limit可以限制被分割的片段數量。
// separator引數可以是一個字串或一個正規表示式。
// 如果separator是一個空字元,會返回一個單字元的陣列,不會改變原陣列。
var a="0123456";  
var b=a.split("",3);  
console.log(b);//b=["0","1","2"]
// 注意:String.split() 執行的操作與 Array.join 執行的操作是相反的。

程式碼輸出結果

function Dog() {
  this.name = 'puppy'
}
Dog.prototype.bark = () => {
  console.log('woof!woof!')
}
const dog = new Dog()
console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog)

輸出結果:true

解析: 因為constructor是prototype上的屬性,所以dog.constructor實際上就是指向Dog.prototype.constructor;constructor屬性指向建構函式。instanceof而實際檢測的是型別是否在例項的原型鏈上。

constructor是prototype上的屬性,這一點很容易被忽略掉。constructor和instanceof 的作用是不同的,感性地來說,constructor的限制比較嚴格,它只能嚴格對比物件的建構函式是不是指定的值;而instanceof比較鬆散,只要檢測的型別在原型鏈上,就會返回true。

ES6 之前使用 prototype 實現繼承

Object.create() 會建立一個 “新” 物件,然後將此物件內部的 [[Prototype]] 關聯到你指定的物件(Foo.prototype)。Object.create(null) 建立一個空 [[Prototype]] 連結的物件,這個物件無法進行委託。

function Foo(name) {
  this.name = name;
}

Foo.prototype.myName = function () {
  return this.name;
}

// 繼承屬性,透過借用建構函式呼叫
function Bar(name, label) {
  Foo.call(this, name);
  this.label = label;
}

// 繼承方法,建立備份
Bar.prototype = Object.create(Foo.prototype);

// 必須設定回正確的建構函式,要不然在會發生判斷型別出錯
Bar.prototype.constructor = Bar;

 // 必須在上一步之後
Bar.prototype.myLabel = function () {
  return this.label;
}

var a = new Bar("a", "obj a");

a.myName(); // "a"
a.myLabel(); // "obj a"

常見的水平垂直方式有幾種?

//利用絕對定位,先將元素的左上角透過 top:50%和 left:50%定位到頁面的中心,然後再透過 translate 來調整元素的中心點到頁面的中心。該方法需要考慮瀏覽器相容問題。
.parent {
    position: relative;
}

.child {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
}
//利用絕對定位,設定四個方向的值都為 0,並將 margin 設定為 auto,由於寬高固定,因此對應方向實現平分,可以實現水平和垂直方向上的居中。該方法適用於盒子有寬高的情況:
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
//利用絕對定位,先將元素的左上角透過 top:50%和 left:50%定位到頁面的中心,然後再透過 margin 負值來調整元素的中心點到頁面的中心。該方法適用於盒子寬高已知的情況
.parent {
    position: relative;
}

.child {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50px;     /* 自身 height 的一半 */
    margin-left: -50px;    /* 自身 width 的一半 */
}
//使用 flex 佈局,透過 align-items:center 和 justify-content:center 設定容器的垂直和水平方向上為居中對齊,然後它的子元素也可以實現垂直和水平的居中。該方法要**考慮相容的問題**,該方法在移動端用的較多:
.parent {
    display: flex;
    justify-content:center;
    align-items:center;
}
//另外,如果父元素設定了flex佈局,只需要給子元素加上`margin:auto;`就可以實現垂直居中佈局
.parent{
    display:flex;
}
.child{
    margin: auto;
}

相關文章