前端面试梳理(一)

2,680 阅读1小时+

备战秋招,复习基础。如有错误,欢迎批评指正,共同进步!

写在最前

整理自自己的面试经验+网络资料

部分资料参考自网络,在具体内容前均有标出~感恩大家~

由于篇幅原因,拆成两篇~前端面试梳理(二)

电话面试

实话实说!保持自信!要有逻辑!

  • 自我介绍 3分钟作用,讲清重点经历。

    完结话术:...以上就是我的个人介绍。

  • 简历相关 要熟!会检查真实性!

  • 专业技术

    是否具备专业技能

    对工作的看法

    职业规划

    员工稳定性

  • 对公司的了解 尤其是对应岗位的业务。提前准备,列出可能的问题。

  • 兴趣爱好 尽量与工作相关

  • 反问建议 下一步招聘流程

  • 感谢HR!!!

  • 话术:……回到您刚说的问题上

计算机基础

常见概念

JSON: 一种JS对象表示法的数据格式。可以表简单值(不支持Undefined)、对象(无序键值对)、数组(有序值列表)。不支持变量、函数或对象实例。

MIME:多用途互联网邮件扩展类型。

Fiber:一种编程思想。任务拆分和协同。当进程阻塞时,任务分割、异步调用、缓存策略

DN:域名,因特网上某一计算机组的名称。如 baidu.com

DNS:域名系统,将域名和IP一一映射

IP:互联网协议地址,是用户上网的数字标签

http:超文本传输协议

href:指定超链接目标的URL

URL:统一资源定位符。即网址http://www.baidu.com (服务器名 www)(网站名 www.baidu.com)

servlet:java提供的用于开发web服务器应用程序的一个组件。jsp是servlet的一种扩展。

cdn:内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。

restful:URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。

1. 用名词定义接口名,用请求类型区分操作。
2. 用HTTP Status Code传递Server的状态信息。

点击一个Url之后

1 DNS解析
2 http请求
3 TCP三次握手
4 数据传输
5 渲染文档

url中的#和?

参考资料:URL 链接中 井号#、问号?、连接符& 分别有什么作用?

参考资料:VUE.js中访问地址的url带有#的问题

1 ?+任意参数 实现不同参数值生成不同页面或返回不同结果 ?size=100
2 & 连接符,不同参数的间隔符,一般与问号结合使用。?size=100&time=20171120
3 # Hash 代表网页中的一个位置。其右面的字符,就是该位置的标识符。#print
    为网页位置指定标识符,有两个方法。一是使用锚点,比如<a name="print"></a>,二是使用id属性,比如<div id="print" >
    HTTP请求不包括#。
    单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。
    每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。
    window.location.hash这个属性可读可写。读取时,可以用来判断网页状态是否改变;写入时,则会在不重载网页的前提下,创造一条访问历史记录。
    onhashchange事件,当#值发生变化时,就会触发这个事件。
4 vue中的#  VUE默认使用HASH模式
    HASH模式:
    HASH模式就是从访问地址动手脚,在访问地址的后面增加#并且带上需要的参数,这样后台就能对不同的参数显示不同的模块,而且 #以及后面的参数是不会被包含在HTTP请求中,因此对服务器的请求是没有影响的,更改参数也不会刷新页面。例子:网易云音乐的官网https://music.163.com/#
    history模式:
    history模式也是从访问地址动手脚,但是不再使用#,而是想普通的访问地址那样使用/,但如果这样请求的话,服务器是需要另外配置才行,要不然容易出现404错误,具体怎么配置请自行百度。

内存泄漏

1 意外的全局变量无法被回收
2 定时器未被正确关闭,导致使用的外部变量无法被释放
3 事件监听未正确被销毁
4 闭包,会导致父级中变量无法被释放
5 dom引用,dom元素被删除时,内存中的引用未被正确清空
可用chrome中的timeline来进行内存标记~

解决闭包的内存泄露

window.onload = function(){
    var el = document.getElementById("id");
    var id = el.id; //解除循环引用
    el.onclick = function(){
        alert(id); 
    }
    el = null; // 将闭包引用的外部函数中活动对象清除
}

Http1.0 1.1 2.0的区别

资料参考:HTTP1.0、HTTP1.1 和 HTTP2.0 的区别

资料参考:如何优雅的谈论HTTP/1.0/1.1/2.0

HTTP1.1是当前使用最为广泛的HTTP协议

  • HTTP1.0和HTTP1.1相比

    缓存处理:在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。

    带宽优化及网络连接的使用:HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

    错误通知的管理:在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。

    Host头处理:在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。

    长连接:HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。 通过设置http的请求头部和应答头部,保证本次数据请求结束之后,下一次请求仍可以重用这一通道,避免重新握手。

  • HTTP2.0和HTTP1.X相比

    新的二进制格式(Binary Format):HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。

    多路复用(MultiPlexing):即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。

    header压缩:如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用了专门为首部压缩而设计的 HPACK 算法,使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。

    服务端推送(server push):服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。

  • HTTPS与HTTP相比

    HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。

    HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。

    HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

    HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。

  • HTTS

    HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。

    握手过程的简单描述如下:

      1.浏览器将自己支持的一套加密规则发送给网站。
      2.网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
      3.获得网站证书之后浏览器要做以下工作:
          a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
          b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
          c) 使用约定好的HASH计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
      4.网站接收浏览器发来的数据之后要做以下的操作:
          a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
          b) 使用密码加密一段握手消息,发送给浏览器。
      5.浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。
    

    这里浏览器与网站互相发送加密的握手消息并验证,目的是为了保证双方都获得了一致的密码,并且可以正常的加密解密数据。其中非对称加密算法用于在握手过程中加密生成的密码,对称加密算法用于对真正传输的数据进行加密,而HASH算法用于验证数据的完整性。由于浏览器生成的密码是整个数据加密的关键,因此在传输的时候使用了非对称加密算法对其加密。非对称加密算法会生成公钥和私钥,公钥只能用于加密数据,因此可以随意传输,而网站的私钥用于对数据进行解密,所以网站都会非常小心的保管自己的私钥,防止泄漏。 TLS握手过程中如果有任何错误,都会使加密连接断开,从而阻止了隐私信息的传输。正是由于HTTPS非常的安全,攻击者无法从中找到下手的地方,于是更多的是采用了假证书的手法来欺骗客户端,从而获取明文的信息。

http请求

  • 请求报文
    • 请求行:请求方法字段、URL字段和HTTP协议版本 例如:GET /index.html HTTP/1.1
    • 请求头:key value形式,如User-Agent:产生请求的浏览器类型; Accept:客户端可识别的内容类型列表;Host:主机地址
    • 请求数据:post方法中,会把数据以key value形式发送请求
    • 空行:发送回车符和换行符,通知服务器以下不再有请求头
  • 响应报文
    • 状态行:http版本+状态码+状态代码的文本描述 例如:HTTP/1.1 200 ok
    • 消息报头:包含服务器类型,日期,长度,内容类型等
    • 响应正文:服务器返回的HTML页面或者json数据

HTTP缓存机制基原理

资料参考:彻底弄懂HTTP缓存机制及原理

web缓存三类:服务器端缓存、浏览器端缓存、数据库数据缓存

  • 强制缓存:在缓存数据未失效的情况下,可以直接使用缓存数据。浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,响应header中会有两个字段来标明失效规则(Expires/Cache-Control)。

  • 对比缓存:浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

资料参考:HTTP缓存控制小结

优先级从高到低分别是 Pragma -> Cache-Control -> Expires

1.Pragma:禁用缓存。 当该字段值为no-cache的时候(事实上现在RFC中也仅标明该可选值),会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。

2.Expires: 启用缓存和定义缓存时间。Expires的值对应一个GMT(格林尼治时间),比如Mon, 22 Jul 2002 11:12:01 GMT来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。

3.Cache-Control:定义缓存过期时间,实现缓存文件是否更新的验证、提升缓存的复用率

1. Last-Modified

    ⑴ If-Modified-Since: Last-Modified-value
    示例为 If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT
    该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可。
    当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。

    ⑵ If-Unmodified-Since: Last-Modified-value
    该值告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。 Last-Modified 存在一定问题,如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。
    
2. ETag:通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。

    ⑴ If-None-Match: ETag-value
    示例为 If-None-Match: "5d8c72a5edda8d6a:3239" 告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。

    ⑵ If-Match: ETag-value
    告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。
    需要注意的是,如果资源是走分布式服务器(比如CDN)存储的情况,需要这些服务器上计算ETag唯一值的算法保持一致,才不会导致明明同一个文件,在服务器A和服务器B上生成的ETag却不一样。

服务器再次请求流程

用户请求匹配规则(nginx相关)

资料参考:Nginx Location指令URI匹配规则详解

当nginx收到一个请求后,会截取请求的URI部份,去搜索所有location指令中定义的URI匹配模式。在server模块中可以定义多个location指令来匹配不同的url请求,多个不同location配置的URI匹配模式,总体的匹配原则是:先匹配普通字符串模式,再匹配正则模式。

只识别URI部份,例如请求为:/test/abc/user.do?name=xxxx 
  • Nginx匹配请求的流程:

    1. 先查找是否有=开头的精确匹配,如:location = /test/abc/user.do { … }

    2. 再查找普通匹配,以 最大前缀 为原则,如有以下两个location,则会匹配后一项

      • location /test/ { … }
      • location /test/abc { … }
    3. 匹配到一个普通格式后,搜索并未结束,而是暂存当前匹配的结果,并继续搜索正则匹配模式

    4. 所有正则匹配模式location中找到第一个匹配项后,就以此项为最终匹配结果。所以正则匹配项匹配规则,受定义的前后顺序影响,但普通匹配模式不会

    5. 如果未找到正则匹配项,则以3中缓存的结果为最终匹配结果

    6. 如果一个匹配都没搜索到,则返回404

负载均衡和容错方式

参考资料:服务器负载均衡的基本功能和实现原理

负责均衡服务器根据负载均衡算法来分发请求到不同的主服务器。

每个主服务器都是等价的,都可以完成相同的功能。

一般来说负载均衡设备都会默认支持多种负载均衡分发策略,例如:

轮询(RoundRobin)将请求顺序循环地发到每个服务器。当其中某个服务器发生故障,AX就把其从顺序循环队列中拿出,不参加下一次的轮询,直到其恢复正常。

比率(Ratio):给每个服务器分配一个加权值为比例,根椐这个比例,把用户的请求分配到每个服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。

优先权(Priority):给所有服务器分组,给每个组定义优先权,将用户的请求分配给优先级最高的服务器组(在同一组内,采用预先设定的轮询或比率算法,分配用户的请求);当最高优先级中所有服务器或者指定数量的服务器出现故障,AX将把请求送给次优先级的服务器组。这种方式,实际为用户提供一种热备份的方式。

最少连接数(LeastConnection):AX会记录当前每台服务器或者服务端口上的连接数,新的连接将传递给连接数最少的服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常

最快响应时间(Fast Reponse time):新的连接传递给那些响应最快的服务器。当其中某个服务器发生故障,AX就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。

哈希算法( hash):  将客户端的源地址,端口进行哈希运算,根据运算的结果转发给一台服务器进行处理,当其中某个服务器发生故障,就把其从服务器队列中拿出,不参加下一次的用户请求的分配,直到其恢复正常。

基于策略的负载均衡:针对不同的数据流设置导向规则,用户可自行编辑流量分配策略,利用这些策略对通过的数据流实施导向控制。

基于数据包的内容分发:例如判断HTTP的URL,如果URL中带有.jpg的扩展名,就把数据包转发到指定的服务器。

容错(fall-over):容错是负载均衡服务器里面的一个概念。是指当一台主服务器宕机后,集群能够继续提供服务的策略。比如说当主服务器A宕机后,负载均衡服务器要能发现主服务器A不能继续提供服务了,以前分发到主服务器A的请求要分发到其它主服务器。这种处理就是容错处理。

面向对象

参考资料:面向对象(一)|面向对象概念及优点

面向对象三大特性:封装、继承、多态。

面向对象的好处:

  1. 将对象进行分类,分别封装它们的数据和可以调用的方法,方便了函数、变量、数据的管理,方便方法的调用(减少重复参数等),尤其是在编写大型程序时更有帮助。
  2. 用面向对象的编程可以把变量当成对象进行操作,让编程思路更加清晰简洁,而且减少了很多冗余变量的出现

方法重写和重载

方法重写(overriding):

也叫子类的方法覆盖父类的方法,要求返回值、方法名和参数都相同。
子类抛出的异常不能超过父类相应方法抛出的异常。(子类异常不能超出父类异常)
子类方法的的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)。

方法重载(overloading):

重载是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,最常见的重载的例子就是类的构造函数。

java后端如何存取图片文件

资料参考:java后台接受到图片后保存方法

前端代码:

<form enctype="multipart/form-data" method="post" action="/testUploadimg"> 
    图片:<input type="file" name="file" /><br/> 
    <input type="submit" value="上传" />.
</form>

后端代码:

//调整页面请求

@Controllerpublic class UploadController {  
//跳转到上传文件的页面  
@RequestMapping(value = "/gouploadimg", method = RequestMethod.GET)  
    public String goUploadImg() {    
    //跳转到 templates 目录下的 uploadimg.html    
    return "uploadimg";  
}  
 
//处理文件上传  
@ResponseBody //返回json数据  
@RequestMapping(value = "/testUploadimg", method = RequestMethod.POST)  
 
//上传请求方法
public String uploadImg(@RequestParam("file") MultipartFile file, HttpServletRequest request) {    
    tring contentType = file.getContentType();    
    String fileName = file.getOriginalFilename();    
    String filePath = "D:/img";    
    if (file.isEmpty()) {      
        return "文件为空!";    
    }    
    try {      
        uploadFile(file.getBytes(), filePath, fileName);    
    } catch (Exception e) {      
        // TODO: handle exception    
    }    
    //返回json    
    return "上传成功";  
}  

//存储图片方法
public static void uploadFile(byte[] file, String filePath, String fileName) throws Exception {    
    File targetFile = new File(filePath);    
    if (!targetFile.exists()) {      
        targetFile.mkdirs();    
    }    
    FileOutputStream out = new FileOutputStream(filePath +"/"+ fileName);    
    out.write(file);    
    out.flush();    
    out.close();  
    }
}

如何鉴权

资料参考:前后端常见的几种鉴权方式

常用的鉴权有四种:

  1. HTTP Basic Authentication
  2. session-cookie
  3. Token 验证
  4. OAuth(开放授权)
  • HTTP Basic Authentication:HTTP服务器对客户端进行用户身份证的方法

    1. 客户端向服务器请求数据,请求的内容可能是一个网页或者是一个ajax异步请求,此时,假设客户端尚未被验证,则客户端提供如下请求至服务器:Get /index.html HTTP/1.0; Host:www.google.com
    2. 服务器向客户端发送验证请求代码401,(WWW-Authenticate: Basic realm=”google.com”这句话是关键,如果没有客户端不会弹出用户名和密码输入界面)
    3. 当符合http1.0或1.1规范的客户端(如IE,FIREFOX)收到401返回值时,将自动弹出一个登录窗口,要求用户输入用户名和密码。
    4. 用户输入用户名和密码后,将用户名及密码以BASE64加密方式加密,并将密文放入前一条请求信息中,则客户端发送的第一条请求信息则变成如下内容:Get /index.html HTTP/1.0; Host:www.google.com;Authorization: Basic d2FuZzp3YW5n → 用户名:密码 通过base64加密,是浏览器默认的行为,不需要人为加密
    5. 服务器收到上述请求信息后,将Authorization字段后的用户信息取出、解密,将解密后的用户名及密码与用户数据库进行比较验证,如用户名及密码正确,服务器则根据请求,将所请求资源发送给客户端

    缺陷:加密方式简单,是base64加密是可逆的。同时在每个请求的头上都会附带上用户名和密码信息,这样在外网是很容易被嗅探器探测到的。

  • session-cookie:利用服务器端的session(会话)和浏览器端的cookie来实现前后端的认证。在服务器端创建一个会话(seesion),将同一个客户端的请求都维护在各自的会话中,每当请求到达服务器端的时候,先去查一下该客户端有没有在服务器端创建seesion,如果有则已经认证成功了,否则就没有认证。

    1. 服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(我们可以将seesion保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串。
    2. 签名。这一步只是对sid进行加密处理,服务端会根据这个secret密钥进行解密。(非必需步骤)
    3. 浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求的请求头中会带上该域名下的cookie信息。
    4. 服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。
  • Token 验证:客户端在首次登陆以后,服务端再次接收http请求的时候,就只认token了,请求只要每次把token带上就行了,服务器端会拦截所有的请求,然后校验token的合法性,合法就放行,不合法就返回401(鉴权失败)

    1. 客户端使用用户名跟密码请求登录
    2. 服务端收到请求,去验证用户名与密码
    3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
    4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
    5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
    6. 服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据
  • OAuth(开放授权):允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。我们常见的提供OAuth认证服务的厂商有支付宝,QQ,微信。

    1. 向用户请求授权,现在很多的网站在登陆的时候都有第三方登陆的入口,当我们点击等第三方入口时,第三方授权服务会引导我们进入第三方登陆授权页面。
    2. 返回用户凭证(code),并返回一个凭证(code),当用户点击授权并登陆后,授权服务器将生成一个用户凭证(code)。这个用户凭证会附加在重定向的地址redirect_uri的后面
    3. 第三方应用后台通过第二步的凭证(code)向授权服务器请求Access Token
    4. 授权服务器同意授权后,返回一个资源访问的凭证(Access Token)
    5. 第三方应用通过第四步的凭证(Access Token)向资源服务器请求相关资源
    6. 资源服务器验证凭证(Access Token)通过后,将第三方应用请求的资源返回

cookie session localStorage sessionStorage

cookie:客户端保存状态的一种方案。
    服务器在Http响应的头中加入一行特殊的指令用以在客户端生成相应的cookie。
    保存在内存里,不超过4K
    cookie = new Cookie("username","aaa");
    cookie.setMaxAge(0);
    response.addCookie(cookie);
session:在服务端实现。
    检查客户端请求中的sessionID,检索或创建
    服务端会设置一个响应头Set-Cookie,返回给客户端,例如:Set-Cookie:SESSIONID=12345678;客户端接收到这个响应后,此后发送的每一个请求浏览器都会自动带上Cookie请求头,对应内容是Cookie:SESSIONID=12345678。在服务端内存中存有session,将客户端发送的请求中的cookie值与内存中的session进行对比,就可以识别这个客户端了。
    session有一个缺陷:如果web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候session会丢失。

! 重要信息放在session中,其他保留信息放在cookie中!

session token

token的意思是“令牌”,是用户身份的验证方式,最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。还可以把不变的参数也放进token,避免多次查库

session 和 oauth token并不矛盾,作为身份认证token安全性比session好,因为每个请求都有签名还能防止监听以及重放攻击,而session就必须靠链路层来保障通讯安全了。

Session 是一种HTTP存储机制,目的是为无状态的HTTP提供的持久机制。所谓Session 认证只是简单的把User 信息存储到Session 里,因为SID 的不可预测性,暂且认为是安全的。这是一种认证手段。 而Token ,如果指的是OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对App 。其目的是让 某App有权利访问 某用户 的信息。这里的 Token是唯一的。不可以转移到其它 App上,也不可以转到其它 用户 上。 转过来说Session 。Session只提供一种简单的认证,即有此 SID,即认为有此 User的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方App。 所以简单来说,如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。

token就是令牌,比如你授权(登录)一个程序时,他就是个依据,判断你是否已经授权该软件;cookie就是写在客户端的一个txt文件,里面包括你登录信息之类的,这样你下次在登录某个网站,就会自动调用cookie自动登录用户名;session和cookie差不多,只是session是写在服务器端的文件,也需要在客户端写入cookie文件,但是文件里是你的浏览器编号.Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。

localStorage设置过期时间

参考资料:localStorage设置过期时间实例

localStorage的数据如果不进行手动删除或者删除缓存的话,是永久保存的。

给一个过期的时间,放在第二个参数中。在调用数据的时候,判断当前时间是否大于设置的过期时间,如果是,就将数据删除,做一个相应提示或操作即可。

let setTime = new Date().getTime() + (1000 * 60);    // 测试,设置1分钟后数据过期
 
localStorage.setItem('test', JSON.stringify({
    data: 'data1',
    expiration: setTime
}));
 
 
let data = localStorage.test;
data = JSON.parse(data)
 
let time = data.expiration;
let value = data.data;
 
if(new Date().getTime() > time) {
    delete localStorage.test;
    // 这里开始执行超时的代码
}
else {
    // 这里开始执行未超时的代码
}

OSI七层网络模型

资料参考:一张非常强大的OSI七层模型图解

OSI

TCP/IP

资料参考:关于TCP/IP,必须知道的十个知识点

待补充!!

线程和进程

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。

在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)。

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

浏览器三线程:

js引擎线程:是基于事件驱动的单线程
GUI渲染线程:负责渲染浏览器界面,与Js互斥
浏览器事件触发线程:把待处理的任务放到任务队列中,等js主线程空闲再处理

攻击

XSS攻击:

着重:注入代码实现
表现:跨站脚本攻击,允许用户将代码植入,提供给其他用户使用的页面,如html代码和客户端脚本。
防范:设置httpOnly,禁止用document.cookie操作;输入检查:在用户输入的时候进行格式检查;对输出转义。

CSRF攻击:

着重:攻击效果
表现:跨站点请求伪造,欺骗用户的浏览器,发送http请求给目标站点。
防护:1 设置白名单;
      2 限制不被第三方网站请求;
      3 检查 Referer字段:这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下;
      4 添加校验 Token:这种数据通常是表单中的一个数据项。服务器生成token并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 Token 的值为空或者错误,拒绝这个可疑请求。
      5 通过输入验证码来校验合法请求。

sql注入攻击:

攻击方式:服务器上的数据库运行非法的 SQL语句,主要通过拼接字符串的形式来完成,改变sql语句本身的语义。通过sql语句实现无账号登陆,甚至篡改数据库。
防御:1 使用参数化查询:使用预编译语句,预先编译的SQL语句,并且传入适当参数多次执行。由于没有拼接的过程,因此可以防止 SQL注入的发生。使用preparedStatement的参数化sql,通过先确定语义,再传入参数,就不会因为传入的参数改变sql的语义。(通过setInt,setString,setBoolean传入参数)
      2 单引号转换:将传入的参数中的单引号转换为连续两个单引号,PHP 中的 Magic quote 可以完成这个功能。
      3 检查变量数据类型和格式。
      4 使用正则表达式过滤传入的参数,对特殊符号过滤或者转义处理。

扫描二维码登入PC的工作原理

1 pc端随机生成一个含有唯一uid的二维码,并与服务器建立一个长连接;
2 手机扫描二维码,解析出二维码中的uid,并把这个uid和手机端的用户密码进行绑定,上传给服务器;
3 服务器获得客户端信息之后,pc端的长连接轮询操作会获得该消息,显示该账号的信息;
4 pc端会再开一个长连接与手机端保持通信,等待手机端确认登陆后,获得服务器授权的token,就可以在pc端登陆进行正常通信了。

前端基础

ajax

概念

向服务器请求额外的数据而无需卸载页面

HTTP头部信息:服务器接收头部信息,决定后续操作。

header:服务器以HTTP协议传HTML资料到浏览器前所送出的字符串。`协议头字段名:内容`

get和post区别

参考资料:HTTP 方法:GET 对比 POST

  • 简单区别
比较 GET POST
后退按钮/刷新 无害 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。
书签 可收藏为书签 不可收藏为书签
缓存 能被缓存 不能缓存
编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
历史 参数保留在浏览器历史中。 参数不会保存在浏览器历史中。
对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 无限制。
对数据类型的限制 只允许 ASCII 字符。 没有限制。也允许二进制数据。
安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET ! POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。
  • 数据请求过程

    • GET请求(2次交互):用于向服务器查询某些信息

    浏览器请求tcp连接(第一次握手) 服务器答应进行tcp连接(第二次握手) 浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送) 服务器返回200 ok响应。

    • POST请求(3次交互):用于向服务器发送应该被保存的数据。post消耗的资源更多。传送的数据相同,get的速度最多是post的两倍。

    浏览器请求tcp连接(第一次握手) 服务器答应进行tcp连接(第二次握手) 浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送) 服务器返回100 continue响应 浏览器开始发送数据 服务器返回200 ok响应

FormData类型:序列化表单,创建与表格格式相同的数据

超时设定:timeout属性;调用ontimeout事件处理程序

原生js实现

核心:XMLHttpRequest对象。使用XHR对象取得新数据,再通过DOM将新数据插入页面中。与数据格式无关。

创建XHR对象:

var xhr = creatXHR();
//检测状态
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if (( xhr.status >=200 && xhr.status <300 ) || xhr.status == 304 ){
            alert(xhr.responseText);
        }else{
            alert("Request was unsuccessful:" + xhr.statusText);
        }
    }
};
//启动一个请求以备发送
xhr.open("get","example.txt",true);← 是否异步
//发送请求,无数据则必须有null
xhr.send(null);
readyState 状态
0 未open
1 已open未send
2 已send未回应
3 已响应部分
4 已全部响应

react实现

axios:

axios.get(this.props.url).then(function(response){
        // 在这儿实现 setState
    }).catch(function(error){
        // 处理请求出错的情况
    });

reqwest:

reqwest({
    url: 'path/to/data.jsonp?foo=bar'
  , type: 'jsonp'
  , jsonpCallback: 'foo'
})
  .then(function (resp) {
    qwery('#content').html(resp.content)
  }, function (err, msg) {
    qwery('#errors').html(msg)
  })
  .always(function (resp) {
    qwery('#hide-this').hide()
  })

vue实现

基础笔记梳理 - Vue

axios({
    method:'post',
    url:'/user',
    data:{
        firstName:'a',
        lastName:'b'
    }
});

跨域

同源政策(为什么AJAX不能跨域请求)

同源政策:不是同协议/同域名/同端口的网页无法相互访问
策略本质:一个域名的Js在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。

CORS

参考资料:跨域CORS原理及调用具体示例 太难了,弄不懂!

使用自定义的HTTP头部让浏览器与服务器进行沟通。决定请求或相应是否成功。

XDR类型:X Domain Request类型 只支持get和post

创建XDR实例(异步执行):调用open(请求类型,url),再调用send()

使用:只需要向响应头header中注入Access-Control-Allow-Origin,这样浏览器检测到header中的Access-Control-Allow-Origin,则就可以跨域操作了。

实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

  • 简单请求:post get head

    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
    • 浏览器 在头信息之中,添加一个Origin字段,说明本次请求来自哪个源(协议 + 域名 + 端口)
    GET /cors HTTP/1.1
    Origin: http://api.bob.com
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...
    
    • 服务器 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
    Access-Control-Allow-Origin: http://api.bob.com   → 必含,值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
    Access-Control-Allow-Credentials: true  →可选。值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
    Access-Control-Expose-Headers: FooBar → 可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
    Content-Type: text/html; charset=utf-8
    
  • 非简单请求 PUT DELETE,或者Content-Type:application/json。

    • 在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
    浏览器发送请求:
    var url = 'http://api.alice.com/cors';
    var xhr = new XMLHttpRequest();
    xhr.open('PUT', url, true);
    xhr.setRequestHeader('X-Custom-Header', 'value');
    xhr.send();
    
    先预检:
    OPTIONS /cors HTTP/1.1
    Origin: http://api.bob.com
    Access-Control-Request-Method: PUT  → 必须,列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
    Access-Control-Request-Headers: X-Custom-Header  → 逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...
    
    • 服务器确认允许跨源请求,做出回应
    Access-Control-Allow-Methods: GET, POST, PUT
    Access-Control-Allow-Headers: X-Custom-Header → 如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
    Access-Control-Allow-Credentials: true → 
    Access-Control-Max-Age: 1728000 → 可选,用来指定本次预检请求的有效期,单位为秒
    
    • 服务器端代码
    response.setHeader("Access-Control-Allow-Origin", "*");  
    

图像ping

JSONP

原理:通过script标签来引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且把我们需要的json数据作为参数传入。但是jsonp这种方式是需要服务端对应页面进行相应配合的。

修改document.domain

适用:浏览器中不同域的框架之间是不能进行js交互的例如:www.example.com/a.html和example.com/b.html这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

例如:a.b.example.com 中某个文档的document.domain可以设成a.b.example.com、b.example.com、example.com中的任意一个,但是不可以设成c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。

注意:修改document.domain的方法只适用于不同子域的框架间的交互。如果你想通过ajax的方法去与不同子域的页面交互,除了使用jsonp的方法外,还可以用一个隐藏的iframe来做一个代理。原理就是让这个iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发送ajax请求,然后收到的数据我们也可以获得了。

window.name

对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。注意:window.name的值只能是字符串的形式,这个字符串的大小最大能允许2M左右甚至更大的一个容量,具体取决于不同的浏览器,但一般是够用了。

原理:在a.html页面中使用一个隐藏的iframe来充当一个中间人角色,由iframe去获取data.html的数据,然后a.html再去得到iframe获取到的数据。

HTML5中新引入的window.postMessage()方法

原理:window.postMessage(message,targetOrigin)方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。参数:第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 接收:要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。适用:一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。在讨论第二种方法的时候,我们说过,不同域的框架间是可以获取到对方的window对象的,而且也可以使用window.postMessage这个方法

Comet 服务器推送

一种更高级的ajax技术,让服务器几乎能够实时地向客户端推送数据。

实现方式:长轮询、流

短轮询:浏览器   —请求→  服务器  —响应→  浏览器

长轮询:浏览器   —请求→  服务器  —(有数据可发送时)响应→  浏览器

HTTP流:浏览器   —请求→  服务器(保持链接打开)   —周期性发送数据→  浏览器

web sockets

在一个单独的持久链接上实现双向通信。与服务器进行全双工、双向通信的信道。使用自定义的协议而非HTTP,专门为快速传输小数据设计。

通讯相关

前端领域的通讯

前端和后端、前端和移动端、前端页面和iframe、浏览器各tab之间、web worker线程通信、路由间通信、父子组件通信

通讯的要点和目的

要点:发送者和接收者 / 传输媒介 / 数据 / 格式协议
目的:同步数据 / 传递指令

重定向和转发

浏览器发送请求,服务器发送特定响应,实现重定向。浏览器收到响应后,根据状态码判断重定向,并使用指定新URL重新请求。

转发:服务器端。TBC。。。

状态码

状态码 含义
1xx 接受,继续处理
200 成功,并返回数据
201 已创建
202 已接受
203 成功,但未授权
204 成功,但无内容
205 成功,且重置内容
206 成功,部分内容
301 永久移动,需重定向
302 临时移动,可使用URI
304 资源未修改,可使用缓存
305 需使用代理访问
400 请求语法错误
401 需要身份认证
403 拒绝请求
404 资源不存在
500 服务器错误

渲染相关

Reflow和Repaint

应尽量规避!

repaint:重绘。一部分重画,不影响整体布局。如颜色改变。
    例如:visibility:hidden
    监控:使用开发者工具可以监控页面重绘
    
reflow:回流。元素几何尺寸改变,需要重新验证并计算渲染树。
    例如:display:none
    导致reflow的操作:
        1. 调整窗口大小
        2. 改变字体(若用rem设置根目录字体大小,则不回流)
        3. 增加或移除样式表
        4. 内容变化 Input
        5. 激活css伪类
        6. 操作class属性
        7. 基本操作dom
        8. 计算 offsetWidth / offsetHeight 获取位置
        9. 在html中直接设置style(会降低代码利用率,影响性能)
    减少reflow的操作:
        1. 动画效果position:absolute/fixed 使元素脱离文档流
        2. 避免使用table
        3. 避免多项内联样式
        4. 尽可能在元素末端改变class
        5. 精简dom层级
        6. 使用display:none在隐藏期间配置可能导致多次reflow的样式,配置完成后再转为可见
        7. 多次元素css属性操作写到同一个class里
        8. 避免在Js循环里操作dom
        9. 预置css元素的大小

高频触发优化方式:

  • 防抖:多次高频操作,只在最后一次执行。策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。
function debounce(fn, delay) {
    var timer

    return function () {
        var that = this
        var args = arguments

        clearTimeout(timer)
            timer = setTimeout(function () {
            fn.apply(that, args)
        }, delay)
    }
}
  • 节流:每隔一段时间后执行一次。固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。
var throttle = function(func,delay){
    var timer = null;
    var startTime = Date.now();

    return function(){
        var curTime = Date.now();
        var remaining = delay-(curTime-startTime);
        var context = this;
        var args = arguments;

        clearTimeout(timer);
        if(remaining<=0){
            func.apply(context,args);
            startTime = Date.now();
        }else{
            timer = setTimeout(func,remaining);
        }
    }
}

SSR服务端渲染

直接在服务端层获取数据,渲染出完成的html文件,直接返回给用户浏览器。

原理:1. Node服务,让前后端运行同一套代码;
      2. Virtual Dom,让前端代码脱离浏览器运行。
条件:Node中间层 / React或vue框架

客户端渲染路线:1. 请求一个html -> 
                2. 服务端返回一个html -> 
                3. 浏览器下载html里面的js/css文件 -> 
                4. 等待js文件下载完成 -> 
                5. 等待js加载并初始化完成 -> 
                6. js代码终于可以运行,由js代码向后端请求数据( ajax/fetch ) -> 
                7. 等待后端数据返回 -> 
                8. 客户端从无到完整地,把数据渲染为响应页面

服务端渲染路线:1. 请求一个html -> 
                2. 服务端请求数据( 内网请求快 ) -> 
                3. 服务器初始渲染(服务端性能好,较快) -> 
                4. 服务端返回已经有正确内容的页面 -> 
                5. 客户端请求js/css文件 -> 
                6. 等待js文件下载完成 -> 
                7. 等待js加载并初始化完成 -> 
                8. 客户端把剩下一部分渲染完成( 内容小,渲染快 )

图片懒加载

参考资料:图片懒加载原理及实现

场景:一个页面中很多图片,但是首屏只出现几张,这时如果一次性把图片都加载出来会影响性能。这时可以使用懒加载,页面滚动到可视区在加载。优化首屏加载。
实现:img标签src属性为空或同一空白图片,给一个data-xx属性,里面存放图片真实地址,当页面滚动直至此图片出现在可视区域时,用js取到该图片的data-xx的值赋给src。
优点:页面加载速度快,减轻服务器压力、节约流量,用户体验好。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/jquery/2.1.0/jquery.min.js"></script>
    <style>
        .container{
            max-width: 800px;
            margin:0 auto;
        }
        .container:after{
            content:"";
            display: block;
            clear:both;
        }
        .container img{
            width:50%;
            height:260px;
            float:left;
        }
    </style>
</head>
<body>
    <div class="container">
        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1" data-src="http://img4.imgtn.bdimg.com/it/u=951914923,777131061&fm=26&gp=0.jpg">
        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1" data-src="http://img1.imgtn.bdimg.com/it/u=637435809,3242058940&fm=26&gp=0.jpg">
        <img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1" data-src="http://img1.imgtn.bdimg.com/it/u=3990342075,2367006974&fm=200&gp=0.jpg">
    </div>

        <script>

            // 一开始没有滚动的时候,出现在视窗中的图片也会加载
            start();

            // 当页面开始滚动的时候,遍历图片,如果图片出现在视窗中,就加载图片
            var clock; //函数节流
            $(window).on('scroll',function(){
                if(clock){
                    clearTimeout(clock);
                }
                clock = setTimeout(function(){
                    start()
                },200)
            })
            
            function start(){
                 $('.container img').not('[data-isLoading]').each(function () {
                    if (isShow($(this))) {
                        loadImg($(this));
                    }
                })
            }

            // 判断图片是否出现在视窗的函数
            function isShow($node){
                return $node.offset().top <= $(window).height()+$(window).scrollTop();
            }

            // 加载图片的函数,就是把自定义属性data-src 存储的真正的图片地址,赋值给src
            function loadImg($img){
                    $img.attr('src', $img.attr('data-src'));

                    // 已经加载的图片,我给它设置一个属性,值为1,作为标识
                    // 弄这个的初衷是因为,每次滚动的时候,所有的图片都会遍历一遍,这样有点浪费,所以做个标识,滚动的时候只遍历哪些还没有加载的图片
                    $img.attr('data-isLoading',1);
            }
        </script>
</body>
</html>

页面渲染步骤

1. 解析HTML,生成DOM树。
    接收HTML文档,遍历文档节点,生成DOM树。
    可能被CSS和JS加载阻塞!
2. 解析CSS,生成CSSOM规则树。
    每个CSS文件分析成StyleSheet对象,包含CSS规则
3. 将DOM树与CSSOM规则树合并在一起,生成渲染树。
    渲染树:用于显示,不可见元素则不在树中。display:none 不在;visibility:hidden 在。
    先从DOM树根节点开始遍历可见节点,找到适配的CSS。
4. 遍历渲染树开始布局。计算每个节点的位置大小信息。
    从渲染树跟节点开始遍历,确定节点对象的大小和位置,输出盒子模型。
5. 将渲染树每个节点绘制到屏幕
    由UI后端组件完成,调用paint()显示内容。
    
渲染阻塞:script标记将使DOM构建暂停。
    如果脚本中操作了CSSOM,则操作顺序 CSSOM => 脚本 => DOM
    CSS引入优先,JS底部置后。

js和css阻塞

1. 外部样式会阻塞之后的<script>,但<script>会在<link>之后执行
2. 外部css加载时,外部Js也会加载,只是Js执行必须是css执行之后
3. 使用async属性后,Js异步加载,且不必等待css之后执行,也不阻塞后面的Js
4. <script>动态创建时,会在css加载之后加载

async和defer

参考资料:浅谈script标签中的async和defer

普通script:如果遇到script脚本,就会停止页面的解析进行下载

script
defer:在后台进行下载,但是并不会阻止文档的渲染,当页面解析&渲染完毕后, 会等到所有的defer脚本加载完毕并按照顺序执行,执行完毕后会触发DOMContentLoaded事件。
defer
async:async脚本会在加载完毕后执行。async脚本的加载不计入DOMContentLoaded事件统计
async1
async2

其他前端常考知识

路由的Js实现

参考资料:原生 js 实现一个前端路由 router

  1. 实现原理:现在前端的路由实现一般有两种,一种是 Hash 路由,另外一种是 History 路由。
  • 2.1. History 路由

    History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。

    属性

    History.length 是一个只读属性,返回当前 session 中的 history 个数,包含当前页面在内。举个例子,对于新开一个 tab 加载的页面当前属性返回值 1 。

    History.state 返回一个表示历史堆栈顶部的状态的值。这是一种可以不必等待 popstate 事件而查看状态而的方式。 方法 History.back()前往上一页, 用户可点击浏览器左上角的返回按钮模拟此方法. 等价于 history.go(-1).

    Note: 当浏览器会话历史记录处于第一页时调用此方法没有效果,而且也不会报错。

    History.forward()在浏览器历史记录里前往下一页,用户可点击浏览器左上角的前进按钮模拟此方法. 等价于 history.go(1).

    Note: 当浏览器历史栈处于最顶端时( 当前页面处于最后一页时 )调用此方法没有效果也不报错。

    History.go(n)通过当前页面的相对位置从浏览器历史记录( 会话记录 )加载页面。比如:参数为 -1的时候为上一页,参数为 1 的时候为下一页. 当整数参数超出界限时 ( 译者注:原文为 When integerDelta is out of bounds ),例如: 如果当前页为第一页,前面已经没有页面了,我传参的值为 -1,那么这个方法没有任何效果也不会报错。调用没有参数的 go() 方法或者不是整数的参数时也没有效果。( 这点与支持字符串作为 url 参数的 IE 有点不同)。

    history.pushState() 和 history.replaceState()这两个 API 都接收三个参数,分别是

      a. 状态对象(state object) — 一个JavaScript对象,与用 pushState() 方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate 事件都会被触发,并且事件对象的state 属性都包含历史记录条目的状态对象的拷贝。
    
      b. 标题(title) — FireFox 浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。
    
      c. 地址(URL) — 新的历史记录条目的地址。浏览器不会在调用 pushState() 方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的 URL 不一定是绝对路径;如果是相对路径,它将以当前 URL 为基准;传入的 URL 与当前 URL 应该是同源的,否则,pushState() 会抛出异常。该参数是可选的;不指定的话则为文档当前 URL。
    
      相同之处: 是两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新。
    
      不同之处: pushState 会增加一条新的历史记录,而 replaceState 则会替换当前的历史记录。
    

    例子:

    本来的路由http://biaochenxuying.cn/

    执行:window.history.pushState(null, null, "http://biaochenxuying.cn/home");

    路由变成了:http://biaochenxuying.cn/home

  • 2.2 Hash 路由

    我们经常在 url 中看到 #,这个 # 有两种情况,一个是我们所谓的锚点,比如典型的回到顶部按钮原理、Github 上各个标题之间的跳转等,但是路由里的 # 不叫锚点,我们称之为 hash。

    当 hash 值发生改变的时候,我们可以通过 hashchange事件监听到,从而在回调函数里面触发某些方法。

<meta>标签:

提供有关页面的源信息,如针对搜索引擎和更新频率的描述和关键词。

必选:content → 名称/值对中的值, 可以是任何有效的字符串。 始终要和 name 属性或 http-equiv 属性一起使用。
可选:http-equiv → 没有name时,会采用这个属性的值。常用的有content-type、expires、refresh、set-cookie。把content属性关联到http头部。
      name → 名称/值对中的名称。常用的有author、description、keywords、generator、revised、others。 把 content 属性关联到一个名称。
      scheme → 用于指定要用来翻译属性值的方案。
<meta http-equiv="charset" content="iso-8859-1">

Babel转码器:将ES6转为ES5。

1 将ES6/ES7解析为抽象语法树
2 对抽象语法树进行遍历编译,得到新的抽象语法树
3 将新的抽象语法树转换成ES5

抽象语法树AST:将代码逐字母解析成树状对象。

webpack打包过程:

前端资源加载/打包工具。
把项目当作一个整体,通过一个给定的主文件index.js,从这个文件开始找到项目的所有依赖文件,使用Loader处理,最后打包为浏览器可识别的js文件。

1 读取文件,分析模块依赖
2 对模块进行解析执行(深度遍历)
3 针对不同模块,使用不同的Loader
4 编译模块,生成抽象语法树AST
5 遍历AST,输出js

webpack配置文件

  • Loaders

    • 通过使用不同的loader,webpack有能力调用外部的脚本或工具,实现对不同格式的文件的处理

    • 比如说分析转换scss为css,或者把下一代的JS文件(ES6,ES7)转换为现代浏览器兼容的JS文件,对React的开发而言,合适的Loaders可以把React的中用到的JSX文件转换为JS文件。

    • Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置,Loaders的配置包括以下几方面:

      1. test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须)
      2. loader:loader的名称(必须)
      3. include/exclude: 手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
      4. query:为loaders提供额外的设置选项(可选)
  • babel

    • babel是一种javascript编译器,它能把最新版的javascript编译成当下可以执行的版本,简言之,利用babel就可以让我们在当前的项目中随意的使用这些新最新的es6,甚至es7的语法。说白了就是把各种javascript千奇百怪的语言统统专为浏览器可以认识的语言。
    • babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中
  • plugins

    • 插件(Plugins)是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务。
  • Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西,可以这么来说:

    • loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个。
    • 插件并不直接操作单个文件,它直接对整个构建过程其作用。

webpack常见的Plugin?他们是解决什么问题的?

(1)uglifyjs-webpack-plugin:通过UglifyJS去压缩js代码;

(2)commons-chunk-plugin:提取公共代码;

(3)define-plugin:定义环境变量。

webpack的热更新是如何做到的?说明其原理?

webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

原理:

(1)第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。

(2)第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。

(3)第三步是 webpack-dev-server对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。

(4)第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。

(5)webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。

(6)HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。

(7)而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。

(8)最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

如何利用webpack来优化前端性能?(提高性能和体验)

用webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运行快速高效。

(1)压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css。使用webpack4,打包项目使用production模式,会自动开启代码压缩。

(2)利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径

(3)删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数--optimize-minimize来实现或者使用es6模块开启删除死代码。

(4)优化图片,对于小图可以使用 base64 的方式写入文件中

(5)按照路由拆分代码,实现按需加载,提取公共代码。

(6)给打包出来的文件名添加哈希,实现浏览器缓存文件

如何提高webpack的构建速度?

(1)多入口的情况下,使用commonsChunkPlugin来提取公共代码;

(2)通过externals配置来提取常用库;

(3)使用happypack实现多线程加速编译;

(4)使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。原理上webpack-uglify-parallel采用多核并行压缩来提升压缩速度;

(5)使用tree-shaking和scope hoisting来剔除多余代码。

11、怎么配置单页应用?怎么配置多页应用?

单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可。

多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。

npm打包时需要注意哪些?如何利用webpack来更好的构建?

NPM模块需要注意以下问题:

(1)要支持CommonJS模块化规范,所以要求打包后的最后结果也遵守该规则

(2)Npm模块使用者的环境是不确定的,很有可能并不支持ES6,所以打包的最后结果应该是采用ES5编写的。并且如果ES5是经过转换的,请最好连同SourceMap一同上传。

(3)Npm包大小应该是尽量小(有些仓库会限制包大小)

(4)发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。

(5)UI组件类的模块应该将依赖的其它资源文件,例如.css文件也需要包含在发布的模块里。

基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:

(1)CommonJS模块化规范的解决方案: 设置output.libraryTarget='commonjs2'使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用;

(2)输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 ES5 的代码。再通过开启devtool: 'source-map'输出SourceMap以发布调试。

(3)Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件

(4)不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。

(5)对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现,配置如下:

如何在vue项目中实现按需加载?

经常会引入现成的UI组件库如ElementUI、iView等,但是他们的体积和他们所提供的功能一样,是很庞大的。 不过很多组件库已经提供了现成的解决方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import安装以上插件后,在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。

webpack开发配置API代理解决跨域问题

使用的是http-proxy-middleware来实现跨域代理

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {  → 捕获API的标志,开始匹配代理,比如API请求/api/users, 会被代理到请求 http://www.baidu.com/api/users 。
        target: 'http://www.baidu.com/', → 代理的API地址,就是需要跨域的API地址。地址可以是域名,也可以是IP地址,如果是域名需要额外添加一个参数changeOrigin: true
        pathRewrite: {'^/api' : ''}, → 路径重写,也就是说会修改最终请求的API路径。目的是给代理命名后,在访问时把命名删除掉。
        changeOrigin: true,     // 让target参数是域名
        secure: false,          // 不检查安全问题。设置后,可以接受运行在 HTTPS 上,可以使用无效证书的后端服务器
      },
      '/api2': {
          .....
      }
    }
  }
};

package.json 常见字段

{
  // 名称
  "name": "vue",
  // 版本
  "version": "2.6.10",
  // 描述
  "description": "Reactive, component-oriented view layer for modern web interfaces.",
  // npm包项目的主要入口文件,必须的
  "main": "dist/vue.runtime.common.js",
  // rollup 打包需要的入口文件
  "module": "dist/vue.runtime.esm.js",
  // npm 上所有的文件都开启 cdn 服务地址
  "unpkg": "dist/vue.js",
  // jsdelivr cdn公共库
  "jsdelivr": "dist/vue.js",
  // TypeScript 的入口文件
  "typings": "types/index.d.ts",
  // 当你发布package时,具体那些文件会发布上去
  "files": [
    "src",
    "dist/*.js",
    "types/*.d.ts"
  ],
  // 声明该模块是否包含 sideEffects(副作用),从而可以为 tree-shaking 提供更大的优化空间。
  "sideEffects": false,
  "scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
    "build": "node scripts/build.js",
    "test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex",
    "commit": "git-cz"
    ......
  },
  // 代码质量检查
  "gitHooks": {
    "pre-commit": "lint-staged",
    "commit-msg": "node scripts/verify-commit-msg.js"
  },
  // 代码检查
  "lint-staged": {
    "*.js": [
      "eslint --fix",
      "git add"
    ]
  },
  // git仓库所在位置
  "repository": {
    "type": "git",
    "url": "git+https://github.com/vuejs/vue.git"
  },
  // 关键词
  "keywords": [
    "vue"
  ],
  // 作者
  "author": "Evan You",
  // 开源协议
  "license": "MIT",
  // bug地址
  "bugs": {
    "url": "https://github.com/vuejs/vue/issues"
  },
  // 主页
  "homepage": "https://github.com/vuejs/vue#readme",
  // 依赖
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.1.0",
    "acorn": "^5.2.1",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.7.0",
    ......
  },
  // 设置一些用于npm包的脚本命令会用到的配置参数
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }
  // 本node包依赖的其他依赖包
    "peerDependencies": {
        "vue": "^2.5.2"
    },
    // 指明了该项目所需要的node.js版本
    "engines": {
        "node": ">=8.9.1",
        "npm": ">=5.5.1",
        "yarn": ">=1.3.2"
    },
    // 支持的浏览器
    "browserslist": [
        "last 3 Chrome versions",
        "last 3 Firefox versions",
        "Safari >= 10",
        "Explorer >= 11",
        "Edge >= 12",
        "iOS >= 10",
        "Android >= 6"
    ]
}

web兼容性问题

参考资料:web前端兼容性问题总结

MVC和MVVM

参考资料:浅析前端开发中的 MVC/MVP/MVVM 模式

  • Model层:用于封装和应用程序的业务逻辑相关的数据 [和对应处理数据的方法]。
  • View层:作为视图层,主要负责数据的展示。
  • controller层:定义用户界面对用户输入的响应方式,连接模型和视图,用于控制应用程序的流程,处理用户的行为和数据上的改变。

MVC:允许在不改变视图的情况下改变视图对用户输入的响应方式,用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新。

MVP:是MVC模式的改良。Controller/Presenter负责业务逻辑,Model管理数据,View负责显示。 MVP中的View并不能直接使用Model,而是通过为Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View。 解耦View和Model,完全分离视图和模型使职责划分更加清晰;由于View不依赖Model,可以将View抽离出来做成组件,它只需要提供一系列接口提供给上层操作。

MVVM:把View和Model的同步逻辑自动化了。View和Model同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可。View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View。

设计模式

参考资料:JavaScript设计模式 单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现方法:先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
适用场景:一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次。

发布/订阅模式

定义:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
场景:订阅感兴趣的专栏和公众号。

策略模式

定义:将一个个算法(解决方案)封装在一个个策略类中。
优点:策略模式可以避免代码中的多重判断条件。
      策略模式很好的体现了开放-封闭原则,将一个个算法(解决方案)封装在一个个策略类中。便于切换,理解,扩展。
      策略中的各种算法可以重复利用在系统的各个地方,避免复制粘贴。
      策略模式在程序中或多或少的增加了策略类。但比堆砌在业务逻辑中要清晰明了。
      违反最少知识原则,必须要了解各种策略类,才能更好的在业务中应用。
应用场景:根据不同的员工绩效计算不同的奖金;表单验证中的多种校验规则。

单页面应用

在一个页面上集成多种功能,所有业务功能都是子版块,通过特定的方式挂接到主界面上。
采用MV*框架,在JS层创建模块分层和通信机制。
代码隔离:
    子功能代码隔离
    页面模板隔离
样式规划:
    基准样式分离(浏览器样式、全局样式、布局、响应支持
    组件样式划分(界面组件及子元素样式、修饰样式)
    堆叠次序管理(提前为UI组件规划次序)
前后端天然分离,以API为分界
缺陷:不利于搜索引擎优化
把产品功能划分为若干状态,每个状态映射到相应的路由
在非首次请求中,使用缓存或本地存储
采用websocket实时通讯。前端只响应确实产生业务数据的事件。

单页面应用的优劣

  • 优点:

    • 分离前后端关注点,前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起;
    • 易于代码复用,同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端;
    • 页面切换速度快。视觉上页面的切换,只是技术上同一页面两个区块之间的切换
  • 缺点:

    • SEO问题,现在可以通过Prerender等技术解决一部分;
    • 前进、后退、地址栏等,需要程序进行管理;
    • 书签,需要程序来提供支持;
    • 请求资源大小增加,打开页面速度变慢,可以通过拆分加载解决

web性能优化

资料参考:嗨,送你一张Web性能优化地图

性能优化

  • 度量标准

    1. 首次有效绘制(First Meaningful Paint,简称FMP,当主要内容呈现在页面上)
    2. 英雄渲染时间(Hero Rendering Times,度量用户体验的新指标,当用户最关心的内容渲染完成)
    3. 可交互时间(Time to Interactive,简称TTI,指页面布局已经稳定,关键的页面字体是可见的,并且主进程可用于处理用户输入,基本上用户可以点击UI并与其交互)
    4. 输入响应(Input responsiveness,界面响应用户输入所需的时间)
    5. 感知速度指数(Perceptual Speed Index,简称PSI,测量页面在加载过程中视觉上的变化速度,分数越低越好)
    6. 自定义指标,由业务需求和用户体验来决定。
  • 编码优化

    1. 数据读取速度:对象嵌套的越深,读取速度就越慢;对象在原型链中存在的位置越深,找到它的速度就越慢
    2. DOM:尽可能减少访问DOM的次数;减少重排与重绘的次数;善于使用事件委托
    3. 流程控制:减少迭代的次数;基于循环的迭代比基于函数的迭代快8倍;在JS中倒序循环会略微提升性能
  • 静态资源优化

    1. 使用Brotli或Zopfli进行纯文本压缩
    2. 图片优化:尽可能通过srcset,sizes和元素使用响应式图片。
  • 交付优化:对页面加载资源以及用户与网页之间的交付过程进行优化

    1. 异步无阻塞加载JS:将Script标签放到页面的最底部;使用defer或async
    2. 使用Intersection Observer实现懒加载:延迟加载图片、视频、广告脚本、或任何其他资源
    3. 优先加载关键的CSS:将首屏渲染必须用到的CSS提取出来内嵌到中,然后再将剩余部分的CSS用异步的方式加载
    4. 资源提示:Resource Hints
    5. Preload:通过一个现有元素(例如:img,script,link)声明资源会将获取与执行耦合在一起
    6. 快速响应的用户界面:使用骨架屏或添加一些Loading过渡动画提示用户体验
  • 构建优化:影响构建后文件的体积、代码执行效率、文件加载时间、首次有效绘制指标

    1. 使用预编译
    2. 使用 Tree-shaking、Scope hoisting、Code-splitting
    3. 服务端渲染(SSR):使用服务端渲染静态HTML来获得更快的首次有效绘制,一旦JavaScript加载完毕再将页面接管下来
    4. 使用import函数动态导入模块,按需加载
    5. 使用HTTP缓存头:推荐使用Cache-control: immutable避免重新验证

静态资源优化

资料参考:Web前端性能优化——如何有效提升静态文件的加载速度

  1. 代码压缩 使用webpack
  2. 文件合并 合并js脚本文件 合并css样式文件
  3. 在webpack的配置中增加gzip压缩配置
  4. CDN和缓存

App端网页和PC端网页设计起来有什么区别?

1. 手机网页主要webkit,PC网页主要ie。且手机端需要跨系统平台
2. app端,需要基于phoneGap调用手机核心功能接口(地理定位、联系人、加速器、声音、振动)。模拟tive app,编译成各系统平台的app
3. 设置高度、宽度是否能缩放。
    <meta name="viewPort" content="width=device-width,inital-scale=1.0, minimun-scale=0.5,maximum=scale=1.0,user-scalable=no"
4. 自适应,对不同像素的屏幕写不同的样式
    @media only screen and (min-device-width:320px) and (max-device-width:480){...}
5. 用户体验不同(鼠标与触屏)

计算在一个页面上的停留时间

方案1:websocket,前端开个长连接,后台统计长连接时间。
方案2:ajax轮询,隔几秒发一个查询,后台记录第一与最后一个查询间隔时间。
方案3:关闭窗口或者跳转的时候会触发window.onbeforeunload函数,可以在该函数中做处理(有兼容性问题);统计完数据记录到本地cookies中,一段时间后统一发送。

浏览器内核有哪些,移动端用的是哪个

Trident内核:IE,MaxThon,TT,The Word,360,搜狗浏览器等。[又称为MSHTML]
Gecko内核:Netscape6及以上版本,FF,MozillaSuite/SeaMonkey等;
Presto内核:Opera7及以上。[Opera内核原为:Presto,现为:Blink]
Webkit内核:Safari,Chrome等。[Chrome的:Blink(Webkit的分支)]

对于Android手机而言,使用率最高的就是Webkit内核。

其他

至今遇到印象最深的bug(最好是搜索不到的)

印象最深的项目

是否了解最新技术,前端技术更新的看法

为什么要做前端

未来的职业规划

平时是怎么学习的

为什么选择x公司