前端面试题

自己收集的一些前端面试题,部分来自网络,部分自己写的,不保证正确,如有错误可以评论告知。

前端性能优化有哪些?
1.减少HTTP请求数
2.合理设置 HTTP缓存,很少变化的资源可以直接通过 HTTP Header中的Expires设置
一个很长的过期头 ;变化不频繁而又可能会变的资源可以使用 Last-Modifed来做请求验证
3.html,css,js,图片等资源的合并/压缩
4.CSS Sprites(精灵图)
5.Inline Images(base64图片)
6.懒加载
7.代码按需加载(webpack)
8.'将js放于页面的底部,css放于顶部',让浏览器先加载css与html结构,以便更早的渲染出
页面结构,脚本的运行会阻碍资源的加载.
9.'为script增加async 或 defer来异步加载脚本'
10.避免过多的操作DOM节点
11.使用CDN(根据用户ip,获取就进服务上的静态资源,加快访问速度)
12.添加 link dns-prefetch 对于访问过的地址直接跳过dns解析
输入url按下回车后发生了什么?
1.解析URL
2.DNS解析,将域名解析为IP(本地,DNS服务器,如果查找不到,页面则无法访问)
3.通过TCP协议,向服务器发送请求(三次握手,建立连接)
4.浏览器向web服务器发起HTTP请求
5.服务器接收请求,响应请求
6.浏览器接收响应,开始下载并渲染,将页面呈现在我们面前
  {
    解析HTML生成DOM树,
    解析CSS文件生成CSSOM树,
    并解析Javascript代码
    CSS和DOM组合形成渲染树,
    进行布局,在浏览器中绘制渲染树中节点的属性(位置,宽度,大小等)
    绘制元素,绘制各个节点的可视属性(颜色背景等,此时可能会形成多个图层)
    合并图层,将页面呈现给用户面前
  }
强缓存和协商缓存

强缓存

强缓存是利用http的返回头中的Expires或Cache-Control两个字段来控制的,用来表示资源的缓存时间。

Expires是http1.0的规范,值为一个绝对时间的GMT格式的时间字符串,这个时间代
表着资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由
于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。

Cache-Control是http1.1时出现的,主要是利用该字段的`max-age`值来进行判断,它
是一个相对时间,例如Cache-Control:max-age=3600,代表着资源的有效期是3600
秒。
注: Expires与Cache-Control同时启用的时候Cache-Control优先级高。

协商缓存

协商缓存是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让
服务器判断请求资源是否可以缓存访问,这主要涉及到下面两组header字段,这两组搭档都是成对出现的,
即第一次请求的响应头带上某个字段(Last-Modified或者Etag),则后续请求则会带上对应的请求字段(If-
Modified-Since或者If-None-Match),若响应头没有Last-Modified或者Etag字段,则请求头也不会有对应的
字段。

Last-Modify/If-Modify-Since

第一次请求资源,服务器会加上Last-Modify,一个时间标识该资源的最后修改时间,
再次请求时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-
Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。
如果命中缓存,则返回304,并且不会返回资源内容,并且不会返回Last-Modify。

ETag/If-None-Match

Etag/If-None-Match返回的是一个校验码。ETag可以保证每一个资源是唯一的,资源
变化都会导致ETag变化。服务器根据浏览器上送的If-None-Match值来判断是否命中缓存。
注: ETag 优先级高于 Last-Modified
iframe有那些缺点?
  *iframe会阻塞主页面的Onload事件;
  *爬虫无法解读这种页面,不利于SEO;
  *iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。

  使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript
  动态给iframe添加src属性值,这样可以绕开以上两个问题。
CSS的盒子模型
IE 盒模型、标准盒模型
盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border)
区  别: IE的content部分把 border 和 padding计算了进去
BFC
块级格式化上下文
具有 BFC 特性的元素可以看作是隔离了的独立容器,'容器里面的元素不会在布局上影响到外面的元素',并且 BFC 具有普通容器所没有的一些特性。

触发BFC: 
浮动元素:float 除 none 以外的值
绝对定位元素:position (absolute、fixed)
display 为 inline-block、table-cells、flex
overflow 除了 visible 以外的值 (hidden、auto、scroll)
new操作符具体干了什么
 1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
 2、属性和方法被加入到 this 引用的对象中。
 3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。
 var obj  = {};
 obj.__proto__ = Base.prototype;
 Base.call(obj);
实现一个 new 操作符
function New(func) {
    var res = {};
    if (func.prototype !== null) {
        res.__proto__ = func.prototype;
    }
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
    return res;
}
var obj = New(A, 1, 2);
// equals to
var obj = new A(1, 2);
实现call、apply 与 bind
// call
Function.prototype.myCall = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  const args = [...arguments].slice(1)
  const result = context.fn(...args)
  delete context.fn
  return result
}

// apply,apply与call的区别在于对参数的处理
Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  let result
  // 处理参数和 call 有区别
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

// bind
Function.prototype.myBind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const _this = this
  const args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
}
实现 instanceOf
function instanceOf(left,right) {
    let proto = left.__proto__;
    let prototype = right.prototype
    while(true) {
        if(proto == null) return false
        if(proto == prototype) return true
        proto = proto.__proto__;
    }
}
PC端常见兼容问题
1.IE6浮动的双被边距bug
解决方法:为其增加display:inline;属性
2.IE8的input没有placeholder
解决方法:内容为空且没有focus,为其设置默认文字,focus时去除默认文字;未focus且已输入文字,则不设置默认文字
3.不同浏览器的标签默认的margin和padding不同
解决方法:重置所有标签{margin:0;padding:0;}
4.li标签之间有空白
解决方法:1. 标签不要换行,首尾连接;2. 父元素设置 display:inline;3. 父元素设置 font-size: 0;
5.margin重叠(取最大的)
解决方法:两个元素外包一层div,为该div怎加overflow:hidden;(BFC)
手机端兼容问题
1.字体、图片模糊
解决方法:由于像素比的原因,可根据设备像素比,动态设置meta的缩放比例。
2.ios fixed bug
解决方法:(1) header main footer 全部fixed,main区域overflow: hidden; mian区域的输入框focus,就没有问题。
(2)focus时,fixed改为absolute
3.点击iOS的可点击的元素时,覆盖显示的高亮颜色。
解决方法:-webkit-tap-highlight-color:transparent;
4.ios中滚动区域卡顿
解决方法:webkit-overflow-scrolling: touch; overflow-scrolling: touch;
5.Retina屏的1px边框
解决方法:设置1px的div,将其缩放0.5。
6.transition闪屏
解决方法:
/设置内嵌的元素在3D 空间如何呈现:保留3D /
-webkit-transform-style: preserve-3d;
/ 设置进行转换的元素的背面在面对用户时是否可见:隐藏 / 
-webkit-backface-visibility:hidden;
7.移动端点击300ms延迟
解决方法:产生的原因是手机要检测双击,解决的方法可以用zepto的tap事件,或者使用fastclick.js,或者自己使用touch相关事件来取代click。
8.Retina屏的1px边框
解决方法:设置1px的div,将其缩放0.5。
js继承参考这里(https://www.cnblogs.com/humin/p/4556820.html
ajax
// GET
//步骤一:创建异步对象
var ajax = new XMLHttpRequest();
//步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
ajax.open('get','getStar.php?starName='+name);
//步骤三:发送请求
ajax.send();
//步骤四:注册事件 onreadystatechange 状态改变就会调用
ajax.onreadystatechange = function () {
   if (ajax.readyState==4 &&ajax.status==200) {
    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
    console.log(xml.responseText);//输入相应的内容
    }
}
// POST
//创建异步对象  
var xhr = new XMLHttpRequest();
//设置请求的类型及url
//post请求一定要添加请求头才行不然会报错
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
 xhr.open('post', '02.post.php' );
//发送请求
xhr.send('name=fox&age=18');
xhr.onreadystatechange = function () {
    // 这步为判断服务器是否正确响应
  if (xhr.readyState == 4 && xhr.status == 200) {
    console.log(xhr.responseText);
  } 
};
jq大致原理
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<script>
    (function(window){
        function Jquery (el) {
            return new Jquery.fn.init(el)
        }
        Jquery.fn = Jquery.prototype = {
            constructor: Jquery,
            init: function (el) {
                var el = document.querySelectorAll(el)
                this.length = el.length
                for (var i = 0; i < el.length; i++) {
                    this[i] = el[i]
                }
            },
            each: function (cb) {
                for (var i = 0; i < this.length; i++) {
                    cb.call(this[i], this[i], i)
                }
            },
            get:  function (index) {
                return this[index]
            },
            html: function (param) {
                if(typeof param !== "undefined"){
                    this.each(function(){
                        this.innerHTML = param
                    })
                }else{
                    return this[0].innerHTML
                }
                return this
            }
        }
        Jquery.fn.init.prototype = Jquery.fn 
        // jq的方法定义在Jquery.prototype上,
        // Jquery.prototype.init的原型上并没有方法
        // 这一句Jquery.prototype.init与Jquery共享原型

        window.$ = Jquery
    })(window)

    // 扩展一个方法
    $.fn.get1 = function (index) {
        return this[index]
    }
</script>
<body>
    <h1 class="app">1</h1>
    <h1 class="app">2</h1>
    <h1 id="app"></h1>
</body>
<script>
    console.log($('.app').get(0))
    $('.app').each(function(e, i){
        console.log(e)
        console.log(i)
    })
    $('#app').html('FakeJquery')
</script>
</html>
vue原理
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Two-way data-binding</title>
</head>
<body>
  
  <div id="app">
    <input type="text" v-model="text">
    {{ text }}
  </div>

  <script>
    function observe (obj, vm) {
      Object.keys(obj).forEach(function (key) {
        defineReactive(vm, key, obj[key]);
      });
    }
    function defineReactive (obj, key, val) {
      var dep = new Dep();
      Object.defineProperty(obj, key, {
        get: function () {
          // 添加订阅者watcher到主题对象Dep
          if (Dep.target) dep.addSub(Dep.target);
          return val
        },
        set: function (newVal) {
          if (newVal === val) return
          val = newVal;
          // 作为发布者发出通知
          dep.notify();
        }
      });
    }
    function nodeToFragment (node, vm) {
      var flag = document.createDocumentFragment();
      var child;
      
      while (child = node.firstChild) {
        compile(child, vm);
        flag.appendChild(child); // 将子节点劫持到文档片段中
      }
      return flag;
    }
    function compile (node, vm) {
      var reg = /\{\{(.*)\}\}/;
      // 节点类型为元素
      if (node.nodeType === 1) {
        var attr = node.attributes;
        // 解析属性
        for (var i = 0; i < attr.length; i++) {
          if (attr[i].nodeName == 'v-model') {
            var name = attr[i].nodeValue; // 获取v-model绑定的属性名
            node.addEventListener('input', function (e) {
              // 给相应的data属性赋值,进而触发该属性的set方法
              vm[name] = e.target.value;
            });
            node.value = vm[name]; // 将data的值赋给该node
            node.removeAttribute('v-model');
          }
        };
        new Watcher(vm, node, name, 'input');
      }
      // 节点类型为text
      if (node.nodeType === 3) {
        if (reg.test(node.nodeValue)) {
          var name = RegExp.$1; // 获取匹配到的字符串
          name = name.trim();
          new Watcher(vm, node, name, 'text');
        }
      }
    }
    function Watcher (vm, node, name, nodeType) {
      Dep.target = this;
      this.name = name;
      this.node = node;
      this.vm = vm;
      this.nodeType = nodeType;
      this.update();
      Dep.target = null;
    }
    Watcher.prototype = {
      update: function () {
        this.get();
        if (this.nodeType == 'text') {
          this.node.nodeValue = this.value;
        }
        if (this.nodeType == 'input') {
          this.node.value = this.value;
        }
      },
      // 获取data中的属性值
      get: function () {
        this.value = this.vm[this.name]; // 触发相应属性的get
      }
    }
    function Dep () {
      this.subs = []
    }
    Dep.prototype = {
      addSub: function(sub) {
        this.subs.push(sub);
      },
      notify: function() {
        this.subs.forEach(function(sub) {
          sub.update();
        });
      }
    };
    function Vue (options) {
      this.data = options.data;
      var data = this.data;
      observe(data, this);
      var id = options.el;
      var dom = nodeToFragment(document.getElementById(id), this);
      // 编译完成后,将dom返回到app中
      document.getElementById(id).appendChild(dom); 
    }
    var vm = new Vue({
      el: 'app',
      data: {
        text: 'hello world'
      }
    });
  </script>

</body>
</html>
算法
// 1.冒泡 O(n2)
// <1>.比较相邻的元素。如果第一个比第二个大,就交换它们两个;
// <2>.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
// <3>.针对所有的元素重复以上的步骤,除了最后一个;
// <4>.重复步骤1~3,直到排序完成。
var arr1 = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]
function bubbleSort (arr) {
    var len = arr.length
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {
                var t = arr[j+1]
                arr[j+1] = arr[j]
                arr[j] = t
            }
        }
        console.log(arr)
    }
    return arr
}
console.log(bubbleSort(arr1))

// 2.快排 O(nlogn)
// <1>.从数列中挑出一个元素,称为 “基准”(pivot);
// <2>.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
// <3>.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
var arr2 = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]
function quickSort (arr) {
    console.time('2.快速排序耗时')
    if (arr.length <= 1) return arr
    var num = arr.splice(0, 1)
    // console.log(num)
    var left = []
    var right = []
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] < num) {
            left.push(arr[i])
        } else {
            right.push(arr[i])
        }
    }
    // console.log(left)
    // console.log(right)
    console.timeEnd('2.快速排序耗时')
    return quickSort(left).concat(num, quickSort(right))
}
console.log(quickSort(arr2))

// 3.选择排序 O(n2)
// <1>.初始状态:无序区为R[1..n],有序区为空;
// <2>.第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
// <3>.n-1趟结束,数组有序化了。
// 每次循环选择最小的数,将其放到前面
var arr3 = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]
function selectionSort (arr) {
    var minIndex // 当前循环最小索引
    for (var i = 0; i < arr.length - 1; i++) { // arr.length - 1 最后一个就是最大的
        minIndex = i // 默认当前最小索引为外层循环的第一个
        for (var j = i + 1; j < arr.length; j++) { // 内循环比外循环索引多1
            if( arr[j] < arr[minIndex] ){ // 内循环的没一个与当前外循环对比
                // 保存当前最小索引为 j
                minIndex = j
                // console.log(minIndex)
            }
        }
        // 交换当前最小值
        var t = arr[i]
        arr[i] = arr[minIndex]
        arr[minIndex] = t
        // console.log(arr[i])
        // console.log(arr[minIndex])
    }
    return arr
}
console.log(selectionSort(arr3))


// 4.插入排序 O(n2)
// <1>.从第一个元素开始,该元素可以认为已经被排序;
// <2>.取出下一个元素,在已经排序的元素序列中从后向前扫描;
// <3>.如果该元素(已排序)大于新元素,将该元素移到下一位置;
// <4>.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
// <5>.将新元素插入到该位置后;
// <6>.重复步骤2~5。
var arr4 = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]
function insertionSort (arr) {
    if (arr.length <= 1) return arr
    for (var i = 0; i < arr.length; i++) {
        var key = arr[i]
        var j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
    return arr
}
console.log(insertionSort(arr4))




// 5.归并排序 O(nlogn)
// <1>.把长度为n的输入序列分成两个长度为n/2的子序列;
// <2>.对这两个子序列分别采用归并排序;
// <3>.将两个排序好的子序列合并成一个最终的排序序列。
var arr5 = [3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]
function mergeSort (arr) {
    var len = arr.length
    if(len <= 1) return arr
    var middle = Math.floor(len / 2) // 中间值
    var left = arr.slice(0, middle) // 
    var right = arr.slice(middle)
    return merge(mergeSort(left), mergeSort(right))
}
function merge (left, right) {
    var result = [];
    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }
    while (left.length)
        result.push(left.shift());
    while (right.length)
        result.push(right.shift());
    return result;
}
console.log(mergeSort(arr))

/*  */
// 数组去重1
var arr6 = [3,2,3,5,47,15,3,26,4,2,46,4,19,5,48]
var removeReapt = new Set(arr6)
console.log([...removeReapt])

// 数组去重2
var arr7 = [3,2,3,5,47,15,3,26,4,2,46,4,19,5,48]
function rReapt (arr) {
    var array = []
    for (var i = 0; i < arr.length; i++) {
        if (array.indexOf(arr[i]) == -1) {
            array.push(arr[i])
        }
        
    }
    return array
}
console.log(rReapt(arr7))


// 数组去重3
var arr8 = [3,2,3,5,47,15,3,26,4,2,46,4,19,5,48]
function rReapt2 (arr) {
    var obj = {}
    var array = []
    for (var i = 0; i < arr.length; i++) {
        if (!arr[i] in obj) {
            obj[arr[i]] = true
        }
    }
    for (var x in obj){
        array.push(x)
    }
    return array
}
console.log(rReapt(arr7))


参考:
移动端兼容:https://blog.csdn.net/hardgirls/article/details/51722519
js继承:https://www.cnblogs.com/humin/p/4556820.html
vue原理:https://www.jianshu.com/p/c2fa36835d77
算法:https://www.cnblogs.com/beli/p/6297741.html

推荐阅读更多精彩内容

  • 1.一些开放性题目 1.自我介绍:除了基本个人信息以外,面试官更想听的是你与众不同的地方和你的优势。 2.项目介绍...
    55lover阅读 404评论 0 6
  • 在线阅读 http://interview.poetries.top[http://interview.poetr...
    前端进阶之旅阅读 96,732评论 23 438
  • 面试题一:https://github.com/jimuyouyou/node-interview-questio...
    R_X阅读 1,253评论 0 5
  • 【转载】CSDN - 张林blog http://blog.csdn.net/XIAOZHUXMEN/articl...
    竿牍阅读 2,871评论 1 14
  • D17~3月11日 今天读《圣经》创世记,读到50 :19 约瑟对他们说:“不要害怕,我岂能代替 神呢?”这...
    林林Amy阅读 120评论 0 0