春招面试题集

React和Vue对比

相同点:

  1. 数据驱动视图,提供了响应式(Reactive)和组件化(Composable)的视图组件。
  2. 都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponents规范
  3. 数据流动单向
  4. 都支持服务端渲染
  5. 都有支持native的方案,React的React native,Vue的weex

不同点:

  1. 社区:React社区还是要比Vue大很多;

  2. 开发模式:React在view层侵入性还是要比Vue大很多的,React严格上只针对MVC的view层,Vue则是MVVM模式的一种实现;

  3. 数据绑定:Vue有实现了双向数据绑定,React需要自己实现

  4. 数据渲染:对于大规模数据渲染,React要比Vue更快,Vue渲染机制启动时候要做的工作比较多;

  5. 数据更新方面:Vue 由于采用依赖追踪,默认就是优化状态:你动了多少数据,就触发多少更新,不多也不少。React在复杂的应用里有两个选择:

    (1). 手动添加 shouldComponentUpdate 来避免不需要的 vdom re-render。

    (2). Components 尽可能都用 pureRenderMixin,然后采用 redux 结构 + Immutable.js;

  6. 开发风格的偏好:React 推荐的做法是 JSX + inline style,也就是把 HTML 和 CSS 全都写进 JavaScript 了,即”all in js”;Vue进阶之后推荐的是使用 webpack + vue-loader 的单文件组件格式,即html,css,js写在同一个文件;

  7. 使用场景:React配合Redux架构适合超大规模多人协作的复杂项目;Vue则适合小快灵的项目。对于需要对 DOM 进行很多自定义操作的项目,Vue 的灵活性优于 React;

  8. Vue要比React更好上手,具体可能体现在很多人不熟悉React的JSX语法和函数式编程的思想,以及想要发挥出React的最大威力需要学习它一系列生态的缘故;

  9. Vue着重提高开发效率,让前端程序员更快速方便的开发应用。React着重于变革开发思想,提升前端程序员编程的深度与创造力,让前端工程师成为真正的程序员而不是UI的构建者;

gulp和webpack区别

  1. gulp是一种自动化构建工具,我们可以用它来优化前端的工作流程,比如自动刷新页面、combo、压缩css、js、编译less等等。具体体现为:在gulp的配置文件中书写一个个的task,webpack则是一种打包工具,或者说是一种模块化解决方案,实际上很大一部分人刚开始使用webpack的方式就是通过gulp-webpack这个插件,写好task来使用webpack对前端的一些文件进行打包;
  2. gulp的处理任务需要自己去写,webpack则有现成的解决方案,只需要在webpack.config.js配置好即可;

防止重复发送Ajax请求

  1. 用户点击之后按钮disabled;
  2. 函数节流
  3. abort掉上一个请求。

事件模型

  • 事件捕获阶段(capturing phase)。事件从document一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
  • 事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
  • 事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。

浏览器缓存机制

  1. Expires策略

Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。

  1. Cache-Control策略

Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓读取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires

以上是设置缓存时间的两种方法。那么当缓存时间过了咋整呢?有人肯定说了,那就再次发起请求啊,这是对的。问题是如果服务器资源并没有更新呢?比如说我有一个jQuery.js文件已经缓存了,当它的缓存时间到了之后服务器的jQuery.js文件也没有更新,那实际上我们直接使用本地缓存的文件就可以啊!没必要浪费带宽和时间去重新请求一个新的文件啊!这时候我们就需要再进一步看一下HTTP协议里这几个参数的作用了。

  1. Last-Modified/If-Modified-Since

首先Last-Modified/If-Modified-Since要配合Cache-Control使用。

  • Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间(这个参数是和Cache-Control一起过来的)。
  • If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since ,则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。
  1. ETag/If-None-Match

Etag/If-None-Match也要配合Cache-Control使用。

  • Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器觉得)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
  • If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match(Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
  1. ETag和Last-Modified

HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
  • 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存
  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

Ajax的状态值与HTTP状态码

  • Ajax的状态值

    0: (未初始化)还没有调用open()方法。
    1: (载入)已经调用open()方法,正在派发请求,send()方法还未被调用.。
    2: (载入完成)send()已经调用,响应头和响应状态已经返回.。
    3: (交互)响应体下载中; responseText中已经获取了部分数据.。
    4: (完成)响应内容已经解析完成,用户可以调用。

  • HTTP状态码

    200 & OK: 请求成功;

    204 & No Content: 请求处理成功,但没有资源可以返回;

    206 & Partial Content: 对资源某一部分进行请求(比如对于只加载了一般的图片剩余部分的请求);

    301 & Move Permanently: 永久性重定向;

    302 & Found: 临时性重定向;

    303 & See Other: 请求资源存在另一个URI,应使用get方法请求;

    304 & Not Modified: 服务器判断本地缓存未更新,可以直接使用本地的缓存;

    307 & Temporary Redirect: 临时重定向;

    400 & Bad Request: 请求报文存在语法错误;

    401 & Unauthorized: 请求需要通过HTTP认证;

    403 & Forbidden: 请求资源被服务器拒绝,访问权限的问题;

    404 & Not Found: 服务器上没有请求的资源;

    500 & Internal Server Error: 服务器执行请求时出现错误;

    502 & Bad Gateway: 错误的网关;

    503 & Service Unavailable: 服务器超载或正在维护,无法处理请求;

    504 & Gateway timeout: 网关超时;

React-router原理

1.History

  • 老浏览器的history: 主要通过hash来实现,对应createHashHistory
  • 高版本浏览器: 通过html5里面的history,对应createBrowserHistory
  • node环境下: 主要存储在memeory里面,对应createMemoryHistory

内部createHistory实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 内部的抽象实现
function createHistory(options={}) {
...
return {
listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
listen, // location发生改变时触发回调
transitionTo, // 执行location的改变
push, // 改变location
replace,
go,
goBack,
goForward,
createKey, // 创建location的key,用于唯一标示该location,是随机生成的
createPath,
createHref,
createLocation, // 创建location
}
}

createLocation方法:

1
2
3
4
5
6
7
8
9
10
function createLocation() {
return {
pathname, // url的基本路径
search, // 查询字段
hash, // url中的hash值
state, // url对应的state字段
action, // 分为push、replace、pop三种
key // 生成方法为: Math.random().toString(36).substr(2, length)
}
}

三种方法各自执行URL前进的方式:

  • createBrowserHistory: pushState、replaceState
  • createHashHistory: location.hash=*** location.replace()
  • createMemoryHistory: 在内存中进行历史记录的存储

伪代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// createBrowserHistory(HTML5)中的前进实现
function finishTransition(location) {
...
const historyState = { key };
...
if (location.action === 'PUSH') ) {
window.history.pushState(historyState, null, path);
} else {
window.history.replaceState(historyState, null, path)
}
}
// createHashHistory的内部实现
function finishTransition(location) {
...
if (location.action === 'PUSH') ) {
window.location.hash = path;
} else {
window.location.replace(
window.location.pathname + window.location.search + '#' + path
);
}
}
// createMemoryHistory的内部实现
entries = [];
function finishTransition(location) {
...
switch (location.action) {
case 'PUSH':
entries.push(location);
break;
case 'REPLACE':
entries[current] = location;
break;
}
}
  1. React-router的基本原理

URL对应Location对象,而UI是由react的 components来决定的,这样就转变成locationcomponents之间的同步问题。

什么是原型链

​ 访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。

​ 在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空——执行上下文环境。

​ 作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

每一个对象都会在内部链接到另一个对象(该对象的原型对象),该对象有一个原型prototype,当访问对象的属性或是方法的时候,不仅仅会在原对象上查找,还会顺着原型链在原型对象的原型链上查找,直到查到null(所有原型链的顶层)为止。原型是JavaScript实现继承的基础,new关键字做的主要的事情就是将实例对象的__proto__属性指向原型对象的prototype。

什么是闭包

  • 闭包是javascript支持头等函数的一种方式,它是一个能够引用其内部作用域变量(在本作用域第一次声明的变量)的表达式,这个表达式可以赋值给某个变量,可以作为参数传递给函数,也可以作为一个函数返回值返回。

  • 闭包是函数开始执行的时候被分配的一个栈帧,在函数执行结束返回后仍不会被释放(就好像一个栈帧被分配在堆里而不是栈里!)

  • 闭包的应用:

    • 比如写柯里化函数的时候利用闭包,保存参数在内存中;
1
2
3
4
5
6
7
8
9
var currying = function(fun) {
//格式化arguments
var args = Array.prototype.slice.call(arguments, 1);
return function() {
//收集所有的参数在同一个数组中,进行计算
var _args = args.concat(Array.prototype.slice.call(arguments));
return fun.apply(null, _args);
};
}
  • 模拟私有变量或是私有方法;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const people = (num) => {
var num = num;
return {
increase: () => {
num++;
},
get: () => {
return num;
}
}
}
const man = people(4);
man.increase();
man.get();
  • 避免引用错误
1
2
3
4
5
6
7
for (var i = 0; i < 4; i++) {
(function(_i) {
setTimeout(function() {
console.log(_i)
}, 1000)
})(i)
}

图片懒加载与预加载

  • 图片懒加载的原理就是暂时不设置图片的src属性,而是将图片的url隐藏起来,比如先写在data-src里面,等某些事件触发的时候(比如滚动到底部,点击加载图片)再将图片真实的url放进src属性里面,从而实现图片的延迟加载

  • 图片预加载,是指在一些需要展示大量图片的网站,实现图片的提前加载。从而提升用户体验。常用的方式有两种,一种是隐藏在css的background的url属性里面,一种是通过javascript的Image对象设置实例对象的src属性实现图片的预加载。相关代码如下:

    1. CSS预加载图片方式:
    1
    2
    3
    #preload-01 { background: url(http://damonare.cn/image-01.png) no-repeat -9999px -9999px; }  
    #preload-02 { background: url(http://damonare.cn/image-02.png) no-repeat -9999px -9999px; }
    #preload-03 { background: url(http://damonare.cn/image-03.png) no-repeat -9999px -9999px; }
    1. Javascript预加载图片的方式:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function preloadImg(url) {
    var img = new Image();
    img.src = url;
    if(img.complete) {
    //接下来可以使用图片了
    //do something here
    } else {
    img.onload = function() {
    //接下来可以使用图片了
    //do something here
    };
    }
    }

类型数组

JavaScript 是一种提供了访问原始二进制数据机制的类似数组的对象。

为了最大的灵活性和效率(mdn这么说的,不明觉厉),JavaScript类型化数组提供了两部分来实现,一种是缓冲(ArrayBuffer),一种是视图(TypedArray和DataView)。

所谓的缓冲其实是一种数据类型,表示一个通用的、固定长度的二进制数据缓冲区。该对象没法直接访问具体的数据,仅仅提供’缓冲’作用,要访问具体的数据,必须通过视图;

所谓的视图分为两种:TypedArrayDataView

TypedArray

共包括9种类型的视图,比如Uint8Array(无符号8位整数)数组视图, Int16Array(16位整数)数组视图, Float32Array(32位浮点数)数组视图等等。

数据类型字节长度含义对应的C语言类型
Int818位带符号整数signed char
Uint818位不带符号整数unsigned char
Uint8C18位不带符号整数(自动过滤溢出)unsigned char
Int16216位带符号整数short
Uint16216位不带符号整数unsigned short
Int32432位带符号整数int
Uint32432位不带符号的整数unsigned int
Float32432位浮点数float
Float64864位浮点数double

DataView

DataView提供可以操作缓冲区中任意数据的读写接口。这对操作不同类型数据的场景很有帮助。而不再像TypedArray一样局限于某一种类型数据。

应用

  • FileReader API(readAsArrayBuffer方法)
  • XMlHttpRequest的send方法(支持类型数组作为参数)
  • Canvas

说明:类型数组并不是支持所有的原生数组的API(比如push和pop就不可用,因为ArrayBuffer给定了字节数,TypedArray视图自然无法调用)。

跨域

跨域的方式有很多种,最常用的是jsonp主要利用了script的开放策略:通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用。缺点在于只支持get请求而且存在安全问题,可能会导致CSRF,因为请求的数据来源于其他网站,因为恶意攻击者可以利用这段代码进行请求,获取数据,有可能会泄露用户密码等重要信息。

CORS跨域,关键在于服务器,如果服务器实现了CORS跨域的接口,那么就可以使用ajax(请求路径为绝对路径)进行跨域请求。CORS请求分为两种,一种是简单请求,一种是非简单请求。简单请求是指请求方法在HEAD,GET,POST三者之间并且请求头信息局限在

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

非简单请求请求头:

(1)Access-Control-Request-Method

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

(2)Access-Control-Request-Headers

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

执行简单请求的时候,浏览器会在请求头信息增加origin字段,服务器据此来判断请求域名是否在许可范围之内,来决定是否返回Access-Control-Allow-Origin字段。响应头有以下几种:

(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里面指定。

(4)Access-Control-Max-Age

Access-Control-Max-Age 首部字段指明了预检请求的响应的有效时间。

(5)Access-Control-Allow-Methods

Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

(6)Access-Control-Allow-Headers

Access-Control-Allow-Headers首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

其他方法:document.domin(IE6,7配合iframe;IE6,7发送POST跨域请求),html5的postMessage,window.name

函数节流和函数防抖

函数节流让指函数有规律的进行调用,应用场景:window.resize,游戏中子弹发射(1s只能发射一颗子弹)等;

函数防抖让函数在”调用’’之后的一段时间后生效,应用场景:输入框(例:在用户停止输入的500ms后再处理用户数据)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//函数节流
/*
* @params {Function} fun 调用函数
* @params {Number} delay 延迟时间
*/
const throttle = (fun, delay, ...rest) => {
let last = null;
return () => {
const now = + new Date();
if (now - last > delay) {
fun(rest);
last = now;
}
}
}
//实例
const throttleExample = throttle(() => console.log(1), 1000);
//调用
throttleExample();
throttleExample();
throttleExample();
//函数防抖
const debouce = (fun, delay, ...rest) => {
let timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
fun(rest);
}, delay);
}
}
//实例
const debouceExample = debouce(() => console.log(1), 1000);
//调用
debouceExample();
debouceExample();
debouceExample();

百度实习遇到的问题和解决方案

  1. sortAPI的问题,场景:后端返回的某一段数据经过前端sort方法排序后,顺序乱掉了。深入研究得知:sort方法底层算法是这样的:22以内是插入排序,22以上是快速排序。该场景下基准选择的是第一个数据,导致第一次快排结束后数据的顺序乱掉了。解决方法:直接提取,因为后端返回的数据是正确的。
  2. react-router,问题:onEnter钩子函数的问题,传入第三个参数callback,官方文档说callback得异步执行,但必须在异步调用一次后在主线程再调用一次才会生效,看源码未解。最后放弃了传入callback改为同步执行解决。给react-router提了issue,未得到回应。

快速排序

  1. 从数列中挑出一个元素,称为”基准”(pivot),
  2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition) 操作。
  3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

时间复杂度平均情况:O(n\log n) 最快:O(n^{2}) 空间复杂度: O(\log n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var quickSort = function(arr) {
console.time('2.快速排序耗时');
  if (arr.length <= 1) { return arr; }
  var pivot = arr.splice(0, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
console.timeEnd('2.快速排序耗时');
  return quickSort(left).concat([pivot], quickSort(right));
};

var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(quickSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

模块加载器加载原理

  • id即路径原则
    通常我们的入口是这样的: require( [ ‘a’, ‘b’ ], callback ) 。这里的 ‘a’、’b’ 都是 ModuleId。通过 id 和路径的对应原则,加载器才能知道需要加载的 js 的路径。在这个例子里,就是 baseUrl + ‘a.js’ 和 baseUrl + ‘b.js’。但 id 和 path 的对应关系并不是永远那么简单,比如在 AMD 规范里就可以通过配置 Paths 来给特定的 id 指配 path。

  • createElement(‘script’) & appendChild
    知道路径之后,就需要去请求。一般是通过 createElement(‘script’) & appendChild 去请求。这个大家都知道,不多说。有时候有的加载器也会通过 AJAX 去请求脚本内容。

  • document.currentScript
    a.js 里可能是 define( id, factory ) 或者是 define( factory ),后者被称为匿名模块。那么当 define(factory) 被执行的时候,我们怎么知道当前被定义的是哪个模块呢,具体地说,这个匿名模块的实际模块 id 是什么? 答案是通过 document.currentScript 获取当前执行的

文章目录
  1. 1. React和Vue对比
  2. 2. gulp和webpack区别
  3. 3. 防止重复发送Ajax请求
  4. 4. 事件模型
  5. 5. 浏览器缓存机制
  6. 6. Ajax的状态值与HTTP状态码
  7. 7. React-router原理
  8. 8. 什么是原型链
  9. 9. 什么是闭包
  10. 10. 图片懒加载与预加载
  11. 11. 类型数组
  12. 12. 跨域
  13. 13. 函数节流和函数防抖
  14. 14. 百度实习遇到的问题和解决方案
  15. 15. 快速排序
  16. 16. 模块加载器加载原理
|