前端面试总结

211 阅读25分钟

CSS

1、CSS 盒模型构成

1.1、概念和基本组成

盒子模型,任何的HTML元素都可以看做盒子,用来设计和布局。

盒子模型分为标准盒模型怪异盒模型(IE盒模型)

基本上由以下几部分组成:

  • Margin(外边距) - 清除边框外的区域,外边距是透明的。
  • Border(边框) - 围绕在内边距和内容外的边框。
  • Padding(内边距) - 清除内容周围的区域,内边距是透明的。
  • Content(内容) - 盒子的内容,显示文本和图像。

1.2、标准盒模型和怪异盒模型

根据 W3C 的规范,元素内容占据的空间是由 width 属性设置的,而内容周围的 padding 和 border 值是另外计算的。不幸的是,IE5.X 和 6 在怪异模式中使用自己的非标准模型。这些浏览器的 width 属性不是内容的宽度,而是内容、内边距和边框的宽度的总和。

即:

标准盒模型:

  • width = content;
  • 盒子大小 = width + padding + border + margin

怪异盒模型:

  • width = content + padding + border;
  • 盒子大小 = width + margin

1.3、box-sizing属性

可以通过box-sizing 设置盒模型:

  • box-sizing : content-box ; 标准盒模型
  • box-sizing : border-box; IE盒模型

1.4、外边距重叠

垂直方向上相邻的块级元素外边距发生重叠。左右不存在重叠现象。

1.4.1、发生重叠的两种情景
  • 父子元素的外边距重叠

    	<div class="parent">
    		<div class="child"></div>
    	</div>
    
            .parent {
                background-color: cadetblue;
                width: 150px;
            }
    
            .child {
                background-color: crimson;
                margin-top: 20px;
                height: 100px;
                width: 100px;
            }
    

    父元素的高度不会是120px (子元素高度(100px)加上子元素设置的margin-top(20px)),而是100px,父元素高度塌陷。

    究其原因,我们应该思考子元素设置的margin-top,到底是相对于谁的外边距。

    如果父元素在子元素的margin的同向上有padding或border的话,子元素的margin相对于父元素,否则相对于父元素以外的元素。

        <div class="other">look at me</div>
        <div class="parent">
            <div class="child"></div>
        </div>
        
            .parent {
                background-color: cadetblue;
                width: 150px;
            }
            .child {
                background-color: crimson;
                width: 100px;
    
                margin-top: 20px;
                height: 100px;
            }
            .other{
                background-color: darkgoldenrod;
            }
    

    parent不存在top方向的padding和border,child设置的margin-top是相对于other的。

    image

    给父元素添加padding或者border:

        <div class="parent">
            <div class="child"></div>
        </div>
            .parent {
                background-color: cadetblue;
                width: 150px;
    
                padding-top: 10px;
                /* border-top: 10px solid red; */
            }
    
            .child {
                background-color: crimson;
                width: 100px;
    
                margin-top: 20px;
                height: 100px;
            }
    

    image

  • 兄弟元素的外边距重叠(处于同一个BFC纵向外边距重叠)

        <p>1</p>
        <p>2</p>
        <p>3</p>
    
            p {
                background-color: crimson;
    
                margin: 20px auto 40px;
            }
    

    image

    可以看到1和2,2和3之间的距离是40px并不是60px,取最大的值

1.4.2、折叠后的margin计算

(1)margin都是正值时取较大的margin值

(2)margin都是负值时取绝对值较大的,然后负向位移。

(3)margin有正有负,从负值中选绝对值最大的,从正值中选取绝对值最大的,然后相加

1.4.3、解决方案
  • 设置BFC布局(为paren设置BFC或者为某一个兄弟元素设置BFC)

    1. float的值不为none
    2. overflow的值不为visible(hidden、auto、scroll)
    3. display的值为inline-block、table-cell、table-caption、flex
    4. position的值为absolute或fixed
       // 兄弟元素
       <div>
            <p>1</p>
        </div>
        <p>2</p>
        <p>3</p>
        
            p {
                background-color: red;
                margin: 20px auto 40px;
            }
    
            div { //用div包起来,生成BFC
                overflow: hidden;
            }
        // 父子元素
        <div class="parent">
            <div class="child"></div>
        </div>
            .parent {
                background-color: cadetblue;
                width: 150px;
    
                overflow: hidden;
            }
    
            .child {
                background-color: crimson;
                width: 100px;
                height: 100px;
    
                margin-top: 20px;
            }    
    
  • 外层父元素加透明边框 border:solid 1px transparent;

            .parent {
                background-color: cadetblue;
                width: 150px;
    
                border: solid 1px transparent;
            }
    
            .child {
                background-color: crimson;
                width: 100px;
                height: 100px;
    
                margin-top: 20px;
            }
    
  • 改为padding,子元素设置margin-top,改为为父元素设置padding-top;

        <div class="parent">
            <div class="child"></div>
        </div>
        
            .parent {
                background-color: cadetblue;
                width: 150px;
    
                padding-top: 20px;
            }
    
            .child {
                background-color: crimson;
                width: 100px;
                height: 100px;
            }
    

2、CSS的position属性

  • static

    默认值,没有定位,元素出现在正常文档流中,会忽略top,bottom,left,right。

  • fixed

    绝对定位,相对浏览器窗口进行定位。位置通过top,left,right,bottom来进行设置。元素将会脱离正常的文档流,会覆盖其他元素,一般用于达到小广告的效果,不随鼠标滑动而滚动

  • relative

    相对定位,相对于其正常文档流中的位置进行定位。元素所占的空间位置不变显示位置发生偏移

    relative周围的元素会忽略他的移动,不忽略他在正常文档中的位置。

  • absolute

    绝对定位,相对static以外第一个父元素定位,没有非static父元素时相对于以< html >作为原点定位。

    如果不设置left等,或覆盖后边正常元素。

  • inherit

    从父元素继承 position 属性的值。

代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .static {
            width: 200px;
            height: 200px;
            background-color: yellow;

            position: static;
            left: 50px;
            top: 50px;
        }

        .fixed {
            position: fixed;
            top: 100px;
            left: 100px;
        }

        .relative {
            position: relative;
            height: 200px;
            width: 200px;
            border: 2px solid green;

            left: 50px;
            top:10px
        }
        .absolute{
            position: absolute;
            height: 200px;
            width: 200px;

            border: 2px solid violet;
            left: 50px;
            /* top: 10px; */
        }
    </style>
</head>

<body>
    <div class="static">static</div>
    <div class="fixed">fixed</div>
    <div class="relative">relative</div>
    <div>look_relative占据了正常文档流</div>
    <div class="absolute">absolute</div>
    <div>look_absolute没有占据正常文档流的位置</div>
</body>

</html>

对于定位元素absolute,relative,fixed,除了用left,right,top,bottom以外,还可以用z-index来进行定位。

z-index代表的是元素的堆叠顺序,越高则显示越优先。当z-index相同时,会优先显示后加载的元素

 <div style="position: absolute;z-index: 1;background-color: violet;">i am absolute</div>
 <div style="position: relative;z-index: 2; background-color: teal;">i am relative</div>

3、CSS 水平垂直居中布局的方法

宽度未知情况下:

  • 父元素设置 display:flex/grid,子元素 margin:auto
  • flex布局
  • absolute + transform
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>水平垂直居中</title>
  <style>
    .flex_grid {
      height: 100px;
      background-color: teal;

      /* display: flex; */
      display: grid;
    }

    .flex_grid_child {
      margin: auto;
    }

    .flex {
      background-color: turquoise;
      height: 100px;

      display: flex;
      justify-content: center;
      align-items: center;
    }

    .absolute {

      /* height: 100px; */

      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
  </style>
</head>

<body>
  <div class="flex_grid">
    <div class="flex_grid_child">1、flex/grid + margin:auto </div>
  </div>

  <div class="flex">
    <div>2、flex</div>
  </div>
  <!-- 可以通过给父元素设置relative来规定absolute是相对父元素偏移 -->
  <div style="background-color: violet;height:100px;position: relative;">
    <div class="absolute">3、absolute + transform</div>
  </div>
</body>

</html>

宽度已知情况下:

  • absolute + margin:auto
  • absolute + transform

代码:

  <div style="background-color:darksalmon;height:100px;position: relative;">
    <div class="width_margin">1、固定宽度高度元素居中 margin:auto</div>
  </div>
  <div style="background-color: khaki;height:100px;position: relative;">
    <div class="width_trans">2、固定元素居中 absolute+transform</div>
  </div>
  
    .width_margin {
      width: 400px;
      height: 20px;
      background-color: floralwhite;

      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      margin: auto;
    }

    .width_trans {
      background-color: lavender;
      width: 400px;
      height: 20px;

      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-200px, -10px);
    }

3.1、flex的align-items、align-content、align-self

  • align-items

    容器属性,定义项目在交叉轴(竖直)上的对齐方式。

    属性值有: flex-start | flex-end | center | baseline(项目第一行文字基线对齐) | stretch;

    • flex-start:交叉轴的起点对齐。
    • flex-end:交叉轴的终点对齐。
    • center:交叉轴的中点对齐。
    • baseline: 项目的第一行文字的基线对齐。
    • stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
  • align-content

    容器属性,定义多个主轴的对齐方式,只有一条轴线不起作用

    属性值有:flex-start | flex-end | center | space-between | space-around | stretch;

    • flex-start:与交叉轴的起点对齐。
    • flex-end:与交叉轴的终点对齐。
    • center:与交叉轴的中点对齐。
    • space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
    • space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
    • stretch(默认值):轴线占满整个交叉轴。
  • align-self

    项目属性,允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch

    属性值有:auto | flex-start | flex-end | center | baseline | stretch;除了auto,其他都与align-items属性完全一致。

3.2、对margin:auto的理解

margin:auto 带有强烈的计算意味,用来计算元素对应方向应该获得的剩余空间大小。

填充规则(设置了元素宽度width的前提下):

对于left和right,auto占用可用空间

  • 一侧为定值,一侧为auto:auto为剩余空间大小;

  • 两侧都为auto:平分剩余空间

      <div class="container">
        <div class="item"></div>
      </div>
      
        .container{
          background-color: tan;
          width: 400px;
          height: 300px;
        }
        .item{
          background-color: teal;
          width: 200px;
          height: 200px;
          margin-right: 20px;
          margin-left: auto;
        }
    

    结果:

    image

    container为400px,item为200px,剩余200px,外边距20px,则左侧占剩下全部的180px。

    如果left和right都是auto,则两侧各占100px。

借用margin-right:auto可以替代float:left实现左对齐。

对于top和bottom,auto为0px:

  • W3C规范:如果”margin-top“或”margin-bottom“为”auto“,则其使用值为0”。

  • 在绝对定位元素中除外,这也是为什么前文中margin:auto + absolute 可以实现水平垂直居中。

3.2.1、margin : 0 auto 居中原理

首先要清楚父级元素宽度等于什么:

width(父级元素宽度) = margin-left + border-left-width + paddding + width(子元素宽度) + padding-right + border-right-width + margin-right

其中width,margin-left,margin-right可以设置为auto。

  • 其中一个值为auto,另外两个为具体值,auto负责满足宽度等于父级框。
  • 两个为auto:
    • margin-left和margin-right设为auto,width为具体值:两侧平分余下宽度,达到居中效果。
    • width和某一外边距为auto:外边距auto变为0,width和具体值外边距满足父级框的宽度。
    • 三个值全为auto:两个外边距变为0,width满足父元素的宽度。(这也是前边居中布局中要设宽度的原因)
3.2.2、垂直方向居中原理

垂直方向上除了absolute,margin:auto变为0

在纵向同样存在一个等式:

height = top + margin-top + border-top-width + padding-top-width + height + padding-bottom + border-bottom-width + margin-bottom + bottom

将top和bottom设置为0,margin为auto,这时会把上下距离等分给margin。

3.3、margin属性值

  • margin:1px:上右下左均为1px
  • margin:1px 2px:上下为1px,左右为2px
  • margin:1px 2px 3px:上为1px,左右为2px,下为3px
  • margin:1px 2px 3px 4px :上为1px,右2px,下3px,左4px

3.4、top:50%; margin-top:50%; 分别相对于谁偏谁的50%

  • top等用于绝对定位,必须设置position:absolute,否则不起作用。margin是相对定位。

  • top的绝对定位是相对最近父级非static元素或者body。margin是相对于相邻元素定位。

  • margin、padding是相对于父元素的width计算的:

      <div class="father">
        <div class="one">one</div>
        <div class="two">two</div>
      </div>
      
        .one {
          width: 200px;
          height: 100px;
          background-color: brown;
        }
    
        .two {
          width: 100px;
          height: 100px;
          background-color: cadetblue;
          margin-top: 50%;
        }
    
        .father {
          width: 400px;
          border: 2px solid red;
        }
    
  • CSS设置绝对定位后,top,bottom百分比按照父元素的高度来计算,left,right百分比按照父元素的宽度来计算。

  • 如果父元素设置有padding值 则子元素定位 top的'百分比'值是 其父元素的height加上上下padding的百分比值,left是父元素宽度加上左右padding的百分比。

  • 如果父元素设置有border值,子元素 top定位的位置是根据父元素的高度来定位的,与边框无关。left是根据宽度,与边框无关

    <body>
      <div class="father">
        <div class="item"> </div>
      </div>
    </body>
    
      <style>
        .father{
          position: relative;
          width: 800px;
          height: 400px;
          /* padding会计算在内 */
          /* padding: 40px; */
          /* border 不会计算*/
          border: solid 40px cadetblue;
        }
        .item{
          position:absolute;
          background-color: chocolate;
          width: 400px;
          height: 200px;
          top: 50%;
          left: 50%;
        }
      </style>
    

为什么margin-top/margin-bottom的百分数也是相对于width而不是height呢?

正常流中的大多数元素都会足够高以包含其后代元素(包括外边距),如果一个元素的上下外边距是父元素的height的百分数,就可能导致一个无限循环,父元素的height会增加,以适应后代元素上下外边距的增加,而相应的,上下外边距因为父元素height的增加也会增加,如此循环。

JS

1、ES6 Map 和 {}的区别

Map和Object都用于保存键值对。

  1. 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值
  2. Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是(他们会先排数字开头的key值,然后才是字符串开头的key值)。
  3. Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  4. Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

2、Object.create() 和 {}的区别

参考

  1. 字面量和new关键字创建的对象是Object的实例,原型指向Object.prototype,继承内置对象Object
  2. Object.create(arg, pro)创建的对象的原型取决于argargnull,新对象是空对象,没有原型,不继承任何对象;arg为指定对象,新对象的原型指向指定对象,继承指定对象

Object.create():

语法:Object.create(proto[, propertiesObject])

  • proto必填参数,是新对象的原型对象,注意,如果这个参数是null,那新对象就彻彻底底是个空对象,没有继承Object.prototype上的任何属性和方法,如hasOwnProperty()、toString()等。

    var a = Object.create(null);
    console.dir(a); // {}
    console.log(a.__proto__); // undefined
    console.log(a.__proto__ === Object.prototype); // false
    console.log(a instanceof Object); // false 没有继承`Object.prototype`上的任何属性和方法,所以原型链上不会出现Object
    
    
  • propertiesObject是可选参数,指定要添加到新对象上的可枚举的属性(即其自定义的属性和方法,可用hasOwnProperty()获取的,而不是原型对象上的)的描述符及相应的属性名称。

     var bb = Object.create(null, {
          a: {
            value: 2,
            writable: true,
            configurable: true
          }
        });
        console.dir(bb); // {a: 2} 除此之外没有任何东西
        console.log(bb.__proto__); // undefined
        console.log(bb.__proto__ === Object.prototype); // false
        console.log(bb instanceof Object); // false 没有继承`Object.prototype`上的任何属性和方法,所以原型链上不会出现Object
    
        // ----------------------------------------------------------
    
        var cc = Object.create({ b: 1 }, {
          a: {
            value: 3,
            writable: true,
            configurable: true
          }
        });
        console.log(cc); // {a: 3}
        console.log(cc.hasOwnProperty('a'), cc.hasOwnProperty('b')); // true false 说明第二个参数设置的是新对象自身可枚举的属性
        console.log(cc.__proto__); // {b: 1} 新对象cc的__proto__指向{b: 1}
        console.log(cc.__proto__ === Object.protorype); // false
        console.log(cc instanceof Object); // true cc是对象,原型链上肯定会出现Object
    

    手动实现object.create():

        Object.mycreate = function (proto, properties) {
          function F() { };
          F.prototype = proto;s
          let res = new F()
          if (properties) {
            Object.defineProperties(res, properties);
          }
          return res;
        }
        var hh = Object.mycreate({ a: 1 }, {mm:{value:'isme'}});
        console.dir(hh);
    
    

3、代码考核

3.1、异步与同步

<body>
    <script>
        alert(1);

        setTimeout(function () {
            alert(2)
        }, 1000); //将1000换成0,执行结果顺序不变,差别就是:1--3--等一秒---2
        alert(3)
    </script>
</body>
// 结果:1---3---2
执行结果解析: 
alert 1
setTimeout异步任务,进入EventTable满足延时之后,进入EventQueue注册回调事件
alert 3 
同步任务执行完毕,从微任务队列读取回调事件,alert 2

上边异步变同步,执行结果为:1---2---等一会---3;

    alert(1);

    const sleep = (wait) => {
      return new Promise((resolve) => setTimeout(resolve, wait))
    }

    alert(2);

    (async () => {
      await sleep(2000);
      alert(3)
    })()

这是我想到的办法,但是当时面试官说alert(1)alert(3) 不许动。不晓得怎么搞~~~

3.2、原型,原型链

var Obj = function () {
  this.a = 100;
};
Obj.prototype.a = 300;

Obj.a = 200; // 注意这里的Obj大写的

var obj = new Obj();

// 问:解释下行代码为什么是100,因为先在实例内部找a,找到就返回,找不到去原型链上找
console.log(obj.a); //100

// 问: 如何获取到300
console.log(obj.__proto__.a) //300

此外:
 console.log(obj.__proto__.constructor.a) // 200

image

3.3、对象拷贝

以下代码考核的应该是引用数据类型赋值时传递的是实体的引用,以及数组下标赋值

var a = [];
var b = a;
a[2] = 2;
b[3] = 3;
console.log(a.length); //4  不是2
console.log(b.length); //4
console.log(a); //[ <2 empty items>, 2, 3 ]
console.log(a[0]); //undefined
console.log(b); //[ <2 empty items>, 2, 3 ]
var a = {};
var b = a;
a[2] = 2;
b[3] = 3;

//对象没有length属性,但是伪数组有
console.log(a.length); //undefined
console.log(b.length); //undefined

console.log(a); //{ '2': 2, '3': 3 }
console.log(a[0]); //undefined
console.log(b); //{ '2': 2, '3': 3 }

3.4、补充:数组,伪数组,对象之间的差别

什么是伪数组:

var obj = {
  obj_0: "obj_0",
};
var arr = ["arr_o"];

arr[1] = "arr_1";
obj[2] = "obj_1";

console.log("arr", arr); // arr [ 'arr_o', 'arr_1' ]
console.log("obj", obj); // obj { '2': 'obj_1', obj_0: 'obj_0' } 

console.log(arr[1]); //arr_1 按照【索引值1】进行查找的
console.log(obj[2]); //obj_1 按照【键值‘2’】查找的

console.log(arr[0]);  // arr_o
console.log(obj[1]);  // undefined

console.log(arr.length); // 2
console.log(obj.length); // undefined

数组和对象的差别:

  • 数组通过索引查找与存储,对象通过键值查找与存储

  • 数组存在length属性,而对象没有

伪数组:

  • 是一个对象

  • 拥有length属性

  • 所有的属性都是非负整数

  • 伪数组可以使用数组的大部分方法

组常见的伪数组是函数中的arguments对象:

function arg(a){
  console.log(arguments)
}
arg(1,2,3,'333')
//[Arguments] { '0': 1, '1': 2, '2': 3, '3': '333',length:4 }

image

4、同源和跨域

定义:同源策略限制了同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在而以文件的重要安全机制。

同源的定义:如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的

目的:为了保证用户信息的安全,防止恶意的网站窃取数据。

设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?

很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

限制范围:目前,如果非同源,共有三种行为受到限制。

  • CookieLocalStorageIndexDB 无法读取。
  • DOM 无法获得。
  • AJAX 请求不能发送。

Cookie(document.domain)

Cookie是服务端写入客户端的一小段信息,只有同源的网页才能共享。但是,两个网页的一级域名相同,只是二级域名不同,浏览器允许通过设置 document.domain共享 Cookie

举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。

document.domain = 'example.com';

现在,A网页通过脚本设置一个 Cookie。

document.cookie = "test1=hello";

B网页就可以读到这个 Cookie。

var allCookie = document.cookie;

注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。

另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com

Set-Cookie: key=value; domain=.example.com; path=/

这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

window.postMessage

HTML5为解决跨域问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。

这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。

var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

父窗口和子窗口都可以通过message事件,监听对方的消息。

window.addEventListener('message', function(e) {
  console.log(e.data);
},false);

message事件的事件对象event,提供以下三个属性。

  • event.source:发送消息的窗口
  • event.origin: 消息发向的网址
  • event.data: 消息内容

LocalStorage

通过 window.postMessage,读写为他窗口的 LocalStorage也成为了可能。

AJAX

同源政策规定,AJAX请求只能发给同源的网址,否则就报错。

除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。

  • JSONP
  • WebSocket
  • CORS

JSONP

JSONP是服务器和客户端跨源通信的常用方法。最大的特点就是简单使用,老式浏览器全部支持,服务器改造非常小。

JSONP的优点是:

  • 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;

  • 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。

    缺点就是只支持GET方法,不支持POST,只支持跨域的HTTP请求这样的问题,不能解决不同域两个页面之间如何进行JavaScript调用。

基本思想:网页通过添加一个 <script>元素,向服务器请求 JSON数据,这种做法不受同源策略限制;服务器收到请求后,将数据放在一个指定名字的回调函数传回去。

首先,网页动态添加 <script>元素,由它向跨源网址发出请求

function addScriptTag(src){
    var script=document.createElement('script);
    sript.setAttribute("type","text/javascript");
    sript.src=src;
    document.body.appendChild(script);
}

window.onload=function(){
    addScriptTag('http://example.com/ip?callback=foo')
}

function foo(data){
    console.log("返回的数据”,data)
}

注意:该请求的查询字符串有一个 callback参数,用来指定回调函数的名字,这对于 JSONP是必须的。

服务器收到请求后,会将数据放在回调函数的参数位置返回。

foo({
    //JSON数据
})

WebSocket

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

CORS

CORS是一个 W3C标准,全称是“跨域资源共享”

它允许浏览器向跨源服务器,发出 XMLHTTPRequest请求,从而克服了 AJAX只能同源使用的限制。

一、简介

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10.

整个 CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者而言, CORS通信与同源的 AJAX通信没有差别,代码完全一样。浏览器一旦发现 AJAX请求跨源,就会自动添加一些附加 的头信息,有时还会多出一次附加请求,但用户不会有感觉。

因此, CORS通信的关键是服务器,只要服务器实现了 CORS接口,就可以跨院通信。

二、两种请求

浏览器将 CORS请求分为两类:简单请求和非简单请求。

只要同时满足以下两大条件,就属于简单请求。

(1)请求方法是以下三种之一:

  • HEAD
  • GET
  • POST

(2) HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同时满足以上两个条件的,就属于非简单请求。

三、简单请求

基本流程

对于简单请求,浏览器直接发出 CORS请求。具体来说,就是在头信息之中,增加一个 Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息 Origin 用来说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,决定是否同意这次请求。

如果 Origin指定的源,不在许可范围内,服务器会返回一个正常的 HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

(1)Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

withCredentials 属性

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials: true

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials

xhr.withCredentials = false;

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

四、非简单请求

预检请求

非简单请求是那种服务器有特殊要求的请求,比如请求方法是 PUTDELETE,或者 Content-Type字段的类型是 application/json

非简单请求的 CORS请求,会在正式通信之前,增加一次 HTTP查询请求,称为 “预检”请求。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHTTPRequest请求,否则就报错。

“预检”请求用的请求方法是 OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是 Origin,表示请求来自哪个域。

除了Origin字段,"预检"请求的头信息包括两个特殊字段。

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT

(2)Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

预检请求的回应

服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

服务器回应的其他CORS相关字段如下。

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

浏览器的正常请求和回应

一旦服务器通过了 “预检”请求,以后每次浏览器正常的 CORS请求,就都跟简单请求一样,会有一个 Origin头信息字段。服务器的回应,也哦度会有一个 Access-Control-Allow-Origin头信息字段。

五、与JSONP相比

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

4.1、如何让 JSONP支持post方法

4.2、CORS 如何设置

当时我回答了服务端设置Access-Control-allow-origin等响应头,面试官不太认同的样子。。。

5、promise.finally实现

可以参考这篇文章

6、防抖节流

这篇文章~~~

HTTP

1、状态码

状态码可以说是必问的一项了,但是面试官问,当你遇到这些验证码时怎么办~~~

可以看一下这篇文章,比较详细的介绍了各种状态码~~~

浏览器

1、DOM及DOM的事件模型

捕获和冒泡

整理不易,点赞鼓励~~~