前端知识点

1,545 阅读29分钟

html

Doctype作用?标准模式与兼容模式各有什么区别?

  1. 声明位于HTML文档的第一行,告知浏览器的解析器用什么文档标准解析这个文档。DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。
  2. 标准模式的排版和JS运作模式都是以该浏览器支持的最高标准运行。在兼容模式中,页面以宽松的向后兼容的方式显示,模拟老式浏览器的行为以防止站点无法工作。简单的说,就是尽可能的能显示东西给用户看。

HTML5 为什么只需要写 <!DOCTYPE HTML>

HTML5 不基于 SGML,因此不需要对DTD进行引用,但是需要doctype来规范浏览器的行为(让浏览器按照它们应该的方式来运行);

而HTML4.01基于SGML,所以需要对DTD进行引用,才能告知浏览器文档所使用的文档类型。

什么是盒子模型?CSS-标准盒模型和怪异盒模型的区别?哪个css可以改变盒子模型?


css盒子模型 又称为框模型(Box Model),包含了元素内容(content)、内边距(padding)、边框(border)、外边距(margin)几个要素。

标准盒模型

怪异盒模型

区别:当不对doctype进行定义时,会触发怪异模式。

  • 在标准模式下,一个块的总宽度= width + margin(左右) + padding(左右) + border(左右)
  • 在怪异模式下,一个块的总宽度= width + margin(左右)(即width已经包含了padding和border值)

CSS

什么是外边距合并?

外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距。
合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。
w3school介绍网址: http://www.w3school.com.cn/css/css_margin_collapsing.asp

注意:水平方向上的外边距不会合并,而是会相加

如何居中div

  • 水平居中:给div设置一个宽度,然后添加margin:0 auto;属性
div{
 	width:200px;
 	margin:0 auto;
  }
  • 让绝对定位的div居中
div {
 	position: absolute;
 	width: 300px;
 	height: 300px;
 	margin: auto;
 	top: 0;
 	left: 0;
 	bottom: 0;
 	right: 0;
 	background-color: pink;	/* 方便看效果 */
 }
  • 水平垂直居中一

确定容器的宽高 宽500 高 300 的层 设置层的外边距

div {
 	position: relative;		/* 相对定位或绝对定位均可 */
 	width:500px;
 	height:300px;
 	top: 50%;
 	left: 50%;
 	margin: -150px 0 0 -250px;     	/* 外边距为自身宽高的一半 */
 	background-color: pink;	 	/* 方便看效果 */

  }
  • 水平垂直居中二

未知容器的宽高,利用 transform 属性

div {
 	position: absolute;		/* 相对定位或绝对定位均可 */
 	width:500px;
 	height:300px;
 	top: 50%;
 	left: 50%;
 	transform: translate(-50%, -50%);
 	background-color: pink;	 	/* 方便看效果 */

 }

  • 水平垂直居中三

利用 flex 布局 实际使用时应考虑兼容性

.container {
 	display: flex;
 	align-items: center; 		/* 垂直居中 */
 	justify-content: center;	/* 水平居中 */

 }
 .container div {
 	width: 100px;
 	height: 100px;
 	background-color: pink;		/* 方便看效果 */
 }  

左侧固定,右侧自适应

法一:

多列等高布局

来源:codepen.io/yangbo5207/…

  1. 视觉等高实现之绝对定位

html代码

<div class="outer">
  <div class="left">
    <div class="equh"></div>
    <div class="left-con">
      <p>left</p>
      <p>left</p>
      <p>left</p>
    </div>
  </div>
  <div class="right">
    <p>right</p>
    <p>right</p>
    <p>right</p>
    <p>right</p>
    <p>right</p>
  </div>
</div>

css代码

.outer {
  width: 960px;
  margin: 0 auto;
  border: 1px solid #000;
  overflow: hidden;
  background-color: green;
  color: #fff;
}
.left {
  width: 200px;
  position: relative;
  float: left;
}
.equh {
  width: 100%;
  height: 999em;
  position: absolute;
  left: 0;
  top: 0;
  border-right: 1px solid #000;
  background-color: orange;
}

.left-con {
  padding: 1em;
  position: relative;
  z-index: 1;
}
.right {
  padding: 1em;
  overflow: hidden;
}

  1. 视觉等高实现之padding-bottom与margin-bottom

核心代码

padding-bottom: 9999px;
margin-bottom: -9999px;

html代码

<div class="box">
  <div class="sub">
    <p>a</p>
  </div>
  <div class="sub">
    <p>b</p>
    <p>b</p>
  </div>
  <div class="sub">
    <p>c</p>
    <p>c</p>
    <p>c</p>
  </div>
</div>

css代码

.box {
  width: 600px;
  overflow: hidden;
  margin: 10px auto;
  border: 1px solid #888;
}
.sub {
  float: left;
  width: 30%;
  margin-right: 3%;
  border: 1px solid orange;
  padding-bottom: 9999px;
  margin-bottom: -9999px;
}
  1. 真实等高实现之table-cell

该方案利用了所有单元格高度都相等的特性,不过由于ie6/7不支持该属性,因此略有瑕疵。不过总的来说还是非常不错的方案。

html 代码

<div class="box">
  <div class="row">
    <div class="cell">你一定也有过这种感觉的。当你心事重重,渴望找一个人聊一聊的时候,那个可以聊的人来了,可是你们却并没有聊什么。当然,聊是聊了,可是他聊他的,你也试着开始聊你的,只是到后来,你放弃了……那么,最后的办法就是静下来,啃啮自己的寂寞。或者反过来说,让寂寞来吞噬你。------罗兰《寂寞的感觉》</div>
    <div class="cell">作为一个被基阿异捅过两个大血窟窿的人。告诉后来的基友们一句:一命二运三风水,四积阴功五读书。</div>
    <div class="cell">奔波了一天,收到了无数的生日快乐,享受了电影见面会现场各种形式的祝福和礼物,以及场面宏大的生日快乐歌,感谢<西风烈>,感谢支持我的朋友们!现在机场举长寿面祝你们都永远幸福快乐!</div>
  </div>
</div>

css代码

.box {
  width: 600px;
  margin: 40px auto;
  font-size: 12px;
}
.row {
  display: table-row;
  overflow: hidden;
}
.cell {
  display: table-cell;
  width: 30%;
  padding: 1.6%;
  background-color: #f5f5f5;
  // 在IE6/7下使用上一方法,添加一些hack即可,这样就能做到全部兼容了
  *float: left;
  *padding-bottom: 9999px;
  *margin-bottom: -9999px;
}
  1. 真实等高实现之弹性盒模型

若不考虑兼容性,此方法最简单

html代码

<div class="box">
  <div class="cell">你一定也有过这种感觉的。当你心事重重,渴望找一个人聊一聊的时候,那个可以聊的人来了,可是你们却并没有聊什么。当然,聊是聊了,可是他聊他的,你也试着开始聊你的,只是到后来,你放弃了……那么,最后的办法就是静下来,啃啮自己的寂寞。或者反过来说,让寂寞来吞噬你。------罗兰《寂寞的感觉》</div>
  <div class="cell">作为一个被基阿异捅过两个大血窟窿的人。告诉后来的基友们一句:一命二运三风水,四积阴功五读书。</div>
  <div class="cell">奔波了一天,收到了无数的生日快乐,享受了电影见面会现场各种形式的祝福和礼物,以及场面宏大的生日快乐歌,感谢<西风烈>,感谢支持我的朋友们!现在机场举长寿面祝你们都永远幸福快乐!</div>
</div>

css代码

.box {
  width: 600px;
  margin: 20px auto;
  display: flex;
}
.cell {
  width: 30%;
  border: 1px solid red;
}

总结: 如果需要兼容到ie6/ie7,则使用方法三即可,其中结合了方法二的思路。如果仅仅只是移动端的h5页面的实现,那么毫不犹豫的使用弹性盒模型来实现。简单高效。

position取值

  • static 默认值 没有定位
  • inherit 规定应该从父元素继承position属性的值
  • relative 生成相对定位的元素,相对于其正常位置进行定位
  • absolute 绝对定位 相对于static以外的第一个父元素进行定位
  • fixed 绝对定位 相对于浏览器窗口进行定位

sticky布局

法一:

dom结构

    <div class="wrapper">
        <header>header</header>
        <section style="height:300px">内容</section>
    </div>
    <footer>designer by echo hu</footer>

css

/*相同dom不同css*/
第一种样式
footer {
    height: 7em;
    background: #3c3c3c;
}
.wrapper {
    width: 100%;
    min-height: calc(100vh - 7em);
}

第二种样式
html,body{
    height:100%;
}
footer{
    height:7em;
}
.wrapper{
    height:calc(100% - 7em);
}

法二:

dom结构

<div class="wrapper">
    <div class="main">
       <div class="content"></div>
    </div>
    <div class="close"></div>
</div>

css样式

.wrapper{
    width:100%;
    height:100%;
}
.main{
    min-height:100%;
}
.close{
    width:100%;
    height:32px;
    margin-top:-32px;
}

flex布局

what is flex?

flex是Flexible Box的缩写,意为弹性布局。

任何一个容器都可指定为flex布局

.box{
    display:flex;
}

行内元素也可以使用flex布局

.box{
    display:inline-flex;
}

注意:设为flex布局后,子元素的clear,float,vertical-align属性将失效

容器的属性

+ flex-direction
+ flex-wrap
+ flex-flow
+ justify-content
+ align-items
+ align-content

flex-direction属性决定主轴的方向

.box{
    flex-direction: row | row-reverse | column | column-reverse
}

它有四个值

+ row(默认值):主轴为水平方向,起点在左端
+ row-reverse:主轴为水平方向,起点在右端。
+ column:主轴为垂直方向,起点在上沿。
+ column-reverse:主轴为垂直方向,起点在下沿。

flex-wrap属性定义,如果一条轴线排不下,如何换行。默认情况下,项目都排在一条线上

.box{
    flex-wrap: nowrap(不换行) | wrap(换行,第一行在上方) | wrap-reverse(换行,第一行在下方)
}

flex-flow属性是flex-directionflex-wrap的简写,默认row nowrap

.box {
  flex-flow: <flex-direction> || <flex-wrap>;
}

justify-content属性定义了项目在主轴上的对齐方式。

它有五个值,具体对齐方式与轴的方向有关。下面假设主轴为从左往右。

+ flex-start(默认值):左对齐
+ flex-end:右对齐
+ center:居中
+ space-between:两端对齐,项目之间的间隔都相等
+ space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

align-items属性定义项目在交叉轴上如何对齐 它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。

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

align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

该属性可能取6个值。

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

项目的属性

以下6个属性设置在项目上。

+ order 定义项目的排列顺序。数值越小,排列越靠前,默认为0
+ flex-grow 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
+ flex-shrink 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
+ flex-basis 属性定义了在分配多余空间之前,项目占据的主轴空间
+ flexflex-grow, flex-shrinkflex-basis的简写,默认值为0 1 auto
+ align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

display取值,元素默认是什么,内联元素块级元素可变元素有哪些?img是什么?

img是内联元素

display CSS属性指定用于元素的呈现框的类型。在 HTML 中,默认的 display 属性取决于 HTML 规范所描述的行为或浏览器/用户的默认样式表。

默认值是inline

display取值如下:

none	此元素不会被显示。
block	此元素将显示为块级元素,此元素前后会带有换行符。
inline	默认。此元素会被显示为内联元素,元素前后没有换行符。
inline-block	行内块元素。(CSS2.1 新增的值)
list-item	此元素会作为列表显示。
run-in	此元素会根据上下文作为块级元素或内联元素显示。
compact	CSS 中有值 compact,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。
marker	CSS 中有值 marker,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。
table	此元素会作为块级表格来显示(类似 <table>),表格前后带有换行符。
inline-table	此元素会作为内联表格来显示(类似 <table>),表格前后没有换行符。
table-row-group	此元素会作为一个或多个行的分组来显示(类似 <tbody>)。
table-header-group	此元素会作为一个或多个行的分组来显示(类似 <thead>)。
table-footer-group	此元素会作为一个或多个行的分组来显示(类似 <tfoot>)。
table-row	此元素会作为一个表格行显示(类似 <tr>)。
table-column-group	此元素会作为一个或多个列的分组来显示(类似 <colgroup>)。
table-column	此元素会作为一个单元格列显示(类似 <col>)
table-cell	此元素会作为一个表格单元格显示(类似 <td> 和 <th>)
table-caption	此元素会作为一个表格标题显示(类似 <caption>)
inherit	规定应该从父元素继承 display 属性的值。

float:left情况下是怎样的,此时如果超出了宽度范围

该元素从网页的正常流动中移除,尽管仍然保持部分的流动性

css3的transform

CSS transform 属性允许你修改CSS视觉格式模型的坐标空间。使用它,元素可以被转换(translate)、旋转(rotate)、缩放(scale)、倾斜(skew)。

javaScript

什么是闭包?闭包的优缺点


答:闭包是将外部作用域中的局部变量封闭起来的函数对象。被封闭起来的变量与封闭它的函数对象有相同的生命周期。

优点:一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在f1调用后被自动清除。

缺点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

输入一个URL后会发生什么?

总体来说分为以下几个过程:

  • DNS解析

  • TCP连接

  • 发送HTTP请求

  • 服务器处理请求并返回HTTP报文

  • 浏览器解析渲染页面

  • 连接结束

tcp 协议,http协议

hit-alibaba.github.io/interview/b…

tcp协议即是传输控制协议,

http协议:

HTTP构建于TCP/IP协议之上,默认端口号是80 HTTP是无连接无状态的

如何实现一个LazyMan?

题目

实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出: Hi! This is Hank!

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出 Hi! This is Hank! //等待10秒..Wake up after 10 Eat dinner~

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出 Hi This is Hank! Eat dinner~ Eat supper~

LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出 //等待5秒 Wake up after 5 Hi This is Hank! Eat supper 以此类推。

实现

  1. 队列
function _LazyMan(name) {
        this.tasks = []
        var self = this
        var fn = (function(n) {
          var name = n
          return function() {
            console.log('Hi! this is ' + name + '!')
            self.next()
          }
        })(name)
        this.tasks.push(fn)
        setTimeout(function() {
          self.next()
        }, 0)
        // 在下一个事件循环启动任务
      }
      /* 事件调度函数 */

      _LazyMan.prototype.next = function() {
        var fn = this.tasks.shift()
        fn && fn()
      }
      _LazyMan.prototype.eat = function(name) {
        var self = this
        var fn = (function(name) {
          return function() {
            console.log('Eat ' + name + ' ~')
            self.next()
          }
        })(name)
        this.tasks.push(fn)
        return this // 实现链式调用
      }
      _LazyMan.prototype.sleep = function(time) {
        var self = this
        var fn = (function(time) {
          return function() {
            setTimeout(function() {
              console.log('Wake up after ' + time + ' s!')
              self.next()
            }, time * 1000)
          }
        })(time)
        this.tasks.push(fn)
        return this
      }
      _LazyMan.prototype.sleepFirst = function(time) {
        var self = this
        var fn = (function(time) {
          return function() {
            setTimeout(function() {
              console.log('Wake up after ' + time + ' s!')
            }, time * 1000)
          }
        })(time)
        this.tasks.unshift(fn)
        return this
      } /* 封装 */
      function LazyMan(name) {
        return new _LazyMan(name)
      }
  1. promise
//lazyman里边含有链式调用,那么每一个子任务 return this;这个程序支持任务优先顺序,那么就需要两个贯穿全场的Promise对象:第一,普通顺序promise;第二,插入顺序promise,同时插入顺序是阻塞普通顺序的,代码如下:
      function _LazyMan(name) {
        this.orderPromise = this.newPromise() // 定义顺序promise对象
        this.insertPromise = this.newPromise() // 定义插入promise对象
        this.order(function(resolve) {
          console.log(name)
          resolve()
        })
      }
      _LazyMan.prototype = {
        /*实例化promise对象工厂*/

        newPromise: function() {
          return new Promise(function(resolve, reject) {
            resolve()
          })
        },
        order: function(fn) {
          var self = this
          this.orderPromise = this.orderPromise.then(function() {
            return new Promise(function(resolve, reject) {
              //如果有insertPromise,阻塞
              orderPromise.self.fir
                ? self.insertPromise.then(function() {
                    fn(resolve)
                  })
                : fn(resolve)
            })
          })
        },
        insert: function(fn) {
          var self = this
          this.fir = true
          this.insertPromise = this.insertPromise.then(function() {
            return new Promise(function(resolve, reject) {
              fn(resolve)
              self.fir = false
            })
          })
        },
        sleepFirst: function(time) {
          this.insert(function(resolve) {
            setTimeout(function() {
              console.log('wait ' + time + ' s,other logic')
              resolve()
            }, time * 1000)
          })
          return this
        },
        eat: function(something) {
          this.order(function(resolve) {
            console.log(something + ' ~~')
            resolve()
          })
          return this
        },
        sleep: function(time) {
          this.order(function(resolve) {
            setTimeout(function() {
              console.log('sleep ' + time + ' s')
            }, time * 1000)
          })
          return this
        }
      } 
      //接口封装。
      function LazyMan(name) {
        return new _LazyMan(name)
      } 
      //调用测试
      LazyMan('RoryWu')
        .firstTime(1)
        .sleep(2)
        .firstTime(3)
        .eat('dinner')
        .eat('breakfast') // 弹出: // wait 1 s, other logic // wait 3 s, other logic // RoryWu // sleep 2 s // dinner~~ // breakfast~~

用JS代码求出页面上一个元素的最终的background-color,不考虑IE浏览器,不考虑元素float情况。

代码实例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .button {
    height: 2em;
    border: 0;
    border-radius: .2em;
    background-color: #34538b;
    color: #fff;
    font-size: 12px;
    font-weight: bold;
}
  </style>
</head>
<body>
  <input type="button" id="button" class="button" value="点击我,显示背景色" />
  <script>
    document.getElementById("button").onclick = function() {
    var oStyle =window.getComputedStyle(this, null); // null不是必须
    // 如果考虑IE var oStyle = this.currentStyle? this.currentStyle : window.getComputedStyle(this, null);
    alert(oStyle.getPropertyValue("background-color")); //这里也可以用键值获取,建议用getPropertyValue("background-color")
   };
  </script>
</body>
</html>

setTimeout(fn,0)

题目

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 0);
    console.log(i);
}

题解

结果是:0 1 2 3 3 3

很多公司面试都爱出这道题,此题考察的知识点还是蛮多的。

为了防止初学者栽在此问题上,此文稍微分析一下。

都考察了那些知识点呢? 异步、作用域、闭包,你没听错,是闭包。

我们来简化此题:

setTimeout(function() {
        console.log(1);
}, 0);
console.log(2);
先打印2,后打印1。

因为是setTimeout是异步的。

正确的理解setTimeout的方式(注册事件):

有两个参数,第一个参数是函数,第二参数是时间值。

调用setTimeout时,把函数参数,放到事件队列中。等主程序运行完,再调用。

没啥不好理解的。就像我们给按钮绑定事件一样:

btn.onclick = function() {
        alert(1);
};
这么写完,会弹出1吗。不会!!只是绑定事件而已!

必须等我们去触发事件,比如去点击这个按钮,才会弹出1。

setTimeout也是这样的!只是绑定事件,等主程序运行完毕后,再去调用。

setTimeout的时间值是怎么回事呢?

比如:

setTimeout(fn, 2000)
我们可以理解为2000之后,再放入事件队列中,如果此时队列为空,那么就直接调用fn。如果前面还有其他的事件,那就等待。 因此setTimeout是一个约会从来都不准时的童鞋。

继续看:

setTimeout(function() {
        console.log(i);
}, 0);
var i = 1;
程序会不会报错?

不会!而且还会准确得打印1。

为什么?

因为真正去执行console.log(i)这句代码时,var i = 1已经执行完毕了!

所以我们进行dom操作。可以先绑定事件,然后再去写其他逻辑。

window.onload = function() {
        fn();
}
var fn = function() {
        alert('hello')
};
这么写,完全是可以的。因为异步! es5中是没有块级作用域的

for (var i = 0; i < 3; i++) {}
console.log(i);
也就说i可以在for循环体外访问到。所以是没有块级作用域。

但此问题在es6里终结了,因为es6,发明了let。

这回我们再来看看原题。

原题使用了for循环。循环的本质是干嘛的?

是为了方便我们程序员,少写重复代码。

让我们倒退50年,原题等价于:

var i = 0;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
console.log(i);
i++;
因为setTimeout是注册事件。根据前面的讨论,可以都放在后面。 原题又等价于如下的写法:

var i = 0;
console.log(i);
i++;
console.log(i);
i++;
console.log(i);
i++;
setTimeout(function() {
    console.log(i);
}, 0);
setTimeout(function() {
    console.log(i);
}, 0);
setTimeout(function() {
    console.log(i);
}, 0);
这回你明白了为啥结果是0 1 2 3 3 3了吧。

那个,说它是闭包,又是怎么回事?

为了很好的说明白这个事情,我们把它放到一个函数中:

var fn = function() {
        for (var i = 0; i < 3; i++) {
                setTimeout(function() {
                        console.log(i);
                }, 0);
                console.log(i);
        }
};
fn();
上面的函数跟我们常见另一个例子(div绑定事件)有什么区别:

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = function() {
                        alert(i);
                };
        }
};
fn();
点击每个div都会弹出3。道理是一样的。因为alert(i)中的i是fn作用越中的,因而这是闭包。

《javascript忍者秘籍》书里把一个函数能调用全局变量,也称闭包。

因为作者认为全局环境也可以想象成一个大的顶级函数。 怎么保证能弹出0,1, 2呢。

解决之道:以毒攻毒! 再创建个闭包!!

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                divs[i].onclick = (function(i) {
                        return function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();
或者如下的写法:

var fn = function() {
        var divs = document.querySelectorAll('div');
        for (var i = 0; i < 3; i++) {
                (function(i) {
                        divs[i].onclick = function() {
                                alert(i);
                        };
                })(i);
        }
};
fn();
因此原题如果也想setTimeout也弹出0,1,2的话,改成如下:

for (var i = 0; i < 3; i++) {
    setTimeout((function(i) {
        return function() {
            console.log(i);
        };
    })(i), 0);
    console.log(i);
}

原型与继承

题目

请用js实现一个类P,包含成员变量a,成员变量b,成员函数sum,sum输出a与b的和,a,b默认值都为0。实现一个类M,M继承自P,在P的基础上增加成员变量c,成员函数sum变成输出a,b,c的和。

题目分析

Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也有原型,实现继承的方法很多,这里使用原型链和构造继承,即组合继承的方式。

function P(a, b) {
    this.a = a || 0;
    this.b = b || 0;
    this.sum = function() {
        return this.a + this.b;
    }
}
 
function M(a, b, c) {
    P.call(this, a, b) //继承P类的成员对象
    this.c = c; //在自己的构造函数中定义的
    this.sum = function() {
        return this.a + this.b + this.c;
    }
}
M.prototype = new P();
var m = new M(2, 2, 2);
M.sum(); //输出6

js继承的实现方式

既然要实现继承,那么首先我们得有一个父类,代码如下:

//定义一个动物类
function Animal(name, eye, skin) {
 
    //属性
    this.name = name || 'Animal';
    this.eye = eye;
    this.skin = skin;
 
    //实例方法
    this.sleep = function() {
        console.log(this.name + '正在睡觉');
    }
}
 
//原型方法
Animal.prototype.eat = function(food) {
    console.log(this.name + '正在吃:' + food);
};

下面给大家列出几种继承方式的实现

原型链继承

实现父类代码在(js继承的实现方式中)

核心: 将父类的实例作为子类的原型

function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
 
//Test Code
var cat = new Cat();
console.log(cat.name);                     //cat
console.log(cat.eat('fish'));              //cat正在吃:fish
console.log(cat.sleep());                  //cat正在睡觉!
console.log(cat instanceof Animal);        //true
console.log(cat instanceof Cat);           //true
特点:

1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例

2. 父类新增原型方法/原型属性,子类都能访问到

3. 简单,易于实现

缺点:

1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中

2. 无法实现多继承

3. 来自原型对象的引用属性是所有实例共享的

4. 创建子类实例时,无法向父类构造函数传参(即无法像这样var cat=new Cat(hair,eye,skin)传参给父类)

推荐指数:★★(34两大致命缺陷)

构造继承

核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Cat(name) {
    Animal.call(this);
    this.name = name || 'Tom';
}
 
//Test Code
var cat = new Cat();
console.log(cat.name);                 //Tom
//console.log(cat.eat('fish'));        //报错
console.log(cat.sleep());              //Tom正在睡觉!
console.log(cat instanceof Animal);    //false
console.log(cat instanceof Cat);       //true
特点:

1. 解决了1中,子类实例共享父类引用属性的问题

2. 创建子类实例时,可以向父类传递参数(可通过Animal.call(this,name,eye,skin)或者Animal.apply(this,[name,eye,skin])实现)

3. 可以实现多继承(call多个父类对象)

缺点:

1. 实例并不是父类的实例,只是子类的实例

2. 只能继承父类的实例属性和方法,不能继承原型属性/方法

3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

推荐指数:★★(缺点3

实例继承

核心:为父类实例添加新特性,作为子类实例返回

function Cat(name) {
    var instance = new Animal();
    instance.name = name || 'Tom';
    return instance;
}
 
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //false
特点:

不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

缺点:

实例是父类的实例,不是子类的实例

不支持多继承

推荐指数:★★

拷贝继承

function Cat(name) {
    var animal = new Animal();
    for (var p in animal) {
        Cat.prototype[p] = animal[p];
    }
    Cat.prototype.name = name || 'Tom';
}
 
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //false
console.log(cat instanceof Cat); //true
特点:

支持多继承

缺点:

1. 效率较低,内存占用高(因为要拷贝父类的属性)

2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

推荐指数:★(缺点1

组合继承

核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name) {
    Animal.call(this);
    this.name = name || 'Tom';
}
Cat.prototype = new Animal();
 
//Test Code
var cat = new Cat();
console.log(cat.name);                 //Tom
console.log(cat.sleep());              //Tom正在睡觉!
console.log(cat instanceof Animal);    //true
console.log(cat instanceof Cat);       //true
特点:

1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法

2. 既是子类的实例,也是父类的实例

3. 不存在引用属性共享问题

4. 可传参

5. 函数可复用

缺点:

调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

推荐指数:★★★★(仅仅多消耗了一点内存)

寄生组合继承

核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

function Cat(name) {
    Animal.call(this);
    this.name = name || 'Tom';
} (function() {
    // 创建一个没有实例方法的类
    var Super = function() {};
    Super.prototype = Animal.prototype;
    //将实例作为子类的原型
    Cat.prototype = new Super();
})();
 
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:

堪称完美

缺点:

实现较为复杂

推荐指数:★★★★(实现复杂,扣掉一颗星)

4.9 附录代码
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉!');
  }
  //实例引用属性
  this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal();
 
var tom = new Cat('Tom');
var kissy = new Cat('Kissy');
 
console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []
 
tom.name = 'Tom-New Name';
tom.features.push('eat');
 
//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']
原因分析:

关键点:属性查找过程

执行tom.features.push,首先找tom对象的实例属性(找不到),
那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的
features属性中插入值。
在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。

new关键字

假设已经定义了父类Base对象

我们执行如下代码

var obj = new Base(); 
这样代码的结果是什么,我们在Javascript引擎中看到的对象模型是:
![](https://img-blog.csdn.net/20180725134719974?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzU4MDIzNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

new操作符具体干了什么呢?其实很简单,就干了三件事情。

var obj = {}; 
obj.__proto__ = Base.prototype; 
Base.call(obj); 

第一行,我们创建了一个空对象obj

第二行,我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象

第三行,我们将Base函数对象的this指针替换成obj,然后再调用Base函数

注意:new的过程会执行构造函数Base() 再对空对象进行构造

如何实现图片懒加载

图片懒加载的原理很简单,就是我们先设置图片的data-set属性(当然也可以是其他任意的,只要不会发送http请求就行了,作用就是为了存取值)值为其图片路径,由于不是src,所以不会发送http请求。 然后我们计算出页面scrollTop的高度和浏览器的高度之和, 如果图片举例页面顶端的坐标Y(相对于整个页面,而不是浏览器窗口)小于前两者之和,就说明图片就要显示出来了(合适的时机,当然也可以是其他情况),这时候我们再将 data-set 属性替换为 src 属性即可。

同源,跨域

推荐阅读 浏览器同源政策及其规避方法

js有几种类型值,画内存图

栈:原始数据类型(UndefinedNullBooleanNumberString)

堆:引用数据类型(对象、数组和函数)

两种类型的区别是:

存储位置不同;

原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;

引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/3/20/169991ff8c378649~tplv-t2oaga2asx-image.image)

["1", "2", "3"].map(parseInt)

[1, NaN, NaN] 因为 parseInt 需要两个参数 (val, radix), 其中 radix 表示解析时用的基数。 map 传了 3 个 (element, index, array),对应的 radix 不合法导致解析失败。

声明提升

var let const

那些操作会造成内存泄漏?

内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。

垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收。

setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。

闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)

关于arguments

1. 定义

由于JavaScript允许函数有不定数目的参数,所以我们需要一种机制,可以在函数体内部读取所有参数。这就是arguments对象的由来。

arguments对象包含了函数运行时的所有参数,arguments[0]就是第一个参数,arguments[1]就是第二个参数,以此类推。这个对象只有在函数体内部,才可以使用。

var f = function(one) {
    console.log(arguments[0]); //1
    console.log(arguments[1]); //2
    console.log(arguments[2]); //3
}
f(1, 2, 3);
arguments对象除了可以读取参数,还可以为参数赋值(严格模式不允许这种用法)

var f = function(a, b) {
    arguments[0] = 3; //对a重新赋值
    arguments[1] = 2; //对b重新赋值
    return a + b;
}
console.log(f(1, 1)); //5
可以通过arguments对象的length属性,判断函数调用时到底带几个参数。

var f = function() {
    return arguments.length;
}
console.log(f(1, 2, 3));               //3
console.log(f(1, 2));                  //2
console.log(f(1));                     //1
console.log(f());                      //0
2. 与数组的关系

需要注意的是,虽然arguments很像数组,但它是一个对象。数组专有的方法(比如slice和forEach),不能在arguments对象上直接使用。

但是,可以通过apply方法,把arguments作为参数传进去,这样就可以让arguments使用数组方法了。

// 用于apply方法
 myfunction.apply(obj, arguments). 
// 使用与另一个数组合并 
Array.prototype.concat.apply([1,2,3], arguments) 
要让arguments对象使用数组方法,真正的解决方法是将arguments转为真正的数组。下面是两种常用的转换方法:slice方法和逐一填入新数组。

var args = Array.prototype.slice.call(arguments); 
 
// or
 
var args = []; 
for (var i = 0; i < arguments.length; i++) { 
    args.push(arguments[i]); 
}
3. callee属性

arguments对象带有一个callee属性,返回它所对应的原函数。

var f = function(one) {
    console.log(arguments.callee === f);
}
f(1);
可以通过arguments.callee,达到调用函数自身的目的。这个属性在严格模式里面是禁用的,因此不建议使用。

4. 题目sum(2)(3)

// 写一个 function 让下面两行代码输出的结果都为 5 
console.log(sum(2, 3)); 
console.log(sum(2)(3)); 
说实话,第一眼看到的时候心里是有点虚的(因为第一次看到它)。sum(2)(3),这种形式的代码确实少见。但是第一反应就是链式调用。

链式调用我们熟悉啊,特别是 jQuery 里面,我们常常能看到连着写的代码。实现原理就是在方法结束时 return 合适的元素对象。

$('#id').parent().siblings('selector').css({
    color: 'red'
});
这道题考什么呢?认真分析了一下,应该有链式调用,toString,柯里化,数组操作等相关内容。大概这些可以满足需求吧?

如何写代码,脑海中大体上有构思了,但是当时手上仅有笔和纸,思路连不上来啊。还好面前放着一台台式机(嘿嘿嘿,机器上写完再抄回纸上)

我的实现大概是这样的。

var sum = (function() {
    var list = [];
 
    var add = function() {
        // 拼接数组
        var args = Array.prototype.slice.call(arguments);
        list = list.concat(args);
        return add;
    }
    // 覆盖 toString 方法
    add.toString = function() {
        // 计算总和
        var sum = list.reduce(function(pre, next) {
            return pre + next;
        });
        // 清除记录
        list.length = 0;
        return sum;
    }
 
    return add;
})();
 
sum(2, 3);
// 5
sum(2)(3);
// 5
这个方法比较复杂,下面介绍个简便的。

var add = function add() {
    var cache;
    if (arguments.length === 1) {
        cache = arguments[0];
        return function(number) {
            return cache + number;
        }
    } else {
        return arguments[0] + arguments[1];
    }
}
console.log(add(2, 3));
console.log(add(2)(3));

写一个js判断全等的方法

//利用JSON.stringify,将两个对象转化为字符串。
字符串相等的话,说明两个对象全等。
let a = {a:0,b:1,c:2};
let b = {a:0,b:1,c:2};
let c = {a:1,b:1,c:2};
let x = JSON.stringify(a) == JSON.stringify(b);
let y = JSON.stringify(a) == JSON.stringify(c);
console.log(x);
console.log(y);

遍历对象的方法

var obj={a:'A',b:'B',c:'C'};

1. for ... in 循环
for(key in obj){
    console.log(key);// a,b,c
}

2. Object.keys()
Object.keys(obj);//["a", "b", "c"]

防抖和节流

1、防抖

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

  • 思路

每次触发事件时都取消之前的延时调用方法

function debounce(fn) {
      let timeout = null; // 创建一个标记用来存放定时器的返回值
      return function () {
        clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
        timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
          fn.apply(this, arguments);
        }, 500);
      };
    }
    function sayHi() {
      console.log('防抖成功');
    }

    var inp = document.getElementById('inp');
    inp.addEventListener('input', debounce(sayHi)); // 防抖

2、节流

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

  • 思路

每次触发事件时都判断当前是否有等待执行的延时函数

function throttle(fn) {
      let canRun = true; // 通过闭包保存一个标记
      return function () {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
          fn.apply(this, arguments);
          // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
          canRun = true;
        }, 500);
      };
    }
    function sayHi(e) {
      console.log(e.target.innerWidth, e.target.innerHeight);
    }
    window.addEventListener('resize', throttle(sayHi));
  • 问题

为什么要 fn.apply(this, arguments);而不是这样 fn()

答:加上 apply 确保 在 sayHi 函数里的 this 指向的是 input对象(不然就指向 window 了,不是我们想要的)。 这里的箭头函数依旧是指向 input 对象。

https怎么保证安全的?

什么情况下会碰到跨域问题?有哪些解决方法?

  • 跨域问题是这是浏览器为了安全实施的同源策略导致的,同源策略限制了来自不同源的document、脚本,同源的意思就是两个URL的域名、协议、端口要完全相同。
  • script标签jsonp跨域、nginx反向代理、node.js中间件代理跨域、后端在头部信息设置安全域名、后端在服务器上设置cors。

如何判断一个变量是对象还是数组?

function isObjArr(value){
     if (Object.prototype.toString.call(value) === "[object Array]") {
            console.log('value是数组');
       }else if(Object.prototype.toString.call(value)==='[object Object]'){//这个方法兼容性好一点
            console.log('value是对象');
      }else{
          console.log('value不是数组也不是对象')
      }
}
//ps:千万不能使用typeof来判断对象和数组,因为这两种类型都会返回"object"。

事件循环机制

html事件循环:
一个浏览器环境,只能有一个事件循环,而一个事件循环可以多个任务队列(task queue),每个任务都有一个任务源(task source)。

相同任务源的任务,只能放到一个任务队列中。

不同任务源的任务,可以放到不同任务队列中。

EcmaScript规范中指出:
任务队列(Job queue)是一个先进先出的队列,每一个任务队列是有名字的,至于有多少个任务队列,取决于实现。每一个实现至少应该包含以上两个任务队列。

结论:EcmaScript的Job queue与HTML的Task queue有异曲同工之妙。它们都可以有好几个,多个任务队列之间的顺序都是不保证的。

例子:
setImmediate(function(){ 
   console.log(1); 
},0); 

setTimeout(function(){ 
   console.log(2); 
},0);

new Promise(function(resolve){ 
    console.log(3); 
    resolve(); 
    console.log(4); 
}).then(function(){
    console.log(5); 
}); 

console.log(6); 

process.nextTick(function(){ 
    console.log(7); 
}); 

console.log(8);

结果:3 4 6 8 7 5 2 1

事件注册顺序如下:
setImmediate - setTimeout - promise.then - process.nextTick

优先级关系:
process.nextTick > promise.then > setTimeout > setImmediate

V8实现中,两个队列各包含不同的任务:
macrotasks(宏任务): script(整体代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks(微任务): process.nextTick, Promises, Object.observe, MutationObserver

执行过程如下:
js引擎首先从macrotask queue中取出第一个任务,执行完毕后,将microtask queue中的所有任务取出,按顺序全部执行;然后再从macrotask queue中取下一个,执行完毕后,再次将microtask queue中的全部取出; 循环往复,直到两个queue中的任务都取完。

setTimeout会默认延迟4毫秒(ms)。

问题:
process.nextTick也会放入microtask quque,为什么优先级比promise.then高呢?

答:process.nextTick 永远大于promise.then,原因其实很简单。
在Node中,_tickCallback在每一次执行完TaskQueue中的一个任务后被调用,而这个_tickCallback中实质上干了两件事:
1. nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)
2. 第一步执行完后执行_runMicrotasks函数,执行microtask中的部分(promise.then注册的回调)
所以很明显process.nextTick > promise.then

深拷贝和浅拷贝

区别

1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用

2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

为什么要使用深拷贝?

我们希望在改变新的数组(对象)的时候,不改变原数组(对象)

深拷贝的要求程度

我们在使用深拷贝的时候,一定要弄清楚我们对深拷贝的要求程度:是仅“深”拷贝第一层级的对象属性或数组元素,还是递归拷贝所有层级的对象属性和数组元素?

怎么检验深拷贝成功

改变任意一个新对象/数组中的属性/元素, 都不改变原对象/数组

只做第一层深拷贝

深拷贝数组(只拷贝第一级数组元素)

  1. 直接遍历
var arr = [1,2,3,4];


function copy(arg){
  
  var newArr = [];
  
  for(var i = 0; i < arr.length; i++) {
    newArr.push(arr[i]);
  }
  
  return newArr;
}

var newArry = copy(arr);
console.log(newArry);
newArry[0] = 10;
console.log(newArry); // [10,2,3,4]
console.log(arr)  // [1,2,3,4]
  1. slice()
var arr = [1,2,3,4]
var copyArr = arr.slice();
copyArr[0] = 10;
console.log(copyArr); // [10,2,3,4]
console.log(arr); // [1,2,3,4]

// slice() 方法返回一个从已有的数组中截取一部分元素片段组成的新数组(不改变原来的数组!)
用法:array.slice(start,end) start表示是起始元素的下标, end表示的是终止元素的下标
当slice()不带任何参数的时候,默认返回一个长度和原数组相同的新数组

  1. concat()
var arr = [1,2,3,4]
var copyArr = arr.concat();
copyArr[0] = 10;
console.log(copyArr); // [10,2,3,4]
console.log(arr); // [1,2,3,4]

//concat() 方法用于连接两个或多个数组。( 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。)
用法:array.concat(array1,array2,......,arrayN)
因为我们上面调用concat的时候没有带上参数,所以var copyArray = array.concat();实际上相当于var copyArray = array.concat([]);
也即把返回数组和一个空数组合并后返回

深拷贝对象

1.直接遍历

 var obj = {
    name: "张三",
    job: "学生"
  }
  
  function copy (obj) {
    let newobj = {}
    for(let item in obj) {
      newobj[item] = obj[item];
    }
    return newobj;
  }
  
  var copyobj = copy(obj)
  copyobj.name = "李四"
  console.log(copyobj) // {name: '李四', job:: '学生'}
  console.log(obj) // {name: '张三', job:: '学生'}

  1. ES6的Object.assign
var obj = {
 name: '张三',
 job: '学生'
}

var copyobj = Object.assign({},obj)
copyobj.name = '李四'
console.log(copyobj) // {name: '李四', job:: '学生'}
console.log(obj)    // {name: '张三', job:: '学生'}

Object.assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target
用法: Object.assign(target, source1, source2);  所以 copyObj = Object.assign({}, obj);  这段代码将会把obj中的一级属性都拷贝到 {}中,然后将其返回赋给copyObj
  1. ES6扩展运算符:
var obj = {
  name: '张三',
  job: '学生'
}

var copyobj = {...obj}
copyobj.name = '李四'
console.log(copyobj)
console.log(obj)

扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中

ajax请求

// 1.XMLHttpRequest对象用于在后台与服务器交换数据   
var xhr = new XMLHttpRequest();
// 2.
xhr.open('GET', url, true);
//3.发送请求
xhr.send();
//4.接收返回
//客户端和服务器端有交互的时候会调用onreadystatechange
xhr.onreadystatechange = function() {
    // readyState == 4说明请求已完成
    if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { 
        // 从服务器获得数据 
        fn.call(this, xhr.responseText);  
    }
};

前端安全性问题

1、xss跨站脚本攻击(原理、如何进行的、防御手段是什么,要说清楚)

2、CSRF跨站请求伪造(如何伪造法?怎么防御?等等都要说清楚)

3、sql脚本注入(注入方式,防御方式)

4、上传漏洞 (防御方式)

你是如何处理前端性能问题的?

减少 HTTP 请求数量

在浏览器与服务器进行通信时,主要是通过 HTTP 进行通信。浏览器与服务器需要经过三次握手,每次握手需要花费大量时间。而且不同浏览器对资源文件并发请求数量有限(不同浏览器允许并发数),一旦 HTTP 请求数量达到一定数量,资源请求就存在等待状态,这是很致命的,因此减少 HTTP 的请求数量可以很大程度上对网站性能进行优化。

  • CSS Sprites:国内俗称 CSS 精灵,这是将多张图片合并成一张图片达到减少 HTTP 请求的一种解决方案,可以通过 CSS background 属性来访问图片内容。这种方案同时还可以减少图片总字节数。
  • 合并 CSS 和 JS 文件:现在前端有很多工程化打包工具,如:grunt、gulp、webpack等。为了减少 HTTP 请求数量,可以通过这些工具再发布前将多个 CSS 或者 多个 JS 合并成一个文件。
  • 采用 lazyLoad:俗称懒加载,可以控制网页上的内容在一开始无需加载,不需要发请求,等到用户操作真正需要的时候立即加载出内容。这样就控制了网页资源一次性请求数量。

控制资源文件加载优先级

浏览器在加载 HTML 内容时,是将 HTML 内容从上至下依次解析,解析到 link 或者 script 标签就会加载 href 或者 src 对应链接内容,为了第一时间展示页面给用户,就需要将 CSS 提前加载,不要受 JS 加载影响。 一般情况下都是 CSS 在头部,JS 在底部。

利用浏览器缓存

浏览器缓存是将网络资源存储在本地,等待下次请求该资源时,如果资源已经存在就不需要到服务器重新请求该资源,直接在本地读取该资源。

减少重排(Reflow)

基本原理:重排是 DOM 的变化影响到了元素的几何属性(宽和高),浏览器会重新计算元素的几何属性,会使渲染树中受到影响的部分失效,浏览器会验证 DOM 树上的所有其它结点的 visibility 属性,这也是 Reflow 低效的原因。如果 Reflow 的过于频繁,CPU 使用率就会急剧上升。 减少 Reflow,如果需要在 DOM 操作时添加样式,尽量使用 增加 class 属性,而不是通过 style 操作样式。

减少 DOM 操作

图标使用 IconFont 替换

算法

写一个冒泡算法

思路:
    a)比较两个相邻的元素,如果后一个比前一个大,则交换位置

    b) 第一轮的时候最后一个元素应该是最大的一个

    c) 按照第一步的方法进行两个相邻的元素的比较,由于最后一个元素已经是最大的了,所以最后一个元素不用比较。
代码:
    
    function sort(element){
        for(var i = 0;i<element.length-1;i++) {
            for(var j = 0;j<element.length-i-1;j++){
                if(element[j]>element[j+1]){
                    //把大的数字放到后面
                    var swap = element[j];
                    element[j] = element[j+1];
                    element[j+1] = swap;
                }
            }
        }
    }
    var element = [3,5,1,2,7,8,4,5,3,4];
    sort(element);

写一个快速排序

"快速排序"的思想很简单,整个排序过程只需要三步:

1)在数据集之中,选择一个元素作为"基准"(pivot)。

(2)所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。

(3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
function quickSort(arr) {
  if(arr.length < 2) {
    return arr;
  } else {
    const pivot = arr[0]; // 基准值
    const pivotArr = []; // 一样大的放中间
    const lowArr= []; // 小的放左边
    const hightArr = []; // 大的放右边
    arr.forEach(current => {
      if(current === pivot) pivotArr.push(current);
      else if(current > pivot) hightArr.push(current);
      else lowArr.push(current);
    })
    return quickSort(lowArr).concat(pivotArr).concat(quickSort(hightArr));
  }
}

移动端适配

插件:amfe-flexible + postcss-px2rem

amfe-flexible:自动根据不同设备改变data-dpr的值,这样就可以根据不同的data-dpr设置字体大小不变,仅放大相应倍数。

postcss-px2rem:打包的时候把项目里面的px统一转换成rem,转换的基准值根据配置设置的(.postcssrc.js) /因为我是以750px(iphone6)宽度为基准,所以remUnit为37.5/

经过试验结果:

postcss-px2rem:只负责把项目里面的px按照基准值转换成rem,并不负责根节点动态font-size的计算。 例如,代码里面有个高度固定:180px, 基准值是:37.5, 那最后界面上的rem=180/37.5=4.8rem 不管换不同客户端手机,不同分辨率,界面上都是固定4.8rem【rem的值是固定的,根据根节点的font-size不同,在界面显示的px也不同】,界面上显示的px = 16(没有设置font-size的话默认是16px)* 4.8rem = 76.8px