記一次平淡的失敗的百度面試

1 盒模型

1.1 概念

當你對一個文檔進行佈局時,瀏覽器引擎會根據css-box模型將全部元素描述爲一個盒子,css會決定這些盒子的大小,位置,屬性(顏色邊框等)css

1.2 分類

盒模型分爲兩類:IE盒模型和標準盒模型,二者區別在於html

IE盒模型的width/height = content +border + padding
標準盒模型的width/height = content
複製代碼

IE盒模型jquery

標準盒模型

1.3 IE盒模型的引用場景

當你想讓兩個子容器float:left,寬度各50%,而後給一點padding,最後讓子容器並排充滿父容器,一切想的挺美好,然而你發現結果並非這麼美好,由於子容器的盒子寬度已經超出了父容器的一半,致使了折行,因而,width就不能50%了,只能是50%再減去padding的像素值程序員

1.4 改變盒模型

// W3C 標準盒模型(瀏覽器默認)
box-sizing: content-box;
// IE 怪異盒模型
box-sizing: border-box;
//避免css在不一樣瀏覽器下的表現不一樣,最好加上
*, *:before, *:after {
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
複製代碼

2. BFC(塊級元素格式化上下文)

2.1 BFC原理

  1. 在BFC的垂直方向上,邊距會發生重疊
  2. BFC區域不會與浮動區域重疊
  3. BFC在頁面上是一個獨立的容器,與其餘元素互不影響
  4. 計算BFC高度時,浮動元素也會參與計算

2.2建立BFC

  1. float 值不爲 none,只要設置了浮動,當前元素就建立了一個 BFC
  2. position值不爲static,只要設置了定位,當前元素就建立了一個 BFC
  3. display 值不爲默認,只要設置了display,當前元素就建立了一個 BFC
  4. overflow 值不爲 visible,只要設置了overflow,當前元素就建立了一個 BFC

2.3BFC特性

  1. 使 BFC 內部浮動元素不會處處亂跑;

當子元素設置position或是float時,子元素會亂跑,給父元素設置BFC,子元素便可以依舊被父元素包裹 2. 和浮動元素產生邊界。web

通常狀況下若是沒有 BFC的話,咱們想要讓普通元素與浮動元素產生左右邊距,須要將 maring 設置爲浮動元素的寬度加上你想要產生邊距的寬度。編程

這裏的浮動元素的寬度爲 200px ,若是想和它產生 20px 的右邊距,須要將非浮動元素的 margin-left 設置爲 200px+20px 。windows

2.4 BFC使用場景

  1. 解決邊距重疊問題 當元素都設置了 margin 邊距時,margin將取最大值。爲了避免讓邊距重疊,能夠給子元素加一個父元素,並設置該父元素爲 BFC
<div class="box" id="box">
  <p>Lorem ipsum dolor sit.</p>
  <div style="overflow: hidden;">
    <p>Lorem ipsum dolor sit.</p>
  </div>
  <p>Lorem ipsum dolor sit.</p>
</div>
複製代碼
  1. 侵佔浮動元素的位置
.one {
    float: left;
    width: 100px;
    height: 100px;
    background-color: pink;
  }
  .two {
    height: 200px;
    background-color: red;
    /* 設置 BFC */
    overflow: hidden;
  }
  
  
<div class="one"></div>
<div class="two"></div>
複製代碼

侵佔 數組

設置BFC

3 聖盃佈局和雙飛翼佈局

聖盃佈局和雙飛翼佈局解決的問題是同樣的,就是兩邊定寬,中間自適應的三欄佈局(中間欄放在文檔流前面優先渲染!)瀏覽器

聖盃佈局和雙飛翼佈局解決問題的方案在前一半是相同的: 也就是三欄所有浮動,但左右兩欄加上負margin讓其跟中間欄div並排。bash

不一樣在於解決 「中間欄div內容不被遮擋」 問題的思路不同。

3.1 聖盃佈局

3.1.1 浮動

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
</head>
<style>
  body {
    min-width: 550px;  /* 2x leftContent width + rightContent width */
    font-weight: bold;
    font-size: 20px;
  }

  #header, #footer {
    background: rgba(29, 27, 27, 0.726);
    text-align: center;
    height: 60px;
    line-height: 60px;
  }
  #footer {
    clear: both;
  }

  #container {
    padding-left: 200px;   /* leftContent width */
    padding-right: 150px;  /* rightContent width */
    overflow: hidden;
  }

  #container .column {
    position: relative;
    float: left;
    text-align: center;
    height: 300px;
    line-height: 300px;
  }

  #center {
    width: 100%;
    background: rgb(206, 201, 201);
  }

  #left {
    width: 200px;           /* leftContent width */
    right: 200px;           /* leftContent width */
    margin-left: -100%;
    background: rgba(95, 179, 235, 0.972);
  }

  #right {
    width: 150px;           /* rightContent width */
    margin-right: -150px;   /* rightContent width */
    background: rgb(231, 105, 2);
  }

</style>

<body>
  <div id="header">#header</div>
  <div id="container">
    <div id="center" class="column">#center</div>
    <div id="left" class="column">#left</div>
    <div id="right" class="column">#right</div>
  </div>
  <div id="footer">#footer</div>


</body>

</html>
複製代碼

3.1.2 彈性佈局

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
</head>
<style>
  body {
    min-width: 550px;  
    font-weight: bold;
    font-size: 20px;
  }
  #header, #footer {
    background: rgba(29, 27, 27, 0.726);
    text-align: center;
    height: 60px;
    line-height: 60px;
  }
  #container {
   display: flex;
  }
  #container .column {
    text-align: center;
    height: 300px;
    line-height: 300px;
  }
  #center {
    flex: 1;
    background: rgb(206, 201, 201);
  }
  #left {
    width: 200px;        
    background: rgba(95, 179, 235, 0.972);
  }
  #right {
    width: 150px;           
    background: rgb(231, 105, 2);
  }
</style>

<body>
  <div id="header">#header</div>
  <div id="container">
    <div id="left" class="column">#left</div>
    <div id="center" class="column">#center</div>
    <div id="right" class="column">#right</div>
  </div>
  <div id="footer">#footer</div>
</body>

</html>
複製代碼

3.2 雙飛翼佈局

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>實現三欄水平佈局之雙飛翼佈局</title>
    <style type="text/css">
    .left, .main, .right {
        float: left;
        min-height: 130px;
        text-align: center;
    }
    .left {
        margin-left: -100%;
        background: green;
        width: 200px;
    }
    .right {
        margin-left: -300px;
        background-color: red;
        width: 300px;
    }
    .main {
        background-color: blue;
        width: 100%;
    }
    .content{
      /* 關鍵點:用margin把div擠到中間正常展現*/
        margin: 0 300px 0 200px;
    }
    </style>
</head>
<body>
<div class="container"> 
&emsp;   <div class="main">
    &emsp;&emsp; <div class="content">main</div> 
       </div>
&emsp;&emsp;<div class="left">left</div> 
&emsp;&emsp;<div class="right">right</div> 
</div>
</body>
</html>
複製代碼

4 原型與原型鏈

4.1原型概念

JS中一切皆對象,而每一個對象都有一個原型(Object除外),這個原型,大概就像Java中的父類,因此,基本上你能夠認爲原型就是這個對象的父對象,即每個對象(Object除外)內部都保存了它本身的父對象,這個父對象就是原型。通常建立的對象若是沒有特別指定原型,那麼它的原型就是Object(這就很相似Java中全部的類默認繼承自Object類)。

4.2 對象建立

在js中,對象建立方法常見的以下

//第一種,手動建立
var a={'name':'lala'};   

//第二種,構造函數
function A(){
    this.name='lala';
}
var a=new A();

//第三種,class (ES6標準寫法)

class A{
    constructor(){
        //super();此處沒有使用extends顯式繼承,不可以使用super()
        this.name='lala';
    }
}
var a=new A()
//其實後面兩種方法本質上是一種寫法

複製代碼

這三種寫法建立的對象的原型(父對象)都是Object,須要提到的是,ES6經過引入class ,extends等關鍵字,以一種語法糖的形式把構造函數包裝成類的概念,更便於你們理解。是但願開發者再也不花精力去關注原型以及原型鏈,也充分說明原型的設計意圖和類是同樣的。

4.3 查看對象原型

當對象被建立以後,查看它們的原型的方法不止一種,之前通常使用對象的proto屬性,ES6推出後,推薦用Object.getPrototypeOf()方法來獲取對象的原型

function A(){
    this.name='lala';
}
var a=new A();
console.log(a.__proto__)  
//輸出:Object {}

//推薦使用這種方式獲取對象的原型
console.log(Object.getPrototypeOf(a))  
//輸出:Object {}
複製代碼

不管對象是如何建立的,默認原型都是Object,在這裏須要說起的比較特殊的一點就是,經過構造函數來建立對象,函數A自己也是一個對象,而A有兩個指向表示原型的屬性,分別是proto和prototype,並且兩個屬性並不相同

function A(){
    this.name='lala';
}
var a=new A();
console.log(A.prototype)  
//輸出:Object {}

console.log(A.__proto__)  
//輸出:function () {}
console.log(Object.getPrototypeOf(A))
//輸出:function () {}
複製代碼

函數的的prototype屬性只有在看成構造函數建立的時候,把自身的prototype屬性值賦給對象的原型。而實際上,做爲函數自己,它的原型應該是function對象,而後function對象的原型纔是Object。

4.4原型的用法

其實原型和類的繼承的用法是一致的:當你想用某個對象的屬性時,將當前對象的原型指向該對象,你就擁有了該對象的使用權了。

function A(){
    this.name='world ';
}
function B(){
    this.bb="hello"
    }
var a=new A();
var b=new B();

Object.setPrototypeOf(a,b);
//將b設置爲a的原型,此處有一個問題,即a的constructor也指向了B構造函數,可能須要糾正
a.constructor=A;
console.log(a.bb)
//輸出 hello
複製代碼

若是使用ES6來作的話則簡單許多,甚至不涉及到prototype這個屬性

class B{
     constructor(){

        this.bb='hello'
     }
}
class A  extends B{
     constructor(){
        super()
        this.name='world'
     }
}

var a=new A();
console.log(a.bb+" "+a.name);
//輸出hello world


console.log(typeof(A))
//輸出  "function"
複製代碼

怎麼樣?是否是已經徹底看不到原型的影子了?活脫脫就是類繼承,可是你也看獲得實際上類A 的類型是function,因此說,本質上class在JS中是一種語法糖,JS繼承的本質依然是原型,不過,ES6引入class,extends 來掩蓋原型的概念也是一個很友好的舉動,對於長期學習那些類繼承爲基礎的面對對象編程語言的程序員而言。

4.5 原型鏈

這個概念其實也變得比較簡單,能夠類比類的繼承鏈條,即每一個對象的原型往上追溯,一直到Object爲止,這組成了一個鏈條,將其中的對象串聯起來,當查找當前對象的屬性時,若是沒找到,就會沿着這個鏈條去查找,一直到Object,若是還沒發現,就會報undefined。那麼也就意味着你的原型鏈不能太長,不然會出現效率問題

5 做用域與做用域鏈

5.1 做用域

做用域是在運行時代碼中的某些特定部分中變量,函數和對象的可訪問性。換句話說,做用域決定了代碼區塊中變量和其餘資源的可見性。簡單來講做用域就是用來隔離變量,不一樣做用域下的同名變量不會有衝突。

5.1.1 全局做用域

任何地方都能訪問到的對象擁有全局做用域

  1. 函數外面定義的變量擁有全局做用域
var a = 2;

function fn() {
	var b =1;
	return b;
}

console.log(fn())//1
console.log(a);//2
console.log(b);//b is not defined
複製代碼
  1. 未定義直接賦值的變量自動聲明爲擁有全局做用域
var a = 2;

function fn() {
	b =1;
	return b;
}


console.log(fn())//1
console.log(a);//2
console.log(b);//1
複製代碼
  1. window對象的屬性擁有全局做用域

5.1.2 局部做用域

局部做用域通常只在固定的代碼片斷內可訪問到,最多見的例如函數內部,因此在一些地方會把這種做用域成爲函數做用域。

上文代碼一中,b是函數內部聲明並賦值,擁有局部做用域,只能帶函數fn內部使用,在fn外部使用就會報錯,這就是局部做用域的特性,外部沒法訪問。

5.1.3 ES6的塊級做用域

ES5只有全局做用域和函數做用域,沒有塊級做用域,會帶來下面問題:

  1. 變量提高
  2. 用來計數的循環變量泄露爲全局變量

ES6引入了塊級做用域,明確容許在塊級做用域中聲明函數,let和const命令都涉及塊級做用域。

塊級做用域容許聲明函數只在使用大括號的狀況下成立,若是未使用大括號,會報錯。

if (true) {
function func1() {} // 不報錯
}

if (true)
function func2() {} // 報錯

複製代碼

5.2做用域鏈

通俗地講,當聲明一個函數時,局部做用域一級一級向上包起來,就是做用域鏈。

1.當執行函數時,老是先從函數內部找尋局部變量

2.若是內部找不到(函數的局部做用域沒有),則會向建立函數的做用域(聲明函數的做用域)尋找,依次向上

var a =1;

function fn() {
	var a = 10;
	function fn1() {
		var a =20;
		console.log(a);//20
	}

	function fn2 () {
		console.log(a);//10
	}
	fn1();
	fn2();
}

fn();
console.log(a)//1
複製代碼

當執行fn1時,建立函數fn1的執行環境,並將該對象置於鏈表開頭,而後將函數fn的調用對象放在第二位,最後是全局對象,做用域鏈的鏈表的結構是fn1->fn->window。從鏈表的開頭尋找變量a,即fn1函數內部找變量a,找到了,結果是20。

一樣,執行fn2時,做用域鏈的鏈表的結構是fn2->fn->window。從鏈表的開頭尋找變量a,即fn2函數內部找變量a,找不到,因而從fn內部找變量a,找到了,結果是10。

最後在最外層打印出變量a,直接從變量a的做用域即全局做用域內尋找,結果爲1。

5.3 閉包

提到做用域就不得不提到閉包,簡單來說,閉包外部函數可以讀取內部函數的變量。

優勢:閉包能夠造成獨立的空間,永久的保存局部變量。

缺點:保存中間值的狀態缺點是容易形成內存泄漏,由於閉包中的局部變量永遠不會被回收

function fn1() {

	var n = 999;
	nAdd = function () {
		n += 1
	}

	function fn2() {
		console.log(n)
	}

	return fn2

}

var result = fn1();
result();//999
nAdd();//執行n +=1;
result();//1000
複製代碼

6.this、 call、apply、bind

6.1 this指向

在 ES5 中,其實 this 的指向,始終堅持一個原理:this 永遠指向最後調用它的那個對象

var name = "windowsName";
    function a() {
        var name = "Cherry";

        console.log(this.name);          // windowsName

        console.log("inner:" + this);    // inner: Window
    }
    a();
    console.log("outer:" + this)         // outer: Window
複製代碼

調用a的時候,前面沒有調用對象那麼即爲全局對象window。這裏咱們沒有使用嚴格模式,若是使用嚴格模式的話,全局對象就是 undefined,那麼就會報錯 Uncaught TypeError: Cannot read property 'name' of undefined。

var name = "windowsName";
    var a = {
        name: "Cherry",
        fn : function () {
            console.log(this.name);      // windowsName
        }
    }

    var f = a.fn;
    f();
複製代碼

這裏你可能會有疑問,爲何不是 Cherry,這是由於雖然將 a 對象的 fn 方法賦值給變量 f 了,可是沒有調用,再接着跟我念這一句話:「this 永遠指向最後調用它的那個對象」,因爲剛剛的 f 並無調用,因此 fn() 最後仍然是被 window 調用的。因此 this 指向的也就是 window。 出處。

6.2 改變this指向

目前主要有如下幾種方法

  • 使用ES6箭頭函數
  • 在函數內部使用this_= this
  • 使用apply、call、bind
  • new實例化一個對象

6.2.1 箭頭函數

箭頭函數的 this 始終指向函數定義時的 this,而非執行時。箭頭函數中沒有 this 綁定,必須經過查找做用域鏈來決定其值,若是箭頭函數被非箭頭函數包含,則 this 綁定的是最近一層非箭頭函數的 this,不然,this 爲 undefined」。

var name = "windowsName";

    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)     
        },

        func2: function () {
            setTimeout( () => {
                this.func1()
            },100);
        }

    };

    a.func2()     // Cherry
複製代碼

6.2.2 函數內部使用 this_ = this

若是不使用 ES6,那麼這種方式應該是最簡單的不會出錯的方式了,咱們是先將調用這個函數的對象保存在變量 this_ 中,而後在函數中都使用這個 this_,這樣 this_ 就不會改變了

var name = "windowsName";

    var a = {

        name : "Cherry",

        func1: function () {
            console.log(this.name)     
        },

        func2: function () {
            var this_ = this;
            setTimeout( function() {
                this_.func1()
            },100);
        }

    };

    a.func2()       // Cherry
複製代碼

6.2.3 使用apply、call、bind

//apply
    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.apply(a),100);
        }

    };

    a.func2()            // Cherry
    
//call
    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.call(a),100);
        }

    };

    a.func2()            // Cherry
//bind
    var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.bind(a)(),100);
        }

    };

    a.func2()            // Cherry
複製代碼

6.3 apply、call、bind 區別

apply定義

apply() 方法調用一個函數, 其具備一個指定的this值,以及做爲一個數組(或相似數組的對象)提供的參數
複製代碼

語法

fun.apply(thisArg, [argsArray])
複製代碼
  • thisArg:在 fun 函數運行時指定的 this 值。須要注意的是,指定的 this 值並不必定是該函數執行時真正的 this 值,若是這個函數處於非嚴格模式下,則指定爲 null 或 undefined 時會自動指向全局對象(瀏覽器中就是window對象),同時值爲原始值(數字,字符串,布爾值)的 this 會指向該原始值的自動包裝對象。
  • argsArray:一個數組或者類數組對象,其中的數組元素將做爲單獨的參數傳給 fun 函數。若是該參數的值爲null 或 undefined,則表示不須要傳入任何參數。從ECMAScript 5 開始可使用類數組對象。瀏覽器兼容性請參閱本文底部內容

6.3.1 apply與call的區別

二者基本相似,區別在於傳入的參數不一樣 call語法

fun.call(thisArg[, arg1[, arg2[, ...]]])
複製代碼

apply 和 call 的區別是 call 方法接受的是若干個參數列表,而 apply 接收的是一個包含多個參數的數組。

var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.apply(a,[1,2])     // 3

複製代碼
var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.call(a,1,2)       // 3
複製代碼

6.3.2 bind與apply、call的區別

bind()方法建立一個新的函數,當被調用時,將其this關鍵字設置爲提供的值,在調用新函數時,在任何提供以前提供一個給定的參數序列。

因此bind是建立一個新的函數,必須手動調用

var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }

    var b = a.fn;
    b.bind(a,1,2)()           // 3

複製代碼

7 手動實現apply、 call、 bind

7.1 實現call

Function.prototype._call = function (context = window) {
    var context = context;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

// 測試一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

bar._call(null); // 2

console.log(bar._call(obj, 'kevin', 18));
// 1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

複製代碼

7.2 實現apply

Function.prototype.apply = function (context = window, arr) {
    var context = context;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}
複製代碼

7.3 實現bind

Function.prototype._bind = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

複製代碼

8 手動實現jq的$each

// 經過字面量方式實現的函數each
var each =  function(object, callback){
  var type = (function(){
          switch (object.constructor){
            case Object:
                return 'Object';
                break;
            case Array:
                return 'Array';
                break;
            case NodeList:
                return 'NodeList';
                break;
            default:
                return 'null';
                break;
        }
    })();
    // 爲數組或類數組時, 返回: index, value
    if(type === 'Array' || type === 'NodeList'){
        // 因爲存在類數組NodeList, 因此不能直接調用every方法
        [].every.call(object, function(v, i){
            return callback.call(v, i, v) === false ? false : true;
        });
    }
    // 爲對象格式時,返回:key, value
    else if(type === 'Object'){
        for(var i in object){
            if(callback.call(object[i], i, object[i]) === false){
                break;
            }
        }
    }
}
複製代碼

參考文獻

做者:拉丁吳 連接:juejin.im/post/57f336… 來源:掘金

做者:sunshine小小倩 連接:juejin.im/post/59bfe8… 來源:掘金

做者:Chris_Ping 連接:juejin.im/post/5d1f1c… 來源:掘金

相關文章
相關標籤/搜索