blog icon indicating copy to clipboard operation
blog copied to clipboard

前端面试常见问题汇总

Open ly2011 opened this issue 7 years ago • 0 comments

前端面试常见问题汇总

CSS

1. @mixin 中的 @include@extend 的区别(sass)

列举不同的清除浮动的技巧

/*  1. 添加新元素  */
<div class="outer">
  <div class="div1"></div>
  <div class="div2"></div>
  <div class="div3"></div>
  <div class="clearfix"></div>
</div>

.clearfix {
  clear: both;
}

/* 2. 为父元素增加样式 */
.clearfix {
  overflow: auto;
  zoom: 1; // 处理兼容性
}

/* 3. :after 伪元素方法(作用于父元素) */
.outer {
  zoom: 1;
  &:after {
    visibility: hidden;
    display: block;
    font-size: 0;
    content: " ";
    clear: both;
    height: 0;
  }
}

CSS 实现移动端 1px 线条的绘制

  • 先放大,再缩小

方法1:

.box:before {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        right: 0;
        height: 1px;
        content: '';
        -webkit-transform: scaleY(.5);
        transform: scaleY(.5);
        background-color: #e2e2e2;
    }

方法2:

.box:before{
    content: "";
    pointer-events: none; /* 防止点击触发 */
    box-sizing: border-box;
    position: absolute;
    width: 200%;
    height: 200%;
    left: 0;
    top: 0;
    border:1px solid #000;
    -webkit-transform(scale(0.5));
    -webkit-transform-origin: 0 0;
    transform(scale(0.5));
    transform-origin: 0 0;
}

缺点:

  1. 占据了该元素的伪元素,代码量相对较多
  2. input元素没有伪元素

2. 用 css 实现垂直居中(子元素宽高不确定?)(兼容ie8?)

  • 方案一:使用css3的新属性transform:translate(x,y)属性

.eight {
    position: absolute;
    width: 80px;
    height: 80px;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    -webkit-transform: translate(-50%, -50%);
    -moz-transform: translate(-50%, -50%);
    -ms-transform: translate(-50%, -50%);
    background: green;
}

这个方法可以不需要设定固定的宽高,在移动端用的会比较多,在移动端css3兼容的比较好

3. 如果设计中使用了非标准的字体,你该如何去实现?

  • 图片替代
  • web: fonts在线字库
  • @font-face

4. 行内元素有哪些?块级元素有哪些?空元素有哪些?

  • 行内元素有:span、img、input、select、strong
  • 块级元素有:div、ul、ol、dl、dt、dd、h1、h2、h3、h4、p...
  • 常见的空元素有:br、hr、img、input、link、meta、、base、area、command、embed、keygen、param、source、track、wbr...

5. 两个div进行两列布局,要求高度不定(父元素也是),我需要两个div实时等高,即左边div高度被其内子元素撑高时,右边的div高度和左边同步【等高布局

  • 父元素 display: table; 左边定宽 width: 200px; display: table-cell;右边 width: 100%;
  • 父元素 display: flex; align-item: stretch; 左边定宽 width: 200px; 右边设置项伸展属性: flex-grow: 1;

6. 按照下图,说说flexbox的原理

JavaScript

1、编写一个函数将列表子元素顺序反转

2、请实现一个Event类,继承自此类的对象都会拥有两个方法on,off,once和trigger

3、如何创建一个ajax请求(https://zhuanlan.zhihu.com/p/27776535)

创建Ajax的过程: 1)创建XMLHttpRequest对象(异步调用对象)

var xhr = new XMLHttpRequest();

2)创建新的Http请求(方法、URL、是否异步)

xhr.open('get', 'example.php', false);

3)设置响应HTTP请求状态变化的函数 onreadystatechange事件中readyState属性等于4。响应的HTTP状态为 200(OK)或者304(Not Modified)。 4)发送http请求

xhr.send(data);

5)获取异步调用返回的数据

详细过程:

const xhr = new XMLHttpRequest();
xhr.open(method, url, async);

// send 方法发送请求,并接受一个可选参数
// 当请求方式为 post 时,可以将请求体的参数传入
// 当请求方式为 get 时,可以不传或传入 null
// 不管是 get 还是 post,参数都需要通过 encodeURIComponent 编码后拼接
xhr.send(data);
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    // HTTP 状态在 200 -300 之间表示请求成功
    // HTTP 状态为 304 表示内容未发生改变,可直接从缓存中获取
    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
      console.log('请求成功', xhr.responseText);
    }
  }
}

// 超时时间单位为毫秒
xhr.timeout = 1000

// 当请求超时时,会触发 ontimeout 方法
xhr.ontimeout = () => console.log('请求超时')

4. javascript 连等赋值问题


var a = { n: 1 };
var b = a;
a.x = a = { n: 2 }
console.log(a.x); // undefined
console.log(b.x); // {n:2}

解析: 解析器在接收到 a = a.x = { n: 2 } 这个语句后,会这样子做:

  1. 在找到 a 和 a.x 的指针。如果已有指针,那么不改变它。如果没有指针, 即那个变量还没被申明,那么就创建它,指向 null。 a 是有指针的,指向 {n: 1}; a.x 是没有指针的,所以创建它,指向 null。

  2. 然后把上面找到的指针,都指向最右赋值的那个指,即是 {n: 2}

5. JS数组深浅复制

浅拷贝:

  • slice 实现
var arr = ['old', '1' , true, null, undefined];
var new_arr = arr.slice();
new_arr[0] = 'new';
console.log(arr);
console.log(new_arr);
  • concat 实现
var arr = ['old', '1' , true, null, undefined];
var new_arr = arr.concat();
new_arr[0] = 'new';
console.log(arr);
console.log(new_arr);
注释:如果数组元素是基本类型,就会拷贝一份新的;但是如果数组元素是对象或者数组,就只会拷贝引用(类似指针),修改其中一个就会影响另外一个。例如:
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];
var new_arr = arr.concat();
new_arr[0] = 'new';
new_arr[3][0] = 'new1';
console.log(arr) // ["old", 1, true, ['new1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]

深拷贝

  • JSON.stringify 实现数组深拷贝
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];
var new_arr = JSON.parse(JSON.stringify(arr));
new_arr[0] = 'new';
new_arr[3][0] = 'new1';
console.log(arr) // ["old", 1, true, ['old1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]

缺点:简单粗暴,但是不能拷贝函数,不推荐。

手动实现深浅拷贝

  • 浅拷贝
var shallowCopy =  function (obj) {
  // 判断是否是数组或者对象
  if (typeof obj !== 'object') {
        return obj;
  }
  var newObj = obj instanceof Array ? [] : {};
  for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key]
        }
  }
  return newObj;
}
  • 深拷贝
var deepCopy = function (obj) {
    // 判断是否是数组或者对象
    if (typeof obj !== 'object') {
        return obj;
    }
    var newObj =  obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

实现一个深度复制的deepCopy 函数

6. 实现dom的增删改查复制

// 创建节点
createDocumentFragment()
createElement()
createTextNode()

// 添加 移除 替换 插入
appendChild()
removeChild()
replaceChild()
insertBefore()

// 查找
document.getElementsByTagName()
document.getElementsByName()
document.getElementsByClassName()
document.getElementById()
document.querySelector()
document.querySelectorAll()

7. 谈谈您对 MVC、MVVM 的理解

MVC

- 视图(View):用户界面。
- 控制器(Controller):业务逻辑。
- 模型(Model):数据保存。

各部分之间的通讯方式如下:

default

1、View 传送指令到 Controller
2、Controller 完成业务逻辑后,要求 Model 改变状态
3、Model 将新的数据发送到 View,用户得到反馈

特点:

所有通讯都是单向的。

MVP

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

default

1、各部分之间的通讯,都是双向的。
2、View 与 Model 不发生联系,都通过 Presenter 传递。
3、View 非常薄,不部署任何业务逻辑,被称为 “被动视图”(Passive View),即没有任何主动性,而 Presenter 非常厚,所有逻辑都部署在那里。

MVVM

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

default

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反应在 ViewModel,反之亦然。

8. vue 的生命周期钩子函数

    1. beforeCreate
    1. created
    1. beforeMount
    1. mounted
    1. beforeUpdate
    1. updated
    1. actived
    1. deactivated
    1. beoforeDestory
    1. destoryed

9. 使用正则实现电话号码和邮箱的验证

10. 使用正则实现trim函数删除字符串前后空格

function trim(str) {
  return str.replace(/(^\s*)|(\s*$)/g, '');
}

11. BOM对象,DOM对象

BOM对象:

  • window对象
  • location对象
  • navigator对象
  • screen对象
  • history对象

DOM对象

  • 常用的DOM方法:

    • getElementById(id)
    • getElementsByTagName()
    • appendChild(node)
    • removeChild(node)
    • replaceChild()
    • insertChild()
    • createELement()
    • createTextNode()
    • getAttribute()
    • setAttribute()
  • 常用的DOM属性:

    • innerHTML 节点(元素)的文本值
    • parentNode 节点(元素)的父节点
    • childNodes
    • attributes 节点(元素)的属性节点

参考: HTML DOM 方法

12. 任务队列优先级(Promise、setTimout、setImmediate)

下面代码输出结果?为什么?

demo1:

setTimeout(() => console.log('a'), 0);
var p = new Promise((resolve) => {
  console.log('b');
  resolve();
});
p.then(() => console.log('c'));
p.then(() => console.log('d'));
console.log('e');
// 结果:b e c d a
// 任务队列优先级:promise.Trick()>promise的回调>setTimeout>setImmediate

demo2:


async function async1() {
    console.log("a");
    await  async2(); //执行这一句后,await会让出当前线程,将后面的代码加到任务队列中,然后继续执行函数后面的同步代码
    console.log("b");

}
async function async2() {
   console.log( 'c');
}
console.log("d");
setTimeout(function () {
    console.log("e");
},0);
async1();
new Promise(function (resolve) {
    console.log("f");
    resolve();
}).then(function () {
    console.log("g");
});
console.log('h');
// 谁知道为啥结果不一样?????????????
// 直接在控制台中运行结果:      d a c f h g b e
// 在页面的script标签中运行结果:d a c f h b g e

13. js中事件代理:

  <ul id="ui-test">
    <li>item1</li>
    <li>item2</li>
    <li>item3</li>
    <li>item4</li>
    <li>item5</li>
    <li>item6</li>
    <li>item7</li>
    <li>item8</li>
    <li>item9</li>
    <li>item10</li>
  </ul>

  <script>
  document.addEventListener('DOMContentLoaded', function(event) {
  var oUL = document.querySelector('#ui-test')
  // var oLI = document.querySelectorAll('#ui-test > li')
  // for (var i = 0, len = oLI.length; i < len; i++) {
  //   console.log(oLI[i]);
  //   oLI[i].addEventListener('click', function() {
  //     console.log(this.innerHTML)
  //   })
  // }

  // 代理模式
  oUL.addEventListener('click', function(ev) {
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if (target.nodeName.toLowerCase() === 'li') {
      console.log(target);
      console.log(target.innerHTML)
    }
  })
});
  </script>

14. js实现数组去重怎样实现?

  • 1、创建一个临时数组来保存数组中已有的元素
  • 2、使用哈希表存储已有的元素
  • 3、使用 indexOf 判断数组元素第一次出现的位置是否为当前位置
  • 4、先排序再去重
  • 5、使用 Set

找出数组中的最大值

  • reduce
var arr = [6, 4, 1, 8, 2, 11, 3];
function max (prev, next) {
    return Math.max(prev, next)
}
console.log(arr.reduce(max));
  • apply
var arr = [6, 4, 1, 8, 2, 11, 3];
console.log(Math.max.apply(null, arr)); // 11

  • ES6
var arr = [6, 4, 1, 8, 2, 11, 3];
function max (arr) {
    return Math.max(...arr);
}
console.log(max(arr));

打乱数组的方法

var arr = [];
for(var i = 0; i < 100; i++) {
    arr[i] = i;
}
arr.sort(function() {
    return 0.5 - Math.random();
})
console.log(arr)

数组扁平化

var arr = [1, [2, [3, 4]]]
function flatten(arr) {
    while(arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }

    return arr;
}

console.log(flatten(arr)); // [1, 2, 3, 4]

简单的字符串模板

var TemplateEngine = function(tpl, data) {
    var re = /<%([^%>]+)?%>/g,
        match;
    while(match = re.exec(tpl)) {
        console.log('match0: ', match[0]);
        console.log('match1: ', match[1]);
        tpl = tpl.replace(match[0], data[match[1]])
    }
    return tpl;
}

var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.<p>';
console.log(TemplateEngine(template, {
    name: 'ly2011',
    age: 16
}))

函数函数柯里化

函数柯里化:在一个函数中首先填充几个参数(然后再返回一个新函数)的技术称为柯里化(Currying)。

  • 通用的柯里化方法
// 通用的函数柯里化构造方法
function curry(fn) {
  // 新建 args 保存参数,注意,第一个参数应该是要科利华的函数,所以 args 里面去掉第一个
  var args = [].slice.call(arguments, 1);

/*  // 新建 _fn 函数作为返回值
  var _fn = function() {
    // 参数长度为 0, 执行 fn 函数,完成该函数的功能
    if(arguments.length === 0) {
      return fn.apply(this, args)
    } else {
      // 否则,存储参数到闭包中,返回本函数
      [].push.apply(this, arguments);
      return _fn;
    }
  }

  return _fn;*/
  return function() {
    var newArgs = args.concat([].slice.call(arguments))
    return fn.apply(null, newArgs)
  }
}
function add() {
  return [].reduce.call(arguments, function(a, b) {
    return a + b;
  } )
}

function multiply() {
  return [].reduce.call(arguments, function(a, b) {
    return a * b;
  })
}
console.log(curry(add, 1, 2, 3, 4, 5)(1)) // 16
console.log(curry(multiply, 1, 2, 3, 4)(5)) // 120
  • 简单的用法
function multiply(x, y) {
  if (y === undefined) {
    return function(z) {
      return x * z
    }
  } else {
    return x * y
  }
}

console.log(multiply(3, 4)); // 12
console.log(multiply(3)(4)); // 12

柯里化的作用

  • 延迟计算
  • 参数复用
  • 动态创建函数

多个标签页之间通信(如何实现浏览器中多个标签页之间的通信?)

websocket协议、localstorage、以及使用 html5浏览器的新特性 SharedWorker。

请描述一下 cookies, sessionStorage 和 localStorage 的区别?

  • cookie 是网站为了标示用户身份而储存在用户本地终端(Client Slide) 上的数据(通常经过加密)
  • cookie 数据始终在同源的 http 请求中携带(即使不需要),既会在浏览器和服务器间来回传递
  • sessionStoragelocalStorage 不会自动把数据发给服务器,仅在本地保存

存储大小:

  • cookie 数据大小不能超过 4k
  • sessionStoragelocalStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或 更大

有效时间:

  • localStorage 存储持久数据,浏览器关闭后数据不丢失除非浏览器主动删除数据。
  • sessionStorage 数据在当前浏览器窗口关闭后自动删除
  • cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭

js实现循环 setTimeout输出 0, 1, 2, 3, 4

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

结果为: 5,5,5,5,5

解决办法

  • 第一种:闭包
for(var i = 0; i < 5; i++) {
  (function(j) { // j = i
    setTimeout(function() {
      console.log(j)
    }, 1000)
  })(i)
}
  • 第二种:值类型传递
var output = function(i) {
  setTimeout(function() {
    console.log(i)
  }, 1000)
}

for(var i = 0; i < 5; i++) {
  output(i) // 这里传过去的 i 被复制了
}
  • 第三种:es6 let
for(let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i)
  }, 1000);
}

如果要让0-4一秒一秒地输出来呢?

  • 第一种:
for(var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j)
    }, 1000 * j ) // 这里修改 0-4 的定时器时间
  })(i)
}

第二种:

const tasks= []
const output = (i) => new Promise((resolve) => {
  setTimeout(() => {
    console.log(i)
    resolve() // 这里一定要 resolve, 否则代码不会按预期 work
  }, 1000 * i)
})
// 生成全部的异步操作
for(var i = 0; i < 5; i++) {
  tasks.push(output(i))
}
// 异步操作完成之后,输出最后的i
Promise.all(tasks).then(() => {
  setTimeout(() => {
    console.log(i)
  }, 1000)
})
  • 第三种
// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (timeountMS) => new Promise((resolve) => {
    setTimeout(resolve, timeountMS);
});

(async () => {  // 声明即执行的 async 函数表达式
    for (var i = 0; i < 5; i++) {
        await sleep(1000);
        console.log(new Date, i);
    }

    await sleep(1000);
    console.log(new Date, i);
})();

15. 谈谈对this的理解

1)this总是指向函数的直接调用者(而非间接调用者) 2)如果有new关键字,this指向new出来的那个对象 3)在事件中,this指向目标元素,特殊的是IE的attachEvent中的this总是指向全局对象window。

16. eval是做什么的?

它的功能是把对应的字符串解析成JS代码并运行;应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。

17. ['1', '2', '3'].map(parseInt)答案是多少?

[1, NaN, NaN]

解析:

  1. Array.prototype.map() array.map(callback[, thisArg]) callback函数的执行规则 参数:自动传入三个参数 currentValue(当前被传递的元素) index(当前被传递的元素的索引) array(调用map方法的数组)

  2. parseInt parseInt方法接收两个参数 第三个参数['1', '2', '3']将被忽略。parseInt方法将会通过以下方式被调用 parseInt('1', 0) parseInt('2', 1) parseInt('3', 2)

parseInt的第二个参数radix为0时,ECMAScript5将string作为十进制数字的字符串解析。 parseInt的第二个参数radix为1时,解析结果为NaN; parseInt的第二个参数radix在2-36之间时,如果string参数的第一个字符(除空白以外),不属于radix指定进制下的字符,解析结果为NaN。 parseInt('3', 2)执行时,由于 '3' 不属于二进制字符,解析结果为 NaN。

18. 什么是闭包(closure),为什么要用它?

闭包指的是一个函数可以访问另一个函数作用域中变量。常见的构造方法,是在一个函数内部定义另外一个函数。内部函数可以引用外层的变量;外层变量不会被垃圾回收机制回收。 注意:闭包的原理是作用域链,所以闭包访问的上级作用域中的变量是个对象,其值为其运算结束后的左后一个值。 优点:避免全局变量污染。 缺点:容易造成内存泄漏。

例子:

function makeFunc() {
  var name = 'Mozilla'
  function displayName() {
    console.log(name)
  }
  return displayName
}
var myFunc = makeFunc()
myFunc() // 输出Mozilla

myFunc变成一个闭包。闭包是一种特殊的对象。它由两部分构成: 函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。在我们的例子中,myFunc是一个闭包,由displayName函数和闭包创建时存在的 'Mozilla' 字符串组成。

19. JS延迟加载的方式有哪些?

defer和async、动态创建DOM方式、按需异步载入JS defer: 延迟脚本。立即下载,但延迟执行(延迟到整个页面都解析完毕后再运行),按照脚本出现的先后顺序执行。 async: 异步脚本。下载完立即执行,但不保证按照脚本出现的先后顺序执行。

20. 什么是跨域问题,如何解决跨域问题?

21. 页面编码和被请求的资源编码不一致如何处理?

若请求的资源编码,如外引js文件编码与页面编码不同。可根据外引资源编码方式定义为 charset="utf-8"或"gbk"。 比如:http://www.yyy.com/a.html 中嵌入了一个http://www.xxx.com/test.js a.html 的编码是gbk或gb2312的。 而引入的js编码为utf-8的 ,那就需要在引入的时候

22. 渐进增强与优雅降级

渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进,达到更好的用户体验。 优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

23. 在一个DOM上同时绑定两个点击事件:一个用捕获,一个用冒泡。事件会执行几次,先执行冒泡还是捕获?

  • 该DOM上的事件如果被触发,会执行两次(执行次数等于绑定次数)
  • 如果该DOM是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
  • 如果该DOM是处于事件流中的非目标元素,则先执行捕获,后执行冒泡

24. 事件的代理/委托

  • 事件委托是指将事件绑定目标元素到父元素上,利用冒泡机制触发该事件

优点:

  • 可以减少事件注册,节省大量内存占用
  • 可以将事件应用于动态添加的子元素上

缺点:

  • 使用不当会造成事件在不应该触发时触发

26. javascript如何实现继承?

27. 给出一个字符串 'abfecg', 如何快速反转?

28.给出一个不存在重复元素的数组 [...'item'...], 如何最快速插入一个新元素 'itemNew'?

29. 说说普通函数与箭头函数的区别?

30. 说说循环数组和对象,你用过哪些方法?

  • for

  • forEach

  • map

  • filter

  • some

对象循环: for ... in

  • every

31. 你对浏览器的兼容性有了解过吗?

32. 哪些操作会引起浏览器重绘(repaint)和重排(reflow), 延伸:重绘和重排谁更消耗性能?

  • postion:absolute; left:100px;会不会引起?
  • translateX:100px;会不会引起?
  • getBoundingClientRect会不会引起?
  • getClientWidth、getClientHeight会不会引起?

原理:

触发重排 页面布局和元素几何属性的改变就会导致重排 下列情况会发生重排

  • 页面初始渲染
  • 添加/删除可见DOM元素
  • 改变元素位置
  • 改变元素尺寸(宽、高、内外边距、边框等)
  • 改变元素内容(文本或图片等)
  • 改变窗口尺寸

以下属性或方法会刷新渲染队列

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • getComputedStyle()(IE中currentStyle)

减少重绘和重排的原理很简单:

  • 元素脱离文档
  • 改变样式
  • 元素回归文档

如何在浏览器中查看页面渲染时间

HTTP

1、从浏览器地址栏输入url到显示页面的步骤(以HTTP为例)

主要过程是: 浏览器解析->查询缓存->dns查询->建立链接->服务器处理请求->服务器发送响应->客户端收到页面->解析HTML->构建渲染树->开始显示内容(白屏时间)->首屏内容加载完成(首屏时间)->用户可交户(DOMContentLoaded)->加载完成(load)

  1. 在浏览器地址栏输入URL
  2. 浏览器查看 缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
    1. 如果资源未缓存,发起新请求
    2. 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
    3. 检验新鲜通常有两个HTTP头进行控制 ExpiresCache-Control
      • HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期
      • HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
  3. 浏览器解析URL获取协议,主机,端口,path
  4. 浏览器组装一个HTTP(GET)请求报文
  5. 浏览器获取主机ip地址,过程如下:
    1. 浏览器缓存
    2. 本机缓存
    3. hosts文件
    4. 路由器缓存
    5. ISP DNS缓存
    6. DNS递归查询(可能存在负载均衡导致每次IP不一样)
  6. 打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:
    1. 客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口
    2. 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
    3. 客户端发送ACK=Y+1, Seq=Z
  7. TCP链接建立后发送HTTP请求
  8. 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序
  9. 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
  10. 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
  11. 服务器将响应报文通过TCP连接发送回浏览器
  12. 浏览器接收HTTP响应,然后根据情况选择 关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下
    1. 主动方发送Fin=1, Ack=Z, Seq= X报文
    2. 被动方发送ACK=X+1, Seq=Z报文
    3. 被动方发送Fin=1, ACK=X, Seq=Y报文
    4. 主动方发送ACK=Y, Seq=X报文
  13. 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
  14. 如果资源可缓存,进行缓存
  15. 对响应进行解码(例如gzip压缩)
  16. 根据资源类型决定如何处理(假设资源为HTML文档)
  17. 解析HTML文档,构建DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
  18. 构建DOM树
    1. Tokenizing:根据HTML规范将字符流解析为标记
    2. Lexing:词法分析将标记转换为对象并定义属性和规则
    3. DOM construction:根据HTML标记关系将对象组成DOM树
  19. 解析过程中遇到图片、样式表、js文件,启动下载
  20. 构建CSSOM树
    1. Tokenizing:字符流转换为标记流
    2. Node:根据标记创建节点
    3. CSSOM:节点创建CSSOM树
  21. 根据DOM树和CSSOM树构建渲染树:
    1. 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none
    2. 对每一个可见节点,找到恰当的CSSOM规则并应用
    3. 发布可视节点的内容和计算样式
  22. js解析如下
    1. 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
    2. HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容
    3. 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素
    4. 当文档完成解析,document.readState变成interactive
    5. 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
    6. 浏览器在Document对象上触发DOMContentLoaded事件
    7. 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
  23. 显示页面(HTML解析过程中会逐步显示页面)

安全

综合

1、如何进行网站性能优化

  • content 方面
  1. 减少 HTTP 请求:合并文件、CSS 精灵、inline Image
  2. 减少 DNS 查询: DNS 缓存、将资源分布到恰当数量的主机名
  3. 减少 DOM 元素数量
  • server 方面
  1. 使用 CDN
  2. 配置 ETag
  3. 对组件使用 Gzip 压缩
  • cookie 方面
  1. 减少 Cookie 大小
  • css 方面
  1. 将样式表放到页面顶部
  2. 不使用 CSS 表达式
  3. 使用 不使用 @import
  • javascript 方面
  1. 将脚本放到页面底部
  2. 将 javascript 和 css 从外部引入
  3. 压缩 javascript和css
  4. 删除不需要的脚本
  5. 减少 DOM 访问
  • 图片方面
  1. 优化图片:根据实际颜色需要选择色深、压缩
  2. 优化 css 精灵
  3. 不要在 HTML 中拉伸图片

渲染

如何提高webpack的编译速度?

  • 版本升级
  • 利用多核(happypack)
  • 利用缓存(dll)
  • 开发环境去掉组件懒加载、不压缩图片、不分离css文件

算法

  • 冒泡排序
解析:
1. 比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2. 第一轮的时候最后一个元素应该是最大的一个。
3. 按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。
function sort(elements) {
  for(var i = 0; i < elements.length - 1; i++) {
    for(var j = 0; j < elements.length - i - 1; j++) {
      if (elements[j] > elements[j + 1]) {
        var swap = elements[j];
        elements[j] = elements[j + 1];
        elements[j + 1] = swap;
      }
    }
  }
}

var elements = [3, 1, 5, 7, 2, 4, 9, 6, 10, 8];
console.log('before: ', elements)
sort(elements)
console.log('after: ', elements)
  • 快速排序
解析:
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。
function quickSort(elements) {
  if (elements.length <= 1) {
    return elements;
  }
  var pivotIndex = Math.floor(elements.length / 2);
  var pivot = elements.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < elements.length; i++) {
    if (elements[i] < pivot) {
      left.push(elements[i])
    } else {
      right.push(elements[i])
    }
  }
  return quickSort(left).concat([pivot] , quickSort(right))
}

var elements = [5, 6, 2, 1, 3, 8, 7, 1.2, 5.5, 4.5]
console.log(quickSort(elements))
  • 查找字符串中出现次数最多的字符和次数

例如:sdddrtkjsfkasjdddj中出现最多的字符是d,出现了6次

var str = "sdddrtkjsfkkkasjdddj";
var max = 0;
var char;

function Search(str) {
   var json = {};
   for (var i = 0; i < str.length; i++) {
       if (!json[str[i]]) {
           json[str[i]] = str[i];
       } else {
           json[str[i]] += str[i];
       }
   }

   for (var i = 0; i < str.length; i++) {
       if (json[str[i]].length > max) {
           max = json[str[i]].length;
           char = str[i];
       }
   }
   console.log('json: ', json)
   console.log("出现次数最多的字符是" + char + ",出现了" + max + "次")
}
Search(str);
  • 计算数组中每个元素出现的次数
// 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
var countedNames = names.reduce(function(allNames, name) {
  if(name in allNames) {
    allNames[name]++
  } else {
    allNames[name] = 1
  }
  return allNames;
}, {})
console.log(countedNames)
  • for(var i=0,j=0;i<10,j<6;i++,j++) { sum = i+j }, sum值最后是多少?
var sum = 0;
for(var i=0,j=0;i<10,j<6;i++,j++) {
  sum = i+j
}
console.log(sum)

解析: 答案是: 10, 首先每次for循环的i和j值是相等的: 第一次:i=0, j =0; 符合条件, sum = i + j = 0; 第二次:i=1,j=1;符合条件, sum = i + j = 2; 第三次:i=2,j=2;符合条件, sum = i + j = 4; 第四次:i=3,j=3;符合条件,sum = i + j = 6; 第五次:i=4,j=4;符合条件, sum = i + j = 8; 第六次:i=5,j=5;符合条件,sum = i + j =10; 第七次:i=6,j=6;不符合条件((这里需要注意,循环继续的判断依据以分号前的最后一项为准,即判断j<6符不符合条件),循环结束。

注意: 这里值得一提的是 如果把条件i<10,j<6;改成i<6,j<10; 结果将完全不同,此时循环执行到j<10才会结束,此时sum=18。 参考地址

  • 说下你所知道的排序方法? 冒泡排序、快排、二分法、广度遍历、深度遍历...

情景题

如何限制用户在一定时间内(例如2~3s)重复点击按钮?

  • 利用防抖/节流(推荐)
  • 利用setTimeout定时器
  • rxjs

平常你是如何保证前端代码质量的?

参考文档

  1. 2018美团前端面试题
  2. 2017前端面试题及答案总结
  3. 【面试】2018前端常见问题整理
  4. js十大排序算法
  5. 前端面试之道
  6. 在酷家乐做面试官的日子

ly2011 avatar Apr 09 '18 13:04 ly2011