记一次平淡的失败的百度面试

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… 来源:掘金

相关文章
相关标签/搜索