前端常见面试问题 part 1
下面的大部分问题来自Github的这个仓库,排名不分先后
1. JS中如何定义自定义事件,实现订阅/发布模式
明确需求:可以通过on
和emit
绑定和触发事件。
方案:创建全局事件管理器events
,构建事件名和回调函数数组的键值对。on
和emit
分别写和读events
。大概像下面这样。
1 | var EventUtil = { |
当使用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
15function 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.call
和Function.apply
的区别
call
和apply
同为改变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 API,File API,performance等。
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.readFileSync
,fs.writeFileSync
,或通过流的方式使用fs.createReadStream
,fs.createWriteStream
。还有pipe
将流连接在一起。除此之外,path
,join
和normalize
常用在处理文件路径。
和网络操作相关的包包括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.port
和location.protocol
可以得到任意想要的URL参数。
另外,新的APIURLSearchParams中有些方法可以对querystring做方便的增删改查的操作。
append
增加一个检索参数delete
删除一个检索参数get
获取检索参数的第一个值getAll
获取检索参数的所有值has
检查是否存在某检索参数set
设置一个检索参数的新值,会覆盖原值keys
和values
分别返回键和值组成的数组
16. js中的正则匹配
js中的正则匹配和Perl的正则匹配规则基本类似。在js中,使用一个正则表达式字面量,由包含在斜杠之间的模式组成。正则表达式同时也是RegExp对象。除了简单模式外,考察对正则表达式的熟悉在它的特殊字符使用上。
一些常见的特殊字符:
\
用于转义^
用于匹配开始或表示一个反向字符集(如[^xyz]
)$
用于匹配结尾*
匹配前一个表达式0或多次 ={0,}
+
匹配前一个表达式1或多次 ={1,}
?
匹配0或1次 ={0,1}
;紧跟量词后使匹配非贪婪.
匹配除换行符外任何单字符(x)
捕获匹配,会包括在最后结果中,也可以通过$1, $n来访问(?:x)
非捕获分组,匹配但不捕获x(?=y)
断言匹配,捕获后跟y的xx|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或falsematch
对字符串执行查找匹配的String方法,返回匹配结果search
在字符串中测试匹配,返回位置索引或-1replace
在字符串中执行查找匹配,并使用替换字符串替换匹配的子字符串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的状态一旦多起来,这种框架模式的优势就很明显了。