前端常见面试问题 part 1

下面的大部分问题来自Github的这个仓库,排名不分先后

1. JS中如何定义自定义事件,实现订阅/发布模式

明确需求:可以通过onemit绑定和触发事件。

方案:创建全局事件管理器events,构建事件名和回调函数数组的键值对。onemit分别写和读events。大概像下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var EventUtil = {
// 全局事件管理
var events = {},
// 注册事件
on = function (type, handler) {
if (events[type]) {
events[type].push(handler);
} else {
events[type] = [handler];
}
},
// 触发事件
emit = function (type) {
if (!events[type]) return;
for (var i = 0, len = events[type].length; i < len; i++) {
events[type][i];
}
};
};

当使用Object.assign实现继承时,会出现events共享的问题。可以通过在第一次调用on时,通过Object.defineProperty的方式创建避免共享。

2. js中的this

首先,this永远是对象。

  • 全局上下文内,this全局对象
  • 函数上下文内,根据调用场景分情况讨论
    • 直接调用:全局对象
    • 通过对象的方法调用:调用方法的对象
    • 构造函数中:即将被创建的对象,有return语句时,以return的返回值为准
    • call和apply:传入的第一个值
    • bind方法:永久绑定到第一个参数

3. js跨域问题和解决方案

跨域(Cross-domain)是网景最初基于安全性考虑提出的策略。意为不同域名不同协议不同端口间的Ajax通信是被禁止的。根据使用需求,可以分为跨站请求资源和跨页面共享资源(我自己发明的说法)

跨站请求资源

  • jsonp(json with padding)跨域,利用了<script>标签的可跨域完成,自己写一遍就能搞懂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function getJSONP(url, success) {
    var ud = '_' + +new Date,
    script = document.createElement('script'),
    head = document.getElementsByTagName('head')[0]
    || document.documentElement;
    window[ud] = function(data) {
    head.removeChild(script);
    success && success(data);
    };
    script.src = url.replace('callback=?', 'callback=' + ud);
    head.appendChild(script);
    }
  • CORS,使用CORS进行跨域请求时,在现代浏览器中已经可以像普通Ajax请求那样使用XMLHttpRequest即可,可以参考这个。需要后台服务器支持

  • 后台反向代理,需要一台中转的服务器
  • 建立websocket通信,需要后台服务器支持

跨页面共享资源,结合<iframe>有以下几种方案

  • 修改document.domain,使两个页面位于同一域名下,注意只能从精确->模糊修改域名
  • 通过window.name传递消息,利用了iframe location变化后,window.name不变的特点
  • location.hash
  • html5中的postMessage API在不同window间传递消息

这里附上一个链接.

4. js的作用域链

这是JavaScript最有特点同时也是最基础的内涵之一。红宝书和犀牛书都做了详尽和透彻的解释。这个问题理解了,什么是闭包就能很好地理解了。

执行环境是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们便习得代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

全局执行环境是最外围的一个执行环境。…。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局环境知道应用程序退出时才会销毁)
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript程序中的执行流正是有这个方便的机制控制着。

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,时钟都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象做为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而在下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中最后一个对象。

5. Function.callFunction.apply的区别

callapply同为改变this的方法,前者一个一个接收输入参数,后者以数组的形式接收。

6. 浏览器渲染页面的原理

可以参考经典的文章how browsers work,或者中译版.

7. 列举一些HTML5的改进

可以参考MDN给出的summary。比如:

  • 语义化,语义化标签<header>, <article>,语义化元素<figure>, <time>等和新的多媒体标签<audio>, <video>
  • 网络通信,Websocket,WebRTC
  • 图像,Canvas和WebGL
  • 离线存储,Storage接口和IndexDB
  • 性能,Web Worker,XMLHttpRequest2(支持进度等新特性),History API,Fullscreen,PointerLock,requestAnimationFrame等
  • CSS,CSS3的特性,有些甚至演进到了Level 4

8. HTML5中的定位API

Geolocation API,新的API,红宝书中有提到。通过navigator.geolocation对象实现,允许用户提供自己的所在地理位置。需要用户确认同意才可使用。最常用的方法是getCurrentPosition()。这个方法接受三个参数——成功回调、可选的失败回调、可选的选项。

类似的不常见的API还有Battery APIFile APIperformance等。

9. 一些前端框架的双向绑定原理

不是所有框架都提倡双向绑定。有的框架如Angular使用数据双向绑定,适合于表单很多的站点,React和Vue这样的使用的是单向绑定。在单向绑定背景下,可以通过addEventListener实现双向绑定。

实现原理上分为几种:

  • 发布-订阅模式,显式地绑定事件和监听函数。backbone就是这么做的,显式地通过Model.set修改数据触发change事件来更新视图。React也是通过setState显式地触发虚拟DOM树更新和重新渲染的。
  • 脏检查(digest cycle),通过特定事件触发脏检查。脏检查即一种不关心你如何以及何时改变的数据,只关心在特定的检查阶段数据是否改变的数据监听技术。过程大致是$update或其他手段触发digest阶段,遍历通过$watch绑定的watcher。对比值是否改变触发更新。优点是无需知道更改数据的方式,可以统一更新view,缺点是watcher较多时会有严重的性能问题。
  • 数据劫持Object.defineProperty,Vue使用这种方式实现隐式的绑定(当然在具体实现中复杂了许多)。这么做的问题是版本只支持到IE9+,且在数组更新时有所局限。

10. webpack的配置文件写法

除了常用的entry, output, module, plugins外,webpack的使用方法实在太多,建议去官网查看完整的配置信息。

11. node文件和网络API

文件操作上,常用的有fs.readFileSyncfs.writeFileSync,或通过流的方式使用fs.createReadStreamfs.createWriteStream。还有pipe将流连接在一起。除此之外,pathjoinnormalize常用在处理文件路径。

和网络操作相关的包包括http, https, url, querystring, zlib等。其中前两个包更为常用,尤其是http.createServer方法。

另外,在进程上有process, child_process等包。这里 有一篇文章做了比较详细的介绍。当然,有空最好还是去官方文档.

12. 和@import的区别

它们的最常见的使用方式都是引入CSS文件到html中。它们的区别在于

  • link是XHTML标签,除了加载CSS外,还可以引入RSS等其他资源;@import属于CSS范畴,只能加载CSS。
  • link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
  • link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
  • 由于link是标签,可以通过JavaScript控制来改变样式,后者不行。

13. cookie,localStorage和sessionStorage的区别和联系

cookie设计的初衷是用来为无状态的http访问添加用户状态消息的。大小4KB以下,会携带在请求头中。大多包含敏感信息,会和服务器端的session配合使用。

Storage API是HTML5的新API。又可以细分为localStorage和sessionStorage。它们一般只存储在客户端,用来缓存用户非敏感数据,大小因浏览器而异,容量约可达到5MB。sessionStorage在浏览器关闭后清除,localStorage则在超过时限或手动clear后清除。

cookie中的内容很少变化,且最好秘文储存,并通过HttpOnly添加限制(后台修改set-cookie头)。Storage则很可能会频繁读写。

14. HTTP状态码

根据状态码开头的数字确定状态码类型。下面列举一些常用的。

1xx 信息:这一类型的状态码,代表请求已被接受,需要继续处理。这类响应是临时响应。

  • 100 继续:客户端应当继续发送请求。
  • 101 切换协议:将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。

2xx 成功:这一类型的状态码,代表请求已成功被服务器接收、理解、并接受。

  • 200 OK:请求已成功,请求所希望的响应头或数据体将随此响应返回
  • 201 已创建:请求已经被实现,而且有一个新的资源已经依据请求的需要而创建,且其URI已经随Location头信息返回
  • 202 已接受:服务器已接受请求,但尚未处理
  • 204 No Content:服务器成功处理了请求,但不需要返回任何实体内容,用户浏览器应保留发送了该请求的页面
  • 205 Reset Content:和204的唯一不同是返回此状态码的响应要求请求者重置文档视图
  • 206 服务器已经成功处理了部分GET请求。该请求必须包含Range头信息来指示客户端希望得到的内容范围,多用于下载工具

3xx 重定向:这类状态码代表需要客户端采取进一步的操作才能完成请求。通常,这些状态码用来重定向,后续的请求地址(重定向目标)在本次响应的Location域中指明。

  • 300 多选择:被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息。用户或浏览器能够自行选择一个首选的地址进行重定向。
  • 301 永久移动:被请求的资源已永久移动到新位置
  • 302 临时移动:请求的资源现在临时从不同的URI响应请求
  • 303 重定向:对应当前请求的响应可以在另一个URI上被找到,而且客户端应当采用GET的方式访问那个资源
  • 304 如果客户端发送了一个带条件的GET请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变
  • 305 使用中介:被请求的资源必须通过指定的代理才能被访问

4xx 客户端错误:代表了客户端看起来可能发生了错误,妨碍了服务器的处理

  • 400 无法理解的请求:由于包含语法错误,当前请求无法被服务器理解
  • 401 需要验证:当前请求需要用户验证。响应必须包含一个适用于被请求资源的WWW-Authenticate信息头用以询问用户信息。
  • 403 禁止访问:服务器已经理解请求,但是拒绝执行它
  • 404 未找到:请求所希望得到的资源未被在服务器上发现
  • 405 方法不允许:请求行中指定的请求方法不能被用于请求相应的资源,响应中必须返回一个Allow头信息用以表示出当前资源能够接受的请求方法的列表
  • 406 头部不对:请求的资源的内容特性无法满足请求头中的条件
  • 408 请求超时:客户端没有在服务器预备等待的时间内完成一个请求的发送
  • 411 需要指定长度:服务器拒绝在没有定义Content-Length头的情况下接受请求
  • 413 请求实体太长
  • 414 URI太长

5xx 服务器错误:代表了服务器在处理请求的过程中有错误或者异常状态发生

  • 500 内部错误:一般来说,这个问题会在服务器的代码出错时出现
  • 501 未实现:服务器不支持当前请求所需要的某个功能
  • 502 Bad GateWay:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应
  • 503 服务不可达:由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。
  • 504 网关超时:作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器或者辅助服务器收到响应
  • 505 HTTP协议版本不正确

15. URL去参数

location.search, location.href, location.origin分别代表url中的querystring,完整url和域名。再结合location.pathname, location.portlocation.protocol可以得到任意想要的URL参数。

另外,新的APIURLSearchParams中有些方法可以对querystring做方便的增删改查的操作。

  • append增加一个检索参数
  • delete删除一个检索参数
  • get获取检索参数的第一个值
  • getAll获取检索参数的所有值
  • has检查是否存在某检索参数
  • set设置一个检索参数的新值,会覆盖原值
  • keysvalues分别返回键和值组成的数组

16. js中的正则匹配

js中的正则匹配和Perl的正则匹配规则基本类似。在js中,使用一个正则表达式字面量,由包含在斜杠之间的模式组成。正则表达式同时也是RegExp对象。除了简单模式外,考察对正则表达式的熟悉在它的特殊字符使用上。

一些常见的特殊字符:

  • \ 用于转义
  • ^ 用于匹配开始或表示一个反向字符集(如[^xyz]
  • $ 用于匹配结尾
  • * 匹配前一个表达式0或多次 = {0,}
  • + 匹配前一个表达式1或多次 = {1,}
  • ? 匹配0或1次 = {0,1};紧跟量词后使匹配非贪婪
  • . 匹配除换行符外任何单字符
  • (x) 捕获匹配,会包括在最后结果中,也可以通过$1, $n来访问
  • (?:x) 非捕获分组,匹配但不捕获
  • x(?=y) 断言匹配,捕获后跟y的x
  • x|y 匹配x或y
  • {n} 量词,匹配n次,还有{n,m}和{n,}的用法
  • [xyz] 字符集,可以使用-连接,如[x-z]
  • \d 一个数字
  • \D 一个非数字
  • \s 一个空白字符,包含空格,制表符,分页符,换行符
  • \S 一个非空白字符
  • \w 一个单字字符,等价于[A-Za-z0-9_]
  • \W 一个非单字字符

另外,正则表达式还有几个可选参数辅助搜索类型

  • g 全局搜索
  • i 不区分大小写
  • m 多行搜索
  • y 粘性搜索

有一些方法用于和正则表达式相关

  • exec 在字符串中执行匹配,返回匹配结果
  • test 测试是否能匹配RegExp,返回true或false
  • match 对字符串执行查找匹配的String方法,返回匹配结果
  • search 在字符串中测试匹配,返回位置索引或-1
  • replace 在字符串中执行查找匹配,并使用替换字符串替换匹配的子字符串
  • split 使用一个正则表达式或字符串分割一个字符串,并储存在数组中

常见的考法有,书写一个邮箱或手机号的正则表达式:

  • 邮箱 /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  • 手机号 /^0*(13|15|18|14|17)(\d{9}|\d-\d{3}-\d{5}|\d-\d{4}-\d{4}|\d{2}-\d{3}-\d{4}|\d{2}-\d{4}-\d{3})$/

17. MVC和MVVM框架

框架模式不是一门写代码的学问,而是一门管理与组织代码的学问。其本质是一种软件开发的模型。与设计模式不同,设计模式是在解决某类特定问题时总结抽象出的公共方法,是方法论的范畴,一种框架模式往往使用了多种设计模式,且和技术栈有耦合的关系。

视图(View)从本质上讲是数据在图像上的一种体现和映射。用户在操作图像时可以达到操作数据的目的,在数据更改后,需要重新将数据映射到视图上。这实际上就是MVC的出发点。

  • View: 放置视图相关的代码,原则上里面不应该有任何业务逻辑。
  • Controller: 放置视图与模型之间的映射,原则上这里应该很薄,他只放一些事件绑定相关的代码(router),但并不实现真正的功能,他只是一个桥梁。
  • Model: 这里的model不是说实体类,它是主要实现业务逻辑的地方。

开发流程是先创建视图组件,再将之关联到Model上,通过View修改Model中的值时,Model会触发绑定在之上的所有View的更新。Backbone是个典型的例子。这么做部分分离了视图和逻辑。但是,在情况复杂时,Model的代码量将会大大膨胀。

MVP因此而生,其中Presenter(分发器)代替了原来的Controller,分担了Model的部分功能。针对上面的问题,Presetner隔断了Model和View,当M改变时,会通知P去更新视图。业务逻辑和绑定逻辑从V和M中分离出来到P中。使得MVP三方分工更加鲜明。绝大多数的PHP框架都是MVP类型的。

MVVM是Model-View-ViewModel的缩写。在MVVM中,View和ViewModel是双向或单向数据绑定的关系。当ViewModel反应了Model中的数据模型,并绑定到视图属性上,反过来,视图属性变化后也会通过ViewModel影响Model。React,Vue这些流行的前端框架都是MVVM类型的。

不管是MVC还是MVP或MVVM,他们都是数据驱动的。核心上基于M推送消息,V或P来订阅这个模型。使用者需要维护的不再是UI树,而是抽象的数据。当UI的状态一旦多起来,这种框架模式的优势就很明显了。