Git Product home page Git Product logo

blog's Issues

2018年阅读书单

《拖延心理学》

有个笑话: 一个有拖延症的人买了这本书,结果因为拖延症,这本书从来没有被翻过。

我是在年前看完的,现在来做总结老实说印象已经不是很深刻了,对书中说到的解决方法也并没有很好的去执行。唯一让自己感受到的是造成拖延的原因其实是多方面,比如:家庭所带来的价值观、周围不良的环境、内心的不坚定与不自信、以及最重要的一点是学识不够多,导致无法正确地客观地看待活着所遭遇的事情而手忙脚乱、焦虑且迷茫的度过每一天。

推荐指数: ★★★★☆

《影响力》

这本书由公司经理推荐阅读,看来他是希望我能理解各种生活中看似怪诞其实却存在合理性的事情,毫无疑问的是,这本书解决了我很多的人生疑问。查看目录就知道这本书是有多有意思

  • 第1章 影响力的武器 :动物可能会因为看到某种颜色的羽毛而变得具有攻击性,或是听到某种叫声就对自己的天敌呵护有加。动物这种愚蠢可笑的机械反应在人类身上也有,当某一个触发特征出现时,我们会不假思索地作出相应的反应。之所以会这样,就是因为我们被难以察觉的影响力武器摆布了。
  • 第2章 互惠 :互惠原理认为,我们应该尽量以类似的方式报答他人为我们所做的一切。简单地说,就是对他人的某种行为,我们要以一种类似的行为去加以回报。如果人家施恩于你,你就应该以恩情报之,而不能对此不理不睬,更不能以怨报德。于是,我们身边这一最有效的影响力武器,就被某些人利用谋取利益了。
  • 第3章 承诺与一致 :承诺和一致原理认为,一旦作出了一个选择或采取了某种立场,我们就会立刻碰到来自内心和外部的压力迫使我们的言行与它保持一致。在这样的压力之下,我们想法设法地以行动证明自己先前的决定是正确的。
  • 第4章 社会认同 :社会认同原理认为,在判断何为正确时,我们会根据别人的意见行事。尤其是当我们正赛特定情形下判断某一行为是否正确时。如果我们看到别人在某种场合做某件事,我们就会断定这样做是有道理的。
  • 第5章 喜好 :我们大多数人总是更容易答应自己认识和喜爱的人所提出的要求,对于这一点,恐怕不会有人感到吃惊。令人吃惊的是,有些我们完全不认识的人却想出了上百种方法利用这条简单的原理,让我们顺从他们的要求。
  • 第6章 权威 :权威所具有的强大力量会影响我们的行为,即使是具有独立思考能力的成年人也会为了服从权威的命令而作出一些完全丧失理智的事情来
  • 第7章 稀缺 :“机会越少见,价值似乎就越高”的稀缺原理会对我们行为的方方面面造成影响,对失去某种东西的恐惧似乎要比对获得同一物品的渴望,更能激发人们的行动力
  • 尾声 即时的影响力 :在正常情况下,促使我们作出顺从决策的几个最常用的信息,都可以引导我们作出正确的决策,这就是为什么我们在决策时频繁、机械地使用互惠、言行一致、社会认同、喜好、权威以及稀缺原理的原因。每个原理本身都能够极为可靠地提示我们,什么时候说“是”比说“不”更加有利。但现实中,大量的、极易伪造的信息被人利用,他们借此引诱我们作出机械的反应并从中获利,我们不得不防。

推荐指数: ★★★★☆

《暗时间》

本书的开篇,就让人觉得惊叹:

每个人的生命就像沙漏,里面装的沙子总量大致相当,不同的是,有的沙漏颈部较细,有的沙漏颈部较粗。颈部较细的沙漏能够抓住每一粒时间之沙,即使沙子总量一样,也能拥有更长的生命。

在序言部分,我们可以看到这是一本偏心理学的书。而身为理工男的作者是因为什么要花这么大的力气去探索心理学这一块的知识,在书中也给出了答案:“我一向认为,正确的思维方式,是一切高效学习的基础”

书中每个章节的摘要都用了朴素平淡的文字吸引着我们忍不住想去一探究竟,从而也让我认为作者对心理学的掌握足够老道。在整本书中,对单独个体的探索——《逃出你的肖申克》,有很多值得让人细细品味的点。

推荐指数: ★★★★★

柴犬绅士

很快就能看完的一本男士服装搭配指南,全程的眼光都在那条帅气的柴犬上,不知道它现在怎么样了。总得来说,书中的一些穿搭建议以及展示都很靠谱和绅士,虽然可能部分穿搭并不是很适合国内这个环境,但是最大的问题是:没钱

推荐指数: ★★★★☆

前端工程师面试指南——网络篇

OSI七层网络模型

image
参考

一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?(流程说的越详细越好)

(1)、浏览器会开启一个线程来处理这个请求,对 URL 分析判断如果是 http 协议就按照 Web 方式来处理;
(2)、调用浏览器内核中的对应方法,比如 WebView 中的 loadUrl 方法;
(3)、通过DNS解析获取网址的IP地址,设置 UA 等信息发出第二个GET请求;
(4)、进行HTTP协议会话,客户端发送报头(请求报头);
(5)、进入到web服务器上的 Web Server,如 Apache、Tomcat、Node.JS 等服务器;
(6)、进入部署好的后端应用,如 PHP、Java、JavaScript、Python 等,找到对应的请求处理;
(7)、处理结束回馈报头,此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304;
(8)、浏览器开始下载html文档(响应报头,状态码200),同时使用缓存;
(9)、文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie;
(10)、页面开始渲染DOM,JS根据DOM API操作DOM,执行事件绑定等,页面显示完成。

浏览器渲染页面的过程

  • 根据HTML结构生成DOM Tree;
  • 根据CSS生成CSSOM(视图模块);
  • 将DOM和CSSOM整合形成RenderTree(渲染树);
  • 根据RenderTree开始渲染和展示;
  • 遇到<script>时,会执行并阻塞渲染。

TCP和UDP的区别是什么?

  • TCP(Transmission Control Protocol,传输控制协议): 是面向连接的协议,也就是说,在收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来,其中的过程非常复杂,只简单的描述下这三次对话的简单过程:主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

  • TCP三次握手过程:

(1)主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B ,向主机B 请求建立连接,
通过这个数据段,主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。

(2)主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应
主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用哪佧序列号作为起始数据段来回应我。

(3)主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:"我已收到回复,
我现在要开始传输实际数据了。这样3次握手就完成了,主机A和主机B 就可以传输数据了
  • 三次握手的特点: 没有应用层的数据;SYN这个标志位只有在TCP建产连接时才会被置1;握手完成后SYN标志位被置0

  • TCP四次挥手过程:

(1)当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求

(2)主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1

(3)由B 端再提出反方向的关闭请求,将FIN置1

(4)主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束


由TCP的三次握手和四次断开可以看出,TCP使用面向连接的通信方式,大大提高了数据通信的可靠性,
使发送数据端和接收端在数据正式传输前就有了交互,为数据正式传输打下了可靠的基础
  • UDP(User Data Protocol,用户数据报协议):
(1) UDP是一个非连接的协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取
来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用
程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,
应用程序每次从队列中读一个消息段。

(2) 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,
因此一台服务机可同时向多个客户机传输相同的消息。

(3) UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。

(4) 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。

(5)UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。

(6)UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。
既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。
我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方
主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,
那么网络就是通的
  • 总结TCP与UDP的区别:
1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

http中的cookie字段,cookie一般存的是什么,session cookie怎么存在的?

  • Cookie通常也叫做网站cookie,浏览器cookie或者httpcookie,是保存在用户浏览器端的,并在发出http请求时会默认携带的一段文本片段。它可以用来做用户认证,服务器校验等通过文本数据可以处理的问题。
  • Session Cookie这个类型的cookie只在会话期间内有效,即当关闭浏览器的时候,它会被浏览器删除。设置session cookie的办法是:在创建cookie不设置Expires即可。参考

http请求方式有哪些?

  • HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
  • HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
  • 更多请看:HTTP请求方法

http的强缓存和协商缓存

  • 强制缓存: 在缓存数据未失效的情况下,可以直接使用缓存数据;在没有缓存数据的时候,浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应header中。它的缺点就是:生成的是绝对时间,但是客户端时间可以随意修改,会导致误差。
  • 协商缓存: 浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。它的缺点就是:Last-Modified 标注的最后修改时间只能精确到秒,如果有些资源在一秒之内被多次修改的话,他就不能准确标注文件的新鲜度了。如果某些资源会被定期生成,当内容没有变化,但 Last-Modified 却改变了,导致文件没使用缓存有可能存在服务器没有准确获取资源修改时间,或者与代理服务器时间不一致的情形。

https怎么进行加密

  • http是一种属于应用层的协议,简称为超文本传输协议。它有如下的缺点和优点
(1)通信使用明文(不加密),内容可能会被窃听

(2)不验证通信方的身份,因此有可能遭遇伪装

(3)无法证明报文的完整性,所以有可能已遭篡改


优点就是传输速度快。
  • HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL (安全套接字层)和TLS (安全传输层协议)代替而已。即添加了加密及认证机制的 HTTP 称为 HTTPS ( HTTP Secure )。也就是说:HTTP + 加密 + 认证 + 完整性保护 = HTTPS。它是使用两把密钥的公开密钥加密。公开密钥加密使用一对非对称的密钥。一把叫做私钥,另一把叫做公钥。私钥不能让其他任何人知道,而公钥则可以随意发布,任何人都可以获得。使用公钥加密方式,发送密文的一方使用对方的公钥进行加密处理,对方收到被加密的信息后,再使用自己的私钥进行解密。利用这种方式,不需要发送用来解密的私钥,也不必担心密钥被攻击者窃听而盗走。

http状态码有那些?分别代表是什么意思

  • 100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
  • 200 OK 正常返回信息
  • 201 Created 请求成功并且服务器创建了新的资源
  • 202 Accepted 服务器已接受请求,但尚未处理
  • 301 Moved Permanently 请求的网页已永久移动到新位置。
  • 302 Found 临时性重定向。
  • 303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
  • 304 Not Modified 自从上次请求后,请求的网页未修改过,也就是协商缓存中返回的状态码。
  • 400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
  • 401 Unauthorized 请求未授权。
  • 403 Forbidden 禁止访问。
  • 404 Not Found 找不到如何与 URI 相匹配的资源。
  • 500 Internal Server Error 最常见的服务器端错误。
  • 503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)

POST和GET的区别:

  • GET在浏览器回退时是无害的,而POST会再次提交请求;
  • GET产生的URL地址可以被收藏,而POST不可以;
  • GET请求会被浏览器主动缓存,而POST不会,除非手动设置;
  • GET请求只能进行url编码,而POST支持多种编码方式;
  • GET请求参数会被完整保留在浏览器历史记录里,而POST的参数不会被保留;
  • GET请求在url中传送的参数是有长度限制的,而POST是没有限制的;
  • 对参数的数据类型,GET只接受ASCll字符,而POST没有限制;
  • GET比POST更不安全,因为参数直接暴露在url上,所以不能用来传递敏感信息;
  • GET参数通过url传递,POST放在Request body中。

什么是持久连接:

HTTP协议 采用“请求-应答”模式,当使用普通模式,即非Keep-Alive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议);

当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。(前提HTTP 1.1版本)

HTTP协议的主要特点

简单快速、灵活、无连接、无状态。

  • (1)HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • (2)HTTP是无状态:无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

什么是管线化:

在使用持久连接的情况下,某个连接上消息的传递类似于:请求1->响应1->请求2->响应2->请求3->响应3;
在使用管线化后某个连接上的消息变成了类似这样的:请求1->请求2->请求3->响应1-响应2->响应3

其他:

  • 管线话机制通过持久连接完成,仅HTTP/1.1支持此技术;
  • 只有GET和HEAD请求可以进行管线化,而POST则有所限制;
  • 初次创建连接时不应该启动管线机制,因为服务器不一定支持HTTP/1.1版本的协议;
  • 管线化不会影响响应到来的顺序,如上面的例子所示,响应返回的顺序并未改变;
  • HTTP/1.1要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可;
  • 由于上面提到的服务器端问题,开启管线化可能并不会带来大幅度的性能提升,而且很多服务器端和代理程序对管线化支持并不好,因此现代浏览器默认并未开启管线化支持。

HTTP1.1版本新特性

  • 默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求
  • 管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应
  • 断点续传原理

更多优质文章

1、HTTP面试指南

前端工程师面试指南——开发模式篇

MVC和MVVM的区别

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

MVC是将响应机制封装在controller对象中,当用户和你的应用产生交互时,控制器中的事件触发器就开始工作了。

MVVM把View和Model的同步逻辑自动化了。以前Controller负责的View和Model同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可。也就是双向数据绑定,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。点击查看更多

JavaScript设计模式有哪些

参考

前端开发面试题大合集

前言

本文收集了一些前端面试题(为了方便阅读,建议先安装gayhub),主要是为了今后的求职,同时也借助这些面试题更进一步系统的学习、透彻的学习,形成自己的知识链。这并不是投机取巧,临时抱佛脚哈,自己也明白如果这么做肯定对未来的发展不利,不说这么多废话,下面正式开始。

前端开发所需掌握知识点概要(HTML&CSS):

  • 对Web标准的理解(结构、表现、行为)
  • 浏览器内核
  • 渲染原理
  • 依赖管理
  • 兼容性
  • CSS语法,层次关系,常用属性,布局,选择器,权重,盒模型,Hack,CSS预处理器
  • Flexbox布局
  • CSS Modules
  • Document flow
  • BFC概念以及应用
  • HTML5(离线 & 存储、Histoy,多媒体、WebGL\SVG\Canvas);

HTML

1、Doctype作用?标准模式与兼容模式各有什么区别?

(1)<!DOCTYPE>声明位于HTML文档中的第一行,处于 <html> 标签之前。告知浏览器的解析器用什么文档标准解析这个文档。DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。
(2)标准模式的排版 和JS运作模式都是以该浏览器支持的最高标准运行。在兼容模式中,页面以宽松的向后兼容的方式显示,模拟老式浏览器的行为以防止站点无法工作。

2、HTML5 为什么只需要写 <!DOCTYPE HTML>

HTML5 不基于 SGML,因此不需要对DTD进行引用,但是需要doctype来规范浏览器的行为(让浏览器按照它们应该的方式来运行);而HTML4.01基于SGML,所以需要对DTD进行引用,才能告知浏览器文档所使用的文档类型。

3、行内元素有哪些?块级元素有哪些? 空(void)元素有那些?

首先:CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,如div的display默认值为“block”,则为“块级”元素;span默认display属性值为“inline”,是“行内”元素。

(1)行内元素有:a b span img input select strong(强调的语气)
(2)块级元素有:div ul ol li dl dt dd h1 h2 h3 h4…p
(3)常见的空元素:<br> <hr> <img> <input> <link> <meta>
(4)鲜为人知的空元素:<area> <base> <col> <command> <embed> <keygen> <param> <source> <track> <wbr>

4、页面导入样式时,使用link和@import有什么区别?

(1)link属于XHTML标签,除了加载CSS外,还能用于定义RSS, 定义rel连接属性等作用;而@import是CSS提供的,只能用于加载CSS;
(2)页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载;
(3)import是CSS2.1 提出的,只在IE5以上才能被识别,而link是XHTML标签,无兼容问题;

5、html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?

(1)新特性:绘画 canvas;用于媒介回放的 video 和 audio 元素;语意化更好的内容元素,比如 article、footer、header、nav、section;表单控件,calendar、date、time、email、url、search;新的技术webworker, websocket, Geolocation;本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失; sessionStorage 的数据在浏览器关闭后自动删除;
(2)移除的元素:纯表现的元素:basefont,big,center,font, s,strike,tt,u;对可用性产生负面影响的元素:frame,frameset,noframes;
(3)兼容问题:IE8/IE7/IE6支持通过document.createElement方法产生的标签,可以利用这一特性让这些浏览器支持HTML5新标签,浏览器支持新标签后,还需要添加标签默认的样式。当然也可以直接使用成熟的框架、比如html5shim

6、简述一下你对HTML语义化的理解?

用正确的标签做正确的事情。html语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析;即使在没有样式CSS情况下也以一种文档格式显示,并且是容易阅读的;搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO;使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。

7、HTML5的离线储存怎么使用,工作原理能不能解释一下?

(1)在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,更新用户机器上的缓存文件。
(2)原理:HTML5的离线存储是基于一个新建的.appcache文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示。
(3)使用:离线存储使用案例

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

(1)存储位置:cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密),cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递。sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
(2)存储大小:cookie数据大小不能超过4k。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
(3)存储时间:localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage 数据在当前浏览器窗口关闭后自动删除;cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。

9、iframe有那些缺点?

(1)iframe会阻塞主页面的Onload事件;
(2)搜索引擎的检索程序无法解读这种页面,不利于SEO;
(3)iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。
【拓展】
如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题。

10、Label的作用是什么?是怎么用的?

(1)作用:label标签来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上。
(2)用法:

  <label for="Name">Number:</label>
  <input type=“text“name="Name" id="Name"/>

  <label>Date:<input type="text" name="B"/></label>

11、HTML5的form如何关闭自动完成功能?

给不想要提示的 form 或某个 input 设置为 autocomplete=off。

12、如何实现浏览器内多个标签页之间的通信?

(1)WebSocket、SharedWorker;
(2)也可以调用localstorge、cookies等本地存储方式;

13、webSocket如何兼容低浏览器?

(1)Adobe Flash Socket ;
(2)ActiveX HTMLFile (IE) ;
(3)基于 multipart 编码发送 XHR;
(4)基于长轮询的 XHR。

14、页面可见性(Page Visibility API) 可以有哪些用途?

(1)通过 visibilityState 的值检测页面当前是否可见,以及打开网页的时间等;
(2)在页面被切换到其他后台进程的时候,自动暂停音乐或视频的播放;

15、title与h1的区别、b与strong的区别、i与em的区别?

(1)title属性没有明确意义只表示是个标题,H1则表示层次明确的标题,对页面信息的抓取也有很大的影响;
(2)strong是标明重点内容,有语气加强的含义,使用阅读设备阅读网络时:<strong>会重读,而<b>是展示强调内容;
(3)i内容展示为斜体,em表示强调的文本。

16、sessionStorage 、localStorage 和 cookie 之间的区别

(1)共同点:都是保存在浏览器端,且同源的。
(2)区别:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递;cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。
(3)而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
(4)数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
(5)作用域不同,sessionStorage不在不同的浏览器窗口**享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。Web Storage 的 api 接口使用更方便。

17、HTML 5标签是否一定需要闭合?标签大小写是否敏感?

不一定;不敏感,但是推荐要用小写标签

18、HTML 中的DOM树

HTML DOM定义访问和操作HTML文档的标准方法。DOM将HTML文档表达为树结构。
image
W3C 文档对象模型 (DOM) 是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。W3C DOM 标准被分为 3 个不同的部分:
(1)核心DOM,针对任何结构化文档的标准模型;
(2)XML DOM,针对XML文档的标准模型;
(3)HTML DOM,针对HTML文档的标准模型,它是标准对象模型,是标准编程接口;
根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:整个文档是一个文档节点、每个HTML元素是元素节点、HTML元素内的文本是文本节点、每个HTML属性是属性节点、注释是注释节点。

19、XHTML和HTML有什么区别?

(1)HTML是一种基本的web网页设计语言,XHTML是一个基于XML的置标语言;
(2)XHTML元素必须被正确的嵌套;
(3)XHTML元素必须被关闭;
(4)XHTML元素必须使用小写字母;
(5)XHTML文档必须拥有根元素。

20、canvas和svg图形的区别是什么?
image

21、如果我不放入<! DOCTYPE html> ,HTML5还会工作么?

不会,浏览器将不能识别他是HTML文档,同时HTML5的标签将不能正常工作

CSS

1、介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的?如何设置这两种模型?

(1)有两种, IE 盒子模型、W3C 盒子模型;
(2)盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
(3)区 别: 这两种盒子模型最主要的区别就是width的包含范围,在标准的盒子模型中,width指content部分的宽度,在IE盒子模型中,width表示content+padding+border这三个部分的宽度,故这使得在计算整个盒子的宽度时存在着差异。
(4)标准盒模型box-sizing:content-box;IE盒模型box-sizing:border-box

2、CSS选择符有哪些?哪些属性可以继承?

(1)id选择器( # myid)
(2)类选择器(.myclassname)
(3)标签选择器(div, h1, p)
(4)相邻选择器(h1 + p)
(5)子选择器(ul > li)
(6)后代选择器(li a)
(7)通配符选择器( * )
(8)属性选择器(a[rel = "external"])
(9)伪类选择器(a:hover, li:nth-child)

可继承的样式: font-size font-family color, UL LI DL DD DT;
不可继承的样式:border padding margin width height ;

3、CSS优先级算法如何计算?

优先级就近原则,同权重情况下样式定义最近者为准;
载入样式以最后载入的定位为准;

(1)同权重:内联样式表(标签内部)> 嵌入样式表(当前文件中)> 外部样式表(外部文件中)
(2)不同权重:!important > id > class > tag,其中important 比 内联优先级高

4、CSS3新增伪类有那些?

(1)p:first-of-type     选择属于其父元素的首个 <p> 元素的每个<p>元素。
(2)p:last-of-type     选择属于其父元素的最后 <p> 元素的每个<p>元素。
(3)p:only-of-type     选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。
(4)p:only-child     选择属于其父元素的唯一子元素的每个<p>元素。
(5)p:nth-child(2)     选择属于其父元素的第二个子元素的每个 <p> 元素。
(6):checked      单选框或复选框被选中。
(7):disabled     控制表单控件的禁用状态。

5、如何居中div?

(1)水平居中:给div设置一个宽度,然后添加margin:0 auto属性

div {
  width: 200px;
  margin: 0 auto;
}

(2)让绝对定位的div居中

div {
  position: absolute;
  width: 300px;
  height: 300px;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  /* 方便看效果 */
  background-color: pink; 
}

(3)水平垂直居中一

 div {
   /* 相对定位或绝对定位均可 */
   position: relative;
   width: 500px;
   height: 300px;
   top: 50%;
   left: 50%;
   /* 外边距为自身宽高的一半 */
   margin: -150px 0 0 -250px;
   /* 方便看效果 */
   background-color: pink;
 }

(4)水平垂直居中二

div {
  /* 相对定位或绝对定位均可 */
  position: absolute;
  width: 500px;
  height: 300px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* 方便看效果 */
  background-color: pink;
}

(5)水平垂直居中三

.container {
  display: flex;
  height: 500px;
  width: 500px;
  border: 1px solid #ccc;
  /* 垂直居中 */
  align-items: center;
  /* 水平居中 */
  justify-content: center;
}
.container div {
  width: 100px;
  height: 100px;
  /* 方便看效果 */
  background-color: pink;
}

6、display有哪些值?说明他们的作用。

(1)block     块类型。默认宽度为父元素宽度,可设置宽高,换行显示。
(2)none     缺省值。象行内元素类型一样显示。
(3)inline     行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。
(4)inline-block     默认宽度为内容宽度,可以设置宽高,同行显示。
(5)list-item     象块类型元素一样显示,并添加样式列表标记。
(6)table     此元素会作为块级表格来显示。
(7)inherit     规定应该从父元素继承 display 属性的值。

7、position的值relative和absolute定位原点是?

(1)absolute:生成绝对定位的元素,相对于值不为 static的第一个父元素进行定位。
(2)fixed(老IE不支持):生成绝对定位的元素,相对于浏览器窗口进行定位。
(3)relative:生成相对定位的元素,相对于其正常位置进行定位。
(4)static:默认值,没有定位,元素出现在正常的流中(忽略 top, bottom, left, right z-index 声明)。
(5)inherit:规定从父元素继承 position 属性的值。

8、CSS3有哪些新特性?

(1)新增各种CSS选择器    (: not(.input):所有 class 不是“input”的节点)
(2)圆角    (border-radius:8px)
(3)多列布局    (multi-column layout)
(4)阴影和反射    (Shadow\Reflect)
(5)文字特效    (text-shadow、)
(6)文字渲染    (Text-decoration)
(7)线性渐变    (gradient)
(8)旋转    (transform)
(9)缩放,定位,倾斜,动画,多背景
例如:transform:\scale(0.85,0.90)\ translate(0px,-30px)\ skew(-9deg,0deg)\Animation:

9、请解释一下CSS3的Flexbox(弹性盒布局模型),以及适用场景?

(1)一个用于页面布局的全新CSS3功能,Flexbox可以把列表放在同一个方向(从上到下排列,从左到右),并让列表能延伸到占用可用的空间。
(2)较为复杂的布局还可以通过嵌套一个伸缩容器(flex container)来实现。
(3)采用Flex布局的元素,称为Flex容器(flex container),简称"容器"。
(4)它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称"项目"。
(5)常规布局是基于块和内联流方向,而Flex布局是基于flex-flow流可以很方便的用来做局中,能对不同屏幕大小自适应。
(6)在布局上有了比以前更加灵活的空间。
使用指南

10、用纯CSS创建一个三角形的原理是什么?

把上、左、右三条边框隐藏掉(颜色设为 transparent)

div {
  height: 0;
  width: 0;
  display: block;
  border: transparent solid 20px;
  border-left: #005AA0 solid 20px;
}

11、一个满屏 品 字布局 如何设计?

简单的方式:上面的div宽100%,下面的两个div分别宽50%,然后用float或者inline使其不换行即可

12、css多列等高如何实现?

利用padding-bottom|margin-bottom正负值相抵;设置父容器设置超出隐藏(overflow:hidden),这样子父容器的高度就还是它里面的列没有设定padding-bottom时的高度,当它里面的任 一列高度增加了,则父容器的高度被撑到里面最高那列的高度,其他比这列矮的列会用它们的padding-bottom补偿这部分高度差。

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>CSS</title>
    <style type="text/css">
      * {
        margin: 0;
        padding: 0;
        font-size: 12px;
      }
      #wrap {
        overflow: hidden;
        width: 670px;
        margin: 10px auto;
        padding-bottom: 10px;
        position: relative;
      }
      .box {
        float: left;
        display: inline;
        margin-top: 10px;
        width: 190px;
        background: #c8c8c8;
        margin-left: 10px;
        padding: 10px;
        padding-bottom: 820px;
        margin-bottom: -800px;
      }
    </style>
  </head>
  <body>
    <div id="wrap">
      <div class="box">
        <h1>CSS实现三列DIV等高布局</h1>
        <p>
          这确实是个很简单的问题,也许你也已经相当熟悉,但很多人还不知道。 下面介绍的技术是一个简捷的小技巧,它一定可以帮助你解决这个头痛的问题。
        </p>
      </div>
      <div class="box">
        <h1>三列DIV等高</h1>
        <p></p>
      </div>
      <div class="box">
        <h1>CSS实现</h1>
        <p></p>
      </div>
    </div>
  </body>
</html>

13、li与li之间有看不见的空白间隔是什么原因引起的?有什么解决办法?

行框的排列会受到中间空白(回车\空格)等的影响,因为空格也属于字符,这些空白也会被应用样式,占据空间,所以会有间隔,把字符大小设为0,就没有空格了。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      ul {
        list-style: none;
        /*解决空格问题*/
        font-size: 0;
      }
      li {
        display: inline-block;
        height: 70px;
        width: 150px;
        line-height: 70px;
        text-align: center;
        border: #005AA0 solid 2px;
        /*解决空格问题*/
        font-size: 16px;
      }
    </style>
  </head>
  <body>
    <ul>
      <li>li标签</li>
      <li>li标签</li>
      <li>li标签</li>
    </ul>
  </body>
</html>

14、为什么要初始化CSS样式?

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。当然,初始化样式会对SEO有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下初始化。

15、子class选择器设置蓝色,父id选择器设置红色,最终会显示哪个?

一定是子元素设置的选择器最优先,要不然页面就乱套了。所以答案是蓝色。

16、请解释一下为什么需要清除浮动?清除浮动的方式

清除浮动是为了清除使用浮动元素产生的影响。浮动的元素,高度会塌陷,而高度的塌陷使我们页面后面的布局不能正常显示。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      .box {
        width: 510px;
        border: #0000FF solid 1px;
      }
      .box:after {
        content: "";
        visibility: hidden;
        display: block;
        height: 0;
        clear: both;
      }
      .left {
        float: left;
        width: 250px;
        height: 100px;
        background-color: green;
      }
      .right {
        float: left;
        width: 250px;
        height: 100px;
        background-color: red;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="left"></div>
      <div class="right"></div>
    </div>
  </body>
</html>

解析原理:
(1) display:block 使生成的元素以块级元素显示,占满剩余空间
(2)height:0 避免生成内容破坏原有布局的高度
(3) visibility:hidden 使生成的内容不可见,并允许可能被生成内容盖住的内容可以进行点击和交互
(4)通过 content:"."生成内容作为最后一个元素,至于content里面是点还是其他都是可以的,例如oocss里面就有经典的 content:"."有些版本可能content 里面内容为空,一丝冰凉是不推荐这样做的
(5)zoom:1 触发IE hasLayout。

17、什么是外边距合并?

外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距,合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。

18、CSS优化、提高性能的方法有哪些?

(1)关键选择器(key selector),选择器的最后面的部分为关键选择器(即用来匹配目标元素的部分);
(2)如果规则拥有 ID 选择器作为其关键选择器,则不要为规则增加标签,过滤掉无关的规则(这样样式系统就不会浪费时间去匹配它们了);
(3)提取项目的通用公有样式,增强可复用性,按模块编写组件;
(4)增强项目的协同开发性、可维护性和可扩展性;
(5)使用预处理工具或构建工具(gulp对css进行语法检查、自动补前缀、打包压缩、自动优雅降级);

19、浏览器是怎样解析CSS选择器的?

样式系统从关键选择器开始匹配,然后左移查找规则选择器的祖先元素。只要选择器的子树一直在工作,样式系统就会持续左移,直到和规则匹配,或者是因为不匹配而放弃该规则。

20、设置元素浮动后,该元素的display值是多少?

自动变成了 display:block

21、让页面里的字体变清晰,变细用CSS怎么做?

-webkit-font-smoothing: antialiased;

22、什么是CSS 预处理器 / 后处理器?

(1)预处理器:LESS、Sass、Stylus,用来预编译Sass或less,增强了css代码的复用性,还有层级、mixin、变量、循环、函数等,具有很方便的UI组件模块化开发能力,极大的提高工作效率。
(2)后处理器:PostCSS,通常被视为在完成的样式表中根据CSS规范处理CSS,让其更有效;目前最常做的是给CSS属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。

23、em和rem的区别在哪里?

(1)em是相对长度单位,相对于当前对象内文本的字体尺寸;
(2)em是CSS3新增的一个相对单位(root em,根em),这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。

24、假设高度已知,请写出三栏布局,其中左栏和右栏宽度各为300px,中间自适应

(1)利用浮动

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.float .left {
        float: left;
        width: 300px;
        background: red;
      }
      .layout.float .right {
        float: right;
        width: 300px;
        background: blue;
      }
      .layout.float .center {
        background: yellow;
      }
    </style>
  </head>
  <body>
    <section class="layout float">
      <article class="left-right-center">
        <div class="left"></div>
        <div class="right"></div>
        <div class="center">
          <h1>浮动解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
      </article>
    </section>
  </body>
</html>

(2)利用绝对定位

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.absolute .left-center-right>div {
        position: absolute;
      }
      .layout.absolute .left {
        left: 0;
        width: 300px;
        background: red;
      }
      .layout.absolute .center {
        left: 310px;
        right: 310px;
        background: yellow;
      }
      .layout.absolute .right {
        right: 0;
        width: 300px;
        background: blue;
      }
    </style>
  </head>
  <body>
    <section class="layout absolute">
      <article class="left-center-right">
        <div class="left"></div>
        <div class="center">
          <h1>绝对定位解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
        <div class="right"></div>
      </article>
    </section>
  </body>
</html>

(3)利用flexbox布局

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.flexbox .left-center-right {
        display: flex;
      }
      .layout.flexbox .left {
        width: 300px;
        background: red;
      }
      .layout.flexbox .center {
        flex: 1;
        background: green;
      }
      .layout.flexbox .right {
        width: 300px;
        background: yellow;
      }
    </style>
  </head>
  <body>
    <section class="layout flexbox">
      <article class="left-center-right">
        <div class="left"></div>
        <div class="center">
          <h1>flexbox解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
        <div class="right"></div>
      </article>
    </section>
  </body>
</html>

(4)表格布局法

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.table .left-center-right {
        width: 100%;
        display: table;
        height: 100px;
      }
      .layout.table .left-center-right>div {
        display: table-cell;
      }
      .layout.table .left {
        width: 300px;
        background: black;
      }
      .layout.table .center {
        background: green;
      }
      .layout.table .right {
        width: 300px;
        background: burlywood;
      }
    </style>
  </head>
  <body>
    <section class="layout table">
      <article class="left-center-right">
        <div class="left"></div>
        <div class="center">
          <h1>表格布局解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
        <div class="right"></div>
      </article>
    </section>
  </body>
</html>

25、什么是BFC?它的渲染规则是什么?如何创建BFC?它的使用场景有哪些?

(1)概念:
BFC是Block Formatting Context (块级格式化上下文)的缩写,它是W3C CSS 2.1 规范中的一个概念,是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。
(2)渲染规则:

  • 内部的box会在垂直方向,一个接一个的放置;
  • box垂直方向的距离由margin决定,属于同一个BFC的两个相邻box的margin会发生重叠;
  • 每个元素的margin box的左边,与包含块border box的左边相接触,即使存在浮动也是如此;
  • BFC的区域不会与float box重叠;
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素;
  • 计算BFC的高度时,浮动元素也参与计算。
    (3)创建BFC:
  • float属性不为none;
  • position属性值为absolute或fixed;
  • display属性值为inline-block,table-cell,table-caption,flex,inline-flex;
  • overflow属性值不为visible。
    (4)使用场景:
  • 解决垂直方向边距重叠;
  • 解决和浮动元素的重叠;
  • 清除浮动

26、CSS中的文档流

普通流就是正常的文档流,在HTML里面的写法就是从上到下,从左到右的排版布局。其中涉及到了块状元素和内联元素。脱离文档流的几个属性:绝对定位(absolute)、固定定位(fixed)、浮动(float)。

27、CSS中的伪类

(1)概念:伪类存在的意义是为了通过选择器找到那些不存在与DOM树中的信息以及不能被常规CSS选择器获取到的信息。
(2)拓展:伪类由一个冒号:开头,冒号后面是伪类的名称和包含在圆括号中的可选参数。任何常规选择器可以再任何位置使用伪类。伪类语法不区别大小写。一些伪类的作用会互斥,另外一些伪类可以同时被同一个元素使用。并且,为了满足用户在操作DOM时产生的DOM结构改变,伪类也可以是动态的。

28、CSS中的伪元素

(1)概念:伪元素在DOM树中创建了一些抽象元素,这些抽象元素是不存在于文档语言里的(可以理解为html源码)。比如:document接口不提供访问元素内容的第一个字或者第一行的机制,而伪元素可以使开发者可以提取到这些信息。并且,一些伪元素可以使开发者获取到不存在于源文档中的内容(比如常见的::before,::after)。
(2)拓展:伪元素的由两个冒号::开头,然后是伪元素的名称。使用两个冒号::是为了区别伪类和伪元素(CSS2中并没有区别)。当然,考虑到兼容性,CSS2中已存的伪元素仍然可以使用一个冒号:的语法,但是CSS3中新增的伪元素必须使用两个冒号::。一个选择器只能使用一个伪元素,并且伪元素必须处于选择器语句的最后。
(3)分类:::first-letter;::first-line;::before;::after;::selection;(对用户所选取的部分样式改变)

29、伪元素和伪类的区别

(1)CSS伪类:用于向某些选择器添加特殊的效果。
(2)CSS伪元素:用于将特殊的效果添加到某些选择器。伪元素代表了某个元素的子元素,这个子元素虽然在逻辑上存在,但却并不实际存在于文档树中。
(3)总结:

  • 伪类本质上是为了弥补常规CSS选择器的不足,以便获取到更多信息;
  • 伪元素本质上是创建了一个有内容的虚拟容器;
  • CSS3中伪类和伪元素的语法不同;
  • 可以同时使用多个伪类,而只能同时使用一个伪元素。

30、CSS隐藏元素的几种方法

(1)Opacity:元素本身依然占据它自己的位置并对网页的布局起作用。它也将响应用户交互;
(2)Visibility:与 opacity 唯一不同的是它不会响应任何用户交互。此外,元素在读屏软件中也会被隐藏;
(3)Display:display 设为 none 任何对该元素直接打用户交互操作都不可能生效。此外,读屏软件也不会读到元素的内容。这种方式产生的效果就像元素完全不存在;
(4)Position:不会影响布局,能让元素保持可以操作;

31、float和display:inline-block;的区别

(1)文档流(Document flow):
浮动元素会脱离文档流,并使得周围元素环绕这个元素。而inline-block元素仍在文档流内。因此设置inline-block不需要清除浮动。当然,周围元素不会环绕这个元素,你也不可能通过清除inline-block就让一个元素跑到下面去。
(2)水平位置(Horizontal position):
很明显你不能通过给父元素设置text- align:center让浮动元素居中。事实上定位类属性设置到父元素上,均不会影响父元素内浮动的元素。但是父元素内元素如果设置了 display:inline-block,则对父元素设置一些定位属性会影响到子元素。(这还是因为浮动元素脱离文档流的关系)。
(3)垂直对齐(Vertical alignment):
inline-block元素沿着默认的基线对齐。浮动元素紧贴顶部。你可以通过vertical属性设置这个默认基线,但对浮动元素这种方法就不行了。这也是我倾向于inline-block的主要原因。
(4)空白(Whitespace):
inline-block包含html空白节点。如果你的html中一系列元素每个元素之间都换行了,当你对这些元素设置inline-block时,这些元素之间就会出现空白。而浮动元素会忽略空白节点,互相紧贴。

32、关于空白节点的解决方案

(1)删除html中的空白:
不要让元素之间换行,这可能比较蛋疼,但也是一种方法,特别是你元素不多的时候。
(2)使用负边距:
你可以用负边距来补齐空白。但你需要调整font-size,因为空白的宽度与这个属性有关系。
(3)给父元素设置font-size:0:
不管空白多大,由于空白跟font-size的关系,设置这个属性即可把空白的宽度设置为0.在实际使用的时候,你还需要给子元素重新设置font-size。

33、如何解决图片与文字的不对齐

(1)vertical-align:最有效的一种方式;
(2)margin: 需要不断调试图片的高度,精确度难以保证;
(3)position:同样是需要不断调试图片的高度,精确度难以保证。

34、如何实现点击radio的文字描述控制radio的状态

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <label for="man">男</label>
    <input type="radio" id="man" name="sex" checked="checked" />

    <label for="women">
	女
       <input type="radio" id="women" name="sex" />
    </label>
  </body>
</html>

35、position有哪些常用定位属性?定位原点是基于哪个位置?

(1)relative :相对定位,没有脱离文档流,依然占有文档空间,它是根据自身原本在文档流中的位置进行偏移;
(2)absolute:绝对定位,脱离文档流,根据祖先类元素(父类以上)进行定位,而这个祖先类还必须是以position非static方式定位的,如果无父级是position非static定位时是以html作为原点定位。
(3)fixed:固定定位,脱离文档流,根据浏览器窗口进行定位。

36、介绍一下box-sizing属性

(1)box-sizing属性主要用来控制元素的盒模型的解析模式。默认值是content-box。
(2)content-box:让元素维持W3C的标准盒模型。元素的宽度/高度由border + padding + content的宽度/高度决定,设置width/height属性指的是content部分的宽/高
(3)border-box:让元素维持IE传统盒模型(IE6以下版本和IE6~7的怪异模式)。设置width/height属性指的是border + padding + content

37、解释下浮动和它的工作原理?清除浮动的技巧

(1)原理:浮动元素脱离文档流,不占据空间,浮动元素碰到包含它的边框或者浮动元素的边框停留。
(2)使用空标签清除浮动:这种方法是在所有浮动标签后面添加一个空标签定义css clear:both. 弊端就是增加了无意义标签。
(3)使用overflow:给包含浮动元素的父标签添加css属性 overflow:auto; zoom:1; zoom:1用于兼容IE6。
(4)使用after伪对象清除浮动:该方法只适用于非IE浏览器。

#parent:after {
  content: ".";
  height: 0;
  visibility: hidden;
  display: block;
  clear: both;
}

38、浮动元素引起的问题

(1)父元素的高度无法被撑开,影响与父元素同级的元素
(2)与浮动元素同级的非浮动元素(内联元素)会跟随其后
(3)若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构

39、img的白边是因为什么

原因在于,img标签默认情况下display:inline-block;img在div中的白边就是因为inline-block;造成的,所以此时将img的display设置为block;白边就消失了

40、px、pt和em、rem的区别是什么?

(1)px(pixel)指的是像素,是屏幕上显示数据的最基本的点,表示相对大小。不同分辨率下相同长度的px元素显示会不一样,比如同样是14px大小的字,在1366*768显示屏下会显示的小,在1024*768尺寸的显示器下会相对大点。
(2)pt(point)是印刷行业常用的单位,等于1/72英寸,表示绝对长度。
(3)em是相对长度单位,相对于当前对象内文本的字体尺寸,即em的计算是基于父级元素font-size的。

<body style="font-size:14px">
  <p style="font-size:2em">我这里的字体显示大小是28px(14px*2)</p>
  <div style="font-size:18px">
    <p style="font-size:2em">我这里显示字体大小是36px(18px*2),而不是上面计算的28px</p>
  </div>
</body>

(4)rem是css3新增的一个相对单位,与em的区别在于,它是相对于html根元素的。

<body style="font-size:14px">
  <p style="font-size:2rem">我这里的字体显示大小是28px(14px*2)</p>
  <div style="font-size:18px">
    <p style="font-size:2rem">我这里显示字体大小是28px(14px*2),因为我是根据html根元素的font-size大小进行计算的</p>
  </div>
</body>

41、谈谈nth-of-type() 选择器

:nth-of-type(n) 选择器匹配属于父元素的特定类型的第 N 个子元素的每个元素,n 可以是数字、关键词或公式。其中,Odd 和 even 是可用于匹配下标是奇数或偶数的子元素的关键词(第一个子元素的下标是 1)。

p:nth-of-type(2) {
  background: #ff0000;
}

p:nth-of-type(odd) {
  background: #ff0000;
}
p:nth-of-type(even) {
  background: #0000ff;
}

p:nth-of-type(3n+0) {
  background: #ff0000;
}

JavaScript

1、JavaScript对数组的操作方法有哪些?

(1)join()方法:接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串
(2)push()方法:接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度
(3)pop()方法:从数组末尾移除最后一项,减少数组的length值,然后返回移除的项
(4)shift()方法:移除数组中第一个项并返回改项,同时将数组长度减1
(5)unshift()方法:在数组前端添加任意个项并返回新数组的长度
(6)sort()方法:对数组中的数据进行排序
(7)concat()方法:可以基于当前数组中的所有项创建一个新数组
(8)slice()方法:基于当前数组中的一或多个项创建一个新数组
(9)indexOf()方法:从数组的开头(位置0)开始向后查找
(10)lastIndexOf():从数组的末尾开始向前查找
(11)splice()方法:恐怕要算是最强大的数组方法了,它有很多种用法,splice()的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下3种:

  • 删除:可以删除任意数量的项,只需指定2个参数(要删除的第一项的位置和要删除的项数),例如:splice(0,2)会删除数组中的前两项
  • 插入:可以向指定位置插入任意数量的项,只需要提供3个参数(起始位置,0(要删除的项数)和要插入的项),例如:splice(2,0,"red","green")会从当前数组的位置2开始插入字符串"red"和"green"
  • 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需要提供3个参数(起始位置,要删除的项数和要插入的任意数量的项),例如:splice(2,1,"red","green")会删除当前数组位置2的项,然后再从位置2开始插入字符串"red"和"green";
    原文地址

数组面试题
(1)计算给定数组arr中所有元素的总和

function sum(arr) {
  var result = 0;
  for (var i = 0; i < arr.length; i++) {
    result += arr[i];
  }
  return result;
}
var arr = [4,5,6,1];
console.log(sum(arr));

(2)合并数组 arr1 和数组 arr2,不要直接修改数组 arr,结果返回新的数组

function concat(arr1, arr2) {
  var arr3 = arr1.concat(arr2);
  return arr3;
}
var Arr1 = [4, 5, 2];
var Arr2 = [7, 8, 3];
console.log(concat(Arr1, Arr2));

(3)删除数组 arr 第一个元素,不要直接修改数组 arr,结果返回新的数组

function curtail(arr) {
  var arr2 = arr.slice(0);
  arr2.shift();
  return arr2;
}
var Arr = [1, 2, 34, 5];
console.log(curtail(Arr));

(4)在数组 arr 开头添加元素 item,不要直接修改数组 arr,结果返回新的数组

function prepend(arr, item) {
  var arr2 = arr.slice(0);
  arr2.unshift(item);
  return arr2;
}

(5)移除数组 arr 中的所有值与 item 相等的元素,直接在给定的 arr 数组上进行操作,并将结果返回

function removeWithoutCopy(arr, item) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] == item) {
      arr.splice(i, 1);
      i--;
    }
  }
  return arr;
}
var Arr = [3, 4, 5, 6, 6, 6, 1];
console.log(removeWithoutCopy(Arr, 6));

(6)找出元素 item 在给定数组 arr 中的位置

function indexOf(arr, item) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] == item) {
      return i;
    }
  }
  return -1;
}

2、JavaScript如何设置获取盒模型对应的宽和高?

(1)dom.style.width/height(只能获取到内联样式的宽和高,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <div id="box" style="height: 200px;"></div>
    <script>
      var getHeight = document.getElementById("box");
      console.log(getHeight.style.height);
    </script>
  </body>
</html>

(2)dom.currentStyle.width/height(只有IE浏览器支持,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(getHeight.currentStyle.height);
    </script>
  </body>
</html>

(3)window.getComputedStyle(dom).width/height(兼容性好,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(window.getComputedStyle(getHeight).height);
    </script>
  </body>
</html>

(4)dom.getBoundingClientRect().width/height(兼容性好,输出值不带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(getHeight.getBoundingClientRect().height);
    </script>
  </body>
</html>

3、DOM事件的级别

(1)DOM0:element.onclick = function(){}
(2)DOM2:element.addEventListener('click',function(){},false)
【拓展】
所谓的0级dom与2级dom事件就是不同版本间的差异,具体的说就是,对于不同的dom级别,如何定义事件处理,以及使用时有什么不同。 比如在dom0级事件处理中,后定义的事件会覆盖前面的,但是dom2级事件处理中,对一个按钮点击的时间处理就没有被覆盖掉。
(3)DOM3:element.addEventListener('keyup',function(){},false)
(4)建议结合红宝书第6页

4、DOM事件模型

(1)冒泡型事件处理模型(Bubbling):冒泡型事件处理模型在事件发生时,首先在最精确的元素上触发,然后向上传播,直到根节点,反映到DOM树上就是事件从叶子节点传播到根节点。
(2)捕获型事件处理模型(Captrue):相反地,捕获型在事件发生时首先在最顶级的元素上触发,传播到最低级的元素上,在DOM树上的表现就是由根节点传播到叶子节点。
【捕获事件的具体流程】
window==>document==>html==>body==>父级元素==>目标元素
(3)标准的事件处理模型分为三个阶段:

  • 父元素中所有的捕捉型事件(如果有)自上而下地执行
  • 目标元素的冒泡型事件(如果有)
  • 父元素中所有的冒泡型事件(如果有)自下而上地执行
    (4)建议结合红宝书第345页

5、Event对象的常见应用

(1)event.preventDefault():阻止事件的默认行为
(2)event.stopPropagation():阻止事件的进一步传播,也就是阻止冒泡
(3)event.stopImmediatePropagation():阻止剩余的事件处理函数的执行,并防止当前事件在DOM树上冒泡。
(4)event.currentTarget:返回绑定事件的元素
(5)event.target:返回触发事件的元素

6、JavaScript自定义事件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Event</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="ev">
      <span>目标元素</span>
    </div>
    <script>
      var event = new Event('test');
      ev.addEventListener('test', function () {
        console.log('test dispatch');
      })
      ev.dispatchEvent(event);
    </script>
  </body>
</html>

7、JavaScript类的声明

// 1、类的声明
function Animal() {
  this.name = "name";
}
// 2、ES6中类的声明
class Animal2 {
  constructor() {
    this.name = name;
  }
}
// 3、实例化类
console.log(new Animal(), new Animal2());

8、JavaScript类之间的继承

(1)借助构造函数实现不完全继承,无法继承方法:

function Parent1() {
  this.name = 'parent1';
}
function Child1() {
  Parent1.call(this);
  this.type = 'child1';
}
console.log(new Child1());

(2)借助原型链实现继承,所有的属性和方法都得去原型链上去找,因而找到的属性方法都是同一个,所以直接利用原型链继承是不现实的。

function Parent2() {
  this.name = 'parent2';
  this.play = [1, 2, 3];
}
function Child2() {
  this.type = 'child2';
}
Child2.prototype = new Parent2();
console.log(new Child2());
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
s1.play.push(4);

(3)组合方式实现继承

function Parent3() {
  this.name = 'parent3';
  this.play = [1, 2, 3];
}
function Child3() {
  Parent3.call(this);
  this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);

(4)组合继承的优化1

function Parent4() {
  this.name = 'parent4';
  this.play = [1, 2, 3];
}
function Child4() {
  Parent4.call(this);
  this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5.play, s6.play);
console.log(s5 instanceof Child4, s5 instanceof Parent4);
console.log(s5.constructor);

(5)组合继承的优化2(俗称寄生式继承)

function Parent5() {
  this.name = 'parent5';
  this.play = [1, 2, 3];
}
function Child5() {
  Parent5.call(this);
  this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
var s7 = new Child5();
console.log(s7 instanceof Child5, s7 instanceof Parent5);
console.log(s7.constructor);

9、创建对象有几种方法?

(1)字面量:

var k1 = {
  name: 'k1'
};
var k2 = new Object({
  name: 'k2'
});

(2)通过构造函数

var m = function (name) {
  this.name = name;
};
var k3 = new m('k3');

(3)通过Object.create

var p = {
  name: 'ppp'
};
var k4 = Object.create(p);

11、什么是原型和原型链?有什么特点?

(1)每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象不存在这个属性,那么就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
(2)特点:JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的话,就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象。
(3)更多原型知识1

12、JavaScript的运行机制

(1)JavaScript是单线程,一个时间段内,JavaScript只能干一件事情。任务队列分为同步任务和异步任务。
(2)异步任务类型:setTimeout和setInterval、DOM事件、ES6中的Promise

13、JavaScript异步加载的方式

(1)动态脚本加载;
(2)defer
(3)async

14、JavaScript的typeof返回哪些数据类型?

Object number function boolean underfind、String;
【拓展】
比较混淆的是:介绍JavaScript的基本数据类型
答案为:Undefined、Null、Boolean、Number、String、Symbol(创建后独一无二且不可变的数据类型

15、JavaScript中的事件委托是什么?

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

16、为什么要使用JavaScript的事件委托?

为了减少代码的DOM操作,提高程序性能。更多详情

17、JavaScript中的闭包

(1)有权访问另一个函数作用域内变量的函数都是闭包。
(2)闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样。
(3)更多

18、window.onload和DOMContentLoaded的区别是?

window.addEventListener('load',function(){
    //页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener('DOMContentLoaded',function(){
    //DOM渲染完即可执行,此时图片、视频还可能没有加载完
})

19、用JavaScript创建10个标签,点击的时候弹出来对应的序号

var i;
for (i = 0; i < 10; i++) {
  (function (i) {
    var a = document.createElement('a');
    a.innerHTML = i + '<br>';
    a.addEventListener('click', function (e) {
      e.preventDefault();
      alert(i);
    });
    document.body.appendChild(a);
  })(i)
}

20、实现数组的随机排序

Array.prototype.shuffle = function () {
  var input = this;
  for (var i = input.length - 1; i >= 0; i--) {
    var randomIndex = Math.floor(Math.random() * (i + 1));
    var itemAtIndex = input[randomIndex];
    input[randomIndex] = input[i];
    input[i] = itemAtIndex;
  }
  return input;
}
var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
tempArray.shuffle();
console.log(tempArray);

21、JavaScript中有哪些内置函数,与内置对象的区别是什么?

(1)内置函数:是浏览器内核自带的,不用任何函数库引入就可以直接使用的函数,如常规函数(alert等)、数组函数(reverse等)、日期函数(getDate等)、数学函数(floor等)、字符串函数(length等);
(2)内置对象:是浏览器本身自带的,内置对象中往往包含了内置函数。内置对象有Object、Array、Boolean、Number、String、Function、Date、RegExp等。

22、JavaScript变量按照存储方式区分为哪些类型,并描述其特点

//值类型
//变量的交换,按值访问,操作的是他们实际保存的值。
//等于在一个新的地方按照新的标准开了一个空间(栈内存),
//这样a的值对b的值没有任何影响
var a = 100;
var b = a;
b = 200;
console.log("a:" + a + ",b:" + b);
//引用类型
//变量的交换,当查询时,我们需要先从栈中读取内存地址,
//然后再顺藤摸瓜地找到保存在堆内存中的值;
//发现当复制的是对象,那么obj1和obj2两个对象被串联起来了,
//obj1变量里的属性被改变时候,obj2的属性也被修改。
var obj1 = {
  x: 100
};
var obj2 = obj1;
obj2.x = 200;
console.log("obj1:" + obj1.x + ",obj2:" + obj2.x);

23、如何理解JSON?

其实JSON只不过是一个JavaScript对象而已。stringify()是把对象变成字符串;parse()是把字符串变成对象。

24、JavaScript中&&和||

(1)只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值;
(2)只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。
(3)且在js逻辑运算中,0、”“、null、false、undefined、NaN都会判为false,其他都为true。

(4)只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值;
(5)只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值。

25、描述new一个对象的过程

(1)创建空对象:var obj = {};
(2)设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象:obj.proto = ClassA.prototype;
(3)使用新对象调用函数,函数中的this被指向新实例对象:ClassA.call(obj); //{}.构造函数();
(4)将初始化完毕的新对象地址,保存到等号左边的变量中
【注意】
若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象;若返回值是引用类型的值,则实际返回值为这个引用类型。

26、什么是构造函数?

(1)构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。
(2)注意事项:

  • 默认函数首字母大写;
  • 构造函数并没有显示返回任何东西。new 操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型;
  • 也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回。如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回;
  • 因为构造函数也是函数,所以可以直接被调用,但是它的返回值为undefine,此时构造函数里面的this对象等于全局this对象。this.name其实就是创建一个全局的变量name。在严格模式下,当你补通过new 调用Person构造函数会出现错误;

27、函数声明和函数表达式的区别

//成功
fn()
function fn() {
  console.log("函数声明,全局");
}
//报错
fn1()
var fun1 = function () {
  console.log("函数表达式,局部")
}

28、说一下对变量提升的理解

(1)顾名思义,就是把下面的东西提到上面。在JS中,就是把定义在后面的东东(变量或函数)提升到前面中定义。

var v = 'Hello World';
(function () {
  alert(v); //undefined
  var v = 'I love you';
})()

(2)根据上面变量提升原件以及js的作用域(块级作用域)的分析,得知 上面代码真正变成如下:

var v = 'Hello World';
(function () {
  var v;
  alert(v);
  v = 'I love you';
})()

(3)在我们写js code 的时候,我们有2中写法,一种是函数表达式,另外一种是函数声明方式。我们需要重点注意的是,只有函数声明形式才能被提升。

//成功
function myTest() {
  foo();
  function foo() {
    alert("我来自 foo");
  }
}
myTest();
//失败
function myTest() {
  foo();
  var foo = function foo() {
    alert("我来自 foo");
  }
}
myTest();

29、说一下this几种不同的使用场景

(1)作为构造函数执行,如果函数创建的目的是使用new来调用,并产生一个对象,那么此函数被称为构造器函数;

var Niu = function (string) {
  this.name = string;
};

(2)作为对象属性执行,对象成员方法中的this是对象本身,此时跟其他语言是一致的,但是也有差异,JavaScript中的this到对象的绑定发生在函数调用的时候;

var myObj = {
  value: 0,
  increment: function (inc) {
    this.value += typeof inc === 'number' ? inc : 1;
  }
};
myObj.increment(); //1
myObj.increment(2); //3

(3)作为普通函数执行,以普通方式定义的函数中的this:会被自动绑定到全局对象;

var value = 232;
function toStr() {  
  console.log(this.value);
}

(4)对象方法中闭包函数的this

//在以普通方式定义的函数中的this会被自动绑定到全局对象上,
//大家应该可以看出闭包函数定义也与普通方式无异,因此他也会被绑定到全局对象上。
value = 10;
var closureThis = {
  value: 0,
  acc: function () {
    var helper = function () {
      this.value += 2;
      console.log("this.value : %d", this.value);
    }
    helper();
  }
};
closureThis.acc(); //12
closureThis.acc(); //14

var closureThat = {
  value: 0,
  acc: function () {
    that = this;
    var helper = function () {
      that.value += 2;
      console.log("that.value : %d", that.value);
    }
    helper();
  }
};
closureThat.acc(); // 2
closureThat.acc(); // 4

(5)apply函数的参数this,apply方法允许我们选择一个this值作为第一个参数传递,第二个参数是一个数组,表明可以传递多个参数。

30、如何理解JavaScript作用域?

函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。

function foo() {
  var x = 1;
  return function () {
    alert(x);
  }
};
var bar = foo();
bar(); // 1
var x = 2;
bar(); // 1

31、什么是自由变量?

自由变量就是当前作用域没有定义的变量,即“自由变量”

var a = 100;
function F1() {
  var b = 200;
  function F2() {
    var c = 300;
    //a是自由变量
    console.log(a);
    //b是自由变量
    console.log(b);
    console.log(c);
  }
  F2();
}
F1();

32、this的特点

this要在执行时才能确认值,定义时无法确认。

33、同步和异步的区别是什么?分别举一个同步和异步的例子

同步会阻塞代码执行,而异步不会;alert是同步,setTimeout是异步。

33、一个关于setTimeout的笔试题

//输出结果13542
console.log(1);
setTimeout(function () {
  console.log(2);
}, 100);
console.log(3);
setTimeout(function () {
  console.log(4);
}, 99);
console.log(5)

34、何时需要异步?

在可能发生等待的情况、等待过程中不能像alert一样阻塞程序运行、因此所有的等待的情况都需要异步

35、JavaScript中的异步和单线程

(1)其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
(2)js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。

36、使用JavaScript获取当天的日期

function formatDate(dt) {
  if (!dt) {
    dt = new Date();
  }
  var year = dt.getFullYear();
  var month = dt.getMonth() + 1;
  var date = dt.getDate();
  if (month < 10) {
    month = "0" + month;
  }
  if (date < 10) {
    date = "0" + date;
  }
  return year + "-" + month + "-" + date;
}
var dt = new Date();
var formatDate = formatDate(dt);
console.log(formatDate);

37、获取随机数,要求是长度一致的字符串

var random = (Math.random() * 10 + "0000000000").slice(0, 10);
console.log(random);

38、写一个能遍历对象和数组的通用forEach函数

function forEach(obj, fn) {
  if (obj instanceof Array) {
    //准确判断是不是数组
    obj.forEach(function (item, index) {
      fn(index, item);
    })
  } else {
    //不是数组就是对象
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        fn(key, obj[key]);
      }
    }
  }
}
//检测数组
var arr = [1, 2, 3];
//这里顺序换了,为了和对象的遍历格式一致
forEach(arr, function (index, item) {
  console.log(index, item);
});
//检测对象
var obj = {
  x: 100,
  y: 200
};
forEach(obj, function (key, value) {
  console.log(key, value);
})

39、DOM操作的常用API有哪些?

获取DOM节点,以及节点的property和Attribute;获取父节点和子节点;新增节点和删除节点

40、DOM节点的attr和property有何区别?

property只是一个JavaScript对象的属性的修改;Attribute是对html标签属性的修改

41、DOM的本质

浏览器把拿到的HTML代码,结构化一个浏览器能识别并且js可以操作的一个模型而已。

42、手动编写一个ajax,不依赖第三方库

var xmlHttp = new XMLHttpRequest() || new ActiveXObject("Msxml2.XMLHTTP");
xmlHttp.open("提交方式", "提交地址", true);
xmlHttp.onreadystatechange = function () {
  if (xmlHttp.readyState == 4) {
    infoDiv.innerHTML = xmlHttp.responseText;
  }
}
xmlHttp.send();

更多ajax知识,请点击这里

43、什么是跨域?

(1)浏览器有同源策略,不允许ajax访问其他域接口。跨域条件(协议、域名、端口)有一个不同就算是跨域。
(2)可以跨域的三个标签(<img src=xxx>、<link href=xxx>、<script src=xxx>)。三个标签的应用场景(<img>用于打点统计,统计网站可能是其他域、<script>可以使用CDN,CDN的也是其他域、<script>可以用于JSOP)。
(3)跨域注意的事项:所有的跨域请求都必须经过信息提供方允许、如果未经过允许即可获取,那是浏览器同源策略出现漏洞。

44、跨域的几种实现方式

(1)通过jsonp跨域:在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
(2)通过修改document.domain来跨子域:
(3)使用window.name来进行跨域:
(4)使用HTML5中新引进的window.postMessage方法来跨域传送数据:
(5)更详细的内容

45、JavaScript实现字符串反转

(1)第一种方法

var str = "abcdef";
console.log(str.split("").reverse().join(""));

(2)第二种方法

var str = "abcdef";
var i = str.length - 1;
for (var x = i; x >= 0; x--) {
  document.write(str.charAt(x));
}

(3)第三种方法

function reverse(str) {
  if (str.length == 0) return null;
  var i = str.length;
  var dstr = "";
  while (--i >= 0) {
    dstr += str.charAt(i);
  }
  return dstr;
}
var str = "abcdef";
str = reverse(str);
document.write(str);

46、ajax中的get和post两种请求方式的异同

(1) get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到;post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
(2)对于get方式,服务器端用Request.QueryString获取变量的值;对于post方式,服务器端用Request.Form获取提交的数据。两种方式的参数都可以用Request来获得。
(3)get传送的数据量较小,不能大于2KB;post传送的数据量较大,一般被默认为不受限制,但理论上,因服务器的不同而异。
(4)get安全性非常低,post安全性较高。
(5)

跟是一样的,也就是说,method为get时action页面后边带的参数列表会被忽视;而跟是不一样的。

47、找出数字数组中最大的元素

var a = [111, 2, 6, 4, 22, 5, 99, 3];
console.log(Math.max.apply(null, a));

48、实现该语法的功能:var a = (5).plus(3).minus(6)

Number.prototype.plus = function (a) {
  return this.valueOf() + a;
}
Number.prototype.minus = function (a) {
  return this.valueOf() - a;
}
var a = (5).plus(3).minus(6);
console.log(a);

49、有一个大数组,var a = [‘1’,’2’,’3’,…];a数组的长度是100,内容填充随机整数的字符串,请先构造此数组a,然后设计一个算法,将其内容去重。

function Random(n) {
  var arr = [];
  for (var i = 0; i < n; i++) {
    arr[i] = parseInt(Math.random() * 100);
  }
  console.log(arr);
  return arr;
}
//使用indexof  这里也可以使用arr2的indexOf
function DeleRepeat1(arr) {
  var arr2 = [];
  var len = arr.length;
  for (var i = 0; i < len; i++) {
    if (arr.indexOf(arr[i]) == i) {
      arr2.push(arr[i])
    }
  }
  return arr2;
}
var arr = Random(100);
console.log(DeleRepeat1(arr));

50、返回只包含数字类型的数组,比如:’abc234koi45jodjnvi789’ –> [234,45,789]

var str = 'abc234koi45jodjnvi789';
var reg = /\d+/g;
console.log(str.match(reg));

51、slice数组的浅复制:向数组后添加一个元素,返回原数组不变,返回新数组。

function append(arr, item) {
  var arr2 = [];
  arr2 = arr.slice(0);
  arr2.push(item);
  return arr2;
}
var arr = [1, 2, 3, 4];
console.log(append(arr, 10)); //[1, 2, 3, 4,10]
console.log(arr) //[1, 2, 3, 4]

【解析】
如果定义 var arr2=arr的话,那么arr2指向了arr的引用。那么arr2改变也会伴随着arr改变。 而slice浅复制:arr数组并返回了一个新数组,与原数组没有关系了。

52、实现数组内容增添的函数

function insert(arr, item, index) {
  var arr2 = arr.concat();
  arr2.splice(index, 0, item);
  return arr2;
}
//[1,2,'z',3,4]
console.log(insert([1, 2, 3, 4], 'z', 2));

53、什么是同步?什么是异步

(1)同步:脚本会停留并等待服务器发送回复然后再继续
(2)异步:脚本允许页面继续其进程并处理可能的回复

54、documen.write和 innerHTML的区别是什么?

document.write重绘整个页面;innerHTML可以重绘页面的一部分。

55、介绍JavaScript有哪些内置对象?

(1)数据封装类对象:Object、Array、Boolean、Number 和 String
(2)其他对象:Function、Arguments、Math、Date、RegExp、Error

56、JavaScript的作用域链式什么?

全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链。

57、什么是window对象? 什么是document对象?

(1)window对象是指浏览器打开的窗口。
(2)document对象是Documentd对象(HTML 文档对象)的一个只读引用,window对象的一个属性。

58、null,undefined 的区别?

(1)null:表示一个对象是“没有值”的值,也就是值为“空”;
(2)undefined:表示一个变量声明了没有初始化(赋值);
【注意】
在验证null时,一定要使用 === ,因为 == 无法分别 null 和undefined

59、什么是闭包(closure),为什么要用它?

(1)闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
(2)闭包的特性:

  • 函数内再嵌套函数
  • 内部函数可以引用外层的参数和变量
  • 参数和变量不会被垃圾回收机制回收

60、如何判断一个对象是否属于某个类?

if(a instanceof Person){
  alert('yes');
}

61、new操作符具体干了什么呢?

(1)创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型;
(2)属性和方法被加入到 this 引用的对象中;
(3)新创建的对象由 this 所引用,并且最后隐式的返回 this 。
【栗子】

var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

62、Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

(1)javaScript中hasOwnProperty函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
(2)使用方法:

  • object.hasOwnProperty(proName)
  • 其中参数object是必选项。一个对象的实例。
  • proName是必选项。一个属性名称的字符串值。
  • 如果 object 具有指定名称的属性,那么JavaScript中hasOwnProperty函数方法返回 true,反之则返回 false。

63、对JSON 的了解?

(1)JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小,比如:{"age":"12", "name":"back"}
(2)JSON字符串转换为JSON对象:

var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);

(3)JSON对象转换为JSON字符串:

var last=obj.toJSONString();
var last=JSON.stringify(obj);

64、js延迟加载的方式有哪些?

defer和async、动态创建DOM方式(用得最多)、按需异步载入js。

65、异步加载JS的方式有哪些?

(1)defer,只支持IE;
(2)async:
(3)创建script,插入到DOM中,加载完毕后callBack

66、DOM操作——怎样添加、移除、移动、复制、创建和查找节点?

(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
(3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性

67、JQuery一个对象可以同时绑定多个事件,这是如何实现的?

(1)多个事件同一个函数:
$("div").on("click mouseover", function(){});
(2)多个事件不同函数

$("div").on({
  click: function(){},
  mouseover: function(){}
})

68、JavaScript中如何检测一个变量是一个String类型或者Array类型?请写出函数实现

var str = "hello";
if (typeof (str) === "string") {
  console.log("str is String");
}
var arr = [];
if (arr instanceof Array) {
  console.log("arr is Array");
}

69、prototype和_proto_的关系是什么

prototype和__proto__都指向原型对象,任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象,同样任意一个构造函数实例化的对象,都有一个__proto__属性(__proto__并非标准属性,ECMA-262第5版将该属性或指针称为[[Prototype]],可通过Object.getPrototypeOf()标准方法访问该属性),指向构造函数的原型对象。

70、ajax是什么?同步和异步的区别?

(1)ajax是一种无需重新加载整个网页的情况下,能够更新部分网页的技术。
(2)同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

71、事件委托是什么,举个例子

它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。那这是什么意思呢?网上的各位大牛们讲事件委托基本上都用了同一个例子,就是取快递来解释这个现象。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      ul li {
        display: block;
        height: 50px;
        width: 100px;
        text-align: center;
        line-height: 50px;
        background-color: #888;
        margin-bottom: 8px;
      }
    </style>
  </head>
  <body>
    <ul id="Inul">
      <li>111</li>
      <li>222</li>
      <li>333</li>
      <li>444</li>
      <li>555</li>
    </ul>
    <script>
      //不使用事件委托
      /*window.onload = function(){
      	var oUL = document.getElementById("Inul");
      	var aLi = oUL.getElementsByTagName("li");
      	for(var i=0;i<aLi.length;i++){
      	  aLi[i].onclick = function(){
      	    alert(123);
      	  }
      	}
      }*/
      //使用事件委托
      window.onload = function () {
        var oUL = document.getElementById("Inul");
        oUL.onclick = function () {
          alert(123);
        }
      }
    </script>
  </body>
</html>

72、判断字符串是否是这样组成的:第一个必须是字母,后面可以是字母、数字和下划线,总长度为5-20

var str = "adfadfa2343243e";
var reg = /^[a-zA-Z]\w{4,19}$/g;
console.log(reg.test(str));

73、编写一个方法,去掉一个数组的重复元素

Array.prototype.methods = function () {
  //定义一个临时数组
  var arr = [];
  //循环遍历当前数组
  for (var i = 0; i < this.length; i++) {
    //判断当前数组下标为i的元素是否已经保存到临时数组
    //如果已经保存,则跳过,佛则将此元素保存到临时数组中
    //没有保存则说明查不到,返回-1
    if (arr.indexOf(this[i]) === -1) {
      arr.push(this[i]);
    }
  }
  return arr;
}
var arry = [2, 3, 5, 6, 6, 7];
console.log(arry.methods());

74、如何实现JavaScript模块化,请至少使用两种方法

(1)立即执行函数(IIFE):可以达到不暴露私有成员的目的。
(2)对象写法:可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

75、编写一个JavaScript函数,该函数有一个n(数字类型),其返回值是一个数组,该数组内是n个随机且不重复的整数,且整数取值范围是[2,32]。

//定义一个函数用来返回整数的取值范围
function getRand(a, b) {
  //ceil向上取整
  var rand = Math.ceil(Math.random() * (b - a) + a);
  return rand;
}
//定义一个函数用来过滤重复的整数
function checkArrIn(rand, arry) {
  if (arry.indexOf(rand) != -1) {
    return true;
  }
  return false;
}
//定义一个执行函数
function fn(n, min, max) {
  var arr = [];
  //判断n是不是一个数字,包含字符串类型的数字
  var isNum = !isNaN(Number(n));
  //判断n的取值是否符合要求
  var isRandOk = (n >= min && n <= max && n <= (max - min)) ? true : false;
  if (n && isRandOk && isNum) {
    for (var i = 0; i < n; i++) {
      var rand = getRand(min, max);
      //假如checkArrIn()返回true,说明有相同元素,那么就重新执行一次
      if (checkArrIn(rand, arr)) {
        i--;
      } else {
        arr.push(rand);
      }
    }
  }
  console.log(arr);
}
//调用函数
fn(10, 2, 32);

76、[1,2,3].map(parseInt) 的结果是多少?

(1)答案是:[1, NaN, NaN]
(2)原因:parseInt接收的是两个参数,map传递的是3个参数
(3)具体解析

77、构造函数调用方法this指向问题

构造函数其实就是用new操作符调用的一个函数,用new时构造函数内部的this会指向新的实例。

78、getElementsByClassName()和querySelector()的区别是什么?

(1)getElementsByTagName方法返回一个对象数组(准确的说是HTMLCollection集合),返回元素的顺序是它们在文档中的顺序,传递给 getElementsByTagName() 方法的字符串可以不区分大小写;DOM还提供了getElementsByClassName方法来获取指定class名的元素,该方法返回文档中所有指定类名的元素集合,作为 NodeList 对象。NodeList 对象代表一个有顺序的节点列表。
(2)querySelector() 方法返回匹配指定 CSS 选择器元素的第一个子元素 。 该方法只返回匹配指定选择器的第一个元素。如果要返回所有匹配元素,需要使用 querySelectorAll() 方法替代。由于querySelector是按css规范来实现的,所以它传入的字符串中第一个字符不能是数字。
(3)总结

  • query选择符选出来的元素及元素数组是静态的,而getElement这种方法选出的元素是动态的。静态的就是说选出的所有元素的数组,不会随着文档操作而改变。
  • 在使用的时候getElement这种方法性能比较好,query选择符则比较方便。

Vue.js相关知识

1、v-if和v-show的区别是什么?

(1)v-if是条件渲染指令,它根据表达式的真假来删除和插入元素;
(2)v-show也是条件渲染指令,和v-if指令不同的是,使用v-show指令的元素始终会被渲染到HTML,它只是简单地为元素设置CSS的style属性。
(3)总结:v-if 有更高的切换开销,而 v-show 有更高的出事渲染开销.因此,如果需要非常频繁的切换,那么使用v-show好一点;如果在运行时条件不太可能改变,则使用v-if 好点.
(4)点击查看更多

2、Vue的生命周期是什么?

3、Vue的父子组件如何传值?

组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。prop默认是单向绑定:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意修改了父组件的状态。更多知识

4、v-bind指令和v-on指令

(1)v-bind指令可以在其名称后面带一个参数,中间放一个冒号隔开,这个参数通常是HTML元素的特性(attribute),例如:v-bind:class
(2)v-on指令用于给监听DOM事件,它的用语法和v-bind是类似的,例如监听元素的点击事件:<a v-on:click="doSomething">

5、v-text和v-html的区别是什么?

(1)v-text能够把html标签当做字符给渲染出来;

<-- 结果为TWO TODO<span>Vue</span>-->
<h1 v-text="title"></h1>
data() {
    return {
      title: "TWO TODO<span>Vue</span>",
  }
}

(2)v-html能够把html标签直接渲染出来;

<-- 结果为TWO TODOVue-->
<h1 v-html="title"></h1>
data() {
    return {
      title: "TWO TODO<span>Vue</span>",
  }
}

6、Vuejs中关于computed、methods、watch的区别是什么?

(1)watch和computed都是以Vue的依赖追踪机制为基础的,它们都试图处理这样一件事情:当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动。
(2)对methods:methods里面是用来定义函数的,很显然,它需要手动调用才能执行。而不像watch和computed那样,“自动执行”预先定义的函数。
(3)methods 适合小的、同步的计算,而 watch 对于多任务、异步或者响应数据变化时的开销大的操作是有利的。
(4)点击查看更多

其他

1、介绍一下你对浏览器内核的理解?

(1)主要分成两部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎。
(2)渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。
(3)JS引擎则:解析和执行javascript来实现网页的动态效果。最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。

2、常见的浏览器内核有哪些?
(1)Trident内核:IE,MaxThon,TT,The World,360,搜狗浏览器等。[又称MSHTML]
(2)Gecko内核:Netscape6及以上版本,FF,MozillaSuite/SeaMonkey等
(3)Presto内核:Opera7及以上。 [Opera内核原为:Presto,现为:Blink;]
(4)Webkit内核:Safari,Chrome等。 [ Chrome的:Blink(WebKit的分支)]

3、网页验证码是干嘛的,是为了解决什么安全问题。

区分用户是计算机还是人的公共全自动程序。可以防止恶意破解密码、刷票、论坛灌水;有效防止黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。

4、经常遇到的浏览器的兼容性有哪些?原因,解决方法是什么,常用hack的技巧 ?

(1)png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8
(2)浏览器默认的margin和padding不同,解决方案是加一个全局的*{margin:0;padding:0;}来统一
(3)IE6双边距bug:块属性标签float后,又有横行的margin情况下,在ie6显示margin比设置的大
(4)超链接访问过后hover样式就不出现了 被点击访问过的超链接样式不在具有hover和active了解决方法是改变CSS属性的排列顺序:L-V-H-A : a:link {} a:visited {} a:hover {} a:active {}

5、png、jpg、gif 这些图片格式解释一下,分别什么时候用,有没有了解过webp?

(1)GIF是一种索引颜色格式,在颜色数很少的情况下,产生的文件极小。GIF格式支持背景透明;GIF格式支持动画;GIF格式支持图形渐进;GIF格式支持无损压缩。

(2)JPG最主要的优点是能支持上百万种颜色,从而可以用来表现照片。此外,由于JPG图片使用更有效的有损压缩算法,从而使文件长度更小,下载时间更短。JPG较GIF更适合于照片,因为在照片中损失一些细节不像对艺术线条那么明显。另外,JPG对照片的压缩比例更大,而最后的质量也更好。

(3)PNG 是20世纪90年代中期开始开发的图像文件存储格式,其目的是企图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。png是一种无损耗的图像格式。

(4)Webp格式:Google开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器带宽资源和数据空间。Facebook Ebay等知名网站已经开始测试并使用WebP格式。当然其也是一种有损压缩,其主要目的就是加快网络图片的传输效率,让图片能更快的显示在用户的眼前。目前所知道的只有高版本的W3C浏览器才支持这种格式,比如chorme39+,safari7+等等。

6、前端分为哪三层,对应的作用是什么?

(1)网页的结构层(structural layer)由 HTML 或 XHTML 之类的标记语言负责创建。标签,也就是那些出现在尖括号里的单词,对网页内容的语义含义做出了描述,但这些标签不包含任何关于如何显示有关内容的信息。例如,P 标签表达了这样一种语义:“这是一个文本段。”
(2)网页的表示层(presentation layer) 由 CSS 负责创建。 CSS 对“如何显示有关内容”的问题做出了回答。
(3)网页的行为层(behavior layer)负责回答“内容应该如何对事件做出反应”这一问题。这是 Javascript 语言和 DOM 主宰的领域。

7、浏览器的渲染过程
image

8、页面重排(Reflow)的概念和触发reflow的条件

(1)概念:
DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow。
(2)触发的条件:

  • 当你增加、删除、修改DOM节点时,会导致reflow或repaint;
  • 当你移动DOM的位置,或是搞个动画的时候;
  • 当你修改CSS样式的时候;
  • 当你resize窗口的时候(移动端没有这个问题),或是滚动的时候;
  • 当你修改网页默认字体的时候。

9、页面重绘(Repaint)的概念和触发repaint的条件

(1)概念:
当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。
(2)触发的条件:

  • DOM的改动和CSS改动

10、如何实现浏览器内多个标签页之间的通信?

调用localstorge、cookies等本地存储方式

代码分析系列题

1、关于作用域的问题

请问下面几行代码输出什么?简单说一下流程

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

再来看看这几行代码会输出什么?再简单说一下为什么

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

如果要输出0到4,那么该如何修改呢(下面答案先不要看先)?

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

那么把function里的i删掉会输出什么?为什么会是这个结果?

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

再稍微改一改,分析一下输出的结果是什么?

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

最后来看看一段关于Promise的代码,试着分析输出的结果是什么?

setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);

原文地址:请戳这里

2、分析下面两段代码的输出结果,说一说箭头函数中this的指向

//ES3和ES5语法
var factory = function() {
  this.a = 'a';
  this.b = 'b';
  this.c = {
    a: 'c=>a',
    b: function() {
      return this.a
    }
  }
}
console.log(new factory().c.b());

//ES6语法
var factory = function() {
  this.a = 'a';
  this.b = 'b';
  this.c = {
    a: 'c=>a',
    b: () => {
      return this.a
    }
  }
}
console.log(new factory().c.b());

【分析】
在ES3和ES5语法中,this指向的是调用b这个函数的c对象,因此输出结果为“c=>a”,即此时的this是执行时确定的;
在ES6箭头函数语法中,this指向的永远是构造函数(new factory)的实例,即此时的this是声明时确定的。

海枯同学的面试经历

写在前面的话

笔者从17年的2月份开始准备春招,其中遇到不少坑,也意识到自己走过的弯路。故写了这篇文章总结一番,本文适合主动学习的,对自己要学的课程不明确的,对面试有恐惧症的...等将来打算从事技术岗位的同学们。

正文开始。。。

为什么要准备校招?

社招不一样能够进入大公司吗?对于一些不懂校招这个概念的人来说,经常会问这个问题。同时,大公司的校招薪水一般比工作2年经验社招的人还高,为什么企业会给一个没经验的应届毕业生这么高的薪水?理由何在?理清这些问题之前,我们先来看看校招是一个什么概念?

校园招聘是企业直接从学校招聘各类各层次应届毕业生。校招每年时间比较固定,即春季校招(三四月份)和秋季秋招(九到十二月份)。在原则上,春季校招大部分岗位是面向大三与研二(研三毕业)的同学,对这部分的同学提供实习的岗位。少部分企业由于秋招签约率不高或者业务剧增,会针对大四与研三的同学进行补招。

而秋季秋招主要针对大四与研三的同学(这里以每年的九月份来分隔年级),对这部分同学提供的是毕业之后的正职工作。拿到秋招offer的同学,如确定入职需与用人单位签署三方协议,以保证双方的利益不受损失。

有校招需求的企业一般是发展规模较为成型的企业,不论是成熟运作的传统企业,或年轻但已然庞大的互联网公司。在较为成熟的企业管理下,分工细致,完善地各种福利补贴制度,以及专业的导师制培养人才的策略,这些因素无疑能带给应届生们更好的成长。

同时,应届生在校空闲时间多,能学的东西更多,工作之后业务繁多能真正挤出来自主学习的时间太少,计算机基础的课程篇幅过多,出来工作之后就没时间、没精力学了。

而这些计算机基础课程却是成为业界大牛的必经之路,没学这种基础知识,很多东西的底层原理我们是没办法摸透的,这也是为什么互联网BAT大厂重视计算机基础的一个原因。

基础扎实工作之后很多东西很快就可以上手,所以很多互联网大厂宁愿把招聘精力放在校招,而不是社招;其二,应届生作为新鲜血液一般还没有成家,固然能为公司做更大的贡献。

如何准备校招?

既然校招这么重要,那我们应该怎么准备校招呢?

要想在校招中取胜,笔者觉得有几个方面是比较重要的:视野+专注+方法论+心态

1.视野

视野的开阔指对一件事物认知的广度,要对业界有一定的了解,才能明确我们的求知方向;否则一味地蛮干,最后只会南辕北辙。举个例子:对于非一本学校的某些同学来说,可能都不知道校招是怎么一回事,身边的老师也没有跟同学们灌输这些**,不关注技术社区的同学可能就不知道。导致的后果就是不清楚大厂注重的是哪方面的知识(大厂考察的知识一般来说对技术发展很有意义),水平停滞不前,最后毕业去了家不太理想的公司。

很多东西都是这样,身边的人不可能全部传授给你,需要你带着一份热情不断地去挖掘。我们可以在空闲的时间里,去各种技术社区以及一些问答平台上刷刷动态从而不断地开阔我们的视野,比如说:在知乎平台上我们可以看到某些前辈的回答,一般这种回答都具有建设性意义,有时候抛开一些现成的观念,去接受一些新观念何妨不是一件好事呢?

同时,我们可以在社交平台上结交一些应届生“大佬”,询问他们各种学习方法,一般“大佬”都会很热情的帮助你。混熟了以后,可以跟“大佬”交流下面试心得、学习心得等等的东西,不得不说与同类型的人交流是成长最快的一种方式。

2.专注

专注是指在技术方面投入的时间成本。我们都不是圣人,只有不断地去学习与训练才能更大程度地去提升自我。当我们对一件事物有热情时,我们都愿意把所有时间花在它上面。就如我们看上了一个漂亮的女生,对她产生好感了,我们会想尽一切办法去讨好她。

在技术方面也是这样的,对于我们不感兴趣的方向我们从不会主动地去学习,保持对技术的热情才能达到持续的产出。有一句话说的好:时间花在哪里,成就就在哪里。A同学在课后每天都花8小时专研技术,而B同学每天只在课堂上学习。很明显这两人的差距只会越来越大,放心地去努力吧,付出了肯定会有回报的,回报未到只是时候未到。

专注还指对某一领域的专注程度。我们应该对自己以后想从事的职位有一个大概的方向,从而对这个方向应具备的技能进行钻研。有时候看到部分同学既写前端代码,又写PHP后端代码,又写一点硬件底层C语言代码,精力太分散了到最后很难做到每样东西都精通。大厂对应届生的要求还不至于做到全栈工程师的程度,能做到熟悉一领域的开发已经很不错了,当然懂的越多是一个加分项(但非必备)。

反观大厂对应届生的要求是熟悉计算机专业的必修课,诸如:数据库原理、操作系统原理、数据结构与算法、计算机网络等,这类知识可能比较枯燥学起来没什么劲,但却是必备的,其能为以后工作发展铺路。当然每个岗位所要求的侧重点不同,但却百变不离其宗。对这种基础知识要学到哪种程度呢?

是不是考试考到80分以上就代表这门课掌握得不错呢?不是这样的,考试的考点是有局限性的,我们应该通过看一些巨献读物来全面学习。看完之后我们可以通过看别人的面试经历(后面简称“面经”)来检测自己是否掌握这些知识点。

对于不会的知识点,建议大家可以过一遍书本的内容,书本讲的内容一般比较详细。不建议通过搜索引擎查询这种与理论相关的内容,因为大部分搜出来的结果都是摘抄书本的文字。面试服务端开发时,经常会被问到数据库索引的底层实现原理。很多同学可能会问:“为什么要搞懂这些原理性的东西?我会怎么用不就行了吗?”。只懂使用API的人永远是搬运工,工程师最大的使命是去创造,研究原理的时候我们能了解到先人的设计初衷,从而能更快速地在线上出现性能问题的时候根据原理排查问题。

3.方法论

要想达成某个目标都有其特定的方法论,学习技术也不例外,掌握适当的学习方法才能事半功倍。
我们需要形成一个完整的知识体系,强烈建议大家读一些巨献读物,其讲的内容很详细,不会漏掉某些知识点。
读完一章节后建议大家可以做做笔记,坚持读完一本书你会发现受益匪浅,以下是一些巨献读物的推荐:

JavaScript

JavaScript高级程序设计(入门前端必读读物)
你不知道的JavaScript系列(带你探索JavaScript的黑魔法)
JavaScript设计模式与开发实践(让你的代码锦上添花)

计算机网络

图解HTTP(Web开发必会)
计算机网络(第五版)作者:谢希仁

数据结构

网易云课堂浙大的数据结构课程(推荐,基础视频,每节课的配套题目必须独立完成)
大话数据结构(推荐,但是感觉不如浙大的视频讲解)
算法4(推荐,书籍代码是Java语言,不影响理解)

算法

剑指offer(看完之后你会发现面试会遇到原题)
程序员代码面试指南(作者:左程云,里面讲的比剑指深入一点)
LeetCode(看完剑指可以进阶算法)

数据库

数据库概论(基础,重在了解概念)
MySQL必知必会
高性能MySQL

在准备面试的时候可以上牛客网的讨论区看看面经,了解一下面试可能会被问到的问题,对于不会的问题要查漏补缺。当你看完好几篇面经你会发现面试很多问题都是重复的,也就是说这些问题是必须掌握的。同时,可以不断地向更深的层次学习,比如看看源码的实现等等,这些深层次的东西是面试的加分项。

平时的积累也很重要,做项目或者学习知识点的时候可以把心得分享到博客上,一个好处是可以重新梳理知识点,在这个过程中你会对这些知识点印象更加深刻,同时也会给面试加分,面试官看了你的博客之后可以看出你是一个持续学习的人,这将会加大你面试的通过率。

另个好处是在分享的过程中,可以提升自身的表达能力,毕竟把事情讲清楚与心里懂事情是怎么回事是两码事,这也为之后公司的内部分享会奠定基础。

4.心态

在准备校招的过程中难免会遇到一些困难,比如:书本的内容看不懂,这时候可以从搜索引擎里搞清楚一些名词的意思,再把这些名词带入书本中反复地去理解。遇到困难的时候要保持一种愈战愈勇的心态,面试的过程也难免会失利,不要气馁。这时候的重点是把面试被问到不会的知识点搞清楚,争取下次被问到的时候能答上来。

关于简历

书写简历也是门学问,优秀的简历能在内推批次脱颖而出直通面试。

能缩减的信息尽量缩减,比如一些专业必修课的课程就没有必要写上去
个人信息一定要写全,如姓名,电话,邮箱,求职意向(加分项:持续产出的博客与github)
项目介绍用一句话概述,重点在于技术点的描述,建议用一些显著性的数字注明成果(经过xxx,性能提高了30%等)
对于业务型的项目,不要把那些CURD的功能写出来(太low了千篇一律),这种项目在编码的时候要多思考,看看哪些卓越的技术点可以提炼出来
获奖情况方面可以把一些有代表性的比赛及名次写进去,最好不超过3个

关于简历投递

简历投递有几个比较重要的阶段:提前批、正式批、补招。建议大家前期要广投各种企业,这能一定几率地增大面试的可能性。面试多了就有了面试的感觉,拿offer的几率会更大,后期我们能更好地选择offer。

1.提前批

无论是春招还是秋招,提前批都会在校招正式开始前的1~2个月开始内推,一般互联网大厂都有提前批。提前批一般需要在职人员内推,有师兄师姐在名企可以让其帮忙推荐。没有的话也无需担心,一旦有内推在牛客网讨论区总会出现各种信息,把简历投到帖子的邮箱里也可以进行内推。在提前批里只要你的简历通过了筛选就能直通面试了,这就是为什么要提前准备校招。

2.正式批

正式批需要在校园招聘的官网上申请,一般来说会比提前批多一轮线上或线下的笔试,只有笔试过了才能有面试的资格。校招招聘信息可以在梧桐果查看,里面还有宣讲会一栏,错过了提前批的同学可以找到符合自己需求的公司的宣讲会时间,某些公司在宣讲会结束后会进行当场的笔试,一般现场笔试通过后,第二天会进行现场的面试。

3.补招

由于某些“收割机”的弃坑,这时候某些岗位可能还会有几个空缺的名额。招聘的形式与提前批相似,也通过员工内部推荐,简历通过后会进行面试。

关于实习

最后说说找实习的事情,建议大家提前一年准备春招,这样拼进互联网知名大厂(百度、阿里、腾讯、滴滴、美团、京东等等)的可能性很大。实习的工作地点无非太在意,实习最重要的是镀金,有互联网知名大厂的实习经历,在秋招找工作是非常吃香的。

因为实习经历绝大多数情况下决定了你的能力,HR在筛简历时就会认为你既然能被上一家大厂所认可,你的能力肯定不会差到哪里去,就会让你无需笔试直通面试,这样在秋招提前批拿到offer的几率更大!

校招面经

以下是我的校招面经:

网易游戏雷火事业群

1、块级元素和行内元素的区别

2、行内块级元素是什么东西?

3、HTML语义化的理解和作用

4、什么是盒子模型?

5、元素的水平和垂直居中

6、三栏布局,左右定宽,中间自适应

7、JavaScript的基本数据类型,0 == null吗?为什么?

8、跨域怎么做

9、移动端怎么做优化?动画如何做加速?

10、如何做首屏加速的?

11、移动端适配的3种方案

12、单页应用的路由内部原理怎么做的?自己实现过SPA吗?

13、了解过canvas和WebGL吗?

14、如果让你做IE7兼容,你怎么做?(从html,css,js方面说)

15、我们网易游戏要做一个交互性很强的移动端界面,你有什么思路吗?

有赞

1、性能优化

2、图片懒加载怎么做的(getBoundingClientRect)

3、懒加载的滚动如何做优化(函数节流)

4、cookie除了key与value还有哪些参数

5、做过后端吧?如何判断区分一个用户的身份?

6、session的生成规则?sessionid的生成规则?

CVTE

1、项目用REM布局吧?REM如何做自适应的?

2、知道哪5种设计模式吗?

3、Vue双向绑定的原理

4、AMD是什么?解决什么问题?了解AMD,CMD,UMD吗?AMD与CMD的区别?啥叫依赖前置?

5、BootStrap的栅格系统实现原理?

6、什么是原型链

7、三栏布局,左右定宽,中间自适应

8、性能优化

9、为什么选择做前端

10、为什么要用Vue框架?有比较其他框架吗?

11、了解WEB安全吗(XSS、CSRF)

12、项目中有针对WEB安全做防御吗?

百度-网页搜索部

1、项目都用Vue是吧,说说Vue的MVVM如何交互的?

2、知道Vue监测变量如何实现的吗?

3、VueRouter用哪些API实现的?改变hash参数会引起视图的更新吗?

4、说说你项目中实现的Dialog组件?提供了哪些API?如何设计这些API的?

5、用过AJAX吧?说说AJAX是干什么用的?如何实现AJAX?

6、说说你了解的垃圾回收机制

7、绑定事件有哪几种方式?addEventListener有哪些参数?冒泡跟捕获有什么区别?

8、来道算法题吧,在一个数组中求连续最大的累加和

9、居中可以使用哪几个属性?不定宽度与定宽度如何居中?

10、position有哪些属性,都是何意思?

11、啥是闭包?用来干嘛的?

12、CSS有了解过如何做动画吗?animation有哪些参数?

13、HTTP状态码

14、HTTP缓存

15、Git常用操作?merge与rebase有什么区别?

16、数据库有哪些引擎
17、数据库如何实现回滚

18、知道Promise是用来干嘛的吗?Promise底层如何实现的?为什么要用setTimeout去模拟

京东

1、介绍一个你觉得做的最好的项目

2、在做项目遇到过什么问题

3、怎么解决click 300ms的问题

4、fastclick内部实现

5、1px border的问题?为什么会产生?怎么解决?还有什么解决方案

6、touch有哪些事件?tap是原生事件吗?

7、HTTP状态码

8、304缓存

9、eTag跟哪个字段一起用?

10、闭包的定义与作用

11、闭包会产生什么问题?我说内存泄露,他问还有吗

12、原生Ajax的过程

readyState有哪几种值,分别代表什么

es6如何发异步请求? fetch与ajax有啥区别

Promise与setTimeout哪个先执行?为什么

性能优化方式

跨域有哪些方式?window.name有什么问题?

Get与Post有啥区别?哪个更快?

不定宽高3种垂直水平居中。

position取值,啥是stickty

网易游戏互娱事业群

两个栈实现一个队列

快排**,手写快排

数组里有N个偶数个相同的数,只有一个奇数个相同的数,找出这个数

数组中的项是1-100连续的数,把任意一个数变为-1,找出这个数

有三个柜子,每个抽屉都有两个球,第一个抽屉是2个黑球;第二个抽屉是2个白球;第三个是1个白球和1个黑球。求一个抽屉拿到黑球的情况下,另一个球是白球的概率

假设一对夫妻生小孩的观念是这样的,如果第一次生到的是男孩,则不继续生了;如果第二次生到的是女孩,继续生到有男孩为止。求世界男女比例

什么是原型链

看代码说输出结果,并说原因。
var F = function () {}
var f = new F()
console.log(f.proto)
console.log(f.proto.proto)
console.log(f.proto.proto.proto)

说说有哪些请求方法,越多越好

GET与POST有啥区别

介绍一个你觉得最有难度的项目

为什么用Vue不用其他框架

用Vue的原因

说说Vue的优势

看过源码是吧?说说你对Vue哪种机制最熟悉?并说说其源码实现过程

微众银行

除了前端方向,我们还有大数据和JAVA方向,你对哪个有意向?

Vue2有哪些新特性?双向绑定如何实现?

项目有遇到什么问题?

设计一个微信服务器与开发者的交互,判断是否有权限操作

迅雷

介绍一个最近做的一个项目

做项目的时候遇到过什么问题

闭包,如何防止IE下的内存泄露

undefined与null的区别。举个用到undefined与null的例子

介绍一下HTTP协议与HTTP Ruquest

HTTP缓存

Last-Modified的时间如何生成的

GET与POST的区别

我说到POST会发送两次数据包的时候,反问我,你这个是从哪里看的?确定是官方说的?如果POST的数据量很少的时候呢?也会分两次发吗?为什么?

我说到GET参数暴露到url上不安全,而POST更安全,他反问你不会装包吗?都能装到包为什么还安全?GET与POST的区别到底在哪里,为何这两者要区分开。如何防止数据被抓包

我说到GET的传输数据包的体积与POST的不同,反问我,你确定GET只能传4K?有什么办法使GET能传输与POST一样大小的数据包容量?

HTTP2有什么新特性?多路复用中,HTTP2能兼容HTTP1.1的请求吗?比如:会请求多个域名服务器,有些请求是HTTP1的有些请求是HTTP2的?这样子合法吗?

性能优化

数组去重,说出哈希表法的时间复杂度与空间复杂度。hash表查找的过程时间复杂度是多少,为什么?

如何防止非本地域名脚本的恶性注入?(两种方法)

如何防范CSRF攻击(两种方法)

MVC、MVVM的交互流程

typeof有哪些返回值

跨域方式

继承的6种方法

cookie与Web Storge的区别?如何在IE下存储4M的数据

说输出结果题1
console.log(typeof ('a' - 1))

说输出结果题2
for (var i = 0;i < 3;i++) {
setTimeout(function () {
console.log(i++)
}, 0)
console.log(i)
}

说输出结果题3
function bar() {
return foo
foo = 2
function foo() {}
var foo
foo = 'string'
}
console.log(bar())

交换排序与堆排序的时间复杂度是多少

1~100000个连续的数,随机取出两个数。不能用特定的数据结构,不能用数组方法找出这两个数
WPS

介绍BFPRT算法

null是什么?与undefined的区别

说出结果,null > 0,null >= 0,null == 0。并说出原因

写出匹配IP的正则表达式

写出观察者模式,如果要删除某个依赖呢

new运算符做了什么?写出代码

new中的this是何时生成的

实现一个类

实现继承,如何处理重复生成了两个相同的实例属性

Object.create()内部做了什么?

什么是函数?函数为什么可以当参数传递

写一个开头不能连续出现abc的正则,不区分大小写

线程与进程的区别

磁盘读取速度为什么比内存读取速度慢

tcp三次握手

DNS如何找IP

HTTP借用了TCP的哪些优点

HTTP的Content-Type可能取的值?设置哪个值是以json的格式去传输

AJAX实现过程

美团

如何做出五角星并居中

使用Math.random()的点怎样才能等概率地落到等边三角形中

实现这样一个功能:
sum(2, 3) // 5
sum(2)(3) // 5

0.068如何转化成6.8%,这其中有什么坑?要怎么处理?

说说JavaScript的数据类型

如何判断是否是Array

如何判断是NaN

如何实现一个手表,如果是实现一个计秒器呢?

setTimeout发生在什么时刻,如何避免延迟?

说说项目的难点以及遇到的问题

说说v-model的实现原理

如何实现v-model的单向绑定

如何实现跨组件的通信,比如点击一个按钮,要使另一个组件中背景色改变

如何实现跨页面的通信,需求同上

如何实现跨域页面的通信,需求同上

如何实现跨iframe且跨域页面的通信?

字符串反转不能用辅助API

两个有序数组合并成一个有序数组

两个无序数组合并成一个有序数组的两种方式,并说说这两种方式最优的时间复杂度是多少,过程是如何求出来的?

http构成

https原理

为啥要用非对称加密

客户端是如何验证证书的合法性的?

浏览器如何渲染界面

什么是reflow与repaint?哪个性能消耗大

如何避免reflow?

看《JavaScript高级程序设计》的时候觉得哪块最难理解?讲一下

讲解你项目中遇到的难点?

你博客主要写什么内容?发个地址来看看

对什么排序熟悉?我说快排。那来道非递归的快排吧

看你博客写了TCP的内容,说说TCP与UDP的区别

TCP如何实现拥塞控制的?一发生网络堵塞,为什么把拥塞窗口重新设置为1。设置为1,这个发送流量不是很小了吗,这不是前后矛盾了吗?

智力题:有N个物品,其中有一个是很轻的,有一个天平,用最少的次数找出这个轻的物品

富途

说说原型

以下代码中F与f与F.prototype与它们之间是什么关系
function F() {}
var f = new F()
f.constuctor是什么?f自身存在constuctor吗?为什么?

JavaScript如何实现继承

继承之后的child.constuctor是什么值

console.log(f.prototype)的结果是什么

两个升序的数组,判断一个数组中是否包含另一个数组的所有项?还有更优的解决方案吗?原来的时间复杂度是多少,现在的复杂度是多少?

以下代码怎么输出?为什么会这样?如何改善?setTimeout为什么在最后输出?
for (var i = 0; i < 3; i++)
setTimeout(function () {
console.log(i)
}, 0)

说说HTTP缓存

浏览器怎么判断是否是强缓存过期,整个过程是怎么样的?协商缓存具体整个过程?

返回200状态码后,还会重新发送一次请求来获取文件吗?

Web安全

TCP与UDP有什么区别?

HTTP与HTTPS有啥区别?HTTPS握手过程?HTTPS都是怎么加密的?具体点哪里对称加密哪里非对称加密
状态码301与302有啥区别?

说说cookie与session

cookie与离线存储的区别

求第n大的数?时间复杂度多少?

雅虎的N条军规你知道吗?

什么是跨域?怎么解决跨域问题?

为什么要把资源文件放在另一个服务器中?除了服务器压力的问题呢?还有没有其他?

如何破解验证码?

有一堆猴子与一堆桃子,若这些猴子每人分3个桃子,最后剩下59个桃子。若每人分5个桃子,最后一个猴子分到<5个桃子,求有多少个猴子与桃子

服务器突然很卡你会怎么排查?如果看日志看不出问题呢?如果看内存看不出问题呢

怎么设计分页接口的参数?若以id为自增唯一索引,有什么缺陷?那用什么当唯一索引比较好?
一个Room类里面有Door,一个User类,只有主人才能开这个door。问这个开door的函数是放在Room类?还是放在User类?

说说你对富途的了解

荔枝FM

说出输入
function fn() {
return function () {
return inner
var inner = 3
inner = 'a'
function inner() {}
}
}
写出Function.prototype.bind的polyfill

输出z-index属性的元素排布,层叠上下文,还有什么属性可以使z-index生效

有四个接口/a,/b,/c,/d。找出一个相应时间最快的接口,并返回这个时间

写个弹窗组件,有title,body,cancelText,confirmText参数。可以通过on来绑定回调,与及通过off解绑。
var dialog = new Dialog()
dialog.on('show', function () {
console.log('showing')
})
dialog.on('hide', function () {
console.log('hideing')
})

dialog.show() // showing
dialog.hide() // hideing
两栏布局一边定宽一边自适应的多种方法
介绍BFC
如何解决跨域问题
服务器如何监测是否跨域
我不想每次进来都重新加载这个文件,有什么办法
Vue父子组件如何通信,跨级组件呢,不用Vuex呢?
如何与后端协作?在后端没有做出接口的时候,怎么套数据?在线上有接口的时候,怎么套数据?
追一科技
如何实现微信电脑端登录的功能?
一个猴子,一共有100根香蕉,每次最多能拿50根,回家有50m,每走1m需要消耗1根香蕉。问最多带多少根香蕉回家?

前端工程师面试指南——HTML篇

Doctype作用?标准模式与兼容模式各有什么区别

  • 声明位于HTML文档中的第一行,处于 `` 标签之前。告知浏览器的解析器用什么文档标准解析这个文档。DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。
  • 标准模式的排版 和JS运作模式都是以该浏览器支持的最高标准运行。在兼容模式中,页面以宽松的向后兼容的方式显示,模拟老式浏览器的行为以防止站点无法工作。

如果我不放入<! DOCTYPE html> ,HTML5还会工作么

不会,浏览器将不能识别他是HTML文档,同时HTML5的标签将不能正常工作

HTML5 为什么只需要写 <!DOCTYPE HTML>

HTML5 不基于 SGML,因此不需要对DTD进行引用,但是需要doctype来规范浏览器的行为(让浏览器按照它们应该的方式来运行);而HTML4.01基于SGML,所以需要对DTD进行引用,才能告知浏览器文档所使用的文档类型。

meta标签是什么

meta是提供给页面的一些元信息(名称/值对),有助于SEO。有以下几个属性值

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

行内元素有哪些?块级元素有哪些? 空(void)元素有那些

首先:CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,如div的display默认值为“block”,则为“块级”元素;span默认display属性值为“inline”,是“行内”元素。

  • 行内元素: a b span img input select strong(强调的语气)
  • 块级元素: div ul ol li dl dt dd h1 h2 h3 h4…p
  • 常见的空元素: <br> <hr> <img> <input> <link> <meta>
  • 少见的空元素: <area> <base> <col> <command> <embed> <keygen> <param> <source> <track> <wbr>

页面导入样式时,使用link和@import有什么区别

  • link属于XHTML标签,除了加载CSS外,还能用于定义RSS, 定义rel连接属性等作用;而@import是CSS提供的,只能用于加载CSS;
  • 页面被加载的时,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载;
  • import是CSS2.1 提出的,只在IE5以上才能被识别,而link是XHTML标签,无兼容问题;

html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5

  • 新特性: 绘画 canvas;用于媒介回放的 video 和 audio 元素;语意化更好的内容元素,比如 article、footer、header、nav、section;表单控件,calendar、date、time、email、url、search;
  • 新技术: webworker, websocket, Geolocation,本地离线存储 localStorage 长期存储数据,浏览器关闭后数据不丢失; sessionStorage 的数据在浏览器关闭后自动删除;
  • 移除的元素: basefont,big,center,font, s,strike,tt,u;frame,frameset,noframes;
  • 兼容问题解决: IE8/IE7/IE6支持通过document.createElement方法产生的标签,可以利用这一特性让这些浏览器支持HTML5新标签,浏览器支持新标签后,还需要添加标签默认的样式。当然也可以直接使用成熟的框架、比如html5shim

简述一下你对HTML语义化的理解

  • 用正确的标签做正确的事情。
  • html语义化让页面的内容结构化,结构更清晰,便于对浏览器,搜索引擎解析;
  • 即使在没有样式CSS情况下也以一种文档格式显示,并且是容易阅读的;
  • 搜索引擎的爬虫也依赖于HTML标记确定上下文和各个关键字的权重,利于SEO;
  • 使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。

HTML5的离线储存怎么使用,工作原理能不能解释一下

  • 在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,更新用户机器上的缓存文件。
  • 原理: HTML5的离线存储是基于一个新建的.appcache文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示。
  • 使用: 离线存储使用案例

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

  • 存储位置: cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密),cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递。sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
  • 存储大小: cookie数据大小不能超过4k。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
  • 存储时间: localStorage存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage 数据在当前浏览器窗口关闭后自动删除;cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。

iframe有那些缺点

  • iframe会阻塞主页面的Onload事件;
  • 搜索引擎的检索程序无法解读这种页面,不利于SEO;
  • iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。

【拓展】
如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题。

Label的作用是什么?是怎么用的

  • 作用: label标签来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上。
  • 用法:
  <label for="Name">Number:</label>
  <input type=“text“name="Name" id="Name"/>

  <label>Date:<input type="text" name="B"/></label>

HTML5的form如何关闭自动完成功能

给不想要提示的 form 或某个 input 设置为 autocomplete=off。

如何实现浏览器内多个标签页之间的通信

  • WebSocket、SharedWorker;
  • 调用localstorge、cookies等本地存储方式;

webSocket如何兼容低浏览器

  • Adobe Flash Socket ;
  • ActiveX HTMLFile (IE) ;
  • 基于 multipart 编码发送 XHR;
  • 基于长轮询的 XHR。

页面可见性(Page Visibility API) 可以有哪些用途

  • 通过 visibilityState 的值检测页面当前是否可见,以及打开网页的时间等;
  • 在页面被切换到其他后台进程的时候,自动暂停音乐或视频的播放;

title与h1的区别、b与strong的区别、i与em的区别

  • title属性没有明确意义只表示是个标题,H1则表示层次明确的标题,对页面信息的抓取也有很大的影响;
  • strong是标明重点内容,有语气加强的含义,使用阅读设备阅读网络时:<strong>会重读,而<b>是展示强调内容;
  • i内容展示为斜体,em表示强调的文本。

sessionStorage 、localStorage 和 cookie 之间的区别

  • 共同点: 都是保存在浏览器端,且同源的。
  • 不同点: cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递;cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。
  • sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
  • 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
  • 作用域不同,sessionStorage不在不同的浏览器窗口**享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。Web Storage 的 api 接口使用更方便。

HTML 5标签是否一定需要闭合?标签大小写是否敏感

不一定;不敏感,但是推荐要用小写标签

HTML 中的DOM树

HTML DOM定义访问和操作HTML文档的标准方法。DOM将HTML文档表达为树结构。
image
W3C 文档对象模型 (DOM) 是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。W3C DOM 标准被分为 3 个不同的部分:

  • 核心DOM,针对任何结构化文档的标准模型;
  • XML DOM,针对XML文档的标准模型;
  • HTML DOM,针对HTML文档的标准模型,它是标准对象模型,是标准编程接口;

根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:整个文档是一个文档节点、每个HTML元素是元素节点、HTML元素内的文本是文本节点、每个HTML属性是属性节点、注释是注释节点。

XHTML和HTML有什么区别

  • HTML是一种基本的web网页设计语言,XHTML是一个基于XML的置标语言;
  • XHTML元素必须被正确的嵌套;
  • XHTML元素必须被关闭;
  • XHTML元素必须使用小写字母;
  • XHTML文档必须拥有根元素。

canvas和svg图形的区别是什么

image

常见的浏览器内核

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

简单介绍对浏览器内核的理解

浏览器内核主要分成两部分,渲染引擎和JavaScript引擎。最开始渲染引擎和JS引擎并没有区分得很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。下面是关于这两个引擎的一些介绍

  • 渲染引擎: 将代码转换成页面。负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等)、以及计算网页的显示方式,然后会输出至显示器或打印机。浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其他需要编辑、显示网络内容的应用程序都需要内核。
  • JS引擎:解析和执行javascript来实现网页的动态效果。

html5哪些标签可以做SEO优化

title、meta、header、footer、nav、article、aside

src和href的区别

  • src是指向外部资源的位置,指向的内容会嵌入到文档中当前标签所在的位置,在请求src资源时会将其指向的资源下载并应用到文档内,如js脚本,img图片和frame等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,知道将该资源加载、编译、执行完毕,所以一般js脚本会放在底部而不是头部。
  • href是指网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,用于超链接。

渐进增强和优雅降级

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

defer和async的区别

  • defer要等到整个页面在内存中正常渲染结束(DOM结构完全生成,以及其他脚本执行完成),才会执行。多个defer脚本会按照它们在页面出现的顺序加载,意思就是渲染完再执行。
  • async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。多个async脚本是不能保证加载顺序的,意思就是下载完就执行。

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

  • 调用localstorge
// 页面一
<input id="name">  
<input type="button" id="btn" value="提交">

<script type="text/javascript">  
  $(function(){    
      $("#btn").click(function(){    
         var name=$("#name").val();    
          localStorage.setItem("name", name); //存储需要的信息 
      });     
 });    
</script>  

// 页面二
$(function(){   
  //使用storage事件监听添加、修改、删除的动作  
  window.addEventListener("storage", function(event){    
     console.log(event.key + "=" + event.newValue);    
  });   
}); 
  • 使用cookie+setInterval
// 页面一
$(function(){    
  $("#btn").click(function(){    
      var name=$("#name").val();    
      document.cookie="name="+name;    
  });    
});  

// 页面二
$(function(){   
  function getCookie(key) {    
     return JSON.parse("{\"" + document.cookie.replace(/;\s+/gim,"\",\"").replace(/=/gim, "\":\"") + "\"}")[key];    
  }     
  setInterval(function(){    
     console.log("name=" + getCookie("name"));    
  }, 10000);    
});

whistle + SwitchyOmega前端调试利器

安装启动

  • 根据官网的参考步骤进行安装启动
  • 在这过程中可能会发生npm与node不匹配的问题,那么进行npm升级即可
  • 在这过程中可能会发生权限不足导致无法安装的问题,那么使用管理员的身份在cmd命令行安装即可
  • 在这过程中可能会发生cmd命令行无法识别w2或者whistle,那么在系统变量中添加相关path即可

PC端调试步骤

  • w2 start
  • 打开默认网址:http://127.0.0.1:8899/
  • 添加规则
    image
  • 安装SwitchyOmega谷歌浏览器插件,并进行配置
    image
  • 安装证书,下载证书后,双击证书,根据指引安装证书【证书需要存储到受信任的根证书颁发机构下】
    image
  • 之后开启代理,我们就可以将vue项目跑起来的http://localhost:8080/premiu/地址换成http://pre.scp.xx.com/premiur/,那么此时此刻就能够在真实环境中测试代码,并且本地改动的代码能够及时热更新。

移动端调试步骤

  • 电脑和手机在同一wifi中
  • 手机安装证书(1、扫电脑端https二维码暗转;2、电脑将下载好的安装包发送给手机进行安装)
  • 设置手机wifi的“代理设置”(手动、服务器或者主机为电脑IP、端口为whistle端口8899)
  • 此时手机访问http://pre.scp.xx.com/premiur/也是通的

参考

JavaScript正则表达式

一、前言

该博文源自慕课网的《JavaScript正则表达式》课程。老师讲的生动有趣,由浅入深,是一门好课程。为了让自己更好记忆和继续深究正则表达式,因此写下该篇博文。下面推荐两个实用的在线网站:
正则表达式工具
代码及时响应在线网站

二、实例化正则对象

在JavaScript中,一共有两种方法实例化正则对象,下面请看具体的方法。

1、 字面量:var reg = /\bis\b/g;
【注意】
\b代表单词边界,g代表全文进行匹配。如果没有\b和g,会怎么样呢?请动手分别进行测试一下,下面给出测试模板,内容可自行更改。
测试:'he is a boy,This is a dog'.replace(reg,'IS');

2、 构造函数:var reg = new RegExp('\\bis\\b','g');
【注意】
在构造函数中,我们需要\来进行转义。
测试:'he is a boy,This is a dog'.replace(reg,'IS');

三、修饰符

在JavaScript正则表达式中,一共有三种修饰符,分别是一下三种。
1、 g:global 全文搜索,假如不添加的话,那么搜索到第一个匹配即停止;
测试:'He is a boy,she is here?'.replace(/\bis\b/g,'0');

2、 i:ignore case 忽略大小写,默认大小写敏感;
测试:'He is a boy,she IS here?'.replace(/\bis\b/gi,'0');

3、 m:multiple lines 多行搜索。
测试:'@123\n@234\n@345'.replace(/^@\d/gm,'Q');

四、元字符

JavaScript正则表达式中,元字符非常之多,下面我们一起来耐心看看。

1、 \s:匹配单个空格,等同于[\f\n\r\t\v];而\S则恰恰相反,它匹配的是非空格字符。

测试:/\s.+/.exec('This is a test String.');
【分析】
其中.含义是除了回车符和换行符之外的所有字符,+含义是出现一次或多次(至少出现一次),exec() 方法用于检索字符串中的正则表达式的匹配。假如有点晕,再来看看下面的栗子

var regs = /\s.+/
var str = "this is dog stil here"
// thishaha
 console.log(str.replace(regs,"haha"))

【应用场景】
匹配任意空或空白字符,如果你什么也没输入,或输入的只有空格、回车、换行等字符,则匹配成功。这样就可以验证用户是否正确输入内容了。
【用法】

var  reg=/^\s*$/;
if(reg.test(value)){
  alert('请输入有效值');
  return false;
}

【分析】
其中^含义是以什么为开始,*含义是出现零次或多次(任意次),$含义是以什么为结束,test() 方法用于检测一个字符串是否匹配某个模式.

2、 \w:表示单词字符,等同于字符集合[a-zA-Z0-9_];而\W表示非单词字符,等效于[^a-zA-Z0-9_]。

var reg = /\w+/;
var str='fengxiong';
alert(reg.exec(str));

【分析】
返回完整的fengxiong字符串,因为所有字符都是单词字符。

var reg = /\w+/;
var str='.className';
alert(reg.exec(str));

【分析】
结果显示匹配了字符串中的className,只有第一个“.”唯一的非单词字符没有匹配。

var reg = /\w+/;
var str='正则教程';
alert(reg.exec(str));

【分析】
试图用单词字符去匹配中文自然行不通了,返回 null。

var reg = /\W+/;
var str='正则教程';
alert(reg.exec(str));

【分析】
返回完整的字符串,因为,中文算作是非单词字符。

3、其他元字符
\f:匹配换页符;
\n:匹配换行符;
\r:匹配回车符;
\t:匹配制表符;
\v:匹配垂直制表符;
\d:匹配数字;
\D:匹配非数字;
\b:匹配单词边界;
\B:匹配非单词边界

来个测试题:将符合一个ab+数字+任意字符的字符串代替为B。
答案:'ab32432dab2,'.replace(/ab\d./g,'B');

五、重要元字符

1、^:
可以用来创建反向类/负向类,也就是不属于某类的内容,比如:
'a1b2c3d4'.replace(/[^abc]/g,'X');

又有以什么为开头的含义,比如:
'1 fafs'.replace(/^\d\s/g,'Q');

2、[ ]
可以用来构建一个简单的类,也就是几个字符归纳为集合,比如:
'a1b2c3d4'.replace(/[abc]/g,'X');

可以使用[a-z]来连接两个字符表示从a到z的任意字符,比如:
'a1b2d3x4z9'.replace(/[a-z]/g,'A');

在[]组成的类内部是可以连写的,比如:
'a1b2d3x4z9B7A3N4M8'.replace(/[a-zA-Z]/g,'J');

如何在[]把-给算上呢?其实只要把-加在后面即可,比如:
'2018-01-14'.replace(/[0-9-]/g,'A');

六、量词

?:出现零次或一次(最多出现一次);
+:出现一次或多次(至少出现一次);
*:出现零次或多次(任意次);
{n}:出现n次;
{n,m}:出现n到m次;
{n,}:至少出现n次。

八、分组

1、使用()可以达到分组的功能,使用量词作用于分组,比如:
'a1b2c3d4'.replace(/([a-z]\d){3}/g,'X');
'BoyGirl'.replace(/Boy|Girl/g,'X');
'BoyGirlBoyBorl'.replace(/Boy(Gi|Bo)rl/g,'X');

2、分组的反向引用,比如,将2018-01-14 转换为 01/14/2018,对比下面两段代码的结果:
'2018-01-14'.replace(/\d{4}-\d{2}-\d{2}/g,'$2/$3/$1');
'2018-01-14'.replace(/(\d{4})-(\d{2})-(\d{2})/g,'$2/$3/$1');

八、贪婪模式与非贪婪模式

1、贪婪模式:尽可能多的匹配,比如:
'12345678'.replace(/\d{3,6}/g,'X');

2、非贪婪模式:让正则表达式尽可能少的匹配,也就是说一旦成功匹配则不再继续尝试,比如:
'12345678'.replace(/\d{3,6}?/g,'X');

九、正则表达式训练营

1、用正则匹配手机号码

function tele(tel) {
  if (tel.search(/^1[34578]\d{9}$/g) > -1) {
    console.log("1");
  } else {
    console.log("0");
  }
}
tele("13456799014");

【分析】
寻找以13、14、15、17或18开头,以9个数字结尾的字符,找到了就返回1,失败就返回0。search()方法去匹配字符串,如果匹配成功,就返回匹配成功的位置,如果匹配失败就返回-1

还有一种更加简便的方法:

function tele(tel) {
  return /^1[34578]\d{9}$/g.test(tel);
}
console.log(tele("13456799014"));

【分析】
test()方法的返回值是布尔值,通过该值可以匹配字符串中是否存在于正则表达式相匹配的结果,如果有匹配内容,返回ture,如果没有匹配内容返回false。
【拓展】
match()方法去匹配字符串,如果匹配成功,就返回匹配成功的数组,如果匹配不成功,就返回null;
replace()方法去匹配字符串,匹配成功的字符去替换新的字符串。

2、判断字符串是否包含了数字

function contain(str) {
  var reg = /\d/g;
  return reg.test(str);
}
console.log(contain("fds3af"));

3、给定字符串str,检查其是否包含连续重复的字母,包含返回true,否则返回false。

function contain(str) {
   return /([a-zA-Z])\1/.test(str);
}
console.log(contain("fdsaaf"));

【分析】
"小括号包含的表达式所匹配到的字符串" 不仅是在匹配结束后才可以使用,在匹配过程中也可以使用。表达式后边的部分,可以引用前面 "括号内的子匹配已经匹配到的字符串"。引用方法是 "/" 加上一个数字。"/1" 引用第1对括号内匹配到的字符串,"/2" 引用第2对括号内匹配到的字符串……以此类推,如果一对括号内包含另一对括号,则外层的括号先排序号。换句话说,哪一对的左括号 "(" 在前,那这一对就先排序号。

4、判断是否以元音字母结尾。

function contain(str) {
  return /[a,e,i,o,u]$/i.test(str);
}
console.log(contain("fdsaaa"));

5、给定字符串str,检车其是否包含3个连续的数字

function contain(str) {
  return str.match(/\d{3}/g);
}
console.log(contain("1g556777"));

6、判断是否符合指定格式(正确格式示例:556-142-7489)

function contain(str) {
  return /^(\d{3}-){2}\d{4}$/g.test(str);
}
console.log(contain("235-894-5623"));

【解析】
以3个数字加“-”开头,并且重复2次,最后为4个数字。

7、待处理
写一个正则表达式,匹配 ""

let str = '<OPTION  value="待处理">待处理</OPTION>';
let regExp = /^<.*?>/g;
console.log(regExp.exec(str)[0]); 

【分析】
以<开头,匹配除了回车符合换行符之外的所有字符,出现任意次,出现零次或一次,这就匹配到了所有的字符。exec()方法返回一个匹配项的数组,而match()方法返回所有匹配项组成的数组。

8、如何获取一个字符串中的数字字符,并按数组形式输出,如:
dgfhfgh254bhku289fgdhdy675gfh输出[254,289,675]

let str = 'dgfhfgh254bhku289fgdhdy675gfh';
let regExp = /\d+/g;
console.log(str.match(regExp));

【分析】
+含义是出现一次或多次(至少出现一次)。

9、敏感词过滤

let str = '我草**哈哈背景天胡景涛哪肉涯剪短发欲望';
let regExp = /草|肉|欲|胡景涛/g;
let result = str.replace(regExp,"*");
console.log(result);

以上的是缩减版,下面来个完整版的:

let str = '我草**哈哈背景天胡景涛哪肉涯剪短发欲望';
let regExp = /草|肉|欲|胡景涛/g;
let result = str.replace(regExp, function (match) {
  let len = match.length;
  let str;
  switch (len) {
    case 1:
      str = '*';
      break;
    case 2:
      str = "**";
      break;
    case 3:
      str = "***";
      break;
    default:
      str = '****';
  }
  return str;
});
console.log(result); //我***哈哈背景天***哪*涯剪短发*望

10、让2013-6-7 变成 2013.6.7

let str = '2013-6-7';
let regExp = /-/g;
console.log(str.replace(regExp, '.')); //2013-6-7

11、给定这样一个连字符串,写一个function转换为驼峰命名法形式的字符串 getElementById

var s1 = "get-element-by-id";
function camelCased(str) {
  let regExp = /-(\w)/g;
  str.replace(regExp, function(match, p) {
      return p.toUpperCase();
   })
}
camelCased(s1);

【分析】
\w代表的是单词字符

12、判断字符串中是否包含数字

let str1 = 'abc9efh';
let str2 = 'abcefg';
let regExp = /\d/;
console.log(regExp.test(str1)); // true
console.log(regExp.test(str2)); // false

13、判断连续重复字母

let str1 = 'abc3d4e5';
let str2 = 'aab2c3';
let regExp = /([a-zA-Z])\1/;
console.log(regExp.test(str1));//false
console.log(regExp.test(str2));//true

14、给定字符串 str,检查其是否包含 3 个连续的数字

  • 如果包含,返回最新出现的 3 个数字的字符串
  • 如果不包含,返回 false
let str1 = 'abc123efg';
function captureThreeNumbers(str) {
    let res;
    if (res = str.match(/\d{3}/)) {
        return res[0];
    } else {
        return false;
    }
}
console.log(captureThreeNumbers(str1)); //123

15、给定字符串 str,检查其是否符合美元书写格式

  • 以 $ 开始
  • 整数部分,从个位起,满 3 个数字用 , 分隔
  • 如果为小数,则小数部分长度为 2
  • 正确的格式如:$1,023,032.03 或者 $2.03,错误的格式如:$3,432,12.12 或者 $34,344.3
let regExp = /^\$\d{1,3}(,\d{3})*(\.\d{2})?$/;
console.log(regExp.test('$1.23')); //true
console.log(regExp.test('$111.23')); //true 
console.log(regExp.test('$1111.23')); //false
console.log(regExp.test('$1,123.23')); //true

【分析】
以$开头,匹配数字符1到3个;匹配逗号(,)再匹配3个数字符,该规则出现零次或多次(即任意次);匹配句号(.),再匹配2个数字符,该规则出现零次或一次,并在结尾处。

16、对人口数字的格式化处理,三位数字用一个','(逗号)隔开

function numberWithCommas(x) {
    //对右侧人口数字的格式化处理,三位数字用一个','(逗号)隔开
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
console.log(numberWithCommas(12345678))//12,345,678

【分析】
非单词边界,匹配右边有三个数字符的数(正向前瞻),匹配一次或者多次;匹配负向前瞻。

17、将单词is替换为IS

let str = 'English poetry is one of their great heritages';
console.log(str.replace(/\bis\b/,'IS'));
// English poetry IS one of their great heritages

18、实现数字转千分位

var reg = /\d{1,3}(?=(\d{3})+$)/g;
var str = "1234556789";
console.log(str.replace(reg,"$&,"));

【分析】
数字千分位的特点是:第一个逗号后面数字的个数是3的倍数,正则:/(\d{3})+$/;第一个逗号前最多可以有1至3个数字,正则:/\d{1,3}/。加起来就是/\d{1,3}(\d{3})+$/,?=含义是零宽度正预测先行断言,具体用法自行百度^_^
【其他】

var reg = /\d{1,3}(?=(\d{3})+$)/g;
var str = 1234556789;
console.log(str.toString().replace(reg,"$&,"));

19、校验身份证的正确性
完全照搬网上的大佬代码,具体详情戳这里,下面看实现代码

export function idCard(idcode) {
  // 加权因子
  var weight_factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
  // 校验码
  var check_code = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
  var code = idcode + "";
  //最后一个
  var last = idcode[17];
  var seventeen = code.substring(0, 17);
  // 判断最后一位校验码是否正确
  var arr = seventeen.split("");
  var len = arr.length;
  var num = 0;
  for (var i = 0; i < len; i++) {
    num = num + arr[i] * weight_factor[i];
  }
  // 获取余数
  var resisue = num % 11;
  var last_no = check_code[resisue];
  var idcard_patter = /^[1-9][0-9]{5}([1][9][0-9]{2}|[2][0][0|1][0-9])([0][1-9]|[1][0|1|2])([0][1-9]|[1|2][0-9]|[3][0|1])[0-9]{3}([0-9]|[X])$/;
  // 判断格式是否正确
  var format = idcard_patter.test(idcode);
  // 返回验证结果,校验码和格式同时正确才算是合法的身份证号码
  return last === last_no && format ? true : false;
}

十、参考文章

由于正则表达式真的是难以灵活运用和讲清楚,因为它实在是太强大了,只有不断的训练自己,多看并多练才有可能完全掌握它,下面提供一些参考文章。

面试系列:关于正则表达式
正则表达式面试题
一些正则表达式的随记
正则表达式总结1
从面试到正则
正则表达式总结2

CV大佬今日让你彻底的、永久的搞懂JS“==”运算

前言

相信很多戳进来的人都会很好奇,CV到底是个啥东东?很厚脸皮的说那就是:ctrl+c和ctrl+v重度使用者:joy:。简单说一下copy的原因:有一篇我看了好几遍都感到头疼却重要的文章,后来一不小心去看了他的参考文章,也是该篇的原文,才豁然开朗。为了避免这种好文有一天可能莫名其妙的消失不见,因此心中就起了“歹意”,废了点时间将其抄下来。

正文

大家知道,==是JavaScript中比较复杂的一个运算符。它的运算规则奇怪,容易让人犯错,从而成为JavaScript中“最糟糕的特性”之一。

在仔细阅读了ECMAScript规范的基础上,我画了一张图,我想通过它你会彻底地搞清楚关于==的一切。同时,我也试图通过此文向大家证明==并不是那么糟糕的东西,它很容易掌握,甚至看起来很合理。先上图1
image

==运算规则的精确描述在此:The Abstract Equality Comparison Algorithm。但是,这么复杂的描述,你确定看完后脑子不晕?确定立马就能拿它指导实践?

肯定不行,规范毕竟是给JavaScript运行环境的开发人员看的(比如V8引擎的开发人员们),而不是给语言的使用者看的。而上图正是将规范中复杂的描述翻译成了更容易看懂的形式。在详细介绍图1中的每个部分前,我们来复习一下JS中关于类型的知识:

  • JS中的值有两种类型:原始类型(Primitive)、对象类型(Object)。
  • 原始类型包括:Undefined、Null、Boolean、Number和String等五种。
  • Undefined类型和Null类型的都只有一个值,即undefined和null;Boolean类型有两个值:true和false;Number类型的值有很多很多;String类型的值理论上有无数个。
  • 所有对象都有valueOf()和toString()方法,它们继承自Object,当然也可能被子类重写。

现在考虑表达式:

x == y

其中x和y是上述六种类型中某一种类型的值。当x和y的类型相同时,x == y可以转化为x === y,而后者是很简单的(唯一需要注意的可能是NaN),所以下面我们只考虑x和y的类型不同的情况。

一、有和无

在图1中,JavaScript值的六种类型用蓝底色的矩形表示。它们首先被分成了两组:

  • String、Number、Boolean和Object (对应左侧的大矩形框)
  • Undefined和Null (对应右侧的矩形框)

分组的依据是什么?我们来看一下,右侧的Undefined和Null是用来表示不确定、无或者空的,而右侧的四种类型都是确定的、有和非空。我们可以这样说:左侧是一个存在的世界,右侧是一个空的世界。

所以,左右两个世界中的任意值做==比较的结果都是false是很合理的。(见图1中连接两个矩形的水平线上标的false)

二、空和空

JavaScript中的undefined和null是另一个经常让我们崩溃的地方。通常它被认为是一个设计缺陷,这一点我们不去深究。不过我曾听说,JavaScript的作者最初是这样想的:

假如你打算把一个变量赋予对象类型的值,但是现在还没有赋值,那么你可以用null表示此时的状态(证据之一就是typeof null 的结果是'object');相反,假如你打算把一个变量赋予原始类型的值,但是现在还没有赋值,那么你可以用undefined表示此时的状态。

不管这个传闻是否可信,它们两者做==比较的结果是true是很合理的。(见图1中右侧垂直线上标的true)。在进行下一步之前,我们先来说一下图1中的两个符号:大写字母N和P。这两个符号并不是PN结中正和负的意思。而是:

  • N表示ToNumber操作,即将操作数转为数字。它是规范中的抽象操作,但我们可以用JS中的Number()函数来等价替代。
  • P表示ToPrimitive操作,即将操作数转为原始类型的值。它也是规范中的抽象操作,同样也可以翻译成等价的JS代码。不过稍微复杂一些,简单说来,对于一个对象obj:
ToPrimitive(obj)等价于:先计算obj.valueOf(),如果结果为原始值,则返回此结果;
否则,计算obj.toString(),如果结果是原始值,则返回此结果;否则,抛出异常。

注意:此处有个例外,即Date类型的对象,它会先调用toString()方法,后调用valueOf()方法。
  • 在图1中,标有N或P的线表示:当它连接的两种类型的数据做==运算时,标有N或P的那一边的操作数要先执行ToNumber或ToPrimitive变换。

三、真与假

从图1可以看出,当布尔值与其他类型的值作比较时,布尔值会转化为数字,具体来说

true -> 1
false -> 0

这一点也不需浪费过多口舌。想一下在C语言中,根本没有布尔类型,通常用来表示逻辑真假的正是整数1和0。

四、字符的序列

在图1中,我们把String和Number类型分成了一组。为什么呢?在六种类型中,String和Number都是字符的序列(至少在字面上如此)。字符串是所有合法的字符的序列,而数字可以看成是符合特定条件的字符的序列。所以,数字可以看成字符串的一个子集。

根据图1,在字符串和数字做==运算时,需要使用ToNumber操作,把字符串转化为数字。假设x是字符串,y是数字,那么:

x == y -> Number(x) == y

那么字符串转化为数字的规则是怎样的呢?规范中描述得很复杂,但是大致说来,就是把字符串两边的空白字符去掉,然后把两边的引号去掉,看它能否组成一个合法的数字。如果是,转化结果就是这个数字;否则,结果是NaN。例如:

Number('123') // 结果123
Number('1.2e3') // 结果1200
Number('123abc') // 结果NaN
Number('\r\n\t123\v\f') // 结果123

当然也有例外,比如空白字符串转化为数字的结果是0。即

Number('') // 结果0
Number('\r\n\t \v\f') // 结果0

五、单纯与复杂

原始类型是一种单纯的类型,它们直接了当、容易理解。然而缺点是表达能力有限,难以扩展,所以就有了对象。对象是属性的集合,而属性本身又可以是对象。所以对象可以被构造得任意复杂,足以表示各种各样的事物。

但是,有时候事情复杂了也不是好事。比如一篇冗长的论文,并不是每个人都有时间、有耐心或有必要从头到尾读一遍,通常只了解其中心**就够了。于是论文就有了关键字、概述。JavaScript中的对象也一样,我们需要有一种手段了解它的主要特征,于是对象就有了toString()和valueOf()方法。

toString()方法用来得到对象的一段文字描述;而valueOf()方法用来得到对象的特征值。

当然,这只是我自己的理解。顾名思义,toString()方法倾向于返回一个字符串。那么valueOf()方法呢?根据规范中的描述,它倾向于返回一个数字——尽管内置类型中,valueOf()方法返回数字的只有Number和Date

根据图1,当一个对象与一个非对象比较时,需要将对象转化为原始类型(虽然与布尔类型比较时,需要先将布尔类型变成数字类型,但是接下来还是要将对象类型变成原始类型)。这也是合理的,毕竟==是不严格的相等比较,我们只需要取出对象的主要特征来参与运算,次要特征放在一边就行了。

六、万物皆数

我们回过头来看一下图1。里面标有N或P的那几条连线是没有方向的。假如我们在这些线上标上箭头,使得连线从标有N或P的那一端指向另一端,那么会得到(不考虑undefined和null),看下图2
image

发现什么了吗?对,在运算过程中,所有类型的值都有一种向数字类型转化的趋势。毕竟曾经有名言曰:万物皆数。

七、举个栗子

前面废话太多了,这里还是举个例子,来证明图1确实是方便有效可以指导实践的。例,计算下面表达式的值:

[' '] == false

首先,两个操作数分别是对象类型、布尔类型。根据图1,需要将布尔类型转为数字类型,而false转为数字的结果是0,所以表达式变为:

[''] == 0

两个操作数变成了对象类型、数字类型。根据图1,需要将对象类型转为原始类型:

  • 首先调用[].valueOf(),由于数组的valueOf()方法返回自身,所以结果不是原始类型,继续调用[].toString()。
  • 对于数组来说,toString()方法的算法,是将每个元素都转为字符串类型,然后用逗号','依次连接起来,所以最终结果是空字符串'',它是一个原始类型的值。

此时,表达式变为:

' ' == 0

两个操作数变成了字符串类型、数字类型。根据图1,需要将字符串类型转为数字类型,前面说了空字符串变成数字是0。于是表达式变为:

0 == 0

到此为止,两个操作数的类型终于相同了,结果明显是true。从这个例子可以看出,要想掌握==运算的规则,除了牢记图1外,还需要记住那些内置对象的toString()和valueOf()方法的规则。包括Object、Array、Date、Number、String、Boolean等,幸好这没有什么难度。

八、再次变形

其实,图一还不够完美。为什么呢?因为对象与字符串/数字比较时都由对象来转型,但是与同样是原始类型的布尔类型比较时却需要布尔类型转型。实际上,只要稍稍分析一下,全部让对象来转为原始类型也是等价的。所以我们得到了最终的更加完美的图形:
image
有一个地方可能让你疑惑:为什么Boolean与String之间标了两个N?虽然按照规则应该是由Boolean转为数字,但是下一步String就要转为数字了,所以干脆不如两边同时转成数字。

九、总结一下

前面说得很乱,根据我们得到的最终的图3,我们总结一下==运算的规则:

  • undefined == null,结果是true。且它俩与所有其他值比较的结果都是false。
  • String == Boolean,需要两个操作数同时转为Number。
  • String/Boolean == Number,需要String/Boolean转为Number。
  • Object == Primitive,需要Object转为Primitive(具体通过valueOf和toString方法)。

瞧见没有,一共只有4条规则!是不是很清晰、很简单。

十、小练习

为了更好的巩固一下以上所学,我们一起来分析以下的代码结果

1[]==[]

2[]==![]

3{}==!{}

4{}==![]

5![]=={}

6[]==!{}

看了上面的题目,不知道你们有何感想?是不是觉得自己之前的内容没有看懂- -,下面通过分析上面的习题,来更加充分理解之前的内容。

题目1: []==[]为false

因为左边的[]和右边的[]看起来长的一样,但是他们引用的地址并不同,
这个是同一类型的比较,并且除了“==”没有其他运算符,所以相对没那么麻烦。

题目2: []==![]为true

!的优先级较==高,先运算==右侧的操作数:[]是对象,
会转换成true,然后因为!再转成false(加!的一定是转换成boolean);

== 的左操作数是[],数组(对象除了日期对象,都是对象到数字的转换),
碰到==要先调用自己的valueOf()方法=>[](还是本身),
然后调用自己的toString()方法=>空字符串=>false 
(或者空字符串转成0,然后再转成false,但是终归会是false)

false==false,因此结果为true。

题目3: {}==!{}为false

和题目2的分析过程类似,先计算右边结果为false;然后再通过
valueOf()方法=>{}=>toString()方法=>object=>true,得到左边结果为true。

关于valueOf()方法和toString()方法的调用顺序和作用再说几句,下面来看两者的执行顺序这块

如果preferredType为Object,即:
1.调用 obj.valueOf(),如果执行结果是原始值,返回之;
2. 否则调用 obj.toString(),如果执行结果是原始值,返回之;
3. 否则抛异常。

如果preferredType为String,即:
1. 否调用 obj.toString(),如果执行结果是原始值,返回之;
2. 否则调用 obj.valueOf(),如果执行结果是原始值,返回之;
3. 否则抛异常。

下面再来看看两者的作用

// toString()用来返回对象的字符串表示
var obj = {};
console.log(obj.toString());//[object Object]
var arr2 = [];
console.log(arr2.toString());//""空字符串
var date = new Date();
console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (**标准时间)

// valueOf方法返回对象的原始值,可能是字符串、数值或bool值等,看具体的对象
var obj = {
  name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}
var arr1 = [1];
console.log(arr1.valueOf());//[1]
var date = new Date();
console.log(date.valueOf());//1456638436303

题目4: {}==![]为false

相比到了这里大家都知道怎么来计算了,
右边结果为false(原因看题2),左边结果
为true(原因看题3)。所以最终结果为false。

题目5: ![]=={}为fasle
题目6: []==!{}为true

题5和题6相信大家根据前面几道题的分析都能够正确的将它解答出来,因此就不细说了。看到这里,相信你们都搞明白这些让人迷糊的自动类型转换方式了吧。

前端工程师面试指南——ES6篇

ES6模块和CommonJS模块不同的地方

image

箭头函数需要注意的地方

当要求动态上下文的时候,就不能够使用箭头函数,也就是this的固定化。

  • 1、在使用=>定义函数的时候,this的指向是定义时所在的对象,而不是使用时所在的对象;
  • 2、不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误;
  • 3、不能够使用arguments对象;
  • 4、不能使用yield命令;

下面来看一道面试题,重点说明下第一个知识点:

class Animal {
  constructor() {
    this.type = "animal";
  }
  say(val) {
    setTimeout(function () {
      console.log(this); //window
      console.log(this.type + " says " + val);
    }, 1000)
  }
}
var animal = new Animal();
animal.say("hi"); //undefined says hi

【拓展】
《JavaScript高级程序设计》第二版中,写到:“超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象,在严格模式下是undefined”。也就是说在非严格模式下,setTimeout中所执行函数中的this,永远指向window!!

我们再来看看箭头函数(=>)的情况:

class Animal {
  constructor() {
    this.type = "animal";
  }
  say(val) {
    setTimeout(() => {
      console.log(this); //Animal
      console.log(this.type + ' says ' + val);
    }, 1000)
  }
}
var animal = new Animal();
animal.say("hi"); //animal says hi

【特点】

  • 不需要function关键字来创建函数
  • 省略return关键字
  • 继承当前上下文的 this 关键字

let和const

let是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。const定义常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值。

  • 1、let声明的变量具有块级作用域
  • 2、let声明的变量不能通过window.变量名进行访问
  • 3、形如for(let x..)的循环是每次迭代都为x创建新的绑定

下面是var带来的不合理场景

var arr = [];
for (var i = 0; i < 10; i++) {
  arr[i] = function () {
    console.log(i);
  }
}
arr[5]() //10,a[5]输出f(){console.log(i);},后面加个括号代表执行f()

在上述代码中,变量i是var声明的,在全局范围类都有效,所以用来计数的循环变量泄露为全局变量。所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10。

而如果对循环使用let语句的情况,那么每次迭代都是为x创建新的绑定代码如下:

var arr = [];
for (let i = 0; i < 10; i++) {
  arr[i] = function () {
    console.log(i);
  }
}
arr[5]() //5,a[5]输出f(){console.log(i);},后面加个括号代表执行f()

【拓展】
当然,除了这种方式让数组找中的各个元素分别是不同的函数,我们还可以采用ES5中的闭包和立即函数两种方法。

1、采用闭包

function showNum(i) {
  return function () {
    console.log(i)
  }
}
var a = []
for (var i = 0; i < 5; i++) {
  a[i] = showNum(i)(); //循环输出1,2,3,4
}

2、采用立即执行函数

var a = []
for (var i = 0; i < 5; i++) {
  a[i] = (function (i) {
    return function () {
      console.log(i)
    }
  })(i)
}
a[2](); //2

【面试】
把以下代码使用两种方法,依次输出0-9

var funcs = []
for (var i = 0; i < 10; i++) {
  funcs.push(function () {
    console.log(i)
  })
}
funcs.forEach(function (func) {
  func(); //输出十个10
})

方法一:使用立即执行函数

var funcs = []
for (var i = 0; i < 10; i++) {
  funcs.push((function (value) {
    return function () {
      console.log(value)
    }
  }(i)))
}
funcs.forEach(function (func) {
  func(); //依次输出0-9
})

方法二:使用闭包

function show(i) {
  return function () {
    console.log(i)
  }
}
var funcs = []
for (var i = 0; i < 10; i++) {
  funcs.push(show(i))
}
funcs.forEach(function (func) {
  func(); //0 1 2 3 4 5 6 7 8 9
})

方法三:使用let

var funcs = []
for (let i = 0; i < 10; i++) {
  funcs.push(function () {
    console.log(i)
  })
}
funcs.forEach(function (func) {
  func(); //依次输出0-9
})

其他知识点:forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。戳这里查看参考文章

Set数据结构

es6方法,Set本身是一个构造函数,它类似于数组,但是成员值都是唯一的。

const set = new Set([1,2,3,4,4])
console.log([...set] )// [1,2,3,4]
console.log(Array.from(new Set([2,3,3,5,6]))); //[2,3,5,6]

Class的讲解

class语法相对原型、构造函数、继承更接近传统语法,它的写法能够让对象原型的写法更加清晰、面向对象编程的语法更加通俗,下面是class的具体用法。

class Animal {
  constructor() {
    this.type = 'animal'
  }
  says(say) {
    console.log(this.type + 'says' + say)
  }
}
let animal = new Animal()
animal.says('hello') // animal says hello

class Cat extends Animal {
  constructor() {
    super()
    this.type = 'cat'
  }
}
let cat = new Cat()
cat.says('hello') // cat says hell

可以看出在使用extend的时候结构输出是cat says hello 而不是animal says hello。说明contructor内部定义的方法和属性是实例对象自己的,不能通过extends 进行继承。在class cat中出现了super(),这是什么呢?因为在ES6中,子类的构造函数必须含有super函数,super表示的是调用父类的构造函数,虽然是父类的构造函数,但是this指向的却是cat。更详细的参考文章

模板字符串

就是这种形式${varible},在以往的时候我们在连接字符串和变量的时候需要使用这种方式'string' + varible + 'string'但是有了模版语言后我们可以使用string${varible}string这种进行连接。基本用途有如下:

1、基本的字符串格式化,将表达式嵌入字符串中进行拼接,用${}来界定。

//es5 
var name = 'lux';
console.log('hello' + name);
//es6
const name = 'lux';
console.log(`hello ${name}`); //hello lux

2、在ES5时我们通过反斜杠()来做多行字符串或者字符串一行行拼接,ES6反引号(``)直接搞定。

//ES5
var template = "hello \
world";
console.log(template); //hello world

//ES6
const template = `hello
world`;
console.log(template); //hello 空行 world

【拓展】
字符串的其他方法

// 1.includes:判断是否包含然后直接返回布尔值
let str = 'hahay'
console.log(str.includes('y')) // true

// 2.repeat: 获取字符串重复n次
let s = 'he'
console.log(s.repeat(3)) // 'hehehe'

重点“人物”:Promise!

  • 概念: Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合合理、强大。所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promies是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。处理过程流程图:
    image

【面试套路1】
手写一个promise

var promise = new Promise((resolve, reject) => {
  if (操作成功) {
    resolve(value)
  } else {
    reject(error)
  }
})
promise.then(function (value) {
  // success
}, function (value) {
  // failure
})

【面试套路2】
怎么解决回调函数里面回调另一个函数,另一个函数的参数需要依赖这个回调函数。需要被解决的代码如下:

$http.get(url).success(function (res) {
  if (success != undefined) {
    success(res);
  }
}).error(function (res) {
  if (error != undefined) {
    error(res);
  }
});

function success(data) {
  if( data.id != 0 {
    var url = "getdata/data?id=" + data.id + "";
    $http.get(url).success(function (res) {
      showData(res);
    }).error(function (res) {
      if (error != undefined) {
        error(res);
      }
    });
  }
}

【面试套路3】
以下代码依次输出的内容是?

setTimeout(function () {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for (var i = 0; i < 10000; i++) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function () {
  console.log(4);
});
console.log(5);



// 首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列
里面,因此开始肯定不会输出 1  
// 然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。 
// 然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。 
// 因此,应当先输出 5,然后再输出 4 , 最后在到下一个 tick,就是 1 。

【面试套路4】
jQuery的ajax返回的是promise对象吗?

// jquery的ajax返回的是deferred对象,通过promise的resolve()方法将其转换为promise对象。

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

【面试套路5】
promise只有2个状态,成功和失败,怎么让一个函数无论成功还是失败都能被调用?

// 使用promise.all()

// Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

// Promise.all方法接受一个数组作为参数,数组里的元素都是Promise对象的实例,如果不是,就会先调
// 用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数
// 可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)

// 示例:
var p =Promise.all([p1,p2,p3]);
// p的状态由p1、p2、p3决定,分为两种情况。
// 当该数组里的所有Promise实例都进入Fulfilled状态:Promise.all**返回的实例才会变成Fulfilled状态。
// 并将Promise实例数组的所有返回值组成一个数组,传递给Promise.all返回实例的回调函数**。

// 当该数组里的某个Promise实例都进入Rejected状态:Promise.all返回的实例会立即变成Rejected状态。
并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。

【面试套路6】
一、分析下列程序代码,得出运行结果,解释其原因

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)



运行结果:
1 2 4 3

原因:
Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

二、分析下列程序代码,得出运行结果,解释其原因

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)



运行结果:
promise1 Promise { <pending> }
promise2 Promise { <pending> }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
  <rejected> Error: error!!!
    at promise.then (...)
    at <anonymous> }


原因:
promise  3 种状态:pending(进行中)、fulfilled(已完成,又称为Resolved)  rejected(已失败)。状态改变只能是 pending->fulfilled 或者 pending->rejected,状态一旦改变则不能再变。上面 promise2 并不是 promise1,而是返回的一个新的 Promise 实例。

三、分析下列程序代码,得出运行结果,解释其原因

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})

promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })



运行结果:
then:success1
原因:
构造函数中的 resolve  reject 只有第一次执行有效,多次调用没有任何作用,呼应代码二结论:promise 状态一旦改变则不能再变。

四、分析下列程序代码,得出运行结果,解释其原因

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })



运行结果:
1  2
原因:
promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。

五、分析下列程序代码,得出运行结果,解释其原因

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})

const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})



运行结果:
once
success 1001
success 1001
原因:
promise  .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。

六、分析下列程序代码,得出运行结果,解释其原因

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })



运行结果
then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

原因
.then 或者 .catch  return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成其中一种:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))

七、分析下列程序代码,得出运行结果,解释其原因

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)



运行结果
TypeError: Chaining cycle detected for promise #<Promise>
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:667:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:607:3
原因
.then  .catch 返回的值不能是 promise 本身,否则会造成死循环。

八、分析下列程序代码,得出运行结果,解释其原因

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)



运行结果:
1
原因:
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

九、分析下列程序代码,得出运行结果,解释其原因

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })



运行结果:
fail2: Error: error
    at success (...)
    at ...
原因:
.then 可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。.catch  .then 第二个参数的简便写法,但是它们用法上有一点需要注意:.then 的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。

十、分析下列程序代码,得出运行结果,解释其原因

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')



运行结果:
end
nextTick
then
setImmediate
原因:
process.nextTick  promise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。

简单的科学上网

刚开始是使用蓝灯,速度很不错,稳定性也不赖。只是对我来说略贵,可能是因为还无法十分流畅的浏览各种国外网站,假如对英语不再感到头大,并且每天都泡在英文网站中,那么蓝灯那就是首选。下面介绍一款适合简单适用的软件。

地址及使用方式(管理员跑路了MMP)

  • 进入官网,当然了,想要我获得一些推荐奖励的可以点击这里
  • 注册
  • 登录
  • 充值(5¥)
  • 购买套餐
  • 下载客户端
  • 看图配置
  • ok

搬瓦工的使用

上面的网速不太稳定,决定换搬瓦工试试,教程如下
教程1
教程2(需要翻墙浏览)

Github使用心得

😇 老祖宗说得好

兵欲善其事,必先利其器。想要学好编程,Github是一件利器!


记录显示,我是在2016年7月5日注册的Github,意思就是说截止到今天(2018-01-05),时间已经过去549天(如何得出这日期?),但是为啥我的Github还宛如一坨那个啥?!!

反省了一下,除了自身对英语的恐惧感,以及网上各种过时的、说的云里雾里的教程外,还有自己的一个兴趣、意识、学习力、缺乏应用场景等等等。剩下的就不说,相信都懂,说多了容易陷入自我否定的死循环,下面开始进入正题。

基础

🔎熟悉Markdown语法

由于在github上面的文本编辑是完全基于Markdown语法的,如果你想知道什么是Markdown语法,请百度或者Google;如果你想练习Markdown语法,那么请戳这里,老老实实,按部就班敲一敲,很快就能上手,相信你也会很快爱上它,因为它在很大程度上让我们节省了大量的时间和精力,使我们专注于内容,而不是各种花里花俏的排版。

🔅创建应用场景

都说掌握一个工具,掌握一门编程语言最好的方式就是不断地实践,然而大部分无人指点的新手根本无法准确的知道这个工具或者这门编程语言到底能干什么,从哪下手还真是个问题??长期下来,自然而然对其就会失去兴趣。

相信大部分刚接触web开发的新手(当然包括我啦),都十分渴望将自己开发的网站部署到网上,能随时随地让自己或让别人进行浏览。除了购买网络主机能够实现这一点,利用Github也可以。在这一方面,Github无疑是最好的选择,理由是:简单、免费、能够充分展示自己。下面展示如何将自己的网站部署到Github:

1、无比详细的新手教程

2、相信很多有经验的人都能看出新手教程那篇未免过于啰嗦,甚至有些步骤完全没必要。但是,凡事都得有个过程,得一步步来,熟悉后的步骤为:

  • New repository
  • git clone
  • cd
  • git add .
  • git commit -m ""
  • git pull
  • git push

在这个过程中有两个点需要稍微解释下:

1、选择Initialize this repository with a README,是为了初始化一个README文件,看字面意思就知道。

2、在Initialize this repository with a README下方有两个选项,第一个添加规则,表示git push应该忽略掉哪些文件,我们可以填node那个包文件;另一个是选择哪项开源协议的意思,一般填写MIT。

3、选择master branch,是为了给项目一个地址,这样才能通过url访问到你的网站。

📑Github常用板块说明

Overview :

翻译成中文就是“概述”的意思,但是实际上它相当于你的社交账号的首页,上面展示了你的一些项目和基本信息。你可以点击customize your pinned repositories 把你最为得意或者最想让其他人看到的项目展示出来。

Repositories:

根据字面意思可知,这是你的项目仓库,你的所有项目都包含在里面(包括从别人那里fork来的项目)。

Stars:

这是你点赞过或者收藏的项目,假如你认同一个人的项目,无妨给个stars,假如你想对一个人的项目肆无忌惮的动手动脚,那么就将该项目fork下来。

Follwers:

根据字面意思可知,这是你的跟随者,也就是关注你和认同你的粉丝。

Follwing:

根据字面意思可知,这是你跟随的人,也就是你所关注和认同的人。

📗如何利用Github进行学习?

其实感觉现在自己也并没有充分利用到这个全球最大的同性交流平台),不过还是要谈谈自己在这里所得到的成长:

1、开阔眼界:
在这里真的见到了太多的对编程热情和擅长的人,他们的项目以及所记录的事情绝对会让你重新认识何谓编程爱好者、何谓技术人生。如何关注牛人?其实途径有非常多,在这里推荐一个自己欣赏的人jawil。然后你可以根据他所关注的人进行选择关注更多的牛人,从而学习他们,提升自己。

2、学会总结:
也不知道为啥,现在觉得在Github上做相关的总结是十分令人身心愉悦的事情,在这过程中也慢慢地促进了自己的成长。

3、阅读英文文档:
不说了,都是被逼的。逼着逼着,突然就喜欢了。。。

🎯如何更好的玩耍Github?

  • 下载各种有用的插件>
    萝卜青菜,各有所爱,这个还是大家自行搜索吧。之前我用的是jawil开发的GayHub插件,用来优化Github的阅读体验。
  • 使用emoji
    这是Github支持的表情列表,能够让你的Github拥有一定*动性,比如这些:cupid: :hankey: :dancer:,请查看表情列表大全

💤如何用Github Issues写技术博客?

介绍一篇文章,里面说的非常详细,相信看完后能马上学会使用。点击这里

💦当使用git上传项目到GitHub老是要输入账号密码怎么破?

其实导致上面的原因是线上与线下的环境并没有打通,只要我们创建并配置好了SSH-key那么问题就会迎刃而解,其中的原理就是在线下创建一个私钥,线上配置好了一个公钥,私钥和公钥进行连接配对,这样我们在上传和下载代码的时候就不用账号和密码了。关于如何创建并配置GitHub的SSH-key网上有非常多的教程,大家自行搜索,我就不一一说明。

当你配置好SSH-key后,那么在github上创建的项目中,在clone or download那里就能选择是“Use HTTPS”还是“Use SSH”将项目下载到本地上。如果使用的是SSH,那么在git push的时候就不用输入账号与密码;使用HTTPS方式的话,可能每次git push或者git pull的时候就要输入账号和密码了,十分不方便。

🐰GitHub上分支的作用以及创建

在多人协作开发中,分支的作用十分明显,因为能够十分清楚的知道目前的分支内容与上次的分支内容到底改变了什么,利于我们快速还原代码和寻找bug。下面具体介绍下创建一个分支的过程:

  • 假装你已经熟悉我上面所说的内容,在GitHub上创建了一个项目,并且git clone到本地
  • 步骤一:进入本地git clone的项目地址;
  • 步骤二:试着新建一个文档并将该文档push到GitHub上,记得文档上要有内容;
  • 步骤三:在git命令窗口创建一个本地分支(git branch 分支名);
  • 步骤四:切换到新的分支(git checkout 分支名)
  • 步骤五:修改文档内容,并且重新上传到GitHub中;
  • 步骤六:在最后一步上传命令中得修改为git push origin 分支名
  • 步骤七:到GitHub对比下这两个分支的内容,细细体会下分支对日常开发起到的作用。
  • 参考资料:点击我呀

如果你需要将该分支的内容和主分支的合并,那么只需这么做就可以了:

  • 首先是切换到主分支(git checkout master),主分支名默认为master;
  • 然后使用命令git pull origin 主分支名:主分支名,将最新的线上分支代码同步下来
  • 接着是合并内容(git merge origin/分支名);
  • 最后是提交内容(git push或者git push orgin 分支名),假如这步有错误的话说明你线上主分支内容有变化,而你在一开始并没有git pull拉下来让本地和线上代码保持一致

🌻 git回滚到任意版本

在开发项目的时候,往往在完成一个功能或者模块的时候,都会把运行良好的代码push到云端。这样我再继续往下开发的时候,当发现当前代码变得不可维护的时候,我们能够回滚到任何一个已经提交的版本代码,下面是相关步骤:

  • 查看已提交过的代码: git log
commit 9754f375538855f584498fcaecc2ad5852c98896 (HEAD -> zxfHome, origin/zxfHome)
Author: James Wenming <***[email protected]>
Date:   Thu Aug 9 13:45:18 2018 +0800
    投放平台首页
  • 回滚到指定代码版本: git reset --hard commit值(commit值指的是git log后每个版本里面commit的值)

🌻 git回滚到提交前

有时候我们会习惯性执行git add . 和git commit -m ""。当我们需要回到git add .前一步的时候那么执行该命令

  • git reset --soft HEAD^

🐰 git log看不到最新的提交记录怎么破?

有这么一个场景:比如今天是1月6号,你改了一些代码,并且将其push到云端。然后突然发现了一个bug,于是git log查看提交记录,回滚到了1月5号写的代码中进行相关的测试。最后发现是后台童鞋的锅,那怎么办了,好办,再回滚今天1月6号提交的代码,然而,此时问题就来了“git log能查看到的提交记录最新是1月5号,今天1月6号提交的记录不见了!!”

这该如何是好,查看不到提交记录,意味着无法得到commit值,无法回滚到今天的项目版本,这就白写一天了!莫方,解决办法还是有的

  • 1、在一开始提交今天1月6号的代码时,记住git log查看到的1月6号提交的代码commit值
  • 2、在云端上查看commit值,比如在码云上你可以点击分支(如果有的话),再点击最新的提交关键字,之后你会看到这样的一个页面
    image

🌻 git对他人的代码进行修改

步骤一:使用命令git checkout 分支名,切换到需要被修改的分支
步骤二:使用命令git pull origin 分支名:分支名,将最新的线上分支代码同步下来

🐰 git push origin 分支名 失败了怎么办

造成上面的错误往往是因为在push之前没有将最新的线上代码pull下来,如何用命令来改正这个错误呢?其实我暂时也没发现很好的办法,因为在执行push命令的时候往往已经git add .和git commit -m "内容", 如果要pull最新的线上代码,那么就必须把已经暂存到线上的代码全部恢复下来,就是这一步让我不知手措。

下面给出我的一个笨方法:

  • 将更新的代码复制出一份保存在本地
  • 接着使用git log和git reset --hart commit值进行代码回滚
  • 然后再使用git pull origin 分支名:分支名
  • 把复制出来的代码再粘贴回线上最新的项目中
  • 最后就是:git add .、git commit -m "内容"、git push origin 分支名

🌻 解决git pull/push每次都需要输入密码的问题

如果我们git clone的下载代码的时候是连接的https://而不是git@git (ssh)的形式,当我们操作git pull/push到远程的时候,总是提示我们输入账号和密码才能操作成功,频繁的输入账号和密码会很麻烦。当然这个解决办法很简单,只要在命令行中输入:

git config --global credential.helper store

那么就会在你本地生成一个文本,上边记录你的账号和密码。然后你使用上述的命令配置好之后,再操作一次git pull,然后它会提示你输入账号密码,这一次之后就不需要再次输入密码了。

将远程git仓库里的指定分支拉取到本地(本地不存在的分支)

当我想从远程仓库里拉取一条本地不存在的分支时:

  • 方式一:git checkout -b 本地分支名 origin/远程分支名
  • 方式二:git fetch origin 远程分支名x:本地分支名x(事后需要手动切换分支git checkout 分支名)

本地分支改名和删除线上分支

  • 改名:git branch -m 新分支名
  • push到线上:git push origin head
  • 查看线上分支:git branch -a
  • 删除线上分支:git push origin --delete 分支名

恢复另一个分支的代码

场景:在A分支有个a.txt文件,基于A分支创建了B分支,在B分支中修改了a.txt文件并且push到了线上,之后我们想要将B分支中的a.txt内容恢复为A分支的a.txt内容,那么通过git命令该如何做呢?很简单

  • git checkout origin/A -- a.txt (注:需要填写完整的a.txt的本地路径)

拉取线上分支最新代码到本地,合并之后push

  • git fetch
  • git merge origin/目标分支

git跳过eslint代码检查

  • git commit --no-verify -m "备注"

Node开发实战知识整理

工具

介绍一款很方便的在线编辑器——webIDE,点击进入官网

前言

这是一本书的学习总结,书名为《Node.js开发实战详解》,作者为腾讯出身的黄丹华。由于这会是一篇很长的课程学习总结,为了优化大家的阅读体验,强烈建议安装Chrome浏览器的插件——GayHub。下载安装地址

第 1 章:Node.js基础知识

为什么使用Node

因为Node能处理高并发请求,而且由于Node.js是事件驱动,因此能够更好的节约服务器内存资源。同时,Node.js可以单独实现一个server,这也是Node一个非常大的优点,对于那些简单的server,通过Node实现比使用C++实现会简单得多。

最后,牢记Node解决了长连接、多请求引发的成本问题,因此在一些项目中,比如实时在线的游戏、实时聊天室、实时消息推送功能、实时监控系统等开发过程中,应该把握机会,应用Node来开发。

同步调用和异步调用

1、同步调用时一种阻塞式调用,一段代码调用另一段代码时,必须等待这段代码执行结束并返回结果后,代码才能继续执行下去。例如下面的代码

let n1 = 1
let n2 = 2
let n3 = 3
alert(n1)
alert(n2)
alert(n3)

2、异步调用是一种非阻塞式调用,一段异步代码还未执行完,可以继续执行下一段代码逻辑,当其他同步代码执行完之后,通过回调返回继续执行相应的逻辑,而不耽误其他代码的执行。例如下面的代码

let n1 = 1
let n2 = 2
let n3 = 3
alert(n1)
setTimeout(function() {
  alert(n2)
}, 2000)
alert(n3)

当然,关于异步还有另外一个例子,这个栗子也引出了异步调用和回调这两个东东的概念,下面请看代码

function Person() {
  this.think = function(callback) {
    setTimeout(function() {
      console.log('想出来了!')
      callback()
    }, 5000)
  }
  this.answer = function() {
    console.log('我正在思考一个问题^_^')
  }
}
var person = new Person()
person.think(function() {
  console.log('花费5s,得到一个正确的思考')
})
person.answer()

回调和异步调用

首先明确一点,回调并非是异步调用,回调是一种解决异步函数执行结果的处理方法。在异步调用里,如果我们希望将执行的结果返回并且处理时,可以通过回调的方法解决。为了能够更好的区分回调和异步回调的区别,下面看一个简单的栗子

function waitFive(name, callbackfn) {
  var pus = 0
  var currentDate = new Date()
  while(pus < 5000) {
    var now = new Date()
    pus = now - currentDate
  }
  // 执行回调函数
  callbackfn(name)
}
// 定义回调函数echo()
function echo(name) {
  console.log(name)
}
// 调用waitFive方法
waitFive('回调函数被调用啦', echo)
console.log('略略略略')

以上代码是一个回调逻辑,但不是一个异步代码逻辑,因为其中并没有涉及Node的异步调用接口。从上面的代码结果可以看出回调和异步调用的区别,当waitFive()方法执行时,整个代码执行过程都会等待waitFive()方法的执行,而并非如异步调用那样:waitFive()方法未结束,还会继续执行console.log('略略略略')。这也说明了回调还是一种阻塞式调用。

获取异步函数的执行结果

异步函数往往不是直接返回执行结果,而是通过事件驱动的方式,将执行结果返回到回调函数中,之后在回调函数中处理相应的逻辑代码。

如何来理解以上的代码呢?请看下面一个代码案例

var dns = require('dns')
dns.resolve4('id.qq.com', function(err, address) {})
console.log(address)

dns.resolve4()是一个异步函数,由此带来的问题就是console.log(address)输出的结果是undefined,因为你懂得,异步嘛,对不对。

既然异步函数会出现这个问题,那么我们就可以使用回调函数去获取参数,下面请看代码

var dns = require('dns')
dns.resolve4('id.qq.com', function(err, address) {
  if(err) {
    throw err
  }
  console.log(address)
})

第 2 章:模块和NPM

Node模块的概念

  • 原生模块:是Node中API提供的原生模块,原生模块在启动时已经被加载了。
  • 文件模块:是一种动态加载模块,加载文件模块的工作主要由原生模块module来实现和完成。总而言之,原生模块在启动时已经被加载,而文件模块则需要通过调用Node中的require方法来实现加载。

需要了解一点的是,Node会对原生模块和文件模块都进行缓存,因此在第二次require该模块的时候,不会有重复开销去加载模块,只需要从缓存中读取相应模块数据即可。

原生模块的调用

使用Node提供的API——require来加载相应的Node模块,require加载成功后会返回一个Node模块对象,该对象拥有该模块的所有属性和方法,如下代码

var httpModule = require('http')
httpModule.createServer(function(req, res) {
  
}).listen(port)

以上就是一个简单的调用原生模块的方法,Node中其他原生模块的调用方法都是一样的,主要是学会如何查看Node的API文档,以及如何应用其中的模块提供的方法和属性。

文件模块调用

文件模块的调用和原生模块的调用方式基本一致,但是需要注意的是,其两者的加载方式存在一定的区别,原生模块不需要指定模块路径,而文件模块加载时必须指定文件路径。比如我们在项目中创建一个test.js文件,代码如下

exports.name = '能被调用的变量'
exports.happy = function() {
  console.log('能被调用的方法')
}

var yourName = '不能被调用的变量'
function love() {
  console.log('不能被调用的方法')
}

接着我们在同一个目录中创建diaoyong.js文件加载test.js这个文件模块,代码如下

var test = require('./test.js')
console.log(test)

以上代码也指明了,在文件模块中,只有exports和module.exports对象暴露给该外部的属性和方法,才能够通过返回的require对象进行调用,其他方法和属性是无法获取的。

Node原生模块实现web解析DNS

我们使用Node的原生模块和文件模块两个方法实现DNS解析工具,通过分析对比,来说明文件模块存在的必要性,以及其存在的必要性。

下面我们先看一下使用原生模块创建的DNS解析工具代码(先创建parse_dns_ex.js文件)

// 加载创建web的HTTP服务器模块
var http = require('http')
// 加载DNS解析模块
var dns = require('dns')
// 加载文件读取模块
var fs = require('fs')
// 加载URL处理模块
var url = require('url')
// 加载处理请求参数模块
var querystring = require('querystring')
http.createServer(function(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  // 获取当前html文件路径
  var readPath = __dirname + '/' + url.parse('2-1-3.html').pathname
  // 同步读取文件
  var indexPage = fs.readFileSync(readPath)
  res.end(indexPage)
}).listen(3000, '127.0.0.1')

接着我们创建一个html文件,代码如下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>DNS查询</title>
  </head>
  <body>
    <h1 style="text-align: center;">DNS查询工具</h1>
    <div style="text-align: center;">
      <form action="/parse" method="post">
        <span>查询DNS:</span>
        <input type="text" name="search-dns" />
        <input type="submit" value="查询" />
      </form>
    </div>
  </body>
</html>

然后我们在命令行输入命令:node parse_dns_ex.js,就能够在浏览器输入http://127.0.0.1:3000,看到HTML页面内容。

由于以上代码只是实现了一个表单提交的页面,对于DNS解析部分还没有做任何处理,因此我们需要对js文件做如下的处理

var http = require('http')
var dns = require('dns')
var fs = require('fs')
var url = require('url')
var querystring = require("querystring")
http.createServer(function(req, res) {
  // 获取当前请求资源的url路径
  var pathname = url.parse(req.url).pathname
  // 设置编码格式,避免出现乱码
  req.setEncoding("utf8")
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  router(res, req, pathname)
}).listen(3000, "127.0.0.1")
console.log('Server running at http://127.0.0.1:3000/')
// 路由函数实现
function router(res, req, pathname) {
  switch(pathname) {
    case "/parse":
      parseDns(res, req)
      break;
    default:
      goIndex(res, req)
  }
}
// 解析域名函数实现
function parseDns(res, req) {
  var postData = ""
  req.addListener("data", function(postDataChunk) {
    postData += postDataChunk
  });
  req.addListener("end", function() {
    var retData = getDns(postData, function(domain, addresses) {
      res.writeHead(200, {
        'Content-Type': 'text/html'
      });
      res.end(`
        <html>
          <head>
            <title>DNS解析结果</title>
            <meta charset='utf-8'>
          </head>
          <body>
            <div style='text-align:center'>
              Domain:<span style='color:red'>${domain}</span>
              IP:<span style='color:red'>${addresses.join(',')}</span>
            </div>
          </body>
        </html>
      `)
    })
    return
  })
}
// 返回html文件函数实现
function goIndex(res, req) {
  var readPath = __dirname + '/' + url.parse('2-1-3.html').pathname
  // 同步读取html文件的信息
  var indexPage = fs.readFileSync(readPath)
  res.end(indexPage)
}
// 异步解析域名函数
function getDns(postData, callback) {
  var domain = querystring.parse(postData).search_dns;
  dns.resolve(domain, function(err, addresses) {
    if(!addresses) {
      addresses = ['不存在域名']
    }
    callback(domain, addresses)
  })
}

以上代码就能够实现当你输入www.qq.com的时候,显示它的IP。

Node文件模块实现web解析DNS

文件模块的好处在于将业务处理分离,每个模块处理相应的职责,避免业务混乱。接下来我们分析DNS解析系统需要划分哪些模块,以及这些模块之间的功能和作用分别是什么。下面就来看看各个模块的作用以及具体代码。

入口模块(index.js),创建http服务器处理客户端请求

// 加载原生http和url模块
var http = require('http')
var url = require('url')
// 加载文件模块之路由处理模块
var router = require('./router.js')
http.createServer(function(req, res) {
  // HTTP请求路径
  var pathname = url.parse(req.url).pathname
  req.setEncoding('utf8')
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  // router(res,req,pathname)是router文件模块中的exports方法
  router.router(res, req, pathname)
}).listen(3000, '127.0.0.1')
console.log('Server runing http://127.0.0.1:3000')

路由处理模块(router.js),处理所有请求资源,分发到相应处理器。说白了就是负责url转发以及请求资源分配。

// 加载文件模块之DNS解析模块
var ParseDns = require('./parse_dns.js')
// 加载文件模块之首页展示模块
var MainIndex = require('./main_index.js')
exports.router = function(res, req, pathname) {
  switch(pathname) {
    case '/parse':
      ParseDns.parseDns(res, req)
      break;
    default:
      MainIndex.goIndex(res, req)
  }
}

这里需要注意的是,router方法必须应用exports暴露给require返回的对象,如果不使用exports方法,相对于router.js文件模块来说就是私有方法,require router模块返回对象将无法调用。

DNS解析模块(parse_dns.js),DNS处理逻辑,根据获取的域名进行解析,返回相应的处理结果到页面,这部分代码和上面的原生模块的代码类似,主要是parseDns和getDns两个方法。

var querystring = require('querystring')
var dns = require('dns')
exports.parseDns = function(res, req) {
  var postData = ''
  req.addListener('data', function(postDataChunk) {
    postData += postDataChunk
  })
  req.addListener('end', function() {
    var retData = getDns(postData, function(domain, addresses) {
      res.writeHead(200, {
        'Content-Type': 'text/html'
      });
      res.end(`
        <html>
          <head>
            <title>DNS解析结果</title>
            <meta charset='utf-8'>
          </head>
          <body>
            <div style='text-align:center'>
              Domain:<span style='color:red'>${domain}</span>
              IP:<span style='color:red'>${addresses.join(',')}</span>
            </div>
          </body>
        </html>
      `)
    })
    return
  })
  // 获取post数据
  function getDns(postData, callback) {
    var domain = querystring.parse(postData).search_dns
    // 异步解析域名
    dns.resolve(domain, function(err, addresses) {
      if(!addresses) {
        addresses = ['不存在域名']
      }
      callback(domain, addresses)
    })
  }
}

以上的js代码对应html代码为

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
  </head>
  <body>
    <h1 style="text-align: center;">DNS查询工具</h1>
    <div style="text-align: center;">
      <form action="/parse" method="post">
        <span>查询DNS:</span>
        <input type="text" name="search_dns" />
        <input type="submit" value="查询" />
      </form>
    </div>
  </body>
</html>

首页展示模块(main_index.js),处理主页index.html页面的显示,使用fs模块进行读取index.html页面字符数据,然后返回到客户端。

var fs = require('fs')
var url = require('url')
exports.goIndex = function(res, req) {
  var readPath = __dirname + '/' + url.parse('index.html').pathname
  // 同步读取index.html页面数据
  var indexPage = fs.readFileSync(readPath)
  res.end(indexPage)
}

整个过程结束后,运行index.js同样可以实现跟原生模块DNS解析的作用。

exports和module.exports

两者的作用都是将文件模块的方法和属性暴露给require返回的对象进行调用。但是二者存在本质的区别:exports的属性和方法都可以被module.exports替代,反过来则不行。它们之间还有以下的不同点

  • module.exports方法还可以单独返回一个数据类型,而exports只能返回一个object对象,因此,当我们需要返回一个数组、字符串、数字等类型的时候,就必须使用module.exports
  • 当在exports前面使用了moudle.exports,那么exports的任何方法和属性都会失效,请看下面案例代码
index.js文件模块
module.exports = 'exports的属性和方法将被忽视!'
exports.name = '我无法被调用'
exports.showName = function () {
  console.log('我也无法被调用')
}
console.log('内部module.exports值被调用:' + module.exports)

// 调用index.js文件模块
var Book = require('./index.js')
console.log('调用Book:' + Book)
console.log('调用Book中的name:' + Book.name)
console.log('调用Book中的showName():' + Book.showName())

习题检测

(1)实现person.js文件模块,其返回的是一个person函数,该函数中有eat和say方法

module.exports = function() {
  this.eat = function(){}
  this.say = function(){}
}

(2)实现person.js文件模块,其返回的是一个eat方法和say方法的对象

exports.Person = {
  'eat':function() {},
  'say':function() {}
}

(3)实现person.js文件模块,其返回的是一个数组,数组内容为人名

module.exports = ['jack', 'tom', 'lucy']

(4)实现person.js文件模块,其返回的是一个对象,该对象中包含一个数组元素

exports.arr = ['jack', 'tom', 'lucy']

使用Express

express是一个Node.js的web开源框架,该框架可以快速搭建web项目开发的框架。其主要集成了web的http服务器的创建、静态文件管理、服务器url请求处理、get和post请求分支、session处理等功能。下面是使用express的步骤

  • 安装: npm install -g express
  • 安装依赖: npm install -g express-generator
  • 创建一个app应用: express 项目名称
  • 进入到项目中: cd 项目名称
  • 安装jar包: npm install
  • 运行项目: npm start 或者DEBUG=nodedemo:* npm start,总之按照提示走就对了
  • 访问地址:http://127.0.0.1:3000/

查看当前版本:express --version

使用jade模块

该模块的作用就是可以内嵌其他代码到html页面中,比如在html页面中内嵌php代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <?php
	  $name = 'hello php';
	  echo $name;
    ?>
  </body>
</html>

下面我们在一个express项目中使用jade模块。首先创建一个jade.js文件,记得事先安装(npm install jade)好了jade模块

var express = require('express');
var http = require('http');
var app = express();
// 设置模板引擎
app.set('view engine', 'jade');
// 设置模板相对路径(相对当前目录)
app.set('views', __dirname);
app.get('/', function(req, res) {
  // 调用当前路径下的 test.jade 模板
  res.render('test');
})
var server = http.createServer(app);
server.listen(3002);
console.log('server started on http://127.0.0.1:3002/');

接着,创建一个test.jade模板

- console.log('hello'); // 这段代码在服务端执行
- var s = 'hello world' // 在服务端空间中定义变量
p #{s}
p= s

最后,在命令行终端运行:node jade.js。关于更多jade知识,可以点击这里进行学习。

习题检测

在安装好jade模块的express项目中,修改routes/index.js文件,传递一个名为info的json对象,json值为{'name':'jack','book':'Node.js'}。同时在views/index.jade页面使用div展示name,使用h2标签展示book

var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index.jade', {
    name: 'jack',
    book: 'Node.js',
    title: 'a'
  })
});
module.exports = router
 

extends layout
block content
  h2 #{book}
  p Welcome to #{name}

使用forever模块

服务器管理是系统上线后,必须要面对的问题。最好有一个软件可以提供整套的服务器运行解决方案:要求运行稳定,支持高并发,启动/停止命令简单,支持热部署,宕机重启,监控界面和日志,集群环境。接下来,就让我们使用forever模块来实现以上的要求。

使用步骤

  • 安装:npm install forever -g
  • 查看模块内置命令: forerver --help
  • 运行express框架自带的app.js文件:forever start -l forever.log -o out.log -e err.log app.js
-l指定forever运行日志,-o指定脚本流水日志,
-e指定脚本运行错误日志。启动后将会在本项目中产生out.log、err.log文件
  • 查看node进行返回结果:netstat

更多关于forever模块的知识请点击这里,不过到了现在,该模块貌似被PM2给取代了,有兴趣的去看看吧。

使用socket.io模块

socket.io是一个基于Node.js的项目,其作用主要是将WebSocket协议应用到所有的浏览器。该模块主要应用于实时的长连接、多请求项目中,例如在线互联网游戏、实时聊天、实时股票查看、二维码扫描登录等。

由于书中给的案例并没有跑起来,因此只将其代码抄了下来,相关的解释说明等过后再说

// 设置监听端口
var io = require('socket.io').listen(8080)
// 当客户端connection时,执行回调函数
io.sockets.on('connection', function(socket) {
  // 连接成功后发送一个news消息,消息内容为json对象
  socket.emit('news', {
    hello: 'world'
  })
  // 客户端发送my other event消息时,服务器端接受该消息
  // 成功获取该消息后执行回调函数
  socket.on('my other event', function(data) {
    console.log(data)
  })
})

以及客户端代码

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>socket</title>
  </head>
  <body>
    <script src="socket.js"></script>
    <script>
      // 使用socket连接本地socket服务器
      var socket = io.connect('http://localhost:8080')
      // 接受到服务器发送的news消息后,当服务器推送news消息后执行回调函数
      socket.on('news', function(data) {
        console.log(data)
        // 客户端接受news消息成功后,发送my other event消息到
        // 服务器,其发送的消息内容为json对象
        socket.emit('my other event', {
          my: 'data'
        })
      })
    </script>
  </body>
</html>

使用request模块

request模块为Node.js开发者提供了一种简单访问HTTP请求的方法。在开始之前我们需要进行模块安装npm install request,不过在安装之前要记得先使用npm init初始化一个package.json文件,否则模块是安装不上去的。

下面我们来看重点内容,get和post请求。要牢记的是:get和post只是发送机制不同,并不是一个取一个发!

首先看看处理get请求,首先创建一个HTTP服务器发出一个请求(app_get.js)

// 创建一个http服务器
var http = require('http')
http.createServer(function(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/plain'
  })
  // 返回一个字符 ,并且打印出请求的方式
  res.end('Hello World\n' + req.method)
}).listen(1337, "127.0.0.1")
console.log('服务运行地址=> http://127.0.0.1:1337/');

然后通过request模块去请求该服务器数据,并将服务器返回结果打印出来(request_get.js)

var request = require('request')
request.get('http://127.0.0.1:1337', function(error, response, result) {
  console.log(result)
})

应用request模块的get方法,发起一个HTTP请求,请求本地http://127.0.0.1:1337服务器数据,request.get()方法中的两个参数分别是请求url和回调函数。

接下来运行node app_get.js代码,启动服务器,可以到浏览器看看输出的内容;然后再打开一个命令行(记得之前启动服务器的命令行不要关闭),运行node request_get.js代码,可以看到从服务器中请求回来的数据。

下面我们再来看一看关于post请求的一些代码,和get方式类似,也是先创建一个HTTP服务器(app_post.js),该服务器接收客户端的post参数,并将该参数作为字符串响应到客户端

var http = require('http')
// querystring从字面上的意思就是查询字符串,一般是对http请求所带的数据进行解析
var querystring = require('querystring')
http.createServer(function(req, res) {
  var postData = ""
  // 开始异步接收客户端post的数据
  req.addListener("data", function(postDataChunk) {
    postData += postDataChunk
  })
  // 异步post数据接收完成后执行匿名回调函数
  req.addListener("end", function() {
    // 解析客户端发送的post数据,并将其转化为字符
    var postStr = JSON.stringify(querystring.parse(postData))
    res.writeHead(200, {
      'Content-Type': 'text/plain'
    })
    // 响应客户端请求的post数据
    res.end(postStr + '\n' + req.method)
  })
}).listen(1400, "127.0.0.1")
console.log('Server running at http://127.0.0.1:1400/')

接着应用request模块发起HTTP的post请求,将数据传到HTTP服务器中,然后又取得服务器返回的数据(request_post.js)

var request = require('request')
request.post('http://127.0.0.1:1400', {
  // form意思是表单数据
  form: {
    'name': 'danhuang',
    'book': 'node.js'
  }
}, function(error, response, result) {
  console.log(result)
});

接下来运行node app_post.js代码,启动服务器,可以到浏览器看看输出的内容;然后再打开一个命令行(记得之前启动服务器的命令行不要关闭),运行node request_post.js代码,可以看到从服务器中返回来处理过的数据,同时将HTTP请求方式也响应到客户端。

使用Formidable模块

formidable模块实现了上传和编码图片和视频。它支持GB级上传数据处理,支持多种客户端数据提交。有极高的测试覆盖率,非常适合在生产环境中使用。

在原生的Node.js模块中,提供了获取post数据的方法,但是没有直接获取上传文件的方法,因此需要利用formidable模块来处理文件上传逻辑。接下来我们看一下实例(记得先npm init,然后npm install formidable)

var formidable = require('formidable')
var http = require('http')
var util = require('util')
// 创建一个HTTP服务器
http.createServer(function(req, res) {
  // 判断请求路径是否为upload,如果是则执行文件上传处理逻辑
  // 同时还判断HTTP请求方式是否为post
  if(req.url == '/upload' && req.method.toLowerCase() == 'post') {
    // 创建form对象
    var form = new formidable.IncomingForm()
    // 解析post数据
    form.Parse(req, function(err, fields, files) {
      res.writeHead(200, {
        'content-type': 'text/plain'
      })
      res.write('received upload:\n\n')
      // 将json对象转化为字符串
      res.end(util.inspect({
        fields: fields,
        files: files
      }))
    })
    return
  }
  res.writeHead(200, {
    'content-type': 'text/html'
  })
  res.end(`
	<form action='/upload' enctype='multipart/form-data' method='pst'>
	  <input type='text' name='title'><br>
	  <input type='file' name='upload' multiple='multiple'><br>
	  <input type='submit' vaule='upload'>
	</form>
  `)
}).listen(8080)

之后运行这个项目,上传一张图片即可看到效果。

开发一个自己的NPM模块

  • 步骤一:创建一个项目
  • 步骤二:初始化一个package.json文件(npm init)
  • 步骤三:创建一个index.js文件
function testNpm(name) {
  console.log(name)
}
exports.testNpm = testNpm
  • 步骤四:创建一个test.js文件
var fn = require('./index.js')
fn.testNpm('hello,这是测试模块')
  • 步骤五:执行test.js文件,看看是否有错误(node test.js)
  • 步骤六:来到官网注册自己的开发账号
  • 步骤七:使用命令行连接NPM,输入自己的注册信息
$ npm adduser
Username: fengxiong
Password: 9*****8*q
Email: (this IS public) 9*****[email protected]
Logged in as fengxiong on https://registry.npmjs.org/.

$ npm whoami
fengxiong
  • 注意:如果出现403错误,那么很有可能是邮箱尚未验证或者仓库地址使用了淘宝镜像,点击这里查看解决方法
  • 步骤八:发布模块(npm publish)
  • 步骤九:下载使用该模块。
1、创建一个新项目
2、初始化一个package.json文件
3、安装自己的模块:npm install --save "原先模块package.json文件中的name值"

最后是下载该模块的项目结构
image

我们可以在根目录的test.js文件中这么来写

var fn = require('./node_modules/fengxiong/index.js')
fn.testNpm('hello,这是测试模块')

然后在运行node test.js,那么就能得到相应的结果。

模块与类

模块是程序设计中,为完成某一功能所需的一段程序或者子程序;或者指能由编译程序,装配程序等处理的独立程序单位;或者指大型软件系统的一部分。

而在Node.js中可以理解为完成某一功能所需的程序或者子程序,同时也可以将Node.js的一个模块理解为一个“类”,但注意,其本身并非是类,而只是简单意义上的一个对象,该对象拥有多个方法和属性,Node.js模块也拥有私有成员和公有成员。

一般来说exports和module.exports的成员为公有成员,而非exports和module.exports的成员为私有成员。

Node.js中的继承

继承的方式主要是通过Node.js的util模块inherits的API来实现继承,将一个构造函数的原型方法继承到另一个构造函数中。

constructor构造函数的原型将被设置为使用superConstructor构造函数所创建的一个新对象。可以看看下面的栗子,其目的就是使用MyStream继承events.EventEmitter对象

// 获取util和event模块
var util = require("util")
var events = require("events")

// 使用MyStream来继承events.EventEmitter方法属性
function MyStream() {
  events.EventEmitter.call(this)
}

// 应用inherits来实现Mystream继承EventEmitter
util.inherits(MyStream, events.EventEmitter)
// 为MyStream类添加方法
MyStream.prototype.write = function(data) {
  this.emit("data", data)
}

// 创建一个MyStream对象
var stream = new MyStream()
// 判断是否继承了events.EventEmitter
console.log(stream instanceof events.EventEmitter)
// 通过super_获取父类对象
console.log(MyStream.super_ === events.EventEmitter)

// 调用继承来自events.EventEmitter的方法
stream.on("data", function(data) {
  console.log("接收到的数据是:" + data)
})
stream.write("现在是2018-08-29 21:31")

上面的栗子可能稍微抽象些,下面我搞一个比较具体的栗子,比如:学生、老师、程序员继承人这个类,实现学生学习、老师教书、程序员写代码的功能。首先,我们创建一个Person基类作为人

module.exports = function() {
  this.name = 'person'
  // 定义sleep方法
  this.sleep = function() {
    console.log('入夜了,需要睡觉')
  }
  // 定义eat方法
  this.eat = function() {
    console.log('肚子饿了,吃点东西')
  }
}

下面我们再创建一个Student类继承Person类

var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
}
// 将Student类继承Person
util.inherits(Student, Person)
// 重写study方法
Student.prototype.study = function() {
  console.log('我正在学习')
}
// 暴露Student类
module.exports = Student

这样就实现了student继承person类,同时新增类的自我方法study。下面我们再来看看实现一个Teacher类继承Person类

var util = require('util')
var Person = require('./person')
// 定义Teacher类
function Teacher() {
  Person.call(this)
}
// 将Teacher类继承Person类
util.inherits(Teacher, Person)
// 重写teach方法
Teacher.prototype.teach = function() {
  console.log('我正在教学')
}
// 暴露Teacher类
module.exports = Teacher

为了加深下印象,我们再写一个Coder类继承Person类

var util = require("util")
var Person = require("./person")
// 定义个Coder类
function Coder() {
  Person.call(this)
}
// 继承
util.inherits(Coder, Person)
// 重写code方法
Coder.prototype.code = function() {
  console.log("我正在敲代码")
}
// 暴露Coder类
module.exports = Coder

下面我们进入重头戏,看看如何调用继承类,创个app.js文件

var Person = require('./person')
var Student = require('./student')
var Teacher = require('./teacher')
var Coder = require('./coder')
// 创建对象
var personObj = new Person()
var studentObj = new Student()
var teacherObj = new Teacher()
var coderObj = new Coder()

// 执行personObj对象的所有方法
console.log('---Person类---')
personObj.sleep()
personObj.eat()
console.log('----------')
// 执行studentObj对象的所有方法
console.log('---Student类---')
studentObj.sleep()
studentObj.eat()
studentObj.study()
console.log('----------')
// 执行teacherObj对象的所有方法
console.log('---Teacher类---')
teacherObj.sleep()
teacherObj.eat()
teacherObj.teach()
console.log('----------')
// 执行coderObj对象的所有方法
console.log('---Coder类---')
coderObj.sleep()
coderObj.eat()
coderObj.code()
console.log('----------')

之后在终端执行命令:node app.js,那么就可以看到继承的效果出现了。那么该如何改写父类定义好的方法呢?其实很简单,只要直接覆盖就行了,下面就来看看在Student类中重写父类的eat()方法

var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
  this.eat = function() {
    console.log('身为一个学生,肚子饿了也得忍着')
  }
}
// 将Student类继承Person
util.inherits(Student, Person)
// 重写study方法
Student.prototype.study = function() {
  console.log('我正在学习')
}
// 暴露Student类
module.exports = Student

动态类对象和静态类对象

Node.js中可以应用module.exports实现一个动态类对象,那么Node.js中如何实现一个静态类对象呢?例如:假设有一个基类Person,其有继承类Student,但是我们希望使用静态类对象的方式调用Student中的方法和属性,而不希望new一个对象。可以结合exports以及继承方法来实现静态类。

首先,我们先使用动态类对象调用的方式实现一下代码

// 基类Person
module.exports = function() {
  this.name = 'person'
  // 定义sleep方法
  this.sleep = function() {
    console.log('入夜了,需要睡觉')
  }
  // 定义eat方法
  this.eat = function() {
    console.log('肚子饿了,吃点东西')
  }
}

// 子类Student
var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
  // 将Student类继承Person
  util.inherits(Student, Person)
  this.study = function() {
    console.log('我正在学习')
  }
}
// 暴露Student类
module.exports = Student

// 动态调用Student类的方式
var Student = require('./student')
var student = new Student()
student.study()

现在我们使用静态类对象的调用方式,调用Student中的对象,那么我们可以将student这个模块实现方式修改为如下代码(这一处书中代码有误,我修改过来了):

// 子类Student
var util = require("util")
var Person = require("./person")
// 定义Student类
function Student() {
  Person.call(this)
  // 将Student类继承Person
  util.inherits(Student, Person)
  this.study = function() {
    console.log('我正在学习')
  }
}
var student = new Student()
// 暴露以下的方法
exports.study = student.study
exports.eat = student.eat
exports.sleep = student.sleep

// 静态调用Student类的方式
var student = require('./student')
student.study()
student.eat()
student.sleep()

通过在类定义模块中new一个本身对象,并将该对象的方法全部通过exports暴露给外部接口,就无需在每次调用该类的地方new一个该对象了。因此在使用上就可以将student这个类看成一个静态类

这种方法实现很简单,也很容易理解,在很多时候非常有用。这样做的好处是可以避免代码的冗余,当student这个类被多个地方调用时,如果是动态调用的话,就必须每次都去new一个该对象,而如果使用类静态方法调用时,就可以直接通过require返回的对象进行调用。

当然,不是所有的类都可以这样去调用,如果每次在该类的内部new一个对象都需要初始化一些参数变量,那么就可以选择使用静态调用方法。

习题练习

实现一个基类animal,该基类包含方法say,该方法输出内容,接下来实现两个继承类duck和bird,其中duck是一个静态类模块,其有方法say,该方法输出ga..ga...ga..,而bird则是一个动态调用模块,其有方法输出ji...ji...ji...

// 基类
module.exports = function() {
  this.say = function() {
    console.log('动物特性')
  }
}

// 子类A
var util = require('util')
var Animal = require('./animal')
function Duck() {
  // 应用arguments对象获取函数参数
  var _res = arguments[0]
  var _req = arguments[1]
  // 把animal对象中的this指向绑定到Duck对象中
  Animal.call(this)
  // 继承
  util.inherits(Duck, Animal)
  this.say = function() {
    console.log('ga..ga..ga..')
  }
}
// 暴露方法给外部接口进行调用
var duck = new Duck()
exports.say = duck.say

// 子类B
var util = require('util')
var Animal = require('./animal')
function Bird() {
  var _res = arguments[0]
  var _req = arguments[1]
  Animal.call(this)
  util.inherits(Bird, Animal)
  this.say = function() {
    console.log('ji...ji...ji...')
  }
}
module.exports = Bird

// 测试类
var duck = require('./duck')
var Bird = require('./bird')
duck.say()
var bird = new Bird()
bird.say()

单例模式

一般认为单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在,就直接返回,如果不存在,则会创建该对象,并将该对象保存在静态变量中,当下次请求时,则可以直接返回该对象,这就确保了一个类只有一个实例对象。

下面请看一个例子,使用私有变量记录new的相应对象

// 单例类文件
// 舒适化一个私有变量
var _instance = null
module.exports = function(time) {
  // 创建Class类
  function Class(time) {
    this.name = 'no锋'
    this.book = 'Node.js'
    this.time = time
  }
  // 创建类方法
  Class.prototype = {
    constructor: Class,
    show: function() {
      console.log(`《${this.book}》这本书是${this.name}${this.time}编写的`)
    }
  }
  // 获取单例对象接口
  this.getInstance = function() {
    if(_instance === null) {
      _instance = new Class(time)
    }
    return _instance
  }
}

注意,_instance变量是要放在单例方法之外,否则无法实现单例模式。原因是当调用单例方法时每次都会重新将其赋值为null,而放在单例函数之外时,调用单例函数不会影响到_instance变量的值。

接下来,我们再创建一个js文件来调用类对象

var Single = require('./student')
var singleObjOne = new Single('2018-09-02')
var singleClassOne = singleObjOne.getInstance('2018-09-02')
singleClassOne.show()

var singleObjTwo = new Single('2018-09-01')
var singleClassTwo = singleObjTwo.getInstance('2018-09-01')
singleClassTwo.show()

从输出结果可以看出来,第二次new单例对象的时候,没有创建新的Class类对象,而是返回了第一次创建的Class类对象。这样就应用Node.js实现了单例模式。

适配器模式

若将一个类的接口转换成客户希望的另外一个接口,Adapter(适配器)模式可以使原本由于接口不兼容而不能一起工作的那些类可以一起工作。下面我们直接看案例代码

// Target父类
module.exports = function() {
  this.request = function() {
    console.log('这是父类的request方法')
  }
}

// Adaptee类
module.exports = function() {
  this.specialRequest = function() {
    console.log('我才是被子类真正调用的方法')
  }
}

// Adapter子类
var util = require('util')
var Target = require('./target')
var Adaptee = require('./adaptee')
// 定义Adapter函数类
function Adapter() {
  Target.call(this)
  this.request = function() {
    var adapteeObj = new Adaptee()
    adapteeObj.specialRequest()
  }
}
// Adapter类继承Target类
util.inherits(Adapter, Target)
// 暴露Adapter类
module.exports = Adapter

// 一个测试脚本文件
var Adapter = require('./adapter')
var target = new Adapter()
target.request()

从运行结果可以看到,其通过适配器调用了Adaptee中的specialRequest方法,这样就实现了Node.js中的适配器模式。

装饰模式

装饰模式就是动态的给一个对象添加一些额外的职责,就扩展功能而言,它比生成子类方式更为灵活。下面我们就先创一个Component父类

module.exports = function() {
  this.operation = function() {
    console.log('这是父类的operation方法')
  }
}

接着在创建一个子类ConcreteComponent,其作用就是展示父类装饰之前类中的属性和方法,重定义operation方法

var util = require('util')
var Component = require('./component')
// 定义函数类
function ConcreteComponent() {
  Component.call(this)
  this.operation = function() {
    console.log('这是子类的operation方法')
  }
}
// 继承父类component
util.inherits(ConcreteComponent, Component)
// 暴露ConcreteComponent类
module.exports = ConcreteComponent

然后再创建一个Decorator基类,用于装饰Component类

var util = require('util')
var Component = require('./component')
function Decorator() {
  Component.call(this)
}
util.inherits(Decorator, Component)
module.exports = Decorator

创建ConcreteDecoratorA装饰类,该类的目的是为Component类的operation方法提供一些额外的操作,比如添加一些额外的计算规则和输出一些额外的数据

var util = require('util')
var Decorator = require('./decorator')
function ConcreteDecoratorA() {
  Decorator.call(this)
  this.operation = function() {
    // 调用被装饰类的operation基本方法
    Decorator.operation
    console.log('为父类的父类提供额外的一些操作')
  }
}
util.inherits(ConcreteDecoratorA, Decorator)
module.exports = ConcreteDecoratorA

创建ConcreteDecoratorB装饰类,该类的目的是为Component类的operation方法提供一些额外的操作,同时添加新的功能方法

var util = require('util')
var Decorator = require('./decorator')
function ConcreteDecoratorB() {
  Decorator.call(this)
  this.operation = function() {
    // 调用被装饰类的operation基本方法
    Decorator.operation
    console.log('继续为父类的父类提供额外的一些操作')
  }
  this.addedBehavior = function() {
    console.log('装饰类Component添加新的行为动作')
  }
}
util.inherits(ConcreteDecoratorB, Decorator)
module.exports = ConcreteDecoratorB

最后我创建一个测试类

var ConcreteDecoratorA = require('./concreteDecoratorA')
var ConcreteDecoratorB = require('./concreteDecoratorB')
var target = new ConcreteDecoratorA()
target.operation()

var targetone = new ConcreteDecoratorB()
targetone.operation()
targetone.addedBehavior()

装饰类的应用场景是在不改变基类的情况下,为基类新增属性和方法。

工厂模式

定义一个用于创建对象的接口,让子类决定将哪一个类实例化,工厂模式就是使一个类的实例化延迟到其子类。下面我们编写一个基类Product

module.exports = function() {
  this.getProduct = function() {
    console.log('这个是父类的getProduct方法')
  }
}

创建两个子类ProductA和ProductB,分别重写父类的getProduct方法

// 子类ProductA
var util = require('util')
var Product = require('./product')
function ProductA() {
  Product.call(this)
  this.getProduct = function() {
    console.log('这是子类A的getProduct方法')
  }
}
util.inherits(ProductA, Product)
module.exports = ProductA

// 子类ProductB
var util = require('util')
var Product = require('./product')
function ProductB() {
  Product.call(this)
  this.getProduct = function() {
    console.log('这是子类B的getProduct方法')
  }
}
util.inherits(ProductB, Product)
module.exports = ProductB

接着创建工厂对象productFactory,根据不同的参数获取不同的Product对象。这里需要注意的是,createProduct使用exports而不是使用module.exports,目的是传递一个ProductFactory对象,而非一个ProductFactory类

var ProductA = require('./productA')
var ProductB = require('./productB')
exports.createProduct = function(type) {
  switch(type) {
    case 'ProductA' : return new ProductA
    break
    case 'ProductB' : return new ProductB
    break
    default : return false
  }
}

最后创建一个测试文件

var ProductFactory = require('./productFactory')
var ProductA = ProductFactory.createProduct('ProductA')
ProductA.getProduct()

var ProductB = ProductFactory.createProduct('ProductB')
ProductB.getProduct()

从结果可以看出通过传递不同的字符串,获取了不同的对象ProductA和ProductB,在工厂模式中还包括工厂方法和抽象工厂两个模式。

第 3 章:Node.js的Web应用

简单的HTTP服务器

我们首先来看下面一个代码栗子,根据这个栗子进行分析

var http = require('http')
http.createServer(function(req, res) {
  res.writeHead(200, {
    'Content-Type':'text/plain'
  })
  res.end('hello world\n')
}).listen(1337, '127.0.0.1')
console.log('Server running at http://localhost:1337/')

http.createServer()接收一个request事件函数,该事件函数有两个参数req和res。req主要是获取请求资源信息,包括请求的url、客户端参数、资源文件、header信息、HTTP版本、设置客户端编码等;res主要是响应客户端请求数据,包括HTTP的header处理、HTTP请求返回码、响应请求数据等。

http.createServer()调用返回的是一个server对象,server拥有listen和close方法,listen方法可以指定监听的IP和端口。

接下来我们整一个实例:创建一个HTTP服务器,获取并输出请求url、method和headers,同时根据请求资源做不同的输出。

  • 当请求'/index',返回200,并且返回一个html页面数据到客户端
  • 当请求'/img',放回200,并且返回一个图片数据
  • 若为其他情况,则返回404,并输出'can not find source'
var http = require('http')
var fs = require('fs')
var url = require('url')
// 创建一个HTTP服务器
http.createServer(function(req, res) {
  // 获取客户端请求路径
  var pathname = url.parse(req.url).pathname
  // 输出请求地址
  console.log(req.url)
  // 输出请求方法
  console.log(req.method)
  // 输出请求的headers信息
  console.log(req.headers)
  switch(pathname) {
    case '/index':resIndex(res)
      break;
    case '/img':resImage(res)
      break;
    default:resDefault(res)
      break;
  }
}).listen(1337)
// 定义一个/index路径处理返回函数
function resIndex(res) {
  // 获取当前index.html的路径
  var readPath = __dirname + '/' + url.parse('index.html').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  res.end(indexPage)
}
// 定义一个/img路径处理返回函数
function resImage(res) {
  // 获取当前image的路径
  var readPath = __dirname + '/' + url.parse('logo.png').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'image/png'
  })
  res.end(indexPage)
}
// 定义一个根路径处理返回函数
function resDefault(res) {
  res.writeHead(404, {
    'Content-Type': 'text/plain'
  })
  res.end('can not find source')
}
console.log('Server running at http://localhost:1337/')

然后node执行这个js文件(记住在当前项目中得存在一个html文件和logon.png文件)

路由处理

一般情况下,我们可以根据上一节内容那样来使用switch来实现路由,但是当请求资源非常复杂时,使用这种方式来判断处理就会显得很庞大,而且难以维护,下面介绍其他两种路由处理办法

  • 特定规则请求参数:可以根据用户请求的url,依据特定的规则得到相应的执行函数,例如请求参数/index,根据特定规则转化为resIndex(res, req)方法。下面的代码就是将HTTP的请求路径/index,根据一定的规则得到其处理函数resIndex,当我们需要新增处理逻辑时,例如/img,则必须在服务器端新增处理函数resImg
var http = require('http')
var fs = require('fs')
var url = require('url')
// 创建一个HTTP服务器
http.createServer(function(req, res) {
  // 获取客户端请求路径
  var pathname = url.parse(req.url).pathname
  var param = pathname.substr(2)
  // 获取客户端请求的url路径,并获取其第一个参数,将其小写转为大写
  var firstParam = pathname.substr(1, 1).toLocaleUpperCase()
  // 根据pathname获取其需要执行的函数名
  var functionName = 'res' + firstParam + param
  response = res
  if(pathname == '/') {
    resDefault(res)
  } else if(pathname == '/index') {
    resIndex(res)
  } else {
    resImage(res)
  }
  // 输出请求地址
  console.log(req.url)
  // 输出请求方法
  console.log(req.method)
  // 输出请求的headers信息
  console.log(req.headers)
}).listen(1337)
// 定义一个/index路径处理返回函数
function resIndex(res) {
  // 获取当前index.html的路径
  var readPath = __dirname + '/' + url.parse('index.html').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  res.end(indexPage)
}
// 定义一个/img路径处理返回函数
function resImage(res) {
  // 获取当前image的路径
  var readPath = __dirname + '/' + url.parse('logo.png').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'image/png'
  })
  res.end(indexPage)
}
// 定义一个根路径处理返回函数
function resDefault(res) {
  res.writeHead(404, {
    'Content-Type': 'text/plain'
  })
  res.end('can not find source')
}
console.log('Server running at http://localhost:1337/')
  • 利用自带参数来实现路由处理:url路径指定需要执行的模块,通过在HTTP的url中携带一个参数c,表示需要调用的方法名,从而实现简单的路由处理。例如/image?c=img表示获取index模块中img方法,同样/index?c=index表示获取index模块中的index方法
// client.js
var http = require('http')
var url = require('url')
var querystring = require("querystring")
http.createServer(function(req, res) {
  // 获取用户请求的url地址
  var pathname = url.parse(req.url).pathname
  if(pathname == '/favicon.ico') {
    return
  }
  // 根据用户请求的url路径,截取其中的module和controller
  var module = pathname.substr(1)
  var str = url.parse(req.url).query
  // 获取请求参数c,也就是请求模块的方法
  var controller = querystring.parse(str).c
  var classObj = ''
  // 应用try catch来require一个模块,并捕获异常
  try {
    classObj = require('./' + module)
  } catch(err) {
    console.log('错误信息: ' + err)
  }
  // require成功时,则应用call方法,实现类中的方法调用执行
  if(classObj) {
    // 初始化模块参数res和req
    classObj.init(res, req)
    // 执行模块函数
    classObj[controller].call()
  } else {
    res.writeHead(404, {
      'Content-Type': 'text/plain'
    })
    res.end('can not find source')
  }
}).listen(1337);

根据上述实现必须在本地路径创建image.js和index.js文件,其中两个模块中都含有init方法来初始化模块中的res和req变量。image.js中img方法处理图片返回,index.js中index方法处理index.html页面展示

源代码有误,不继续了

Node中的GET

Node.js中HTTP客户端发送的GET请求参数数据都存储在request对象中的url属性中,例如:http://locallhost:1337/test?name=jack。其中ur的请求路径名为test,使用GET传输的name数据暴露在url上。

下面我们使用http模块创建一个服务器,该服务器接收任意的url请求资源,使用GET方法传递参数,服务器接收客户端请求url,输出每次请求的路径名和请求参数的json对象

var http = require('http')
// 路径解析模块
var url = require('url')
// 字符串解析为json对象模块
var querystring = require('querystring')
http.createServer(function(req, res) {
  // 获取HTTP的GET参数
  var pathname = url.parse(req.url).pathname
  // 获取url中的非路径字符串
  var paramStr = url.parse(req.url).query
  // 对字符串进行解析
  var param = querystring.parse(paramStr)
  // 阻止发送图标请求
  if('/favicon.ico' == pathname) {
    return
  }
  console.log(pathname)
  console.log(paramStr ? paramStr : 'no params')
  console.log(param)
  res.writeHead(200, {
    'Content-Type': 'text/plain'
  })
  res.end('success')
}).listen(1337)
console.log('Server running at http://127.0.0.1:1337/')

在命令行运行该文件,分别输入:http://127.0.0.1:1337/http://127.0.0.1:1337/indexhttp://127.0.0.1:1337/index?id=4535 ,观察下输出的结果是什么。

Node中POST

相比较GET请求,POST请求一般比较复杂,Node为了使整个过程非阻塞,会将POST数据拆分成很多小的数据块,然后通过触发特定的事件,将这些小数据块有序传递给回调函数。这部分涉及到request对象中的addListener方法,该方法有两个事件参数data和end,data表示数据传输开始,end表示数据传输结束。下面我们来看一个实例

var http = require('http')
// 读取index.html页面信息
var fs = require('fs')
var url = require('url')
var querystring = require('querystring')
http.createServer(function(req, res) {
  var pathname = url.parse(req.url).pathname
  // 路由处理
  switch(pathname){
    case '/add' : resAdd(res, req)
    break;
    default: resDefault(res)
    break;
  }
}).listen(1337)
function resDefault(res){
  //获取当前index.html的路径
  var readPath = __dirname + '/' +url.parse('index.html').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, { 
    'Content-Type': 'text/html' 
  })
  res.end(indexPage)
}
function resAdd(res, req){
  var postData = '';
  // 设置接收数据编码格式为 UTF-8
  req.setEncoding('utf8')
  // 接收数据块并将其赋值给 postData
  req.addListener('data', function(postDataChunk) {
    postData += postDataChunk;
  })
  // 数据接收完毕后,执行POST参数解析
  req.addListener('end', function() {
  // 数据接收完毕,执行回调函数
  var param = querystring.parse(postData)
  console.log(postData)
  console.log(param)
  res.writeHead(200, { 
    'Content-Type': 'text/plain' 
  })
  res.end('success')
  })
}

运行该js文件,然后打开地址:http://127.0.0.1:1337/ , 接着你会看到一个表单,往表单里面输入数据看看返回了什么,下面是index.html文件代码

<html>
  <head>
    <title>Test Post</title>
  </head>
  <body>
    <div>
      <form action='add' method='POST'>
        <label>name: <input type='text' name='name'></label>
        <label>book: <input type='text' name='book'></label>
        <input type='submit' />
      </form>
    </div>
  </body>
</html>

Node中的POST和GET

上面介绍了HTTP中POST和GET参数的获取方式,现在我们将两个方法作为一个公用的模块,可以在很大程度上减少工作量,毕竟获取GET和POST参数的方法都不是一步完成的,都必须进行多步操作,接下来我们来实践开发一个HTTP参数获取模块,主要是利用其中的两个方法GET和POST来直接得到客户端传递的数据

在该模块起始部分,首先要获取该模块的一些必要的Node.js原生模块,分别是url和querystring,其次需要在代码中应用init方法来初始化该模块对象中的res和req对象,代码如下

// http_param.js文件
var _res, _req
var = url = require('url')
// 解析GET参数,并返回
var = querystring = require('querystring')
// 初始化http中的res和req参数
exports.init = function(req, res) {
  _res = res
  _req = req
}
// 获取GET参数方法
exports.GET = function(key) {
  var paramStr = url.parse(_req.url).query
  // 获取传递参数的json数据方法
  var param = querystring.parse(paramStr)
  return param[key] ? param[key] : ''
}
// 获取POST参数方法
exports.POST = function(key, callback) {
  var postData = ''
  _req.addListener('data', function(postDataChunk) {
    postData += postDataChunk
  })
  _req.addListener('end', function() {
    // 数据接收完毕,执行回调函数
    var param = querystring.parse(postData)
    var value = param[key] ? param[key] : ''
    callback(value)
  })
}

在代码最后使用的是一个callback回调函数作为参数,来解决异步调用中返回结果的传递。这样就简单地实现了一个HTTP中如何获取GET和POST传递的数据模块。

下面这个是调用模块代码

// client.js文件
var http = require('http')
var fs = require('fs')
var url = require('url')
var querystring = require('querystring')
var httpParam = require('./http_param')
http.createServer(function(req, res) {
  var pathname = url.parse(req.url).pathname
  httpParam.init(req, res)
  switch(pathname) {
    case '/add':resAdd(res, req)
      break;
    default:resDefault(res)
      break;
  }
}).listen(1337)

function resDefault(res) {
  // 获取当前index.html的路径 
  var readPath = __dirname + '/' + url.parse('index.html').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  res.end(indexPage)
}

function resAdd(res, req) {
  // 设置接收数据编码格式为 UTF-8
  req.setEncoding('utf8')
  httpParam.POST('name', function(value) {
    res.writeHead(200, {
      'Content-Type': 'text/plain'
    })
    res.end(value)
  })
}

运行该js文件,然后打开地址:http://127.0.0.1:1337/ , 接着你会看到一个表单,往表单里面输入数据看看返回了什么,下面是index.html文件代码

<html>
  <head>
    <title>Test Post</title>
  </head>
  <body>
    <div>
      <form action='add' method='POST'>
        <label>name: <input type='text' name='name'></label>
        <label>book: <input type='text' name='book'></label>
        <input type='submit' />
      </form>
    </div>
  </body>
</html>

HTTP和HTTPS模块介绍

HTTP是一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。

而HTTPS是以安全为目标的HTTP通道,简单的说他就是HTTP的安全版。

两者创建服务器的方式接口都是一致的,都是使用各自模块中的createServer方法,但是HTTPS中的createServer会附加一个参数opts,其中保存有key和cert的信息。

为什么需要静态资源管理

因为在对于前端种种类型的静态资源,服务器端无法为每个静态资源类型实现一个处理逻辑,因此在Web应用开发中我们需要自己设计一个静态资源的管理。请看以下代码

var http = require('http')
var fs = require('fs')
var url = require('url')
http.createServer(function(req, res) {
  // 获取当前index.html的路径
  var readPath = __dirname + '/' + url.parse('index.html').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  res.end(indexPage)
}).listen(1339)
console.log('Server running at http://localhost:1339/')

当你的index.html文件中新增了css外部静态资源时,那么该css将不会被处理

<html>
  <head>
    <title>Test Post</title>
    <link href="reset.css" rel="stylesheet" />
  </head>
  <body>
    <div>
      hello world
    </div>
  </body>
</html>

Node实现简单静态资源管理

接下来我们学习如何利用Node.js实现简单的静态资源管理。创建http.js,首先分析需要的Node.js模块:HTTP模块创建服务器、fs模块处理文件的读写、url模块处理url请求路径

var http = require('http')
var fs = require('fs')
var url = require('url')
// 获取当前脚本路径,BASE_DIR是定义常量数据的规范
var BASE_DIR = __dirname
http.createServer(function(req, res) {
  // 获取当前index.html的路径
  var pathname = url.parse(req.url).pathname
  // 获取静态资源文件存储路径
  var realPath = BASE_DIR + '/static' + pathname
  // 过滤favicon.ico请求
  if(pathname == '/favicon.ico') {
    return
  } else if(pathname == '/index' || pathname == '/') {
    goIndex(res)
  } else {
    dealWithStatic(pathname, realPath, res)
  }
}).listen(1330)
console.log('Server running at http://localhost:1330/')

function goIndex(res) {
  var readPath = BASE_DIR + '/' + url.parse('index.html').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  res.end(indexPage)
}
// 处理静态资源文件逻辑,主要是根据不同静态资源路径,返回相应的静态资源文件信息
// 3个参数分别代码请求资源路径名、静态资源实际存储路径和相应HTTP相应对象res
// 这个函数的作用就是根据不同的请求资源后缀名,返回相应的MMIE类型和数据到客户端
function dealWithStatic(pathname, realPath, res) {
  fs.exists(realPath, function(exists) {
    if(!exists) {
      res.writeHead(404, {
        'Content-Type': 'text/plain'
      })
      res.write("该请求路径:" + pathname + "没有被服务器找到!")
      res.end()
    } else {
      var pointPostion = pathname.lastIndexOf('.')
      // 获取文件后缀名
      var mmieString = pathname.substring(pointPostion + 1)
      var mmieType
      // 根据文件后缀名,设置HTTP响应的content-type类型
      switch(mmieString) {
        case 'css':mmieType = "text/css"
          break;
        case 'png':mmieType = "image/png"
          break;
        default:mmieType = "text/plain"
      }
      fs.readFile(realPath, "binary", function(err, file) {
        if(err) {
          res.writeHead(500, {
            'Content-Type': 'text/plain'
          })
          res.end(err)
        } else {
          res.writeHead(200, {
            'Content-Type': mmieType
          })
          res.write(file, "binary")
          res.end()
        }
      })
    }
  })
}

在该项目中有一个index.html文件,以及以static文件夹以及文件夹下面的logo.png和style.css这两个静态资源文件

<html>
  <head>
    <title>Test Http</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div>Oh, good~!</div>
    <div><img src='logo.png' /></div>
  </body>
</html>

这里只针对两种类型的静态资源文件,对于其他的资源暂时不考虑,代码编写完后,运行http.js,打开浏览器输入http://localhost:1330/ ,那么你将看到静态资源被渲染出来了。

静态资源库设计

HTTP请求中大概有409中MMIE类型,在设计时将这409种MMIE类型作为一个json配置信息存储在mmie_type.json文件中,以便维护和管理,由于代码量多,具体的请看这里。记得在写demo的时候需要添加上去

下面我们创建一个静态资源调用以及解析文件static_module.js

// 本地文件夹绝对路径
var BASE_DIR = __dirname
// 配置文件绝对路径
var CONF = BASE_DIR + '/conf/'
// 静态文件存储路径
// 在这段代码中,CONF末尾加上了一个斜杆,而STATIC却没有,原因
// 在于静态资源请求的pathname会自带一个斜杆
var STATIC = BASE_DIR + '/static'
// 设置缓存的时间
var CACHE_TIME = 60 * 60 * 24 * 365
// MMIE类型的json存储配置内容
var mmieConf
// 工具类模块,主要是对一些异常处理打印debug信息
var sys = require('util')
var http = require('http')
// 解析json配置文件
var fs = require('fs')
var url = require('url')
var path = require('path')
// 获取MMIE的配置文件内容
mmieConf = getMmieConf()
// 响应静态资源请求
exports.getStaticFile = function(pathname, res, req) {
  var extname = path.extname(pathname)
  // 获取请求pathname的后缀名字符串
  extname = extname ? extname.slice(1) : ''
  // 设置静态文件路径
  var realPath = STATIC + pathname
  // 设置请求静态文件的MMIE类型,当配置中不存在该类型时,则默认为'text/plain'类型
  var mmieType = mmieConf[extname] ? mmieConf[extname] : 'text/plain'
  // 实现响应不同种MMIE类型的逻辑处理
  fs.exists(realPath, function(exists) {
    if(!exists) {
      res.writeHead(404, {
        'Content-Type': 'text/plain'
      })
      res.write("This request URL " + pathname + " was not found on this server.")
      res.end()
    } else {
      // 同步执行获取静态文件realPath信息
      var fileInfo = fs.statSync(realPath)
      // 获取文件最后更改时间,并转化UTC字符串
      var lastModified = fileInfo.mtime.toUTCString()
      // 判断请求的资源文件是否需要缓存
      if(mmieConf[extname]) {
        var date = new Date()
        date.setTime(date.getTime() + CACHE_TIME * 1000)
        // 设置响应header的expires值
        res.setHeader("Expires", date.toUTCString())
        // 设置响应header的Cache-Control值,缓存时间
        res.setHeader("Cache-Control", "max-age=" + CACHE_TIME)
      }
      console.log(req.headers['if-modified-since'])
      // 检测浏览器是否发送了If-Modified-Since请求头,并判断服务器文件有没修改
      // 如果客户端发送的最后修改时间与服务器文件的修改时间相同的话,
      // HTTP响应304状态码,不需要再次IO读取磁盘中的静态资源文件数据
      if(req.headers['if-modified-since'] && lastModified == req.headers['if-modified-since']) {
        res.writeHead(304, "Not Modified")
        res.end()
      } else {
        // 当客户端请求服务器端不同的静态文件资源时,
        // 服务端会根据客户端请求的mmieType来响应不同的Content-Type类型
        fs.readFile(realPath, "binary", function(err, file) {
          if(err) {
            res.writeHead(500, {
              'Content-Type': 'text/plain'
            })
            res.end(err)
          } else {
            // 静态文件有所更改时,重新设置浏览器Last-Modified的值
            res.setHeader("Last-Modified", lastModified)
            res.writeHead(200, {
              'Content-Type': mmieType
            })
            res.write(file, "binary")
            res.end()
          }
        })
      }
    }
  })
}
//获取MMIE配置信息,读取配置文件信息,并转化为json对象
function getMmieConf() {
  var routerMsg = {}
  try {
    // utf8编码读取json配置信息
    var str = fs.readFileSync(CONF + 'mmie_type.json', 'utf8')
    routerMsg = JSON.parse(str)
  } catch(e) {
    // 解析错误时,显示debug日志
    sys.debug("JSON parse fails")
  }
  return routerMsg
}

我们为了减轻硬盘IO的承受压力,因此对静态资源文件进行了缓存配置。其中的原理是:浏览器缓存中存有文件副本的时候,不能确定该文件是否有效时,会生成一个get请求,在该请求的header中包含If-Modified-Since参数。如果服务器端文件在这个时间后发生过更改,就发送整个文件给客户端,如果没有修改,则返回304状态码,并不发送整个文件给客户端。

如果确定该副本有效时,客户端不会发送GET请求。判断有效的主要方法是,服务端响应的时候带上expires的头,浏览器会判断expires头,直到指定的日期过期,才会发起新的请求

接着我们创建一个client.js文件,并且在node环境中运行

var http = require('http')
var fs = require('fs')
var url = require('url')
// 获取静态资源管理模块
var staticModule = require('./static_module')
var BASE_DIR = __dirname
http.createServer(function(req, res) {
  // 获取当前index.html的路径
  var pathname = url.parse(req.url).pathname
  if(pathname == '/favicon.ico') {
    return
  } else if(pathname == '/index' || pathname == '/') {
    goIndex(res)
  } else {
    // 如果是静态资源文件的请求,调用getStaticFile来统一项目中的静态资源文件
    staticModule.getStaticFile(pathname, res, req)
  }
}).listen(1337)
console.log('Server running at http://localhost:1337/')

function goIndex(res) {
  var readPath = BASE_DIR + '/' + url.parse('index.html').pathname
  var indexPage = fs.readFileSync(readPath)
  res.writeHead(200, {
    'Content-Type': 'text/html'
  })
  res.end(indexPage)
}

运行之后打开浏览器,查看当前请求资源的network信息,仔细观察下。其中index.html文件的内容是

<html>
  <head>
    <title>Test Http</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div>Oh, good~!</div>
    <div><img src='logo.png' /></div>
  </body>
</html>

文件处理

文件I/O是由标准的POSIX函数封装而成,需要使用require('fs')访问这个模块,所有的方法都提高了异步和同步两种方式

重命名文件

项目中txt格式的文件记得自己先创建一份,下面的几个文件处理也是同样的道理

// 设置当前执行路径
var BASE_DIR = __dirname;
var fs = require('fs');
// 重命名文件
fs.rename(BASE_DIR + '/danhuang.txt', BASE_DIR + '/dan.txt', function(err) {
  if(err) {
    console.log('第一个抛出的错误:' + err)
  }
  console.log('重命名文件完成');
});
fs.stat(BASE_DIR + '/dan.txt', function(err, stats) {
  if(err) {
    console.log('第二个抛出的错误:' + err)
  }
  console.log('stats: ' + JSON.stringify(stats));
});

修改文件权限和文件权限属组

var BASE_DIR = __dirname;
var fs = require('fs');
// 将文件的权限修改为777
fs.chmod(BASE_DIR + '/danhuang.txt', '777', function(err) {
  if(err) throw err;
  console.log('修改权限完毕');
});

获取文件元信息

var BASE_DIR = __dirname;
var fs = require('fs');
fs.stat(BASE_DIR + '/danhuang.txt', function(err, stats) {
  if(err) throw err;
  console.log(stats);
});

验证文件是否存在

var BASE_DIR = __dirname;
var fs = require('fs');
// 验证danhuang.txt文件是否存在,函数异步执行完后
// 将验证结果返回到回调函数的参数中,如existBool
fs.exists(BASE_DIR + '/danhuang.txt', function(existBool) {
  if(existBool) {
    console.log('danhuang.txt 存在');
  } else {
    console.log('danhuang.txt 不存在');
  }
});
fs.exists(BASE_DIR + '/dan.txt', function(existBool) {
  if(existBool) {
    console.log('dan.txt 存在');
  } else {
    console.log('dan.txt 不存在');
  }
});

删除文件

var BASE_DIR = __dirname;
var fs = require('fs');
fs.unlink(BASE_DIR + '/danhuang.txt', function(err) {
  if(err) throw err;
});

图片和文件上传

本节涉及到Node.js中的API主要是HTTP、FileSystem、querystring和url等模块。HTTP模块创建服务器和HTTP的处理请求,FileSystem负责图片文件的处理,querystring处理字符串,url模块处理url解析。其中还涉及到了GET和POST请求处理模块和静态文件管理模块。

文件上传和图片上传前端页面原则上都是一致的,主要是通过POST文件数据,而Node.js服务器端则需要利用NPM中一个应用模块formidable来处理图片的上传。

我们先来看一下文件上传文件index.html

<html>
  <head>
    <meta charset="utf-8">
    <title>上传图片文件</title>
    <link rel="stylesheet" href="static/style.css">
  </head>
  <body>
    <div id='main_content'>
      <div>
        <form enctype="multipart/form-data" action='upload' method='POST'></form>
        <input type="file" name="image" />
        <input type='submit' id='upload' value='上传图片'>
        </form>
      </div>
    </div>
  </body>
</html>

接着来看一下显示文件show_image.html

<html>
  <head>
    <meta charset="utf-8">
    <title>显示已经上传的图片</title>
    <link rel="stylesheet" href="static/style.css">
    <script src="static/jquery-1.8.3.min.js"></script>
  </head>
  <body>
    <div id='main_content'>
      <div>
        <img src='/uploadFile/test.png' alt='upload file' />
      </div>
  </body>
</html>

我们需要一个上传资源处理文件index.js

var http = require('http')
var fs = require('fs')
var url = require('url')
var querystring = require('querystring')
var httpParam = require('./http_param')
// 导入静态文件资源处理库
var staticModule = require('./static_module')
// 安装成功formidable模块后,require该模块
var formidable = require("formidable")
var BASE_DIR = __dirname;
http.createServer(function(req, res) {
  var pathname = url.parse(req.url).pathname;
  // 初始化GET和POST参数获取模块httpParam的req和res
  httpParam.init(req, res);
  // 避免icon请求
  if(pathname == '/favicon.ico') {
    return;
  }
  // 根据pathname来做路由分发处理
  switch(pathname) {
    case '/upload':
      upload(res, req);
      break;
    case '/image':
      showImage(res, req);
      break;
    case '/':
      defaultIndex(res);
      break;
    case '/index':
      defaultIndex(res);
      break;
    case '/show':
      show(res);
      break;
      // 使用静态资源模块来处理
    default:
      staticModule.getStaticFile(pathname, res, req);
      break;
  }
}).listen(1337);

function defaultIndex(res) {
  // 获取当前index.html的路径 
  var readPath = __dirname + '/' + url.parse('index.html').pathname;
  var indexPage = fs.readFileSync(readPath);
  res.writeHead(200, {
    'Content-Type': 'text/html'
  });
  res.end(indexPage);
}

function upload(res, req) {
  var readPath = __dirname + '/' + url.parse('show_image.html').pathname;
  // 读取show_image.html数据
  var indexPage = fs.readFileSync(readPath);
  // 创建formidable表单对象
  var form = new formidable.IncomingForm();
  // 获取上传文件数据,执行form表单数据解析,获取其中的post参数
  form.parse(req, function(error, fields, files) {
    // 同步获取上传文件,并保存在/uploadFile下,重命名为test.png
    fs.renameSync(files.image.path, BASE_DIR + '/uploadFile/test.png');
    res.writeHead(200, {
      'Content-Type': 'text/html'
    });
    res.end(indexPage);
  });
}

function show(res) {
  var readPath = __dirname + '/' + url.parse('show_image.html').pathname;
  var indexPage = fs.readFileSync(readPath);
  res.writeHead(200, {
    'Content-Type': 'text/html'
  });
  res.end(indexPage);
}

function showImage(res, req) {
  var retData = {
    'retCode': 0,
    'imageUrl': '/uploadFile/test.png'
  };
  res.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  res.end(JSON.stringify(retData));
}

当然还有静态资源管理库文件static_module.js

// 本地文件夹绝对路径
var BASE_DIR = __dirname
// 配置文件绝对路径
var CONF = BASE_DIR + '/conf/'
// 静态文件存储路径
// 在这段代码中,CONF末尾加上了一个斜杆,而STATIC却没有,原因
// 在于静态资源请求的pathname会自带一个斜杆
var STATIC = BASE_DIR + '/static'
// 设置缓存的时间
var CACHE_TIME = 60 * 60 * 24 * 365
// MMIE类型的json存储配置内容
var mmieConf
// 工具类模块,主要是对一些异常处理打印debug信息
var sys = require('util')
var http = require('http')
// 解析json配置文件
var fs = require('fs')
var url = require('url')
var path = require('path')
// 获取MMIE的配置文件内容
mmieConf = getMmieConf()
// 响应静态资源请求
exports.getStaticFile = function(pathname, res, req) {
  var extname = path.extname(pathname)
  // 获取请求pathname的后缀名字符串
  extname = extname ? extname.slice(1) : ''
  // 设置静态文件路径
  var realPath = STATIC + pathname
  // 设置请求静态文件的MMIE类型,当配置中不存在该类型时,则默认为'text/plain'类型
  var mmieType = mmieConf[extname] ? mmieConf[extname] : 'text/plain'
  // 实现响应不同种MMIE类型的逻辑处理
  fs.exists(realPath, function(exists) {
    if(!exists) {
      res.writeHead(404, {
        'Content-Type': 'text/plain'
      })
      res.write("This request URL " + pathname + " was not found on this server.")
      res.end()
    } else {
      // 同步执行获取静态文件realPath信息
      var fileInfo = fs.statSync(realPath)
      // 获取文件最后更改时间,并转化UTC字符串
      var lastModified = fileInfo.mtime.toUTCString()
      // 判断请求的资源文件是否需要缓存
      if(mmieConf[extname]) {
        var date = new Date()
        date.setTime(date.getTime() + CACHE_TIME * 1000)
        // 设置响应header的expires值
        res.setHeader("Expires", date.toUTCString())
        // 设置响应header的Cache-Control值,缓存时间
        res.setHeader("Cache-Control", "max-age=" + CACHE_TIME)
      }
      console.log(req.headers['if-modified-since'])
      // 检测浏览器是否发送了If-Modified-Since请求头,并判断服务器文件有没修改
      // 如果客户端发送的最后修改时间与服务器文件的修改时间相同的话,
      // HTTP响应304状态码,不需要再次IO读取磁盘中的静态资源文件数据
      if(req.headers['if-modified-since'] && lastModified == req.headers['if-modified-since']) {
        res.writeHead(304, "Not Modified")
        res.end()
      } else {
        // 当客户端请求服务器端不同的静态文件资源时,
        // 服务端会根据客户端请求的mmieType来响应不同的Content-Type类型
        fs.readFile(realPath, "binary", function(err, file) {
          if(err) {
            res.writeHead(500, {
              'Content-Type': 'text/plain'
            })
            res.end(err)
          } else {
            // 静态文件有所更改时,重新设置浏览器Last-Modified的值
            res.setHeader("Last-Modified", lastModified)
            res.writeHead(200, {
              'Content-Type': mmieType
            })
            res.write(file, "binary")
            res.end()
          }
        })
      }
    }
  })
}
//获取MMIE配置信息,读取配置文件信息,并转化为json对象
function getMmieConf() {
  var routerMsg = {}
  try {
    // utf8编码读取json配置信息
    var str = fs.readFileSync(CONF + 'mmie_type.json', 'utf8')
    routerMsg = JSON.parse(str)
  } catch(e) {
    // 解析错误时,显示debug日志
    sys.debug("JSON parse fails")
  }
  return routerMsg
}

以及POST和GET资源请求处理文件http_param.js

// http_param.js文件
var _res, _req
var = url = require('url')
// 解析GET参数,并返回
var = querystring = require('querystring')
// 初始化http中的res和req参数
exports.init = function(req, res) {
  _res = res
  _req = req
}
// 获取GET参数方法
exports.GET = function(key) {
  var paramStr = url.parse(_req.url).query
  // 获取传递参数的json数据方法
  var param = querystring.parse(paramStr)
  return param[key] ? param[key] : ''
}
// 获取POST参数方法
exports.POST = function(key, callback) {
  var postData = ''
  _req.addListener('data', function(postDataChunk) {
    postData += postDataChunk
  })
  _req.addListener('end', function() {
    // 数据接收完毕,执行回调函数
    var param = querystring.parse(postData)
    var value = param[key] ? param[key] : ''
    callback(value)
  })
}

最后运行node index.js文件,查看效果。记得在那之前先执行npm init ==> npm install formidable --save

jade模板实现图片上传展示功能

由于Node版本更新迭代问题,这个章节的代码跑不起来,因此不做啥笔记了,先放着。

文件读写

由于Node版本更新迭代问题,这个章节的代码跑不起来,因此不做啥笔记了,又只能先放着。

Cookie和Session

Session和Cookie都是基于Web服务器的,不同的是Cookie存储在客户端,而Session存储在服务器端。

当用户在浏览网站的时候,Web服务器会在浏览器上存储一些当前用户的相关信息,而在本地Web客户端存储的就是Cookie数据。当下次用户再浏览同一个网站时,Web服务器就会先查看并读取本地的Cookie资料,如果有Cookie就会依据Cookie里的内容以及判断其过期时间来给用户特殊的数据返回。

Crypto模块加密

加密模块需要底层系统提供OpenSSL的支持,它提供了一种安全凭证的封装方式,可以用于HTTPS安全网络以及普通HTTP连接。该模块还提供了一套针对OpenSS的hash(哈希)、hmac(密钥哈希)、cipher(编码)、decipher(解码)、sign(签名)以及verify(验证)等方法的封装。

哈希

// 获取原生crypto模块
var crypto = require('crypto');
// 使用md5进行加密
var hash = crypto.createHash("md5");
// 使用二进制数据流将字符串进行加密
hash.update(new Buffer("huangdanhua", "binary"));
// 返回hash对象加密后的字符串
var encode = hash.digest('hex');
console.log("二进制加密后的数据: " + encode);

var hash = crypto.createHash("md5");
// 字符加密代码
hash.update("huangdanhua");
var encode = hash.digest('hex');
console.log("字符加密后的数据:" + encode);

// 使用sha1进行加密
var hash = crypto.createHash("sha1");
hash.update("huangdanhua");
var encode = hash.digest('hex');
console.log("使用sha1加密后的数据:" + encode);

hash.digest()这个函数可以传入3个类型的参数hex(十六进制)、binary(二进制)或者base64输出加密后的字符,默认参数时binary。如果传递的参数非指定的这3个字符时,函数会自动使用binary返回加密字符串。

HMAC

HMAC是密钥相关的哈希运算消息认证码,HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。

var crypto = require('crypto');
/*-------------binary md5------------------*/
var hmac = crypto.createHmac("md5", 'dan');
hmac.update(new Buffer("huangdanhua", "binary"));
var encode = hmac.digest('hex');
console.log("binary data: " + encode);
/*-------------string md5------------------*/
var hmac = crypto.createHmac("md5", 'dan');
hmac.update("huangdanhua");
var encode = hmac.digest('hex');
console.log("string:" + encode);
/*-------------string sha1------------------*/
var hmac = crypto.createHmac("sha1", 'dan');
hmac.update("huangdanhua");
var encode = hmac.digest('hex');
console.log("string sha1:" + encode);

Cipher和Decipher

如果希望对一个字符进行加密时,必须保证cipher对象和decipher对象加密的私钥和加密算法是相同的,才能正确的解密,解密和加密调用的所有函数都是类似的。

var crypto = require('crypto')
var key = 'salt_from'
var plaintext = 'fengxiong'
// 创建一个cipher加密对象,第一个参数是算法类型,第二参数是需要加密的秘钥
var cipher = crypto.createCipher('aes-256-cbc', key)
// 创建一个decipher解密对象,第一个参数是算法类型,第二参数是需要加密的秘钥
var decipher = crypto.createDecipher('aes-256-cbc', key);
// 使用参数data更新要加密的内容
cipher.update(plaintext, 'utf8', 'hex');
// 返回所有剩余的加密内容
var encryptedPassword = cipher.final('hex')
// 使用参数data更新要解密的内容
decipher.update(encryptedPassword, 'hex', 'utf8');
// 返回所有剩余的解密内容
var decryptedPassword = decipher.final('utf8');
console.log('encrypted :', encryptedPassword);
console.log('decrypted :', decryptedPassword);

Promise初入门径

前言

这篇博文是根据慕课网教程整理而来,内容几乎都会是讲师的原话,外带一些自己的理解。

Promise是什么

这个英语单词翻译成中文意思就是:许诺;允诺;有可能。因此从字面上就可以知道它代表了即将要发生的事情,从而联想到了JavaScript中异步程序。

按照它的实际用途来看主要有以下几点

  • 用于异步计算
  • 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
  • 可以在对象之间传递和操作Promise,帮助我们处理队列

Promise产生的背景

根源是为了优化表单提交的用户体验,而开发了JavaScript这款包含大量异步操作的脚本语言。在提交表单中异步程序的表现是怎么样的呢?就是当你注册会员的时候,填写了昵称这玩意,然后再填写密码的时候,同时服务器里会检测这个昵称是否已经被注册从而做出一些回应,而不用等你全部信息填写好点击提交才告诉你昵称已经存在。

借由异步的这一个特点,可以想到:异步操作能够避免界面冻结!异步的本质用大白话说就是:将耗时很长的A交付的工作交给系统之后,就去继续做B交付的工作。等到系统完成前面的工作之后,再通过回调或者事件,继续做A交付的剩下的工作。

从观察者的角度看起来,AB工作的完成顺序,和交付它们的时间顺序无关,所以叫“异步”。

咳咳,说重点,以下才是Promise诞生的原因

  • 解决因为异步操作所带来的回调地狱,从而导致维护性差,下面请看回调代码
a(function (resultsFromA) {
  b(resultsFromA, function (resultsFromB) {
    c(resultsFromB, function (resultsFromC) {
      d(resultsFromC, function (resultsFromD) {
        e(resultsFromD, function (resultsFromE) {
          f(resultsFromE, function (resultsFromF) {
            console.log(resultsFromF);
          })
        })
      })
    })
  })
});
  • 总结就是曾经的异步操作依赖的回调函数中存在着“嵌套层次深,难以维护”、“无法正常使用return和throw”、“无法正常检索堆栈信息”和“多个回调之间难以建立联系”这四个主要问题需要被解决,于是Promise横空出世。
  • 最后一点:到底啥是回调函数啊??!

Promise的概念和优点

【优点】

  • Promise是一个代理对象,它和原先要进行的操作并无关系
  • Promise通过引入一个回调,避免了更多的回调

【状态】

  • pending:待定,称为初始状态
  • fulfilled:实现,称为操作成功状态
  • rejected:被否决,称为操作失败状态
  • 当Promise状态发生改变的时候,就会触发.then()里的响应函数来处理后续步骤
  • Promise状态已经改变,就不会再变

Promise的基本语法

new Promise(
    /* 执行器 executor */
    function (resolve, reject) {
      // 一段耗时很长的异步操作
      resolve(); // 数据处理完成
      reject(); // 数据处理出错
    }
 ).then(function A() {
    // 成功,下一步
  }, function B() {
    // 失败,做相应处理
  });

异步操作的常见方法

首先看课程里提供的方法

// 事件侦听与响应
document.getElementById('start').addEventListener('click', start, false);
function start() {
  // 响应事件,进行相应的操作
}

// jQuery 用 `.on()` 也是事件侦听
$('#start').on('click', start);

// 回调,比较常见的有ajax
$.ajax('http://baidu.com', {
  success: function (res) {
    // 这里就是回调函数了
  }
});

// 或者在页面加载完毕后回调
$(function () {
  // 这里也是回调函数
});

以上是课程稍微提到的方法,下面请看阮一峰老师的进一步说明,面试的时候可以使劲说啦,传送门在此。

Promise一个简单的例子

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
      console.log(123);
    }, 2000);
  })
  .then(name => {
    console.log(name + ' world');
  });

以上代码和课程稍微有些不同,目的是和定时器做一些对比,以此发现一点什么。

console.log('here we go');
setTimeout(() => {
  callback("hello");
  console.log(123);
}, 2000)

function callback(name) {
  console.log(name + ' world');
}

通过以上两段代码的运行结果比较,可以浅显的得出:resolve()状态引发的then()是异步的,更多的我暂时就不知道啦。

Promise两步执行的范例

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    return new Promise(resolve => {
      setTimeout(() => {
        resolve('world');
      }, 2000);
    });
  })
  .then(value => {
    console.log(value + ' world');
  });

这个范例主要是简单的演示了Promise如何解决回调地狱这个让人头大的问题。

对已经完成的Promise执行then()

console.log('start');
let promise = new Promise(resolve => {
  setTimeout(() => {
    console.log('the promise fulfilled');
    resolve('hello, world');
  }, 1000);
});

setTimeout(() => {
  promise.then(value => {
    console.log(value);
  });
}, 3000);

讲师的原话:这段代码展示了Promise作为队列这个重要的特性,就是说我们在任何一个地方生成了一个Promise对象,都可以把它当做成一个变量传递到其他地方执行。不管Promise前面的状态到底有没有完成,队列都会按照固定的顺序去执行。

then()不返回Promise

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    console.log('everyone');
    (function () {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('Mr.Laurence');
          resolve('Merry Xmas');
        }, 2000);
      });
    }());
    return false;
  })
  .then(value => {
    console.log(value + ' world');
  });

我对以上代码的理解是这样的:最后一个then()方法里的value值代表的是上一个then()里的返回值,当没有return的时候,默认返回值为undefined。而resolve()里的数据为什么没被调用呢?因为上一个then()方法里return的是false而不是Promise实例。

要想调用resolve()里的数据,只要这么写就可以了

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  })
  .then(value => {
    console.log(value);
    console.log('everyone');
    (function () {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('Mr.Laurence');
          resolve('Merry Xmas');
        }, 2000);
      });
    }()).then(value => {
      console.log(value + ' world');
    });
  })

then()解析

  • then()接受两个函数作为参数,分别代表fulfilled和rejected
  • then()返回一个新的Promise实例,所以它可以链式调用
  • 当前面的Promise状态改变时,then()根据其最终状态,选择特定的状态响应函数执行
  • 状态响应函数可以返回新的Promise或其他值
  • 如果返回新的Promise,那么下一级then()会在新的Promise状态改变之后执行
  • 如果返回其他任何值,则会立刻执行下一级then()

then()的嵌套

then()里面有then()的情况:因为then()返回的还是Promise实例,故会等里面的then()执行完,再执行外面的,因此对于我们来说,此时最好将其展开,会更好的进行阅读。以下是then嵌套的代码

console.log('start');
new Promise(resolve => {
    console.log('Step 1');
    setTimeout(() => {
      resolve(100);
    }, 1000);
  })
  .then(value => {
    return new Promise(resolve => {
        console.log('Step 1-1');
        setTimeout(() => {
          resolve(110);
        }, 1000);
      })
      .then(value => {
        console.log('Step 1-2');
        return value;
      })
      .then(value => {
        console.log('Step 1-3');
        return value;
      });
  })
  .then(value => {
    console.log(value);
    console.log('Step 2');
  });

解套后的代码为:

console.log('start');
new Promise(resolve => {
    console.log('Step 1');
    setTimeout(() => {
      resolve(100);
    }, 1000);
  })
  .then(value => {
    return new Promise(resolve => {
      console.log('Step 1-1');
      setTimeout(() => {
        resolve(110);
      }, 1000);
    })
  })
  .then(value => {
    console.log('Step 1-2');
    return value;
  })
  .then(value => {
    console.log('Step 1-3');
    return value;
  })
  .then(value => {
    console.log(value);
    console.log('Step 2');
  });

两段代码的执行结果一致(话说此时你们清楚的知道结果是啥吗)。

Promise小测试

看以下代码进行分析四种Promise的区别是什么?是什么原因导致了不同的执行流程?

// 问题一
doSomething()
  .then(function () {
    return doSomethingElse();
  })
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultDoSomethingELlse)

// 问题二
doSomething()
  .then(function () {
    doSomethingElse();
  })
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(undefined)
//注意:doSomethingElse(undefined)和finalHandler(undefined)同时执行

// 问题三
doSomething()
  .then(doSomethingElse())
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultOfDoSomething)
//注意:doSomethingElse(undefined)和doSomething()同时执行


// 问题四
doSomething()
  .then(doSomethingElse)
  .then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(resultOfDoSomething) ==> finalHandler(resultOfDoSomethingElse)

Promise错误处理

因为在这一块,讲师貌似犯了些小错误,很多人反应很强烈,至于这错误到底是不是错误我也不太懂,但是不能把有异议的内容也写进来吧,于是我在网上找了篇自己能理解的Promise错误处理,贴上来给大家看看。

谈到Promise错误处理,就要把reject拿出来晾一晾了。reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调(严格来说这不算是错误处理吧。。。),看下面的代码。

function getNumber() {
  var p = new Promise(function(resolve, reject) {
    //做一些异步操作
    setTimeout(function() {
      var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
      if(num <= 5) {
        resolve(num);
      } else {
        reject('数字太大了');
      }
    }, 2000);
  });
  return p;
}
getNumber().then(function(data) {
  console.log('resolved');
  console.log(data);
}, function(reason) {
  console.log('rejected');
  console.log(reason);
});

getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。

运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到“成功”和“失败”的两种结果。

另一种处理错误和异常的方法:catch。 其实它和上面then的第二个参数一样,用来指定reject的回调,用法是这样的:

function getNumber() {
  var p = new Promise(function(resolve, reject) {
    //做一些异步操作
    setTimeout(function() {
      var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
      if(num <= 5) {
        resolve(num);
      } else {
        reject('数字太大了');
      }
    }, 2000);
  });
  return p;
}
getNumber().then(function(data) {
  console.log('resolved');
  console.log(data);
}).catch(function(reason) {
  console.log('rejected');
  console.log(reason);
});

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中,请看下面的代码,然后分别代入自行测试一下

// 测试代码1
getNumber().then(function(data) {
  console.log(name());
  console.log('resolved');
  console.log(data);
}, function(reason, data) {
  console.log('rejected');
  console.log(reason);
});
// 测试代码2
getNumber().then(function(data) {
  console.log(name());
  console.log('resolved');
  console.log(data);
}).catch(function(reason) {
  console.log('rejected');
  console.log(reason);
});

在resolve的回调中,我们console.log(name());而name()这个函数是没有被定义的。如果我们不用Promise中的 catch,代码运行到这里就直接在控制台报错了,不往下运行,但是使用catch就不同了。

也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

Promise.all()解析

Promise.all()具有批量执行的特点,用于将多个Promise实例,包装成一个新的Promise实例,返回的就是普通Promise。

它接受一个数组作为参数,数组里可以是Promise对象,也可以是别的值,只有Promise会等待状态的改变。

当所有子Promise都完成,那么返回新的Promise才认为是完成了,返回值是全部值的数组;有任何一个失败,则新的Promise就认为失败了,返回值是第一个失败的子Promise的结果。下面看代码:

console.log('here we go');
Promise.all([1, 2, 3]).then(all => {
  console.log('1:', all);
  return Promise.all([function() {
    console.log('ooxx');
  }, 'xxoo', false]);
}).then(all => {
  console.log('2:', all);
  let p1 = new Promise(resolve => {
    setTimeout(() => {
      resolve('I\'m P1');
    }, 1500);
  });
  let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('I\'m P2');
    }, 1000);
  });
  let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('I\'m P3');
    }, 3000);
  });
  return Promise.all([p1, p2, p3]);
}).then(all => {
  console.log('all', all);
}).catch(err => {
  console.log('Catch:', err);
});

真是让人头大的代码,建议各位还是先来看看这篇吧——大白话讲解Promise

Promise实现队列

有时候我们不希望所有动作一起发生,而是按照一定顺序,逐个进行,用代码解释就是如下这样的:

let promise = doSomething();
promise = promise.then(doSomethingElse);
promise = promise.then(doSomethingElse2);
promise = promise.then(doSomethingElse3);
.........

实现队列方式一:使用forEach()

function queue(things) {
  let promise = Promise.resolve();
  things.forEach(thing => {
   promise.then(() => {
      return new Promise(resolve => {
        doThing(thing, () => {
          resolve();
        });
      });
    });
  });
  return promise;
}
queue(['lots', 'of', 'things', ....]);

【注意】
常见错误:没有把then()产生的新Promise实例赋给promise,没有生成队列。

实现队列方式二:使用reduce()

function queue(things) {
  return things.reduce((promise, thing) => {
    return promise.then(() => {
      return new Promise(resolve => {
        doThing(thing, () => {
          resolve();
        });
      });
    });
  }, Promise.resolve());
}
queue(['lots', 'of', 'things', ....]);

Promise.resolve()解析

Promise.resolve()返回一个fulfilled状态的Promise实例,或原始的Promise实例,具有如下特点:

  • 参数为空,返回一个状态为fulfilled的Promise实例
  • 参数是一个跟Promise无关的值,同上,不过fulfilled响应函数会得到这个参数
  • 参数为Promise实例,则返回该实例,不做任何修改
  • 参数为thenble,则立刻执行它的then()
    看以下代码逐一分析
console.log('start');
Promise.resolve().then(() => {
  console.log('Step 1');
  return Promise.resolve('Hello');
}).then(value => {
  console.log(value, 'World');
  return Promise.resolve(new Promise(resolve => {
    setTimeout(() => {
      resolve('Good');
    }, 2000);
  }));
}).then(value => {
  console.log(value, ' evening');
  return Promise.resolve({
    then() {
      console.log(', everyone');
    }
  })
})

Promise.reject()解析

Promise.reject()除了不认thenable,其他的特点都和Promise.resolve()类似,请看如下代码:

let promise = Promise.reject('something wrong');
promise.then(() => {
  console.log('it\'s ok');
}).catch(() => {
  console.log('no, it\'s not ok');
  return Promise.reject({
    then() {
      console.log('it will be ok');
    },
    catch() {
      console.log('not yet');
    }
  });
});

Promise.race()解析

类似Promise.all(),区别在于它有任意一个完成就算完成,观察以下代码:

console.log('start');
let p1 = new Promise(resolve => {
  // 这是一个长时间的调用
  setTimeout(() => {
    resolve('I\'m P1');
  }, 10000);
});
let p2 = new Promise(resolve => {
  // 这是个稍短的调用
  setTimeout(() => {
    resolve('I\'m P2');
  }, 2000)
});
Promise.race([p1, p2]).then(value => {
  console.log(value);
});

Promise.race()常见用法是把异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户。这里可能说的有点抽象,希望来这里看一看——大白话讲解Promise,那么很容易就能明了。

现实生活中的Promise应用

  • 把回调函数包装成Promise,使其可读性更好和返回的结果可以加入任何Promise队列
  • 把任何异步操作包装成Promise,假设有这需求:用户点击按钮,弹出确认窗体 ==> 用户确认和取消有不同的处理。那么就能编写如下代码:
// 弹出窗体
let confirm = popupManager.confirm('您确定么?');
confirm.promise.then(() => {
  // do confirm staff
}).catch(() => {
  // do cancel staff
});
// 窗体的构造函数
class Confirm {
  constructor() {
    this.promise = new Promise((resolve, reject) => {
      this.confirmButton.onClick = resolve;
      this.cancelButton.onClick = reject;
    })
  }
}

尾声

呃呃,IE那块我选择不鸟它了,一个连它爸爸都嫌弃的浏览器也是没救了。还有最新的异步函数async和await,不说了,困得一批,劳资下班睡觉去。

最后的最后,强烈推荐一篇反复出现的博文和那位博主,有非常值得学习的地方:大白话讲解Promise

webstorm使用心得

配置主题

1、自带主题设置:File > Setings > Appearance & Behavior > Appearance ,建议选择Theme为Darcula,自我感觉护眼^_^

2、下载主题安装:

  • 主题下载地址,我下载的是sublime的主题Monokai_Sublime.xml
  • 接着将xml文件放进对应的colos文件夹中,注意不是你项目安装的那个地址,具体的请看下图。
    image
  • 然后在编辑器里面进行设置:File > Setings > Editor > Color Scheme > Color Scheme Font,假如你xml放对位置的话,那么将在Scheme中发现Monkai,选择它,最后应用即可。
  • 因为版本的原因,可能一些配置有点差异,可结合这篇文章进行阅读

待续。。。

JavaScript进阶学习总结与资料

前言

该份资料的来源为慕课网教程《JavaScript深入浅出》,内容几乎是全文摘抄下来,不喜勿喷啊。

数据类型

JavaScript被称为是一种弱类型的语言,原因就是数据类型居然能够随意转换而不报错,而且在定义变量的时候不用指定其类型,示例代码如下:

var num = 32;
num = "this is a string";

面试题常问的:JavaScript中原始类型有哪几种? 答:number、string、boolean、null、undefined。

隐式转换

1、加号(+)和减号(-)
在数字与字符串做运算的时候,加号做拼接,减号就做减法

"37" - 7   //30
"37"+7   //377

2、等于号(==)
等于号判断原始类型的时候会有自动类型转换的特点,在判断引用类型的时候会从引用地址上进行判断。

"1.23" == 1.23   //true
0 == false   //true
null == undefined   //true
new Object() == new Object()   //false
[1, 2] == [1, 2]   //false
new String('hi') == 'hi'   //true
new String('456') == 456    //true

3、严格等于(===)
相比于等于号,严格等于不会进行类型转换,它会一开始判断两者之间的类型,如果类型不同,直接返回false。类型相同且内容相同才返回true。

"1.23" === 1.23   //false
0 === false   //false
null === undefined   //false
new Object() === new Object()   //false
[1, 2] === [1, 2]   //false
new String('hi') === 'hi'   //false
new String('456') === 456    //false

包装对象

JavaScript是面向对象的语言,使用”.”操作符可以访问对象的属性和方法,而对于基本类型(null,undefined, bool, number, string)应该是值类型,没有属性和方法,然而

var str = "this is a string";
console.log(str.length); //16
console.log(str.indexOf("is")); //2

结果很简单,但是仔细想想还真奇怪,string不是值类型吗!怎么又有属性又有方法的!其实只要是引用了字符串的属性和方法,JavaScript就会将字符串值通过new String(s)的方式转为内置对象String,一旦引用结束,这个对象就会销毁。所以上面代码在使用的实际上是String对象的length属性和indexOf方法。

同样的道理,数字和布尔值的处理也类似。null和undefined没有对应对象。既然有对象生成,能不能这样

var str = "this is a string";
str.b = 10;
console.log(str.b);  //undefined

结果并没有返回10,而是undefined!不是说好了是个对象吗!正如刚才提到第二行代码只是创建了一个临时的String对象,随即销毁,第三行代码又会创建一个新的临时对象(这就是低版本IE频繁处理字符串效率低的一个原因),自然没有b属性,这个创建的临时对象就成为包装对象。

类型检测

在JavaScript中,有很多种检测数据的类型,主要是有以下几种

1、typeof:一般用于检测原始数据类型,引用数据类型无法具体的检测出来

console.log(typeof ""); //string
console.log(typeof 1); //number
console.log(typeof true); //boolean
console.log(typeof null); //object
console.log(typeof undefined); //undefined
console.log(typeof []); //object
console.log(typeof function() {}); //function
console.log(typeof {}); //object

其实null是js设计的一个败笔,早期准备更改null的类型为null,由于当时已经有大量网站使用了null,如果更改,将导致很多网站的逻辑出现漏洞问题,就没有更改过来,于是一直遗留到现在。

2、instanceof:检测引用数据类型

console.log("1" instanceof String);  //false
console.log(1 instanceof Number);  //false
console.log(true instanceof Boolean);  //false
console.log([] instanceof Array);  //true
console.log(function() {} instanceof Function);  //true
console.log({} instanceof Object);  //true

可以看到前三个都是以对象字面量创建的基本数据类型,但是却不是所属类的实例,这个就有点怪了。后面三个是引用数据类型,可以得到正确的结果。如果我们通过new关键字去创建基本数据类型,你会发现,这时就会输出true,如下:

console.log(new String("1") instanceof String); //true
console.log(new Number(1) instanceof Number); //true
console.log(new Boolean(true) instanceof Boolean); //true
console.log([] instanceof Array); //true
console.log(function() {} instanceof Function); //true
console.log({} instanceof Object); //true

3、constructor:似乎完全可以应对基本数据类型和引用数据类型,都能检测出数据类型

console.log(("1").constructor === String); //true
console.log((1).constructor === Number); //true
console.log((true).constructor === Boolean); //true
console.log(([]).constructor === Array); //true
console.log((function() {}).constructor === Function); //true
console.log(({}).constructor === Object); //true

事实上并不是如此,来看看为什么:

function Fn(){};
Fn.prototype=new Array();
var f=new Fn();

console.log(f.constructor===Fn);  //false
console.log(f.constructor===Array);  //true

声明了一个构造函数,并且把他的原型指向了Array的原型,所以这种情况下,constructor也显得力不从心了。

4、Object.prototype.toString:终极数据检测方式

var a = Object.prototype.toString;

console.log(a.call("aaa"));  //[object String]
console.log(a.call(1));  //[object Number]
console.log(a.call(true));  //[object Boolean]
console.log(a.call(null));  //[object Null]
console.log(a.call(undefined));  //[object Undefined]
console.log(a.call([]));  //[object Array]
console.log(a.call(function() {}));  //[object Function]
console.log(a.call({}));  //[object Object]

表达式和运算符

表达式

概念
表达式是指能计算出值的任何可用程序单元,或者可以这么说:表达式是一种JavaScript短语,可以使JavaScript解释器用来产生一个值。

原始表达式

  • 常量、直接量:比如3.14、“test”等这些;
  • 关键字:比如null,this,true等这些;
  • 变量:比如i,k,j等这些。

复合表达式
比如:10*20

数组和对象的初始化表达式
比如:[1,2]、{x:1,y:2}等这些。

函数表达式
比如:var fe = function(){}或者(functiong(){console.log('hello world');})()

属性访问表达式
比如:var o = {x:1},访问属性的方式有o.x或者o['x']

调用表达式
比如:funName()

对象创建表达式
比如:new Func(1,2)或者new Object

运算符

太多太基础啦,不多说,稍微提一下以下几个运算符

// 逗号运算符
var n = (1,2,3)  //n=3

// 删除运算符
var obj = {x:1}
obj.x;  //1
delete obj.x;
obj.x;  //undefined

var obj = {};
Object.defineProperty(obj,'x',{
  configurable:false,
  value:1
});
delete obj.x;  //false
obj.x;  //1


// in运算符
window.x = 1;
'x'  in window;  //true

// new运算符
function Foo(){}
Foo.prototype.x = 1;
var obj = new Foo();
obj.x;  // 1
obj.hasOwnProperty('x');  // false
obj.__proto__.hasOwnProperty('x');  // true

// this运算符
this;
var obj = {
    func:function(){return this;}
};
obj.func();  //obj

语句

block语句和var语句

block语句
块语句常用于组合0~N个语句,往往用一对花括号定义。

{
  var str = 'hi';
  console.log(str);
}

if(true) {
  console.log('hi');
}

在ES6出来之前,JavaScript是没有块级作用域的,具体看以下两段代码:

for (var i=0; i<10; i++) {                
  var str = 'hi';  
  console.log(str); 
}  
等同于
 var i = 0;
for (; i<10; i++) {                
  var str = 'hi';  
  console.log(str); 
} 

{
  var x = 1;
}
等同于
var x = 1;
{

}

function foo() {
  var a = 1;
  console.log(a);  //1
}
foo();
console.log(typeof a);  //undefined

声明语句var

function foo() {
  //隐式的将b定义为全局变量
  var a = b = 1;
}
foo();
console.log(typeof a);  //undefined
console.log(typeof b);  //number

function foo() {
  //同时定义多个变量的正确方式
  var a =1,b = 1;
}
foo();
console.log(typeof a);  //undefined
console.log(typeof b);  //undefined

try-catch语句

try跟catch搭配使用可以检测try里边的代码有没有抛出error,如果有error就会跳转到catch里执行catch里的程序。执行顺序为:先try捕获异常,执行catch里面的内容,最后执行finally里面的内容,当然也可以只写catch或者finally两个中的一个,但是必须写try语句块。

try {
  throw 'test';
} catch(e) {
  console.log(e); //test
} finally {
  console.log('必須執行的');
}

嵌套使用

try {
  try {
    throw new Error('oops');
  } finally {
    console.log('finally');
  }
} catch(e) {
  console.error('outer', e.message);
}

由于在嵌套层中并没有catch语句,因此输出结果的顺序为:finally、outer、oops。下面请看含有catch语句的

try {
  try {
    throw new Error('oops');
  } catch(ex) {
    console.log('inner', ex.message);
  } finally {
    console.log('finally');
  }
} catch(ex) {
  console.error('outer', ex.message);
}

以上代码输出结果的顺序为:inner、oops、finally。原因是异常已经在内部处理过了,因此不会再到外部去处理。

更复杂的嵌套

try {
  try {
    throw new Error('oops');
  } catch(ex) {
    console.log('inner', ex.message);
    throw ex;
  } finally {
    console.log('finally');
  }
} catch(ex) {
  console.error('outer', ex.message);
}

以上代码输出结果的顺序为:inner、oops、finally、outer、oops。原因在内部的catch语句重新向外抛出了ex这个异常。

for in语句

有以下的特点:1、顺序不确定;2、enumerable为false时不会出现;3、fon in对象属性时会受到原型链的影响

var p;
var obj = { x: 1, y: 2 }
for(p in obj) {
  console.log(p);  //x  y
}

严格模式

这里有一篇非常好的总结,传送门

对象

概述

对象中包含一系列的属性,这些属性是无序的,每一个属性都有一个字符串key和对应的value

var obj = {x:1,y:2};
obj.x;  //1
obj.y; //2

创建对象

创建对象方式1—字面量:

var obj1 = {x:1,y:2};
var obj2 = {
  x:1,
  y:2,
  o:{
    z:3,
    n:4
  }
};

创建对象方式2—通过构造函数

function foo() {}
foo.prototype.z = 3;
var obj = new foo();
obj.x = 1;
obj.y = 2;
console.log(obj.x); //1
console.log(obj.y); //2
console.log(obj.z); //3
console.log(typeof obj.toString()); //string
console.log(typeof obj.toString);  //function
console.log('z' in obj); //true
console.log(obj.hasOwnProperty('z')); //false

创建对象方式3—Object.create

var obj = Object.create({ x: 1 });
console.log(obj.x);  //1
console.log(obj.toString);  //function
console.log(obj.hasOwnProperty('x'));  //false

// null对象的原型链少了Object这一层
var obj1 = Object.create(null);
console.log(obj1.toString);  //undefined

属性操作

属性读写

var obj = {x:1,y:2};
obj.x;  //1
obj['y'];  //2

//使用[]取得属性值一般场景
var obj = {x1:1,x2:2};
for(var i=1; i<=2; i++){
  console.log(obj['x' + i];
}

属性删除

var person = {age:28,title:'test'};
delete person.age;  //true
delete preson['title'];  //true
person.age;  //undefined
delete person.age;  //true

//无法删除原型
delete Object.prototype;  //false
//因为原型的configurable不可配置
var descriptor = Object.getOwnPropertyDescriptor(Object,'prototype');
descriptor.configurable;  //false

属性检测

var cat = new Object();
cat.legs = 4;
cat.name = 'tom';

//for in检测
'legs' in cat;  //true
'abc' in cat;  //false
'toString' in cat;  //true

//检测对象本身的属性
cat.hasOwnProperty('legs');  //true
cat.hasOwnProperty('toString');  //false

//检测对象的属性是否可枚举,包括原型上的
cat.propertyIsEnumerable('legs');  //true
cat.propertyIsEnumerable('toString');  //false

属性枚举

var o = { x: 1, y: 2, z: 3 };
'toString' in o; //true
o.propertyIsEnumerable('toString'); //false
var key;
for(key in o) {
  console.log(key); //x,y,z
}

var obj = Object.create(o);
obj.a = 4;
var key;
for(key in obj) {
  console.log(key); //a,x,y,z
}

var obj = Object.create(o);
obj.a = 4;
var key;
for(key in obj) {
  if(obj.hasOwnProperty(key)) {
    console.log(key); //a
  }
}

getter()和setter()方法

var man = {
  name: 'tom',
  weibo: 'haha',
  get age() {
    return new Date().getFullYear() - 1988;
  },
  set age(val) {
    console.log('你没有权限设置年龄为:' + val);
  }
}
console.log(man.age);  //30
man.age = 100;  //你没有权限设置年龄为:100
console.log(man.age);  //30

//修改一下,使上面的代码更复杂
var man = {
  name: 'tom',
  weibo: 'haha',
  $age: null,
  get age() {
    if(this.$age == undefined) {
      return new Date().getFullYear() - 1988;
    } else {
      return this.$age;
    }
  },
  set age(val) {
    val = +val;
    if(!isNaN(val) && val > 0 && val < 150) {
      this.$age = +val;
    } else {
      console.log('年龄无法设置为:' + val);
    }
  }
}
console.log(man.age); //30
man.age = 100;
console.log(man.age); //100
man.age = 'abc'; //年龄无法设置为:NaN

get/set与原型链

//get和set设置的值不会被直接更改
function foo() {}
Object.defineProperty(foo.prototype, 'z', {
  get: function() {
    return 1;
  }
})
var obj = new foo();
console.log(obj.z); //1
obj.z = 10;
console.log(obj.z); //1

//defineProperty设置的值不会被直接更改
var o = {}
Object.defineProperty(o, 'x', { value: 5 });
var obj = Object.create(o);
console.log(obj.x); //5
obj.x = 200;
console.log(obj.x); //5

//更改defineProperty设置的值
var o = {}
Object.defineProperty(o, 'x', { writable: true, configurable: true, value: 5 });
var obj = Object.create(o);
console.log(obj.x); //5
obj.x = 200;
console.log(obj.x); //200

属性标签

var person = {};
Object.defineProperty(person, 'name', {
  configurable: false,
  writable: false,
  enumerable: true,
  value: 'tom'
})
console.log(person.name);  //tom
person.name = 'jack';
console.log(person.name);  //tom
console.log(delete person.name);  //false
//获得属性的标签值
console.log(Object.getOwnPropertyDescriptor(person,'name'));

//属性标签的应用
var person = {};
Object.defineProperties(person, {
  title: {
    value: 'JavaScript',
    enumerable: true
  },
  corp: {
    value: 'BAT',
    enumerable: true
  },
  salary: {
    value: 50000,
    enumerable: true,
    writable: true
  },
  luck: {
    get: function() {
      return Math.random() > 0.5 ? 'good' : 'bad';
    }
  },
  promote: {
    set: function(level) {
      this.salary *= 1 + level * 0.1;
    }
  }
});
console.log(Object.getOwnPropertyDescriptor(person, 'salary'));
console.log(person.salary); //50000
person.promote = 2;
console.log(person.salary); //60000

image

序列化

//将属性和值序列化为字符串
var obj = { x: 1, y: true, z: [1, 2, 3], nullVal: null };
//{"x":1,"y":true,"z":[1,2,3],"nullVal":null}
console.log(JSON.stringify(obj));

//有点小坑,得注意
var obj1 = { val: undefined, a: NaN, b: Infinity, c: new Date() };
//{"a":null,"b":null,"c":"2018-06-09T10:46:01.929Z"}
console.log(JSON.stringify(obj1));

//把字符串反序列化为属性和值
var obj2 = JSON.parse('{"x":2333}');
//2333
console.log(obj2.x);

数组

数组中的方法

将数组转为字符串

var arr = [1, 2, 3];
console.log(arr.join());  //1,2,3
console.log(arr.join('-'));  //1-2-3

function repeatString(str, n) {
  return new Array(n + 1).join(str);
}
console.log(repeatString('a', 3));  //aaa
console.log(repeatString('hi', 2));  //hihi

将数组逆序输出

var arr = [1, 2, 3];
console.log(arr.reverse());  //3,2,1
console.log(arr);  //3,2,1

将数组进行排序

var arr = [13, 24, 52, 3];
//13,24,3,54
console.log(arr.sort());
//13,24,3,54
console.log(arr);
var arrSort = arr.sort(function(a, b) { return a - b; });
//3,13,24,52
console.log(arrSort);

var arr1 = [
  { age: 25 }, 
  { age: 39 }, 
  { age: 22 }
];
arr1.sort(function(a, b) {
  return a.age - b.age;
})
arr1.forEach(function(item) {
  console.log('age:', item.age);
})

将数组合并

var arr = [1, 2, 3];
//[1,2,3,4,5]
console.log(arr.concat(4, 5));
//[1,2,3]
console.log(arr);
//[1,2,3,10,11,13]
console.log(arr.concat([10, 11], 13));
//[1,2,3,1,[2,3]]
console.log(arr.concat([1, [2, 3]]));

返回部分数组

var arr = [1, 2, 3, 4, 5];
//[2,3]
console.log(arr.slice(1, 3));
//[2,3,4,5]
console.log(arr.slice(1));
//[2,3,4]
console.log(arr.slice(1, -1));
//[2]
console.log(arr.slice(-4, -3));
//[1,2,3,4,5]
console.log(arr);

将数组进行拼接

var arr = [1, 2, 3, 4, 5];
//[3,4,5],从下标为2开始删除
console.log(arr.splice(2));
//[1,2]
console.log(arr);

var arr1 = [1, 2, 3, 4, 5];
//[3,4],从下标为2开始删除,删除两个元素后停止
console.log(arr1.splice(2, 2));
//[1,2,5]
console.log(arr1);

var arr2 = [1, 2, 3, 4, 5];
//[2],从下标为1开始删除,删除一个元素后停止,并且添加a和b这两个元素
console.log(arr2.splice(1, 1, 'a', 'b'));
//[1,"a","b",3,4,5]
console.log(arr2);

将数组进行遍历

var arr = ['a', 'b', 'c', 'd', 'e'];
arr.forEach(function(x, index, a) {
  console.log(x + '-' + index + '-' + (a === arr));
});

将数组进行映射

var arr = [1, 2, 3];
arr.map(function(x) {
  //11,12,13
  console.log(x + 10);
});
//[1,2,3]
console.log(arr);

将数组进行过滤

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.filter(function(x, index) {
  //1,2,3,4,5,6,7,8,9,10
  console.log(x);
  //0,1,2,3,4,5,6,7,8,9
  console.log(index);
  return index % 3 === 0 || x >= 8;
});
//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(arr);

将数组进行判断

var arr = [1, 2, 3, 4, 5];
//判断每一个元素是否小于10
arr.every(function(x) {
  return x < 10;
});
//判断是否有一个元素等于3
arr.some(function(x) {
  return x === 3;
});

将数组元素进行累加操作

var arr = [1, 2, 3];
var sum = arr.reduce(function(x, y) {
  return x + y;
});
//6
console.log(sum);
//[1,2,3]
console.log(arr);

var arr1 = [3, 9, 6];
var max = arr1.reduce(function(x, y) {
  //3-9
  //9-6
  console.log(x + '-' + y);
  return x > y ? x : y;
});
//9
console.log(max);
//[3,9,6]
console.log(arr1);

var arr2 = [3, 9, 6];
var max2 = arr2.reduceRight(function(x, y) {
  //6-9
  //9-3
  console.log(x + '-' + y);
  return x > y ? x : y;
});
//9
console.log(max2);
//[3,9,6]
console.log(arr2);

将数组进行检索

var arr = ['a', 'b', 'c', 'd', 'e', 'a'];
//2,在数组下标为2的位置找到
console.log(arr.indexOf('c'));
//-1,找不到该元素
console.log(arr.indexOf('f'));
//0,在数组下标为0的位置找到,默认是从左到右查找
console.log(arr.indexOf('a'));
//5,在数组下标为5的位置找到,设置是从数组下标为1的位置开始向右查找
console.log(arr.indexOf('a', 1));
//5,在数组下标为5的位置找到,-3表示在d的位置开始向右查找
console.log(arr.indexOf('a', -3));
//0,在数组下标为0的位置找到,-6表示在最左a的位置开始向右查找
console.log(arr.indexOf('a', -6));
//-1,找不到该元素,-3表示在d的位置开始向右查找
console.log(arr.indexOf('b', -3));
//3,在数组下标为3的位置找到
console.log(arr.lastIndexOf('d'));
//-1,找不到该元素,2表示的是在e的位置向右查找
console.log(arr.lastIndexOf('d', 2));
//3,在数组下标为3的位置找到,5表示的是在b的位置向右查找
console.log(arr.lastIndexOf('d', 5));

判断是否为数组

//true
console.log(Array.isArray([]));
//true
console.log(({}).toString.apply([]) === '[object Array]');
//true
console.log([] instanceof Array);
//true
console.log([].constructor === Array);

数组 VS 对象

  • 相同点:都可以继承,数组是对象,对象不一定是数组,都可以当做对象一般添加删除属性
  • 不同点:数组自动更新length,按索引访问数组常常比访问一般对象属性明显迅速,数组对象继承Array.prototype上的大量数组操作方法。

数组 VS 字符串

字符串是一种类数组

var str = 'hello world';
str.charAt(0);  //h
str[1];  //e

Array.prototype.join.call(str,'-');  //h-e-l-l-o--w-o-r-l-d

函数和作用域

函数概念

函数是一块JavaScript代码,被定义一次,但可以执行和调用多次。JavaScript中的函数也是对象,所以函数可以像其他对象那样操作和传递,所以我们也常叫JavaScript中的函数为函数对象。

函数调用方式

  • 直接调用
  • 对象方法调用
  • 构造器调用
  • call/apply/bind调用

函数声音和函数表达式

函数声明

function add(a, b) {
  var x = a;
  var y = b;
  return x + b;
}

函数表达式

//一般表达式
var add = function(a, b) {
	
}
//立即调用表达式
(function() {
	
})();
//返回表达式
return function() {
	
};
//命名函数表达式
var add = function foo(a, b) {
	
}

函数声明和函数表达式的区别
他们最重要的区别就是函数声明能够提前,而函数表达式不行

var num = add(1, 3);
//4
console.log(num);
function add(a, b) {
  var x = a;
  var y = b;
  return x + b;
}

var num1 = add1(1, 3);
//add1 is not a function
console.log(num1);
var add1 = function(a, b) {
  var x = a;
  var y = b;
  return x + b;
}

this的栗子

全局的this(浏览器)

console.log(this.document == document);  //true
console.log(this === window);  //true

this.a = 37;
console.log(window.a);  //37

一般函数中的this

function f1(){
  return this;
}
f1() === window;  //true

function f2(){
'use strict';
return this;
}
f2() === undefined;  //true

作为对象方法中函数的this

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};
console.log(o.f()); //37

var o = { prop: 37 };
function independent() {
  console.log(this.prop); //37
  return this.prop;
}
o.f = independent;
console.log(o.f()); //37

对象原型链上的this

var o = {
  fn: function() {
    return this.a + this.b;
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
//5
console.log(p.fn());

get/set方法与this

function modulus() {
  //abs()返回数的绝对值
  return Math.abs(this.re + this.im);
}
var o = {
  re: 4,
  im: -9,
  get phase() {
    //sqrt()返回数的平方根
    return Math.sqrt(this.re);
  }
};
Object.defineProperty(o, 'modulus', {
  get: modulus,
  enumerable: true,
  configurable: true
});
console.log(o.phase); //2
console.log(o.modulus); //5

构造器中的this

function Abc() {
  this.a = 33;
}
var oo = new Abc();
console.log(oo.a); //33

function Bcd() {
  this.b = 55;
  return { b: 66 };
}
var pp = new Bcd();
console.log(pp.b); //66

call/apply方法与this

function add(c, d) {
  return this.a + this.b + c + d;
}
var o = { a: 1, b: 3 };
//16
console.log(add.call(o, 5, 7));
//34
console.log(add.apply(o, [10, 20]));

function bar() {
  //[object Window]
  console.log(Object.prototype.toString.call(this));
}
bar();
//[object Number]
console.log(bar.call(10));

bind方法与this

function bar() {
  return this.a;
}
var ger = bar.bind({
  a: 'test'
});
//f bar(){ return this.a; }
console.log(ger);
//test
console.log(ger());

var odr = {
  a: 33,
  bn: bar,
  gn: ger
};
//33
console.log(odr.bn());
//test
console.log(odr.gn());

函数属性arguments

function foo(x, y, z) {
  //2
  console.log(arguments.length);
  //3
  console.log(arguments[0]);
  arguments[0] = 100;
  //100
  console.log(x)
  arguments[2] = 200;
  //undefined
  console.log(z);
  //200
  console.log(arguments[2]);
  //true
  console.log(arguments.callee === foo);
}
foo(3, 4);
//3
console.log(foo.length);
//foo
console.log(foo.name);

apply/call方法(浏览器)

function foo(x, y) {
  console.log(x, y, this);
}
//1,2,Number(100)
foo.call(100, 1, 2);
//3,4,Boolean(true)
foo.apply(true, [3, 4]);
//undefined,undefined,window
foo.apply(null);
//undefined,undefined,window
foo.apply(undefined);

function foo(x, y) {
  'use strict';
  console.log(x, y, this);
}
//undefined,undefined,null
foo.apply(null);
//undefined,undefined,undefined
foo.apply(undefined);

bind方法

this.x = 9;
var module = {
  x: 33,
  getX: function() {
    return this.x;
  }
};
console.log(module.getX()); //33

var gn = module.getX;
console.log(gn()); //9

var gn1 = gn.bind(module);
console.log(gn1()); //33

bind与currying
函数有柯里化(currying)的特性,意思函数能拆分成多个单元。

function add(a, b, c) {
  return a + b + c;
}
var fn = add.bind(undefined, 100);
console.log(fn(1, 2)); //103

var fn1 = fn.bind(undefined, 200);
console.log(fn1(10)); //310

柯里化的实际运用

function getConfig(colors, size, otherOptions) {
  console.log(colors, size, otherOptions);
}
var defaultConfig = getConfig.bind(null, '#cc0000', '1024*768');
//#cc0000 1024*768 123
console.log(defaultConfig('123'));
//#cc0000 1024*768 456
console.log(defaultConfig('456'));

bind与new

function foo() {
  this.b = 100;
  return this.a;
}
var fn = foo.bind({ a: 1 });
//1
console.log(fn());
//{b:100}
console.log(new fn());

bind方法模拟

if(!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if(typeof this !== 'function') {
      throw new TypeError('what is trying to be bound is not callable');
    }
    var aArgs = Array.prototype.slice.call(arguments, 1);
    var fToBind = this;
    var fNOP = function() {};
    var fBound = function() {
      return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
    };
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
  };
}

以上代码有做了些修改,原始代码以及分析请看下图:
image

理解闭包

闭包的栗子

function outer() {
  var localVal = 30;
  return localVal;
}
console.log(outer());

function outer1() {
  var localVal2 = 33;
  return function() {
    return localVal2;
  }
}
var fn = outer1();
console.log(fn())

闭包概念
在计算机科学中,闭包(也成为词法闭包或者函数闭包)是指一个函数或者函数的引用,与一个引用环境绑定在一起,这个引用环境是一个存储该函数每个非局部变量(也叫自由变量)的表。

闭包,不同于一般的函数,它允许一个函数在立即词法作用域外调用时,仍可访问非本地变量。

闭包特性

  • 优点:灵活、方便、封装;
  • 缺点:空间浪费、内存泄露、性能消耗。

作用域

全局作用域、函数作用域和eval

//全局变量
var a = 10;
(function() {
  //局部变量
  var b = 20;
})();
//10
console.log(a);
//undefined
console.log(b);

for(var item in { a: 1, b: 2 }) {
  //a  b
  console.log(item);
}
//b
console.log(item);

eval('var c = 100');
//100
console.log(c);

作用域链

function outer1() {
  var local1 = 11;
  function inter() {
    var local2 = 22;
    //11
    console.log(local1);
    //22
    console.log(local2);
    //33
    console.log(global1);
  }
  inter();
}
var global1 = 33;
outer1();

function outer2() {
  var local3 = 44;
  var fn = new Function('console.log(typeof local3);');
  //undefined
  fn();
}
outer2();

执行上下文

这个玩意太抽象了,看完视频对这个概念还是十分模糊,还是来这看一下比较容易理解的文章——王福朋深入理解JavaScript系列

OOP知识点

概念与继承

面向对象程序设计是一种程序设计规范,同时也是一种程序开发的方法。对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

看了课程的相关知识点,发现远远没有下面几篇博文说的这么简单易懂与透彻,在这就不整理课程的内容了,一起来看看以下三个博客中内容:
冴羽博客JavaScript系列
汤姆大叔深入理解JavaScript系列
王福朋深入理解JavaScript系列

JavaScript正则表达式

这节课的内容也不摘抄了,因为之前做过这方面的总结,再继续总结一次我就认为是得不偿失了。
JavaScript正则表达式

参考资料

1、博客园小火柴的前端学习之路
2、冴羽博客JavaScript系列
3、汤姆大叔深入理解JavaScript系列
4、JavaScript包装对象
5、JavaScript检测数据类型四种方法
6、JavaScript中的new到底做了些什么
7、王福朋深入理解JavaScript系列
8、JavaScript正则表达式

前端工程师面试指南——JavaScript篇

JavaScript中数组操作常用方法

  • 检测数组
// 1、检测对象是否为数组,使用instanceof 操作符
if(value instanceof Array)
{
//对数组执行某些操作
}

// 2、获取对象的类型,比较是否为object类型(此方法只能检测是否为Object,不推荐)
if(typeof(value)=="Object")
{
//对数组执行某些操作
}

// 3、检测对象是否为数组,使用Array.isArray()方法
// (只支持ie9+,firefox 4+,safari 5+,opera 10.5+和chrome)
if(Array.isArray(value))
{
//对数组执行某些操作
}
  • 转换方法:所有对象都具有toLocaleString(),toString()和valueOf()方法.
// join()方法:接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串
var colors=["red","green","blue"];
alert(colors.join("||"));//red||green||blue
  • 栈方法:栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构
// push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度
// pop()方法则从数组末尾移除最后一项,减少数组的length值,然后返回移除的项
var colors=new Array();//创建一个数组
var count=colors.push("red","green");//推入两项
alert(count);//2
alert(colors);//["red","green"]

count=colors.push("black");//推入另一项
alert(count);//3

var item=colors.pop();//移除最后一项,并返回最后一项的值
alert(item);//“black”
alert(colors.length);//2


// 以上代码首先我们使用push()将两个字符串推入数组的末尾,并将返回的结果保存在变量count中。
// 然后再推入一个值,而结果任然保存在count中.因为此时数组中包含3项,所以push()返回3。
// 在调用pop()时,它会返回数组的最后一项,即字符串"black".此后,数组中仅剩两项。
  • 队列方法:队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出).
// shift()移除数组中第一个项并返回改项,同时将数组长度减1
// unshift()在数组前端添加任意个项并返回新数组的长度
var n=new Array("张三","李四");
n.push("王五");// 添加参数到数组末尾,并修改数组长度
n.shift();// 移除数组第一项,并返回该项
            
n.unshift("小钱");//添加参数到数组前端
n.pop();// 从数组末尾移除最后一项
            
//循环显示出数组的值
for(var i=0;i<n.length;i++) {
  console.log(n[i]);// 小钱 李四 
}      
  • 重排序方法:数组中已经存在两个可以直接用来重排序的方法reverse()和sort()
var values = [0,1,5,10,15];
values.sort();//进行排序显示
alert(values);//0,1,10,15,5

function compare(value1, value2) {
  return value1 - value2;
}
var values = [0,1,5,10,15];
values.sort(compare);
console.log(values); //0,1,5,10,15
  • 操作方法
// concat()方法可以基于当前数组中的所有项创建一个新数组
var colors=["red","green","blue"];
var color2=colors.concat("yellow",["black","brown"]);
alert(color2);//red,green,blue,yellow,black,brown

// slice()方法基于当前数组中的一或多个项创建一个新数组
var colors=["red","green","blue","yellow","black"];
colors.slice(1);//green,blue,yellow,black
colors.slice(1,4)//green,blue,yellow

// splice()方法恐怕要算是最强大的数组方法了,它有很多种用法(删除、插入和替换)
var colors=["red","green","blue"];
var removed=colors.splice(0,1);//删除第一项
alert(colors);//green,blue
alert(removed);//red,返回的数组中只有一项          
removed=colors.splice(1,0,"yellow","orange");//从位置1开始插入2条数据
alert(colors);//green,yellow,orange,blue
alert(removed);//返回一个空数组          
removed=colors.splice(1,1,"red","purple");//从位置1插入2条数据,并删除位置1的数据
alert(colors);//green,red,purple,orange,blue
alert(removed);//yellow,返回的数组中只包含一项
  • 位置方法
// indexOf():从数组的开头(位置0)开始向后查找
// lastIndexOf():从数组的末尾开始向前查找

var numbers=[1,2,3,4,5,4,3,2,1];
alert(numbers.indexOf(4));//3 返回第一个出现的4位置
alert(numbers.lastIndexOf(4));//5 返回最后一个出现的4位置
alert(numbers.indexOf(4,4));//5  从位置4开始查找第一个4出现的位置
alert(numbers.lastIndexOf(4,4));//3 从位置4开始查找最后一个4出现的位置
var person={name:"ToNi"};
var people=[{name:"ToNi"}];
var morePeople=[person];
alert(people.indexOf(person));//返回-1 必须严格相等,不仅仅是值相等
alert(morePeople.indexOf(person));//返回0

JavaScript如何设置获取盒模型对应的宽和高

(1)dom.style.width/height(只能获取到内联样式的宽和高,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <div id="box" style="height: 200px;"></div>
    <script>
      var getHeight = document.getElementById("box");
      console.log(getHeight.style.height);
    </script>
  </body>
</html>

(2)dom.currentStyle.width/height(只有IE浏览器支持,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(getHeight.currentStyle.height);
    </script>
  </body>
</html>

(3)window.getComputedStyle(dom).width/height(兼容性好,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(window.getComputedStyle(getHeight).height);
    </script>
  </body>
</html>

(4)dom.getBoundingClientRect().width/height(兼容性好,输出值不带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(getHeight.getBoundingClientRect().height);
    </script>
  </body>
</html>

DOM事件的级别

(1)DOM0: element.onclick = function(){}
(2)DOM2: element.addEventListener('click',function(){},false)
【拓展】
所谓的0级dom与2级dom事件就是不同版本间的差异,具体的说就是,对于不同的dom级别,如何定义事件处理,以及使用时有什么不同。 比如在dom0级事件处理中,后定义的事件会覆盖前面的,但是dom2级事件处理中,对一个按钮点击的时间处理就没有被覆盖掉。
(3)DOM3: element.addEventListener('keyup',function(){},false)
(4)建议结合红宝书第6页

DOM事件模型

(1)冒泡型事件处理模型(Bubbling): 冒泡型事件处理模型在事件发生时,首先在最精确的元素上触发,然后向上传播,直到根节点,反映到DOM树上就是事件从叶子节点传播到根节点。
(2)捕获型事件处理模型(Captrue): 相反地,捕获型在事件发生时首先在最顶级的元素上触发,传播到最低级的元素上,在DOM树上的表现就是由根节点传播到叶子节点。
【捕获事件的具体流程】
window==>document==>html==>body==>父级元素==>目标元素
(3)标准的事件处理模型分为三个阶段:

  • 父元素中所有的捕捉型事件(如果有)自上而下地执行
  • 目标元素的冒泡型事件(如果有)
  • 父元素中所有的冒泡型事件(如果有)自下而上地执行

Event对象的常见应用

(1)event.preventDefault(): 阻止事件的默认行为
(2)event.stopPropagation(): 阻止事件的进一步传播,也就是阻止冒泡
(3)event.stopImmediatePropagation(): 阻止剩余的事件处理函数的执行,并防止当前事件在DOM树上冒泡。
(4)event.currentTarget: 返回绑定事件的元素
(5)event.target: 返回触发事件的元素

JavaScript自定义事件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Event</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="ev">
      <span>目标元素</span>
    </div>
    <script>
      var event = new Event('test');
      ev.addEventListener('test', function () {
        console.log('test dispatch');
      })
      ev.dispatchEvent(event);
    </script>
  </body>
</html>

JavaScript类的声明

// 1、类的声明
function Animal() {
  this.name = "name";
}

// 2、ES6中类的声明
class Animal2 {
  constructor() {
    this.name = name;
  }
}

// 3、实例化类
console.log(new Animal(), new Animal2());

JavaScript类之间的继承

(1)借助构造函数实现不完全继承,无法继承方法:

function Parent1() {
  this.name = 'parent1';
}
function Child1() {
  Parent1.call(this);
  this.type = 'child1';
}
console.log(new Child1());

(2)借助原型链实现继承,所有的属性和方法都得去原型链上去找,因而找到的属性方法都是同一个,所以直接利用原型链继承是不现实的。

function Parent2() {
  this.name = 'parent2';
  this.play = [1, 2, 3];
}
function Child2() {
  this.type = 'child2';
}
Child2.prototype = new Parent2();
console.log(new Child2());
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
s1.play.push(4);

(3)组合方式实现继承

function Parent3() {
  this.name = 'parent3';
  this.play = [1, 2, 3];
}
function Child3() {
  Parent3.call(this);
  this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);

(4)组合继承的优化1

function Parent4() {
  this.name = 'parent4';
  this.play = [1, 2, 3];
}
function Child4() {
  Parent4.call(this);
  this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5.play, s6.play);
console.log(s5 instanceof Child4, s5 instanceof Parent4);
console.log(s5.constructor);

(5)组合继承的优化2(俗称寄生式继承)

function Parent5() {
  this.name = 'parent5';
  this.play = [1, 2, 3];
}
function Child5() {
  Parent5.call(this);
  this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
var s7 = new Child5();
console.log(s7 instanceof Child5, s7 instanceof Parent5);
console.log(s7.constructor);

创建对象有几种方法

(1)字面量:

var k1 = {
  name: 'k1'
};
var k2 = new Object({
  name: 'k2'
});

(2)通过构造函数

var m = function (name) {
  this.name = name;
};
var k3 = new m('k3');

(3)通过Object.create

var p = {
  name: 'ppp'
};
var k4 = Object.create(p);

什么是原型和原型链?有什么特点

(1)每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象不存在这个属性,那么就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
(2)特点:JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的话,就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象。
(3)更多原型知识1

JavaScript的运行机制

(1)JavaScript是单线程,一个时间段内,JavaScript只能干一件事情。任务队列分为同步任务和异步任务。
(2)异步任务类型:setTimeout和setInterval、DOM事件、ES6中的Promise

JavaScript异步加载的方式

(1)动态脚本加载;
(2)defer
(3)async

JavaScript的typeof返回哪些数据类型?

Object number function boolean underfind、String;
【拓展】
比较混淆的是:介绍JavaScript的基本数据类型
答案为:Undefined、Null、Boolean、Number、String、Symbol(创建后独一无二且不可变的数据类型

JavaScript中的事件委托是什么

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

为什么要使用JavaScript的事件委托

为了减少代码的DOM操作,提高程序性能。更多详情

JavaScript中的闭包

(1)有权访问另一个函数作用域内变量的函数都是闭包。
(2)闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样。
(3)更多

window.onload和DOMContentLoaded的区别是

window.addEventListener('load',function(){
    //页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener('DOMContentLoaded',function(){
    //DOM渲染完即可执行,此时图片、视频还可能没有加载完
})

用JavaScript创建10个标签,点击的时候弹出来对应的序号

var i;
for (i = 0; i < 10; i++) {
  (function (i) {
    var a = document.createElement('a');
    a.innerHTML = i + '<br>';
    a.addEventListener('click', function (e) {
      e.preventDefault();
      alert(i);
    });
    document.body.appendChild(a);
  })(i)
}

实现数组的随机排序

Array.prototype.shuffle = function () {
  var input = this;
  for (var i = input.length - 1; i >= 0; i--) {
    var randomIndex = Math.floor(Math.random() * (i + 1));
    var itemAtIndex = input[randomIndex];
    input[randomIndex] = input[i];
    input[i] = itemAtIndex;
  }
  return input;
}
var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
tempArray.shuffle();
console.log(tempArray);

JavaScript中有哪些内置函数,与内置对象的区别是什么

(1)内置函数: 是浏览器内核自带的,不用任何函数库引入就可以直接使用的函数,如常规函数(alert等)、数组函数(reverse等)、日期函数(getDate等)、数学函数(floor等)、字符串函数(length等);
(2)内置对象: 是浏览器本身自带的,内置对象中往往包含了内置函数。内置对象有Object、Array、Boolean、Number、String、Function、Date、RegExp等。

JavaScript变量按照存储方式区分为哪些类型,并描述其特点

//值类型
//变量的交换,按值访问,操作的是他们实际保存的值。
//等于在一个新的地方按照新的标准开了一个空间(栈内存),
//这样a的值对b的值没有任何影响
var a = 100;
var b = a;
b = 200;
console.log("a:" + a + ",b:" + b);
//引用类型
//变量的交换,当查询时,我们需要先从栈中读取内存地址,
//然后再顺藤摸瓜地找到保存在堆内存中的值;
//发现当复制的是对象,那么obj1和obj2两个对象被串联起来了,
//obj1变量里的属性被改变时候,obj2的属性也被修改。
var obj1 = {
  x: 100
};
var obj2 = obj1;
obj2.x = 200;
console.log("obj1:" + obj1.x + ",obj2:" + obj2.x);

如何理解JSON

其实JSON只不过是一个JavaScript对象而已。stringify()是把对象变成字符串;parse()是把字符串变成对象。

JavaScript中&&和||

(1)只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值;
(2)只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。
(3)且在js逻辑运算中,0、”“、null、false、undefined、NaN都会判为false,其他都为true。
(4)只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值;
(5)只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值。

描述new一个对象的过程

(1)创建空对象:var obj = {};
(2)设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象:obj.proto = ClassA.prototype;
(3)使用新对象调用函数,函数中的this被指向新实例对象:ClassA.call(obj); //{}.构造函数();
(4)将初始化完毕的新对象地址,保存到等号左边的变量中
【注意】
若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象;若返回值是引用类型的值,则实际返回值为这个引用类型。

什么是构造函数

(1)构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。
(2)注意事项:

  • 默认函数首字母大写;
  • 构造函数并没有显示返回任何东西。new 操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型;
  • 也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回。如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回;
  • 因为构造函数也是函数,所以可以直接被调用,但是它的返回值为undefine,此时构造函数里面的this对象等于全局this对象。this.name其实就是创建一个全局的变量name。在严格模式下,当你补通过new 调用Person构造函数会出现错误;

函数声明和函数表达式的区别

//成功
fn()
function fn() {
  console.log("函数声明,全局");
}
//报错
fn1()
var fun1 = function () {
  console.log("函数表达式,局部")
}

说一下对变量提升的理解

(1)顾名思义,就是把下面的东西提到上面。在JS中,就是把定义在后面的东东(变量或函数)提升到前面中定义。

var v = 'Hello World';
(function () {
  alert(v); //undefined
  var v = 'I love you';
})()

(2)根据上面变量提升原件以及js的作用域(块级作用域)的分析,得知 上面代码真正变成如下:

var v = 'Hello World';
(function () {
  var v;
  alert(v);
  v = 'I love you';
})()

(3)在我们写js code 的时候,我们有2中写法,一种是函数表达式,另外一种是函数声明方式。我们需要重点注意的是,只有函数声明形式才能被提升。

//成功
function myTest() {
  foo();
  function foo() {
    alert("我来自 foo");
  }
}
myTest();
//失败
function myTest() {
  foo();
  var foo = function foo() {
    alert("我来自 foo");
  }
}
myTest();

说一下this几种不同的使用场景

(1)作为构造函数执行,如果函数创建的目的是使用new来调用,并产生一个对象,那么此函数被称为构造器函数;

var Niu = function (string) {
  this.name = string;
};

(2)作为对象属性执行,对象成员方法中的this是对象本身,此时跟其他语言是一致的,但是也有差异,JavaScript中的this到对象的绑定发生在函数调用的时候;

var myObj = {
  value: 0,
  increment: function (inc) {
    this.value += typeof inc === 'number' ? inc : 1;
  }
};
myObj.increment(); //1
myObj.increment(2); //3

(3)作为普通函数执行,以普通方式定义的函数中的this:会被自动绑定到全局对象;

var value = 232;
function toStr() {  
  console.log(this.value);
}

(4)对象方法中闭包函数的this

//在以普通方式定义的函数中的this会被自动绑定到全局对象上
//大家应该可以看出闭包函数定义也与普通方式无异因此他也会被绑定到全局对象上
value = 10;
var closureThis = {
  value: 0,
  acc: function () {
    var helper = function () {
      this.value += 2;
      console.log("this.value : %d", this.value);
    }
    helper();
  }
};
closureThis.acc(); //12
closureThis.acc(); //14

var closureThat = {
  value: 0,
  acc: function () {
    that = this;
    var helper = function () {
      that.value += 2;
      console.log("that.value : %d", that.value);
    }
    helper();
  }
};
closureThat.acc(); // 2
closureThat.acc(); // 4

(5)apply函数的参数this,apply方法允许我们选择一个this值作为第一个参数传递,第二个参数是一个数组,表明可以传递多个参数。

如何理解JavaScript作用域

函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。

function foo() {
  var x = 1;
  return function () {
    alert(x);
  }
};
var bar = foo();
bar(); // 1
var x = 2;
bar(); // 1

什么是自由变量

自由变量就是当前作用域没有定义的变量,即“自由变量”

var a = 100;
function F1() {
  var b = 200;
  function F2() {
    var c = 300;
    //a是自由变量
    console.log(a);
    //b是自由变量
    console.log(b);
    console.log(c);
  }
  F2();
}
F1();

this的特点

this要在执行时才能确认值,定义时无法确认。

同步和异步的区别是什么?分别举一个同步和异步的例

同步会阻塞代码执行,而异步不会;alert是同步,setTimeout是异步。

一个关于setTimeout的笔试题

//输出结果13542
console.log(1);
setTimeout(function () {
  console.log(2);
}, 100);
console.log(3);
setTimeout(function () {
  console.log(4);
}, 99);
console.log(5)

何时需要异步

在可能发生等待的情况、等待过程中不能像alert一样阻塞程序运行、因此所有的等待的情况都需要异步

JavaScript中的异步和单线程

(1)其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
(2)js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。

使用JavaScript获取当天的日期

function formatDate(dt) {
  if (!dt) {
    dt = new Date();
  }
  var year = dt.getFullYear();
  var month = dt.getMonth() + 1;
  var date = dt.getDate();
  if (month < 10) {
    month = "0" + month;
  }
  if (date < 10) {
    date = "0" + date;
  }
  return year + "-" + month + "-" + date;
}
var dt = new Date();
var formatDate = formatDate(dt);
console.log(formatDate);

获取随机数,要求是长度一致的字符串

var random = (Math.random() * 10 + "0000000000").slice(0, 10);
console.log(random);

写一个能遍历对象和数组的通用forEach函数

function forEach(obj, fn) {
  if (obj instanceof Array) {
    //准确判断是不是数组
    obj.forEach(function (item, index) {
      fn(index, item);
    })
  } else {
    //不是数组就是对象
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        fn(key, obj[key]);
      }
    }
  }
}
//检测数组
var arr = [1, 2, 3];
//这里顺序换了,为了和对象的遍历格式一致
forEach(arr, function (index, item) {
  console.log(index, item);
});
//检测对象
var obj = {
  x: 100,
  y: 200
};
forEach(obj, function (key, value) {
  console.log(key, value);
})

DOM操作的常用API有哪些

获取DOM节点,以及节点的property和Attribute;获取父节点和子节点;新增节点和删除节点

DOM节点的attr和property有何区别

property只是一个JavaScript对象的属性的修改;Attribute是对html标签属性的修改

DOM的本质

浏览器把拿到的HTML代码,结构化一个浏览器能识别并且js可以操作的一个模型而已。

手动编写一个ajax,不依赖第三方库

var xmlHttp = new XMLHttpRequest() || new ActiveXObject("Msxml2.XMLHTTP");
xmlHttp.open("提交方式", "提交地址", true);
xmlHttp.onreadystatechange = function () {
  if (xmlHttp.readyState == 4) {
    infoDiv.innerHTML = xmlHttp.responseText;
  }
}
xmlHttp.send();

更多ajax知识,请点击这里

什么是跨域

(1)浏览器有同源策略,不允许ajax访问其他域接口。跨域条件(协议、域名、端口)有一个不同就算是跨域。
(2)可以跨域的三个标签(<img src=xxx>、<link href=xxx>、<script src=xxx>)。三个标签的应用场景(<img>用于打点统计,统计网站可能是其他域、<script>可以使用CDN,CDN的也是其他域、<script>可以用于JSOP)。
(3)跨域注意的事项:所有的跨域请求都必须经过信息提供方允许、如果未经过允许即可获取,那是浏览器同源策略出现漏洞。

跨域的几种实现方式

(1)通过jsonp跨域:在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
(2)通过修改document.domain来跨子域:
(3)使用window.name来进行跨域:
(4)使用HTML5中新引进的window.postMessage方法来跨域传送数据:
(5)更详细的内容

JavaScript实现字符串反转

(1)第一种方法

var str = "abcdef";
console.log(str.split("").reverse().join(""));

(2)第二种方法

var str = "abcdef";
var i = str.length - 1;
for (var x = i; x >= 0; x--) {
  document.write(str.charAt(x));
}

(3)第三种方法

function reverse(str) {
  if (str.length == 0) return null;
  var i = str.length;
  var dstr = "";
  while (--i >= 0) {
    dstr += str.charAt(i);
  }
  return dstr;
}
var str = "abcdef";
str = reverse(str);
document.write(str);

ajax中的get和post两种请求方式的异同

(1) get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到;post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
(2)对于get方式,服务器端用Request.QueryString获取变量的值;对于post方式,服务器端用Request.Form获取提交的数据。两种方式的参数都可以用Request来获得。
(3)get传送的数据量较小,不能大于2KB;post传送的数据量较大,一般被默认为不受限制,但理论上,因服务器的不同而异。
(4)get安全性非常低,post安全性较高。
(5)

跟是一样的,也就是说,method为get时action页面后边带的参数列表会被忽视;而跟是不一样的。

找出数字数组中最大的元素

var a = [111, 2, 6, 4, 22, 5, 99, 3];
console.log(Math.max.apply(null, a));

实现该语法的功能:var a = (5).plus(3).minus(6)

Number.prototype.plus = function (a) {
  return this.valueOf() + a;
}
Number.prototype.minus = function (a) {
  return this.valueOf() - a;
}
var a = (5).plus(3).minus(6);
console.log(a);

有一个大数组,var a = [‘1’,’2’,’3’,…];a数组的长度是100,内容填充随机整数的字符串,请先构造此数组a,然后设计一个算法,将其内容去重。

function Random(n) {
  var arr = [];
  for (var i = 0; i < n; i++) {
    arr[i] = parseInt(Math.random() * 100);
  }
  console.log(arr);
  return arr;
}
//使用indexof  这里也可以使用arr2的indexOf
function DeleRepeat1(arr) {
  var arr2 = [];
  var len = arr.length;
  for (var i = 0; i < len; i++) {
    if (arr.indexOf(arr[i]) == i) {
      arr2.push(arr[i])
    }
  }
  return arr2;
}
var arr = Random(100);
console.log(DeleRepeat1(arr));

返回只包含数字类型的数组,比如:’abc234koi45jodjnvi789’ –> [234,45,789]

var str = 'abc234koi45jodjnvi789';
var reg = /\d+/g;
console.log(str.match(reg));

slice数组的浅复制:向数组后添加一个元素,返回原数组不变,返回新数组。

function append(arr, item) {
  var arr2 = [];
  arr2 = arr.slice(0);
  arr2.push(item);
  return arr2;
}
var arr = [1, 2, 3, 4];
console.log(append(arr, 10)); //[1, 2, 3, 4,10]
console.log(arr) //[1, 2, 3, 4]

【解析】
如果定义 var arr2=arr的话,那么arr2指向了arr的引用。那么arr2改变也会伴随着arr改变。 而slice浅复制:arr数组并返回了一个新数组,与原数组没有关系了。

实现数组内容增添的函数

function insert(arr, item, index) {
  var arr2 = arr.concat();
  arr2.splice(index, 0, item);
  return arr2;
}
//[1,2,'z',3,4]
console.log(insert([1, 2, 3, 4], 'z', 2));

什么是同步?什么是异步

(1)同步: 脚本会停留并等待服务器发送回复然后再继续
(2)异步: 脚本允许页面继续其进程并处理可能的回复

documen.write和 innerHTML的区别是什么

document.write重绘整个页面;innerHTML可以重绘页面的一部分。

介绍JavaScript有哪些内置对象

(1)数据封装类对象: Object、Array、Boolean、Number 和 String
(2)其他对象: Function、Arguments、Math、Date、RegExp、Error

JavaScript的作用域链式什么

全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链。

什么是window对象? 什么是document对象

(1)window对象是指浏览器打开的窗口。
(2)document对象是Documentd对象(HTML 文档对象)的一个只读引用,window对象的一个属性。

null,undefined 的区别

(1)null:表示一个对象是“没有值”的值,也就是值为“空”;
(2)undefined:表示一个变量声明了没有初始化(赋值);
【注意】
在验证null时,一定要使用 === ,因为 == 无法分别 null 和undefined

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

(1)闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
(2)闭包的特性:

  • 函数内再嵌套函数
  • 内部函数可以引用外层的参数和变量
  • 参数和变量不会被垃圾回收机制回收

如何判断一个对象是否属于某个类

if(a instanceof Person){
  alert('yes');
}

new操作符具体干了什么呢

(1)创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型;
(2)属性和方法被加入到 this 引用的对象中;
(3)新创建的对象由 this 所引用,并且最后隐式的返回 this 。
【栗子】

var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是

(1)javaScript中hasOwnProperty函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
(2)使用方法:

  • object.hasOwnProperty(proName)
  • 其中参数object是必选项。一个对象的实例。
  • proName是必选项。一个属性名称的字符串值。
  • 如果 object 具有指定名称的属性,那么JavaScript中hasOwnProperty函数方法返回 true,反之则返回 false。

对JSON 的了解

(1)JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小,比如:{"age":"12", "name":"back"}
(2)JSON字符串转换为JSON对象:

var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);

(3)JSON对象转换为JSON字符串:

var last=obj.toJSONString();
var last=JSON.stringify(obj);

js延迟加载的方式有哪些

defer和async、动态创建DOM方式(用得最多)、按需异步载入js。

异步加载JS的方式有哪些

(1)defer,只支持IE;
(2)async:
(3)创建script,插入到DOM中,加载完毕后callBack

DOM操作——怎样添加、移除、移动、复制、创建和查找节点

(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
(3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性

JQuery一个对象可以同时绑定多个事件,这是如何实现的

(1)多个事件同一个函数:
$("div").on("click mouseover", function(){});

(2)多个事件不同函数

$("div").on({
  click: function(){},
  mouseover: function(){}
})

JavaScript中如何检测一个变量是一个String类型或者Array类型?请写出函数实现

var str = "hello";
if (typeof (str) === "string") {
  console.log("str is String");
}
var arr = [];
if (arr instanceof Array) {
  console.log("arr is Array");
}

prototype和_proto_的关系是什么

prototype和__proto__都指向原型对象,任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象,同样任意一个构造函数实例化的对象,都有一个__proto__属性(__proto__并非标准属性,ECMA-262第5版将该属性或指针称为[[Prototype]],可通过Object.getPrototypeOf()标准方法访问该属性),指向构造函数的原型对象。

ajax是什么?同步和异步的区别?

(1)ajax是一种无需重新加载整个网页的情况下,能够更新部分网页的技术。
(2)同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

事件委托是什么,举个例子

它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。那这是什么意思呢?网上的各位大牛们讲事件委托基本上都用了同一个例子,就是取快递来解释这个现象。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      ul li {
        display: block;
        height: 50px;
        width: 100px;
        text-align: center;
        line-height: 50px;
        background-color: #888;
        margin-bottom: 8px;
      }
    </style>
  </head>
  <body>
    <ul id="Inul">
      <li>111</li>
      <li>222</li>
      <li>333</li>
      <li>444</li>
      <li>555</li>
    </ul>
    <script>
      //不使用事件委托
      /*window.onload = function(){
      	var oUL = document.getElementById("Inul");
      	var aLi = oUL.getElementsByTagName("li");
      	for(var i=0;i<aLi.length;i++){
      	  aLi[i].onclick = function(){
      	    alert(123);
      	  }
      	}
      }*/
      //使用事件委托
      window.onload = function () {
        var oUL = document.getElementById("Inul");
        oUL.onclick = function () {
          alert(123);
        }
      }
    </script>
  </body>
</html>

判断字符串是否是这样组成的:第一个必须是字母,后面可以是字母、数字和下划线,总长度为5-20

var str = "adfadfa2343243e";
var reg = /^[a-zA-Z]\w{4,19}$/g;
console.log(reg.test(str));

编写一个方法,去掉一个数组的重复元素

Array.prototype.methods = function () {
  //定义一个临时数组
  var arr = [];
  //循环遍历当前数组
  for (var i = 0; i < this.length; i++) {
    //判断当前数组下标为i的元素是否已经保存到临时数组
    //如果已经保存,则跳过,佛则将此元素保存到临时数组中
    //没有保存则说明查不到,返回-1
    if (arr.indexOf(this[i]) === -1) {
      arr.push(this[i]);
    }
  }
  return arr;
}
var arry = [2, 3, 5, 6, 6, 7];
console.log(arry.methods());

如何实现JavaScript模块化,请至少使用两种方法

(1)立即执行函数(IIFE): 可以达到不暴露私有成员的目的。
(2)对象写法: 可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

编写一个JavaScript函数,该函数有一个n(数字类型),其返回值是一个数组,该数组内是n个随机且不重复的整数,且整数取值范围是[2,32]。

//定义一个函数用来返回整数的取值范围
function getRand(a, b) {
  //ceil向上取整
  var rand = Math.ceil(Math.random() * (b - a) + a);
  return rand;
}
//定义一个函数用来过滤重复的整数
function checkArrIn(rand, arry) {
  if (arry.indexOf(rand) != -1) {
    return true;
  }
  return false;
}
//定义一个执行函数
function fn(n, min, max) {
  var arr = [];
  //判断n是不是一个数字,包含字符串类型的数字
  var isNum = !isNaN(Number(n));
  //判断n的取值是否符合要求
  var isRandOk = (n >= min && n <= max && n <= (max - min)) ? true : false;
  if (n && isRandOk && isNum) {
    for (var i = 0; i < n; i++) {
      var rand = getRand(min, max);
      //假如checkArrIn()返回true,说明有相同元素,那么就重新执行一次
      if (checkArrIn(rand, arr)) {
        i--;
      } else {
        arr.push(rand);
      }
    }
  }
  console.log(arr);
}
//调用函数
fn(10, 2, 32);

[1,2,3].map(parseInt) 的结果是多少?

(1)答案是:[1, NaN, NaN]
(2)原因:parseInt接收的是两个参数,map传递的是3个参数
(3)具体解析

构造函数调用方法this指向问题

构造函数其实就是用new操作符调用的一个函数,用new时构造函数内部的this会指向新的实例。

getElementsByClassName()和querySelector()的区别是什么

(1)getElementsByTagName方法返回一个对象数组(准确的说是HTMLCollection集合),返回元素的顺序是它们在文档中的顺序,传递给 getElementsByTagName() 方法的字符串可以不区分大小写;DOM还提供了getElementsByClassName方法来获取指定class名的元素,该方法返回文档中所有指定类名的元素集合,作为 NodeList 对象。NodeList 对象代表一个有顺序的节点列表。
(2)querySelector() 方法返回匹配指定 CSS 选择器元素的第一个子元素 。 该方法只返回匹配指定选择器的第一个元素。如果要返回所有匹配元素,需要使用 querySelectorAll() 方法替代。由于querySelector是按css规范来实现的,所以它传入的字符串中第一个字符不能是数字。
(3)总结

  • query选择符选出来的元素及元素数组是静态的,而getElement这种方法选出的元素是动态的。静态的就是说选出的所有元素的数组,不会随着文档操作而改变。
  • 在使用的时候getElement这种方法性能比较好,query选择符则比较方便。

闭包是什么,什么时候闭包会消除?

因为作用域链,外部不能访问内部的变量和方法,这时我们就需要通过闭包,返回内部的方法和变量给外部,从而就形成了一个闭包。

JavaScript是一门具有自动垃圾回收机制的编程语言,主要有两种方式:
(1)标记清除(最常用)

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何标记方式)。然后,
它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被
视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存
清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

(2)引用计数

引用计数(reference counting)的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个
引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的
引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。
当这个值的引用次数变成0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回
收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

导致问题:会导致循环引用的变量和函数无法回收。
解决:将用完的函数或者变量置为null。

怎么理解js是单线程的

主要说一下异步以及事件循环机制,还有事件队列中的宏任务、微任务。

  • macrotask: 主代码块,setTimeout,setInterval、setImmediate等。
  • microtask: process.nextTick(相当于node.js版的setTimeout),Promise 。process.nextTick的优先级高于Promise。更具体的请点击这里

原生ajax的请求过程

创建全平台兼容的XMLHttpRequest对象:

function getXHR(){
  var xhr = null;
  if(window.XMLHttpRequest) {// 兼容 IE7+, Firefox, Chrome, Opera, Safari
    xhr = new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    try {
      xhr = new ActiveXObject("Msxml2.XMLHTTP");// 即MSXML3
    } catch (e) {
      try {
        // 兼容 IE6, IE5,很老的api,虽然浏览器支持,功能可能不完善,故不建议使用
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) {
        alert("您的浏览器暂不支持Ajax!");
      }
    }
  }
  return xhr;
}

Ajax请求数据的过程:

var xhr = getXHR();
xhr.open('GET', url/file,true);  //设置请求方式,url,以及是否异步
xhr.onreadystatechange = function() {   //设置回调监听函数
   if(xhr.readyState==4){
        if(xhr.status==200){
            var data=xhr.responseText;
             console.log(data);
   }
};
xhr.onerror = function() {
  console.log("Oh, error");
};
xhr.send();  //发送请求

怎么用原生js实现一个轮播图,以及滚动滑动

实现原理: 将一些图片在一行中平铺,然后计算偏移量再利用定时器实现定时轮播。

(1)建立HTML基本布局

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>轮播图</title>
</head>
<body>
  <div class="container">
    <div class="wrap" style="left:-600px;">
      <img src="./img/5.jpg" alt="">
      <img src="./img/1.jpg" alt="">
      <img src="./img/2.jpg" alt="">
      <img src="./img/1.jpg" alt="">
      <img src="./img/4.jpg" alt="">
      <img src="./img/5.jpg" alt="">
      <img src="./img/1.jpg" alt="">
    </div>
    <div class="buttons">
      <span>1</span>
      <span>2</span>
      <span>3</span>
      <span>4</span>
      <span>5</span>
    </div>
    <a href="javascript:;" class="arrow arrow_left">&lt;</a>
    <a href="javascript:;" class="arrow arrow_right">&gt;</a>
  </div>
</body>
</html>

(2)编写CSS样式代码:

* {
  margin:0;
  padding:0;
}
a {
  text-decoration: none;
}
/* 
  我们为了让图片只在container中,
  所以需要限定其宽度和高度并且使用overflow:hidden;
  将其余的图片隐藏起来,并且我们希望wrap相对于container左右移动,
  所以设置为relative
*/
.container {
  position: relative;
  width: 600px;
  height: 400px;
  margin:100px auto 0 auto;
  box-shadow: 0 0 5px green;
  overflow: hidden;
} 
/* 
  我们设置wrap是绝对定位的,所以我们就可以通过控制Left和Right
  来控制图片的移动了。设置z-index:1;以对后面将要放置的buttons作为参考。 
  因为共有七张图片,所以width为4200px(每张图片我们设置为600X400),
  我们只需让图片左浮动即可实现占满一排了。
*/
.wrap {
  position: absolute;
  width: 4200px;
  height: 400px;
  z-index: 1;
}
.container .wrap img {
  float: left;
  width: 600px;
  height: 400px;
}
.container .buttons {
  position: absolute;
  right: 50px;
  bottom: 20px;
  width: 145px;
  height: 10px;
  z-index: 2;
}
/* 
  然后将buttons下面的span做一个简单的修饰,
  并且给和图片对应的span设置一个on类
*/
.container .buttons span {
  margin-left: 5px;
  display: inline-block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: green;
  text-align: center;
  color:white;
  cursor: pointer;
}
.container .buttons span.on{
  background-color: red;
}
.container .arrow {
  position: absolute;
  top: 35%;
  color: green;
  padding:0px 14px;
  border-radius: 50%;
  font-size: 50px;
  z-index: 2;
  text-align: center;
  display: none;
}
.container .arrow_left {
  left: 10px;
}
.container .arrow_right {
  right: 10px;
}
.container:hover .arrow {
  display: block;
}
.container .arrow:hover {
  background-color: rgba(0,0,0,0.2);
}

(3)编写脚本文件

var wrap = document.querySelector(".wrap");
var next = document.querySelector(".arrow_right");
var prev = document.querySelector(".arrow_left");
var container = document.querySelector(".container");
var index = 0;
var dots = document.getElementsByTagName("span");
// 手动轮播
next.onclick = function () {
  next_pic();
}
prev.onclick = function () {
  prev_pic();
}
function next_pic () {
  var newLeft;
  if(wrap.style.left === "-3600px"){
    newLeft = -1200;
  }else{
    newLeft = parseInt(wrap.style.left)-600;
  }
  wrap.style.left = newLeft + "px";

  index++;
  if(index > 4){
    index = 0;
  }
  showCurrentDot();
}
function prev_pic () {
  var newLeft;
  if(wrap.style.left === "0px"){
    newLeft = -2400;
  }else{
    newLeft = parseInt(wrap.style.left)+600;
  }
  wrap.style.left = newLeft + "px";

  index--;
  if(index < 0){
    index = 4;
  }
  showCurrentDot();
}
// 自动轮播
var timer = null;
function autoPlay () {
  timer = setInterval(function () {
    next_pic();
  },3000);
}
autoPlay();
container.onmouseenter = function () {
  clearInterval(timer);
}
container.onmouseleave = function () {
  autoPlay();    
}
// 小圆点动画
function showCurrentDot () {
  for(var i = 0, len = dots.length; i < len; i++) {
    dots[i].className = "";
  }
  dots[index].className = "on";
}
showCurrentDot();
// 小圆点点击事件
for (var i = 0, len = dots.length; i < len; i++){
  (function(i){
    dots[i].onclick = function () {
      var dis = index - i;
      if(index == 4 && parseInt(wrap.style.left)!==-3000){
        dis = dis - 5;     
      }
      // 和使用prev和next相同,在最开始的照片5和最终的照片1在使用时会出现问题,
      // 导致符号和位数的出错,做相应地处理即可
      if(index == 0 && parseInt(wrap.style.left)!== -600){
        dis = 5 + dis;
      }
      wrap.style.left = (parseInt(wrap.style.left) +  dis * 600)+"px";
      index = i;
      showCurrentDot();
    }
  })(i);            
}

图片懒加载怎么实现

  • 场景: 一个页面中很多图片,但是首屏只出现几张,这时如果一次性把图片都加载出来会影响性能。这时可以使用懒加载,页面滚动到可视区在加载。优化首屏加载。
  • 实现: img标签src属性为空,给一个data-xx属性,里面存放图片真实地址,当页面滚动直至此图片出现在可视区域时,用js取到该图片的data-xx的值赋给src。
  • 优点: 页面加载速度快,减轻服务器压力、节约流量,用户体验好。
<!DOCTYPE html>
<html>
  <head>
      <meta charset="utf-8">
      <title></title>
  </head>
  <body>
    <!--事先使用一个小图片替换真正要显示的图片,等真正的图片完全加载后再替换小图片-->
    <img id="img1" src="small.jpg" data-realsrc="readlly.jpg" />
    <script>
      var img1 = document.getElementById('img1');
      img1.src = img1.getAttribute('data-realsrc');
    </script>
  </body>
</html>

window.onload和DOMContentLoaded的区别?

window.addEventListener('load',function(){
  //页面的全部资源加载完才会执行,包括图片、视频等
})

document.addEventListener('DOMContentLoaded',function(){
  //DOM渲染完即可执行,此时图片、视频还可能没有加载完
})

高阶函数是什么,怎么去写一个高阶函数

  • 高阶函数: 参数值为函数或者返回值为函数。例如map,reduce,filter,sort方法就是高阶函数。
  • 编写高阶函数,就是让函数的参数能够接收别的函数。

浅谈websocket

一、前言

websocket是HTML5规范中的一部分,为web应用程序客户端和服务端之间提供了一种全双工通信机制(即双方可同时向对方发送消息),由于websocket借鉴了socket这种**,因此我们先一起来看看什么是socket。

二、Socket简介

socket又称为“套接字”,应用程序一般通过“套接字”向网络发出请求或者应答网络请求,作为Unix的进程通信机制,socket可以实现应用程序间的网络通信,同时也能够使用TCP/IP协议或者UDP协议。
image

三、TCP/IP协议

这两种协议是目前应用最为广泛的协议,是构成Internet国际互联网协议最为基础的协议,下面来简单看看这两种协议。
1、TCP协议: 面向连接的、可靠的、基于字节流的传输层通信协议,负责数据的可靠性传输问题。
2、IP协议: 用于报文交换网络的一种面向数据的协议,主要负责给每台网络设备一个网络地址,保证数据传输到正确的目的地。

四、UDP协议

它是一种无连接、不可靠、基于报文的传输层协议,优点是发送后不用管,速度比TCP块。

五、WebSocket简介

产生背景:
在B/S结构的软件项目中,有时候客户端需要实时的获得服务器消息,但是为了可以简化Web服务器,减少服务器的负担,加快响应速度(因为服务器不需要与客户端长时间建立一个通信链接),默认HTTP协议只支持请求响应模式,因此不容易直接完成实时的消息推送功能,如聊天室、后台信息提示、实时更新数据等功能,于是就出现了websocket这门技术。

再简介一次:
websocket是HTML5开始提供的一种浏览器与服务器间进行的全双工通讯网路技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。唯一有些不足的是少部分浏览器不支持,并且有些浏览器支持的程度与方式有区别。

特点:
事件驱动、异步、使用ws或者wss协议的客户端socket、能够实现真正意义上的推送功能。

六、WebSocket客户端

websocket允许通过JavaScript建立与远程服务器的连接,从而实现客户端与服务器间的双向通信。
在websocket中有两个方法:
1、send() 向远程服务器发送数据;
2、close() 关闭该websocket链接。

websocket同时定义了四个监听函数:
1、onopen 当网络连接建立时触发该事件;
2、onerror 当网络发生错误时触发该事件;
3、onclose 当websocket被关闭时触发该事件;
4、onmessage 当websocket接收到服务器发来消息时触发的事件,也是通信中最重要的一个监听事件。

websocket还定义了一个readyState属性,这个属性可以返回websocket所处的状态:
1、CONNECTING(0) websocket正尝试与服务器建立连接;
2、OPEN(1) websocket与服务器已经建立连接;
3、CLOSING(2) websocket正在关闭与服务器的连接;
4、CLOSED(3) websocket已经关闭了与服务器的连接;

注意:
websocket的url开头是ws,如果需要ssl加密可以使用wss,当我们调用websocket的构造方法构建一个websocket对象(new WebSocket(url))的之后,就可以进行即时通信了。点击查看源代码

七、WebSocket的通信原理和机制

既然是基于浏览器的web技术,那么它的通信肯定少不了HTTP,websocket本身虽然也是一种新的应用层协议,但是它也不能够脱离HTTP而单独存在。

具体来说,我们在客户端构建一个websocket实例,并且为它绑定一个需要连接到的服务器地址,当客户端连接到服务端的时候会向服务端发送一个类似下面的HTTP报文:
image
可以看到,这是一个HTTP的get请求报文,注意该报文中有一个upgrade首部,它的作用是告诉服务端需要将通信协议切换到websocket。如果服务端支持websocket协议,那么它就会将自己的通信协议切换到websocket,同时发给客户端类似于以下的一个响应报文头:
image
返回的状态码为101,表示同意客户端协议转换请求,并将它转换为websocket协议。以上过程都是利用HTTP通信完成的,称之为websocket协议握手。

经过这握手之后,客户端和服务端就建立了websocket连接,以后的通信走的都是websocket协议了。所以总结为:websocket握手需要借助于HTTP协议,建立连接后通信过程使用websocket协议。

同时需要了解的是,该websocket连接还是基于我们刚才发起HTTP连接的那个TCP连接,一旦建立连接之后,我们就可以进行数据传输了。在websocket中,提供两种数据传输:文本数据和二进制数据。

基于以上分析,我们可以看到,websocket能够提供低延迟,高性能的客户端和服务端的双向数据通信。它颠覆了之前web开发的请求处理响应模式,并且提供了一种真正意义上的客户端请求,服务器推送数据的模式,特别适合实时数据交互应用开发。

八、在websocket之前,我们在web上要得到实时数据交互都采用了哪些方式?

(1)定期轮询方式:
客户端按照某个时间间隔不断地向服务端发送请求,请求服务端的最新数据然后更新客户端显示。这种方式实际上浪费了大量的流量,并且对服务端造成了很大压力。
(2)基于长轮询的服务端推送技术:
具体来讲,就是客户端首先给服务端发送一个请求,服务端收到该请求之后如果数据没有更新则并不立即返回,服务端阻塞请求的返回,直到数据发生了更新或者发生了连接超时,服务端返回数据之后客户端再次发送同样的请求。
(3)基于流式数据传输的长连接:
通常的做法是在页面中嵌入一个隐藏的iframe,然后让这个iframe的src属性指向我们请求的一个服务端地址,并且为了数据更新,我们将页面上数据更新操作封装为一个js函数,将函数名当做参数传递到这个地址当中,服务端收到请求后解析地址取出参数(客户端js函数调用名),每当有数据更新的时候,返回对客户端函数的调用,并且将要跟新的数据以js函数的参数填入到返回内容当中。

前端工程师面试指南——算法篇

判断一个单词是否是回文字符

概念: 回文是指把相同的词汇或句子,在下文中调换位置或颠倒过来,产生首尾回环的情趣,叫做回文,也叫回环。比如 mamam、redivider等。

很多人拿到这样的题目非常容易想到用for 将字符串颠倒字母顺序然后匹配就行了。其实重要的考察的就是对于reverse的实现。其实我们可以利用现成的函数,将字符串转换成数组,这个思路很重要,我们可以拥有更多的自由度去进行字符串的一些操作。

function checkPalindrom(str) {
  if(str == str.split('').reverse().join('')) {
    console.log("true")
  }
}
checkPalindrom("mamam")

去掉一组整型数组重复的值

这道问题出现在诸多的前端面试题中,主要考察个人对Object的使用,利用key来进行筛选。

function unique(arr) {
  let hashTable = {};
  let data = [];
  for (let i = 0, l = arr.length; i < l; i++) {
    if (!hashTable[arr[i]]) {
      hashTable[arr[i]] = true;
      data.push(arr[i]);
    }
  }
  console.log(data);
}
let arrs = [4, 5, 2, 4, 1];
unique(arrs);

统计一个字符串出现最多的字母

function findMaxDuplicateChar(str) {
  if (str.length <= 1) {
    return str;
  }
  let charObj = {};
  for (let i = 0; i < str.length; i++) {
    // charAt() 方法可返回指定位置的字符。
    // console.log(str.charAt(i))
    if (!charObj[str.charAt(i)]) {
      charObj[str.charAt(i)] = 1;
    } else {
      charObj[str.charAt(i)] += 1;
    }
  }
  // {r:1,e:1,q:1,a:3,d:1,f:1}
  // console.log(charObj)
  let maxChar = "";
  let maxValue = 1;
  for (var k in charObj) {
    if (charObj[k] >= maxValue) {
      maxChar = k;
      maxValue = charObj[k];
    }
  }
  console.log(maxChar);
}
findMaxDuplicateChar("reqaaadf");

排序算法

如果抽到算法题目的话,应该大多都是比较开放的题目,不限定算法的实现,但是一定要求掌握其中的几种,所以冒泡排序,这种较为基础并且便于理解记忆的算法一定需要熟记于心。冒泡排序算法就是依次比较大小,小的跟大的进行位置上的交换。

function bubbleSort(arr) {
  for (let i = 0, l = arr.length; i < l - 1; i++) {
    for (let j = i + 1; j < l; j++) {
      if (arr[i] > arr[j]) {
        let tem = arr[i];
        arr[i] = arr[j];
        arr[j] = tem;
      }
    }
  }
  console.log(arr);
}
let arrs = [3, 55, 12];
bubbleSort(arrs);


// 推理过程
当i=0
j ==> 1
arr[i] ==> 3
arr[j] ==> 55
也就是arr[0] = 0,arr[1] = 55

当i=1
j ==> 2
arr[i] ==> 55
arr[j] ==> 12
tem ==> arr[i]=55
arr[i] = arr[j] = 12
arr[j] = 55
也就是arr[1] = 12,arr[2] = 55

除了冒泡排序外,其实还有很多诸如 插入排序,快速排序,希尔排序等。每一种排序算法都有各自的特点。全部掌握也不需要,但是心底一定要熟悉几种算法。

比如快速排序,其效率很高,算法参考某个元素值,将小于它的值,放到左数组中,大于它的值的元素就放到右数组中,然后递归进行上一次左右数组的操作,返回合并的数组就是已经排好顺序的数组了。

function quickSort(arr) {
  //如果数组<=1,则直接返回
  if (arr.length <= 1) {
    return arr;
  }
  var pivotIndex = Math.floor(arr.length / 2);
  //找基准,并把基准从原数组删除
  var pivot = arr.splice(pivotIndex, 1)[0];
  //定义左右数组
  var left = [];
  var right = [];

  //比基准小的放在left,比基准大的放在right
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] <= pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  //递归
  return quickSort(left).concat([pivot], quickSort(right));
}
console.log(quickSort([4, 2, 6, 7, 1]));

下面是比较复杂难懂但是时间复杂度较低的代码,参考文章

// 原地交换函数,而非用临时数组
function swap(array, a, b) {
  [array[a], array[b]] = [array[b], array[a]];
}

// 划分操作函数
function partition(array, left, right) {
  // 用index取中间值而非splice
  const pivot = array[Math.floor((right + left) / 2)];
  let i = left;
  let j = right;
  while (i <= j) {
    while (compare(array[i], pivot) === -1) {
      i++;
    }
    while (compare(array[j], pivot) === 1) {
      j--;
    }
    if (i <= j) {
      swap(array, i, j);
      i++;
      j--;
    }
  }
  return i;
}

// 比较函数
function compare(a, b) {
  if (a === b) {
    return 0;
  }
  return a < b ? -1 : 1;
}

function quick(array, left, right) {
  let index;
  // 第一次:[3,2,1] 0 2
  // 第二次:[1,2,3] 0 1
  // console.log(array,left,right)
  if (array.length > 1) {
    index = partition(array, left, right);
    // 第一次:2
    // 第二次:1
    // console.log(index)
    if (left < index - 1) {
      quick(array, left, index - 1);
    }
    if (index < right) {
      quick(array, index, right);
    }
  }
  // return array;
  console.log(array);
}

function quickSort(array) {
  return quick(array, 0, array.length - 1);
};
let arrs = [3, 2, 1]
quickSort(arrs);

不借助临时变量,进行两个整数的交换

这种问题非常巧妙,需要大家跳出惯有的思维,利用 a , b进行置换。主要是利用 + – 去进行运算,类似 a = a + ( b – a) 实际上等同于最后 的 a = b;

function swap(a, b) {
  b = b - a;
  a = a + b;
  b = a - b;
  return [a, b];
}
console.log(swap(3, 8));

写一个斐波那契数列,长度限定为9

斐波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列主要考察递归的调用。

function getFibonacci(n) {
  var fibarr = [];
  var i = 0;
  while (i < n) {
    if (i <= 1) {
      fibarr.push(i);
    } else {
      fibarr.push(fibarr[i - 1] + fibarr[i - 2])
    }
    i++;
  }
  return fibarr;
}
console.log(getFibonacci(9))

找出正数组的最大差值

这是通过一道题目去测试对于基本的数组的最大值的查找,很明显我们知道,最大差值肯定是一个数组中最大值与最小值的差。

function getMaxProfit(arr) {
  var minPrice = arr[0];
  var maxProfit = 0;
  for (var i = 0; i < arr.length; i++) {
    var currentPrice = arr[i];
    minPrice = Math.min(minPrice, currentPrice);
    var potentialProfit = currentPrice - minPrice;
    maxProfit = Math.max(maxProfit, potentialProfit);
  }
  return maxProfit;
}
// 出现bug
console.log(getMaxProfit([65, 6, 2]));
// 出现bug
console.log(getMaxProfit([65, 6]));
// 正常
console.log(getMaxProfit([65, 6, 99]));

下面是一个暂时没有bug的实现方式

function getMaxProfit(arr) {
  var max = arr[0];
  var min = arr[0];
  var res = 0;
  for (var i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
      max = arr[i];
    }
    if (arr[i] < min) {
      min = arr[i]
    }
    res = max - min;
  }
  return res;
}
console.log(getMaxProfit([65, 6, 2]))
console.log(getMaxProfit([65, 6]))
console.log(getMaxProfit([65, 6, 99]))

随机生成指定长度的字符串

function randomString(n) {
  let str = 'abcdefghijklmnopqrstuvwxyz9876543210';
  let tmp = '';
  let i = 0;
  let l = str.length;
  for (i = 0; i < n; i++) {
    tmp += str.charAt(Math.floor(Math.random() * l));
  }
  return tmp;
}
console.log(randomString(5))

优质文章

1、面试官:阮一峰版的快速排序完全是错的
2、算法学习指南

《JavaScript高级程序设计》读书笔记

前言

《JavaScript高级程序设计》是一直被推崇的前端开发经典书目,在网上也被成为“红宝书”或“红皮书”。而我在断断续续中把这本书过了一遍,然而却发现好像根本就学到多少!从那时我就开始想问题到底出现在哪,后来才发现其中存在的两个问题:1、妄图记住书中的全部信息,搞得自己身心俱疲;2、不加思考的摘抄和记录,稍过几天,马上忘得一干二净。

根据发现的问题,重新用一种偷懒的方式来整理一遍这本书的知识点,同时还关注了《喜马拉雅》同款说书节目,希望能带来一丝好效果,让自己成为一个更加专业的前端工程师,Let's Go!^_^

第 1 章 JavaScript简介

要想全面理解和掌握JavaScript,关键在于弄清楚它的本质、历史和局限性。

JavaScript实现

一个完整的JavaScript实现应该由以下三部分组成:

  • 核心(ECMAScript):这是由欧洲计算机制造商协会制定的JavaScript实现的基础与标准,目的是统一规范JavaScript程序的开发以及这门编程语言的发展。
  • 文档对象模型(DOM)
  • 浏览器对象模型(BOM)

文档对象模型

概念
文档对象模型是针对XML但经过扩展用于HTML的应用程序编程接口。DOM把整个页面映射为一个多层节点结构,HTML或XML页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据。根据我的理解DOM就是:浏览器渲染HTML后出现的一种结构。

作用
通过DOM创建的这个表示文档的树形图,开发人员获得了控制页面内容和结构的主动权。借助DOM提供的API,开发人员可以轻松自如地删除、添加、替换或修改任何节点。

DOM级别

  • 1级:由两个模块组成组成,DOM核心规定如何映射XML文档结构,为了简化对文档中任意部分的访问和操作;DOM HTML模块则在DOM核心的基础上加以扩展,添加了针对HTML的对象和方法。
  • 2级:增加了更多的事件与模块,也给出了众多新类型和新街口的定义。
  • 3级:进一步扩展了DOM,引入了以统一方式加载保存文档的方法。

浏览器对象模型

开发人员使用BOM可以控制浏览器显示的页面以外的部分,从根本上讲,BOM只处理浏览器窗口和框架,但人们习惯上也把所有针对浏览器的JavaScript扩展算作BOM的一部分,下面就是一些这样的扩展

  • 弹出新浏览器窗口的功能
  • 移动、缩放和关闭浏览器窗口的功能
  • 提供浏览器详细信息的navigator对象
  • 提供浏览器所加载页面的详细信息的location对象
  • 提供用户显示器分辨率详细信息的screen对象
  • 对cookies的支持
  • 像XMLHttpRequest和IE的ActiveXObject这样的自定义对象

第 2 章 在HTML中使用JavaScript

在如今移动互联网当道,以及Vue、React和Angular横行的时代,这个章节中的内容变得并不是那么的重要,下面简单说一下稍微要留意的三个点。

延迟脚本

在script元素中设置defer属性,相当于告诉浏览器立即下载,但是延迟执行。意思就是表明脚本在执行时不会影响页面的构造。

异步脚本

在script元素中设置async属性,目的是不让页面等待脚本下载和执行,从而异步加载页面其他内容。

同步调用和异步调用的区别

1、同步调用时一种阻塞式调用,一段代码调用另一段代码时,必须等待这段代码执行结束并返回结果后,代码才能继续执行下去。例如下面的代码

let n1 = 1
let n2 = 2
let n3 = 3
alert(n1)
alert(n2)
alert(n3)

// 运行结果: 1  2  3

2、异步调用是一种非阻塞式调用,一段异步代码还未执行完,可以继续执行下一段代码逻辑,当其他同步代码执行完之后,通过回调返回继续执行相应的逻辑,而不耽误其他代码的执行。例如下面的代码

let n1 = 1
let n2 = 2
let n3 = 3
alert(n1)
setTimeout(function() {
  alert(n2)
}, 2000)
alert(n3)

// 运行结果:1  3  2

当然,关于异步还有另外一个例子,这个栗子也引出了异步调用和回调这两个东东的概念,下面请看代码

function Person() {
  this.think = function(callback) {
    setTimeout(function() {
      console.log('想出来了!')
      callback()
    }, 5000)
  }
  this.answer = function() {
    console.log('我正在思考一个问题^_^')
  }
}
var person = new Person()
person.think(function() {
  console.log('花费5s,得到一个正确的思考')
})
person.answer()

// 我正在思考一个问题^_^
// 想出来了!
// 花费5s,得到一个正确的思考



第 3 章 基本概念

这章的内容表面看挺浅显的,但是一旦要深挖的话却是能挖掘出很多充满奥妙的东西,例如两个值在比较过程中所发生的类型转换,有兴趣的可以来看看这篇文章

也是从该章的内容才意识到:《JavaScript高级程序设计》这本书其实本质上是一本非常好的工具书,在JavaScript这块遇到不太明白的问题,拿出翻一番就好,要是想要一点不漏的把书中全部的知识在段时间内吸收的话,那简直是不可能的事情,因此接下来的笔记我不会像记录流水账一样把书中大部分内容摘抄下来,而是选择一些有代表性的东西说一说,同时也将网上一些比较好的解读分享出来

第 4 章 变量、作用域和内存问题

这一章对我而言,说简单也算是简单,也难也有难的一方面。简单的是对变量以及作用域的理解,难的是内存那一块看得十分的懵逼,接着就上网翻了一番,发现以下几篇认为比较好的文章,自己比较认同,所以我就不做重复的总结工作了,看看别人的就行,留点时间与精力去做更多的事情
1、javascript中创建变量时作用域和内存详解!
2、JavaScript--变量、作用域及内存
3、JavaScript 内存泄漏教程

第 5 章 引用类型

篇幅十分长的一章,里面涉及到的也是一些重要且基础的知识,有好多学习者们都对这章进行了总结与摘抄,我就不做重复劳动了,下面请看一些总结
1、JavaScript引用类型
2、JavaScript之引用类型

第 6 章 面向对象的程序设计

毫无疑问,这章节是一道坎,一道区分自身是初级程序员还是高级程序员的坎。这个章节的知识太重要了,唯一不足的地方是要掌握这章的知识实在是有难度,我们需要多维度的去学习该章节,比如其他人的总结:
javascript面向对象程序设计系列(一)---创建对象
JS面向对象的程序设计

第 7 章 函数表达式

在网上找了几篇总结,发现都是没办法很好的将该章节的重要内容以及精华全部覆盖和解析,所以呢,看书才是最重要的,网上的文章只能作为一种辅助工具:
JavaScript函数表达式1
JavaScript函数表达式2

第 8 章 BOM对象

这章没啥好说,基础知识,知道有那个对象,知道该对象之下有什么方法即可,最重要的是要脑海里时刻有它们的影子,这样才有灵活去运用它们的基础:
JavaScript中的BOM对象1
JavaScript中的BOM对象2,这篇总结挺好的。

前端开发面试题——JavaScript

前言

本文收集了一些前端面试题,主要是为了今后的求职,同时也借助这些面试题更进一步系统的学习、透彻的学习,形成自己的知识链。这并不是投机取巧,临时抱佛脚哈,自己也明白如果这么做肯定对未来的发展不利。

JavaScript

1、JavaScript对数组的操作方法有哪些?

(1)join()方法:接收一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串
(2)push()方法:接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度
(3)pop()方法:从数组末尾移除最后一项,减少数组的length值,然后返回移除的项
(4)shift()方法:移除数组中第一个项并返回改项,同时将数组长度减1
(5)unshift()方法:在数组前端添加任意个项并返回新数组的长度
(6)sort()方法:对数组中的数据进行排序
(7)concat()方法:可以基于当前数组中的所有项创建一个新数组
(8)slice()方法:基于当前数组中的一或多个项创建一个新数组
(9)indexOf()方法:从数组的开头(位置0)开始向后查找
(10)lastIndexOf():从数组的末尾开始向前查找
(11)splice()方法:恐怕要算是最强大的数组方法了,它有很多种用法,splice()的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下3种:

  • 删除:可以删除任意数量的项,只需指定2个参数(要删除的第一项的位置和要删除的项数),例如:splice(0,2)会删除数组中的前两项
  • 插入:可以向指定位置插入任意数量的项,只需要提供3个参数(起始位置,0(要删除的项数)和要插入的项),例如:splice(2,0,"red","green")会从当前数组的位置2开始插入字符串"red"和"green"
  • 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需要提供3个参数(起始位置,要删除的项数和要插入的任意数量的项),例如:splice(2,1,"red","green")会删除当前数组位置2的项,然后再从位置2开始插入字符串"red"和"green";
    原文地址

数组面试题
(1)计算给定数组arr中所有元素的总和

function sum(arr) {
  var result = 0;
  for (var i = 0; i < arr.length; i++) {
    result += arr[i];
  }
  return result;
}
var arr = [4,5,6,1];
console.log(sum(arr));

(2)合并数组 arr1 和数组 arr2,不要直接修改数组 arr,结果返回新的数组

function concat(arr1, arr2) {
  var arr3 = arr1.concat(arr2);
  return arr3;
}
var Arr1 = [4, 5, 2];
var Arr2 = [7, 8, 3];
console.log(concat(Arr1, Arr2));

(3)删除数组 arr 第一个元素,不要直接修改数组 arr,结果返回新的数组

function curtail(arr) {
  var arr2 = arr.slice(0);
  arr2.shift();
  return arr2;
}
var Arr = [1, 2, 34, 5];
console.log(curtail(Arr));

(4)在数组 arr 开头添加元素 item,不要直接修改数组 arr,结果返回新的数组

function prepend(arr, item) {
  var arr2 = arr.slice(0);
  arr2.unshift(item);
  return arr2;
}

(5)移除数组 arr 中的所有值与 item 相等的元素,直接在给定的 arr 数组上进行操作,并将结果返回

function removeWithoutCopy(arr, item) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] == item) {
      arr.splice(i, 1);
      i--;
    }
  }
  return arr;
}
var Arr = [3, 4, 5, 6, 6, 6, 1];
console.log(removeWithoutCopy(Arr, 6));

(6)找出元素 item 在给定数组 arr 中的位置

function indexOf(arr, item) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] == item) {
      return i;
    }
  }
  return -1;
}

2、JavaScript如何设置获取盒模型对应的宽和高?

(1)dom.style.width/height(只能获取到内联样式的宽和高,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <div id="box" style="height: 200px;"></div>
    <script>
      var getHeight = document.getElementById("box");
      console.log(getHeight.style.height);
    </script>
  </body>
</html>

(2)dom.currentStyle.width/height(只有IE浏览器支持,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(getHeight.currentStyle.height);
    </script>
  </body>
</html>

(3)window.getComputedStyle(dom).width/height(兼容性好,输出值带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(window.getComputedStyle(getHeight).height);
    </script>
  </body>
</html>

(4)dom.getBoundingClientRect().width/height(兼容性好,输出值不带单位)

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #box {
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="box"></div>
    <script>
      var getHeight = document.getElementById("box");
      document.write(getHeight.getBoundingClientRect().height);
    </script>
  </body>
</html>

3、DOM事件的级别

(1)DOM0:element.onclick = function(){}
(2)DOM2:element.addEventListener('click',function(){},false)
【拓展】
所谓的0级dom与2级dom事件就是不同版本间的差异,具体的说就是,对于不同的dom级别,如何定义事件处理,以及使用时有什么不同。 比如在dom0级事件处理中,后定义的事件会覆盖前面的,但是dom2级事件处理中,对一个按钮点击的时间处理就没有被覆盖掉。
(3)DOM3:element.addEventListener('keyup',function(){},false)

4、DOM事件模型

(1)冒泡型事件处理模型(Bubbling):冒泡型事件处理模型在事件发生时,首先在最精确的元素上触发,然后向上传播,直到根节点,反映到DOM树上就是事件从叶子节点传播到根节点。
(2)捕获型事件处理模型(Captrue):相反地,捕获型在事件发生时首先在最顶级的元素上触发,传播到最低级的元素上,在DOM树上的表现就是由根节点传播到叶子节点。
【捕获事件的具体流程】
window==>document==>html==>body==>父级元素==>目标元素
(3)标准的事件处理模型分为三个阶段:

  • 父元素中所有的捕捉型事件(如果有)自上而下地执行
  • 目标元素的冒泡型事件(如果有)
  • 父元素中所有的冒泡型事件(如果有)自下而上地执行

5、Event对象的常见应用

(1)event.preventDefault():阻止事件的默认行为
(2)event.stopPropagation():阻止事件的进一步传播,也就是阻止冒泡
(3)event.stopImmediatePropagation():阻止剩余的事件处理函数的执行,并防止当前事件在DOM树上冒泡。
(4)event.currentTarget:返回绑定事件的元素
(5)event.target:返回触发事件的元素

6、JavaScript自定义事件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Event</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div id="ev">
      <span>目标元素</span>
    </div>
    <script>
      var event = new Event('test');
      ev.addEventListener('test', function () {
        console.log('test dispatch');
      })
      ev.dispatchEvent(event);
    </script>
  </body>
</html>

7、JavaScript类的声明

// 1、类的声明
function Animal() {
  this.name = "name";
}
// 2、ES6中类的声明
class Animal2 {
  constructor() {
    this.name = name;
  }
}
// 3、实例化类
console.log(new Animal(), new Animal2());

8、JavaScript类之间的继承

(1)借助构造函数实现不完全继承,无法继承方法:

function Parent1() {
  this.name = 'parent1';
}
function Child1() {
  Parent1.call(this);
  this.type = 'child1';
}
console.log(new Child1());

(2)借助原型链实现继承,所有的属性和方法都得去原型链上去找,因而找到的属性方法都是同一个,所以直接利用原型链继承是不现实的。

function Parent2() {
  this.name = 'parent2';
  this.play = [1, 2, 3];
}
function Child2() {
  this.type = 'child2';
}
Child2.prototype = new Parent2();
console.log(new Child2());
var s1 = new Child2();
var s2 = new Child2();
console.log(s1.play, s2.play);
s1.play.push(4);

(3)组合方式实现继承

function Parent3() {
  this.name = 'parent3';
  this.play = [1, 2, 3];
}
function Child3() {
  Parent3.call(this);
  this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);

(4)组合继承的优化1

function Parent4() {
  this.name = 'parent4';
  this.play = [1, 2, 3];
}
function Child4() {
  Parent4.call(this);
  this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
console.log(s5.play, s6.play);
console.log(s5 instanceof Child4, s5 instanceof Parent4);
console.log(s5.constructor);

(5)组合继承的优化2(俗称寄生式继承)

function Parent5() {
  this.name = 'parent5';
  this.play = [1, 2, 3];
}
function Child5() {
  Parent5.call(this);
  this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
var s7 = new Child5();
console.log(s7 instanceof Child5, s7 instanceof Parent5);
console.log(s7.constructor);

9、创建对象有几种方法?

(1)字面量:

var k1 = {
  name: 'k1'
};
var k2 = new Object({
  name: 'k2'
});

(2)通过构造函数

var m = function (name) {
  this.name = name;
};
var k3 = new m('k3');

(3)通过Object.create

var p = {
  name: 'ppp'
};
var k4 = Object.create(p);

11、什么是原型和原型链?有什么特点?

(1)每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,如果这个对象不存在这个属性,那么就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
(2)特点:JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的话,就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象。
(3)更多原型知识1

12、JavaScript的运行机制

(1)JavaScript是单线程,一个时间段内,JavaScript只能干一件事情。任务队列分为同步任务和异步任务。
(2)异步任务类型:setTimeout和setInterval、DOM事件、ES6中的Promise

13、JavaScript异步加载的方式

(1)动态脚本加载;
(2)defer
(3)async

14、JavaScript的typeof返回哪些数据类型?

Object number function boolean underfind、String;
【拓展】
比较混淆的是:介绍JavaScript的基本数据类型
答案为:Undefined、Null、Boolean、Number、String、Symbol(创建后独一无二且不可变的数据类型

15、JavaScript中的事件委托是什么?

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

16、为什么要使用JavaScript的事件委托?

为了减少代码的DOM操作,提高程序性能。更多详情

17、JavaScript中的闭包

(1)有权访问另一个函数作用域内变量的函数都是闭包。
(2)闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。这是优点也是缺点,不必要的闭包只会徒增内存消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样。
(3)更多

18、window.onload和DOMContentLoaded的区别是?

window.addEventListener('load',function(){
    //页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener('DOMContentLoaded',function(){
    //DOM渲染完即可执行,此时图片、视频还可能没有加载完
})

19、用JavaScript创建10个标签,点击的时候弹出来对应的序号

var i;
for (i = 0; i < 10; i++) {
  (function (i) {
    var a = document.createElement('a');
    a.innerHTML = i + '<br>';
    a.addEventListener('click', function (e) {
      e.preventDefault();
      alert(i);
    });
    document.body.appendChild(a);
  })(i)
}

20、实现数组的随机排序

Array.prototype.shuffle = function () {
  var input = this;
  for (var i = input.length - 1; i >= 0; i--) {
    var randomIndex = Math.floor(Math.random() * (i + 1));
    var itemAtIndex = input[randomIndex];
    input[randomIndex] = input[i];
    input[i] = itemAtIndex;
  }
  return input;
}
var tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
tempArray.shuffle();
console.log(tempArray);

21、JavaScript中有哪些内置函数,与内置对象的区别是什么?

(1)内置函数:是浏览器内核自带的,不用任何函数库引入就可以直接使用的函数,如常规函数(alert等)、数组函数(reverse等)、日期函数(getDate等)、数学函数(floor等)、字符串函数(length等);
(2)内置对象:是浏览器本身自带的,内置对象中往往包含了内置函数。内置对象有Object、Array、Boolean、Number、String、Function、Date、RegExp等。

22、JavaScript变量按照存储方式区分为哪些类型,并描述其特点

//值类型
//变量的交换,按值访问,操作的是他们实际保存的值。
//等于在一个新的地方按照新的标准开了一个空间(栈内存),
//这样a的值对b的值没有任何影响
var a = 100;
var b = a;
b = 200;
console.log("a:" + a + ",b:" + b);
//引用类型
//变量的交换,当查询时,我们需要先从栈中读取内存地址,
//然后再顺藤摸瓜地找到保存在堆内存中的值;
//发现当复制的是对象,那么obj1和obj2两个对象被串联起来了,
//obj1变量里的属性被改变时候,obj2的属性也被修改。
var obj1 = {
  x: 100
};
var obj2 = obj1;
obj2.x = 200;
console.log("obj1:" + obj1.x + ",obj2:" + obj2.x);

23、如何理解JSON?

其实JSON只不过是一个JavaScript对象而已。stringify()是把对象变成字符串;parse()是把字符串变成对象。

24、JavaScript中&&和||

(1)只要“||”前面为false,不管“||”后面是true还是false,都返回“||”后面的值;
(2)只要“||”前面为true,不管“||”后面是true还是false,都返回“||”前面的值。
(3)且在js逻辑运算中,0、”“、null、false、undefined、NaN都会判为false,其他都为true。

(4)只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值;
(5)只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值。

25、描述new一个对象的过程

(1)创建空对象:var obj = {};
(2)设置新对象的constructor属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象:obj.proto = ClassA.prototype;
(3)使用新对象调用函数,函数中的this被指向新实例对象:ClassA.call(obj); //{}.构造函数();
(4)将初始化完毕的新对象地址,保存到等号左边的变量中
【注意】
若构造函数中返回this或返回值是基本类型(number、string、boolean、null、undefined)的值,则返回新实例对象;若返回值是引用类型的值,则实际返回值为这个引用类型。

26、什么是构造函数?

(1)构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。
(2)注意事项:

  • 默认函数首字母大写;
  • 构造函数并没有显示返回任何东西。new 操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型;
  • 也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回。如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回;
  • 因为构造函数也是函数,所以可以直接被调用,但是它的返回值为undefine,此时构造函数里面的this对象等于全局this对象。this.name其实就是创建一个全局的变量name。在严格模式下,当你补通过new 调用Person构造函数会出现错误;

27、函数声明和函数表达式的区别

//成功
fn()
function fn() {
  console.log("函数声明,全局");
}
//报错
fn1()
var fun1 = function () {
  console.log("函数表达式,局部")
}

28、说一下对变量提升的理解

(1)顾名思义,就是把下面的东西提到上面。在JS中,就是把定义在后面的东东(变量或函数)提升到前面中定义。

var v = 'Hello World';
(function () {
  alert(v); //undefined
  var v = 'I love you';
})()

(2)根据上面变量提升原件以及js的作用域(块级作用域)的分析,得知 上面代码真正变成如下:

var v = 'Hello World';
(function () {
  var v;
  alert(v);
  v = 'I love you';
})()

(3)在我们写js code 的时候,我们有2中写法,一种是函数表达式,另外一种是函数声明方式。我们需要重点注意的是,只有函数声明形式才能被提升。

//成功
function myTest() {
  foo();
  function foo() {
    alert("我来自 foo");
  }
}
myTest();
//失败
function myTest() {
  foo();
  var foo = function foo() {
    alert("我来自 foo");
  }
}
myTest();

29、说一下this几种不同的使用场景

(1)作为构造函数执行,如果函数创建的目的是使用new来调用,并产生一个对象,那么此函数被称为构造器函数;

var Niu = function (string) {
  this.name = string;
};

(2)作为对象属性执行,对象成员方法中的this是对象本身,此时跟其他语言是一致的,但是也有差异,JavaScript中的this到对象的绑定发生在函数调用的时候;

var myObj = {
  value: 0,
  increment: function (inc) {
    this.value += typeof inc === 'number' ? inc : 1;
  }
};
myObj.increment(); //1
myObj.increment(2); //3

(3)作为普通函数执行,以普通方式定义的函数中的this:会被自动绑定到全局对象;

var value = 232;
function toStr() {  
  console.log(this.value);
}

(4)对象方法中闭包函数的this

//在以普通方式定义的函数中的this会被自动绑定到全局对象上,
//大家应该可以看出闭包函数定义也与普通方式无异,因此他也会被绑定到全局对象上。
value = 10;
var closureThis = {
  value: 0,
  acc: function () {
    var helper = function () {
      this.value += 2;
      console.log("this.value : %d", this.value);
    }
    helper();
  }
};
closureThis.acc(); //12
closureThis.acc(); //14

var closureThat = {
  value: 0,
  acc: function () {
    that = this;
    var helper = function () {
      that.value += 2;
      console.log("that.value : %d", that.value);
    }
    helper();
  }
};
closureThat.acc(); // 2
closureThat.acc(); // 4

(5)apply函数的参数this,apply方法允许我们选择一个this值作为第一个参数传递,第二个参数是一个数组,表明可以传递多个参数。

30、如何理解JavaScript作用域?

函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。

function foo() {
  var x = 1;
  return function () {
    alert(x);
  }
};
var bar = foo();
bar(); // 1
var x = 2;
bar(); // 1

31、什么是自由变量?

自由变量就是当前作用域没有定义的变量,即“自由变量”

var a = 100;
function F1() {
  var b = 200;
  function F2() {
    var c = 300;
    //a是自由变量
    console.log(a);
    //b是自由变量
    console.log(b);
    console.log(c);
  }
  F2();
}
F1();

32、this的特点

this要在执行时才能确认值,定义时无法确认。

33、同步和异步的区别是什么?分别举一个同步和异步的例子

同步会阻塞代码执行,而异步不会;alert是同步,setTimeout是异步。

33、一个关于setTimeout的笔试题

//输出结果13542
console.log(1);
setTimeout(function () {
  console.log(2);
}, 100);
console.log(3);
setTimeout(function () {
  console.log(4);
}, 99);
console.log(5)

34、何时需要异步?

在可能发生等待的情况、等待过程中不能像alert一样阻塞程序运行、因此所有的等待的情况都需要异步

35、JavaScript中的异步和单线程

(1)其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。
(2)js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。

36、使用JavaScript获取当天的日期

function formatDate(dt) {
  if (!dt) {
    dt = new Date();
  }
  var year = dt.getFullYear();
  var month = dt.getMonth() + 1;
  var date = dt.getDate();
  if (month < 10) {
    month = "0" + month;
  }
  if (date < 10) {
    date = "0" + date;
  }
  return year + "-" + month + "-" + date;
}
var dt = new Date();
var formatDate = formatDate(dt);
console.log(formatDate);

37、获取随机数,要求是长度一致的字符串

var random = (Math.random() * 10 + "0000000000").slice(0, 10);
console.log(random);

38、写一个能遍历对象和数组的通用forEach函数

function forEach(obj, fn) {
  if (obj instanceof Array) {
    //准确判断是不是数组
    obj.forEach(function (item, index) {
      fn(index, item);
    })
  } else {
    //不是数组就是对象
    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        fn(key, obj[key]);
      }
    }
  }
}
//检测数组
var arr = [1, 2, 3];
//这里顺序换了,为了和对象的遍历格式一致
forEach(arr, function (index, item) {
  console.log(index, item);
});
//检测对象
var obj = {
  x: 100,
  y: 200
};
forEach(obj, function (key, value) {
  console.log(key, value);
})

39、DOM操作的常用API有哪些?

获取DOM节点,以及节点的property和Attribute;获取父节点和子节点;新增节点和删除节点

40、DOM节点的attr和property有何区别?

property只是一个JavaScript对象的属性的修改;Attribute是对html标签属性的修改

41、DOM的本质

浏览器把拿到的HTML代码,结构化一个浏览器能识别并且js可以操作的一个模型而已。

42、手动编写一个ajax,不依赖第三方库

var xmlHttp = new XMLHttpRequest() || new ActiveXObject("Msxml2.XMLHTTP");
xmlHttp.open("提交方式", "提交地址", true);
xmlHttp.onreadystatechange = function () {
  if (xmlHttp.readyState == 4) {
    infoDiv.innerHTML = xmlHttp.responseText;
  }
}
xmlHttp.send();

更多ajax知识,请点击这里

43、什么是跨域?

(1)浏览器有同源策略,不允许ajax访问其他域接口。跨域条件(协议、域名、端口)有一个不同就算是跨域。
(2)可以跨域的三个标签(、、<script src=xxx>)。三个标签的应用场景(用于打点统计,统计网站可能是其他域、<script>可以使用CDN,CDN的也是其他域、<script>可以用于JSOP)。
(3)跨域注意的事项:所有的跨域请求都必须经过信息提供方允许、如果未经过允许即可获取,那是浏览器同源策略出现漏洞。

44、跨域的几种实现方式

(1)通过jsonp跨域:在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。
(2)通过修改document.domain来跨子域:
(3)使用window.name来进行跨域:
(4)使用HTML5中新引进的window.postMessage方法来跨域传送数据:

45、JavaScript实现字符串反转

(1)第一种方法

var str = "abcdef";
console.log(str.split("").reverse().join(""));

(2)第二种方法

var str = "abcdef";
var i = str.length - 1;
for (var x = i; x >= 0; x--) {
  document.write(str.charAt(x));
}

(3)第三种方法

function reverse(str) {
  if (str.length == 0) return null;
  var i = str.length;
  var dstr = "";
  while (--i >= 0) {
    dstr += str.charAt(i);
  }
  return dstr;
}
var str = "abcdef";
str = reverse(str);
document.write(str);

46、ajax中的get和post两种请求方式的异同

(1) get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到;post是通过HTTP post机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
(2)对于get方式,服务器端用Request.QueryString获取变量的值;对于post方式,服务器端用Request.Form获取提交的数据。两种方式的参数都可以用Request来获得。
(3)get传送的数据量较小,不能大于2KB;post传送的数据量较大,一般被默认为不受限制,但理论上,因服务器的不同而异。
(4)get安全性非常低,post安全性较高。
(5)

跟是一样的,也就是说,method为get时action页面后边带的参数列表会被忽视;而跟是不一样的。

47、找出数字数组中最大的元素

var a = [111, 2, 6, 4, 22, 5, 99, 3];
console.log(Math.max.apply(null, a));

48、实现该语法的功能:var a = (5).plus(3).minus(6)

Number.prototype.plus = function (a) {
  return this.valueOf() + a;
}
Number.prototype.minus = function (a) {
  return this.valueOf() - a;
}
var a = (5).plus(3).minus(6);
console.log(a);

49、有一个大数组,var a = [‘1’,’2’,’3’,…];a数组的长度是100,内容填充随机整数的字符串,请先构造此数组a,然后设计一个算法,将其内容去重。

function Random(n) {
  var arr = [];
  for (var i = 0; i < n; i++) {
    arr[i] = parseInt(Math.random() * 100);
  }
  console.log(arr);
  return arr;
}
//使用indexof  这里也可以使用arr2的indexOf
function DeleRepeat1(arr) {
  var arr2 = [];
  var len = arr.length;
  for (var i = 0; i < len; i++) {
    if (arr.indexOf(arr[i]) == i) {
      arr2.push(arr[i])
    }
  }
  return arr2;
}
var arr = Random(100);
console.log(DeleRepeat1(arr));

50、返回只包含数字类型的数组,比如:’abc234koi45jodjnvi789’ –> [234,45,789]

var str = 'abc234koi45jodjnvi789';
function getNum(str) {
  var arr = [];
  str.replace(/(\d+)/g, function (match, data) {
    arr.push(data);
  })
  return arr;
}
console.log(getNum(s
tr));

51、slice数组的浅复制:向数组后添加一个元素,返回原数组不变,返回新数组。

function append(arr, item) {
  var arr2 = [];
  arr2 = arr.slice(0);
  arr2.push(item);
  return arr2;
}
var arr = [1, 2, 3, 4];
console.log(append(arr, 10)); //[1, 2, 3, 4,10]
console.log(arr) //[1, 2, 3, 4]

【解析】
如果定义 var arr2=arr的话,那么arr2指向了arr的引用。那么arr2改变也会伴随着arr改变。 而slice浅复制:arr数组并返回了一个新数组,与原数组没有关系了。

52、实现数组内容增添的函数

function insert(arr, item, index) {
  var arr2 = arr.concat();
  arr2.splice(index, 0, item);
  return arr2;
}
//[1,2,'z',3,4]
console.log(insert([1, 2, 3, 4], 'z', 2));

53、什么是同步?什么是异步

(1)同步:脚本会停留并等待服务器发送回复然后再继续
(2)异步:脚本允许页面继续其进程并处理可能的回复

54、documen.write和 innerHTML的区别是什么?

document.write重绘整个页面;innerHTML可以重绘页面的一部分。

55、介绍JavaScript有哪些内置对象?

(1)数据封装类对象:Object、Array、Boolean、Number 和 String
(2)其他对象:Function、Arguments、Math、Date、RegExp、Error

56、JavaScript的作用域链式什么?

全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找,直至全局函数,这种组织形式就是作用域链。

57、什么是window对象? 什么是document对象?

(1)window对象是指浏览器打开的窗口。
(2)document对象是Documentd对象(HTML 文档对象)的一个只读引用,window对象的一个属性。

58、null,undefined 的区别?

(1)null:表示一个对象是“没有值”的值,也就是值为“空”;
(2)undefined:表示一个变量声明了没有初始化(赋值);
【注意】
在验证null时,一定要使用 === ,因为 == 无法分别 null 和undefined

59、什么是闭包(closure),为什么要用它?

(1)闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
(2)闭包的特性:

  • 函数内再嵌套函数
  • 内部函数可以引用外层的参数和变量
  • 参数和变量不会被垃圾回收机制回收

60、如何判断一个对象是否属于某个类?

if(a instanceof Person){
  alert('yes');
}

61、new操作符具体干了什么呢?

(1)创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型;
(2)属性和方法被加入到 this 引用的对象中;
(3)新创建的对象由 this 所引用,并且最后隐式的返回 this 。
【栗子】

var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

62、Javascript中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

(1)javaScript中hasOwnProperty函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员。
(2)使用方法:

  • object.hasOwnProperty(proName)
  • 其中参数object是必选项。一个对象的实例。
  • proName是必选项。一个属性名称的字符串值。
  • 如果 object 具有指定名称的属性,那么JavaScript中hasOwnProperty函数方法返回 true,反之则返回 false。

63、对JSON 的了解?

(1)JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小,比如:{"age":"12", "name":"back"}
(2)JSON字符串转换为JSON对象:

var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);

(3)JSON对象转换为JSON字符串:

var last=obj.toJSONString();
var last=JSON.stringify(obj);

64、js延迟加载的方式有哪些?

defer和async、动态创建DOM方式(用得最多)、按需异步载入js。

65、异步加载JS的方式有哪些?

(1)defer,只支持IE;
(2)async:
(3)创建script,插入到DOM中,加载完毕后callBack

66、DOM操作——怎样添加、移除、移动、复制、创建和查找节点?

(1)创建新节点
createDocumentFragment() //创建一个DOM片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
(2)添加、移除、替换、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
(3)查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的Name属性的值
getElementById() //通过元素Id,唯一性

67、JQuery一个对象可以同时绑定多个事件,这是如何实现的?

(1)多个事件同一个函数:
$("div").on("click mouseover", function(){});
(2)多个事件不同函数

$("div").on({
  click: function(){},
  mouseover: function(){}
})

68、JavaScript中如何检测一个变量是一个String类型或者Array类型?请写出函数实现

var str = "hello";
if (typeof (str) === "string") {
  console.log("str is String");
}
var arr = [];
if (arr instanceof Array) {
  console.log("arr is Array");
}

69、prototype和_proto_的关系是什么

prototype和__proto__都指向原型对象,任意一个函数(包括构造函数)都有一个prototype属性,指向该函数的原型对象,同样任意一个构造函数实例化的对象,都有一个__proto__属性(__proto__并非标准属性,ECMA-262第5版将该属性或指针称为[[Prototype]],可通过Object.getPrototypeOf()标准方法访问该属性),指向构造函数的原型对象。

70、ajax是什么?同步和异步的区别?

(1)ajax是一种无需重新加载整个网页的情况下,能够更新部分网页的技术。
(2)同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

71、事件委托是什么,举个例子

它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。那这是什么意思呢?网上的各位大牛们讲事件委托基本上都用了同一个例子,就是取快递来解释这个现象。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      ul li {
        display: block;
        height: 50px;
        width: 100px;
        text-align: center;
        line-height: 50px;
        background-color: #888;
        margin-bottom: 8px;
      }
    </style>
  </head>
  <body>
    <ul id="Inul">
      <li>111</li>
      <li>222</li>
      <li>333</li>
      <li>444</li>
      <li>555</li>
    </ul>
    <script>
      //不使用事件委托
      /*window.onload = function(){
      	var oUL = document.getElementById("Inul");
      	var aLi = oUL.getElementsByTagName("li");
      	for(var i=0;i<aLi.length;i++){
      		aLi[i].onclick = function(){
      			alert(123);
      		}
      	}
      }*/
      //使用事件委托
      window.onload = function () {
        var oUL = document.getElementById("Inul");
        oUL.onclick = function () {
          alert(123);
        }
      }
    </script>
  </body>
</html>

72、判断字符串是否是这样组成的:第一个必须是字母,后面可以是字母、数字和下划线,总长度为5-20

var str = "adfadfa2343243e";
var reg = /^[a-zA-Z]\w{4,19}$/g;
console.log(reg.test(str));

73、编写一个方法,去掉一个数组的重复元素

Array.prototype.methods = function () {
  //定义一个临时数组
  var arr = [];
  //循环遍历当前数组
  for (var i = 0; i < this.length; i++) {
    //判断当前数组下标为i的元素是否已经保存到临时数组
    //如果已经保存,则跳过,佛则将此元素保存到临时数组中
    //没有保存则说明查不到,返回-1
    if (arr.indexOf(this[i]) === -1) {
      arr.push(this[i]);
    }
  }
  return arr;
}
var arry = [2, 3, 5, 6, 6, 7];
console.log(arry.methods());

74、如何实现JavaScript模块化,请至少使用两种方法

(1)立即执行函数(IIFE):可以达到不暴露私有成员的目的。
(2)对象写法:可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

75、编写一个JavaScript函数,该函数有一个n(数字类型),其返回值是一个数组,该数组内是n个随机且不重复的整数,且整数取值范围是[2,32]。

//定义一个函数用来返回整数的取值范围
function getRand(a, b) {
  //ceil向上取整
  var rand = Math.ceil(Math.random() * (b - a) + a);
  return rand;
}
//定义一个函数用来过滤重复的整数
function checkArrIn(rand, arry) {
  if (arry.indexOf(rand) != -1) {
    return true;
  }
  return false;
}
//定义一个执行函数
function fn(n, min, max) {
  var arr = [];
  //判断n是不是一个数字,包含字符串类型的数字
  var isNum = !isNaN(Number(n));
  //判断n的取值是否符合要求
  var isRandOk = (n >= min && n <= max && n <= (max - min)) ? true : false;
  if (n && isRandOk && isNum) {
    for (var i = 0; i < n; i++) {
      var rand = getRand(min, max);
      //假如checkArrIn()返回true,说明有相同元素,那么就重新执行一次
      if (checkArrIn(rand, arr)) {
        i--;
      } else {
        arr.push(rand);
      }
    }
  }
  console.log(arr);
}
//调用函数
fn(10, 2, 32);

前端工程师面试指南——安全篇

XSS(跨站脚本攻击)

  • 概念: 恶意攻击者往Web页面里插入恶意的脚本代码,当用户浏览该网页时,嵌入其中的脚本会被执行,从而达到恶意攻击用户的目的。
  • 分类: 内部攻击,主要是利用程序自身的漏洞,构造跨站语句;外部攻击,主要是自己构造XSS跨站漏洞网页或者寻找非目标以外的有跨站漏洞的网页。
  • 类型: 存储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,假如代码,如果没有过滤或者过滤不严,那么这些代码将储存到服务器中,用户访问该页面时触发代码执行;反射型XSS,非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现搜索页面。
  • 防范: 设置httpOnly,禁止用document.cookie操作;输入检查,在用户输入的时候进行格式检查;
    对输出转义。

XSRF(跨站请求伪造)

  • 概念: 跨站请求伪造是一种对网站的恶意利用,尽管听起来像是跨站请求攻击(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行,导致对其进行防范的资源也相当稀少,因此难以防范,所以被认为比XSS更具有危险性。
  • 小栗子: 攻击通过在授权用户访问的页面中包含链接或者脚本的方式工作。例如:一个网站用户A可能正在浏览聊天论坛,而同时另一个用户B也在此论坛中,并且后者刚刚发布了一个具有A银行链接的图片消息。设想一下,B编写一个在A的银行站点上进行取款的form提交的链接,并将此链接作为图片src。如果A的银行在cookie中保存他的授权信息,并且cookie没有过期,那么当A的浏览器尝试装载图片时将提交这个取款form和他的cookie,这样在没经A同意的情况下便授权了这次事务。

  • 攻击原理
    image
    防范:
    (1)检查 Referer 字段:这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。
    (2)添加校验 Token:这种数据通常是表单中的一个数据项。服务器生成token并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 Token 的值为空或者错误,拒绝这个可疑请求。
    (3)通过输入验证码来校验合法请求。

SQL注入

  • 攻击方式: 服务器上的数据库运行非法的 SQL 语句,主要通过拼接字符串的形式来完成,改变sql语句本身的语义。通过sql语句实现无账号登陆,甚至篡改数据库。

防御:
(1)使用参数化查询:使用预编译语句,预先编译的 SQL 语句,并且传入适当参数多次执行。由于没有拼接的过程,因此可以防止 SQL 注入的发生。 使用preparedStatement的参数化sql,通过先确定语义,再传入参数,就不会因为传入的参数改变sql的语义。(通过setInt,setString,setBoolean传入参数)
(2)单引号转换:将传入的参数中的单引号转换为连续两个单引号,PHP 中的 Magic quote 可以完成这个功能。
(3)检查变量数据类型和格式。
(4)使用正则表达式过滤传入的参数,对特殊符号过滤或者转义处理。

flexbox布局属性分析和小demo

什么是flexbox布局?

flexbox的出现是为了解决逐渐复杂的web布局问题,因为这种布局方式很灵活。容器的子元素可以任意方向进行排列。 flex布局模型不同于块模型布局和内联模型布局,块和内联模型的布局计算依赖于块和内联的流方向,而flex布局依赖于flex directions。简单的说:flexbox是布局模块,而不是一个简单的属性,它包含父元素(flex container)和子元素(flex items)的属性。

flexbox布局属性全解析

强大且灵活的flexbox布局,让广大的前端开发者省去了解决盒子浮动所带来的苦恼。在如今手机端开发成为主流时代的,这种布局尤为重要,下面我们一起看看flexbox布局中各种属性所发挥的作用。

1、display(flex container)属性

这个属性是在父元素中设置的,当父元素设置了该属性,那么默认其中的子元素横向布局,就和浮动布局一般,下面来看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style>
      .box {
        display: flex;
        display: -webkit-flex;
      }
      .item1, .item2, .item3 {
        background: #FFA500;
        padding: 10px;
        height: 100px;
        width: 80px;
        margin-top: 10px;
        margin-right: 10px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item1">row1</div>
      <div class="item2">row2</div>
      <div class="item3">row3</div>
    </div>
  </body>
</html>

点击我看演示效果哦。

2、flex-direction(flex container)属性

这个属性在父元素中定义,作用是改变子元素的布局方向,主要有以下几个属性值:

  • row:这个是默认值,定义子元素从左向右布局;
  • row-reverse:定义子元素从右向左布局;
  • column:定义子元素顺序从上到下布局;
  • column:定义子元素反序从上到下布局;

下面贴一部分代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style>
      .box {
        display: flex;
        display: -webkit-flex;
        flex-direction: column;
      }
      .item1, .item2, .item3 {
        background: #FFA500;
        padding: 10px;
        height: 100px;
        width: 80px;
        margin-top: 10px;
        margin-right: 10px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item1">row1</div>
      <div class="item2">row2</div>
      <div class="item3">row3</div>
    </div>
  </body>
</html>

更详细戳我看效果啊,具体实现请看demo中的网页源代码

3、order(flex items)属性

这个属性是定义在子元素中,作用是改变子元素在布局流中的位置。在父元素定义了布局方式为flex时,各个子元素的默认order值为0,当子元素order值设置为负值时向前排序,设置为正值向后排序,设置为0时为正常排序,下面请看代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style>
      .box {
        display: flex;
        display: -webkit-flex;
      }
      .item1, .item2, .item3 {
        background: #FFA500;
        padding: 10px;
        height: 100px;
        width: 80px;
        margin-top: 10px;
        margin-right: 10px;
        text-align: center;
      }
      .item2 {
        order: 1;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item1">order1</div>
      <div class="item2">order2</div>
      <div class="item3">order3</div>
    </div>
  </body>
</html>

点击这里看效果哦。

4、flex-wrap(flex container)属性

这个属性定义在父元素中,它的作用是设置子元素在页面缩小时,到底是在一行显示还是换行显示,主要有以下几个属性值:

  • nowrap:这个是默认值,定义子元素当页面缩小时,还是在一行显示;
  • wrap:定义子元素当页面缩小时,换行顺序显示;
  • wrap-reverse:定义子元素当页面缩小时,换行反序显示;

下面贴核心代码,默认源代码请到项目源地址或者网页上查看,不说第二次了

.boxThree {
    display: flex;
    display: -webkit-flex;
    flex-wrap: wrap-reverse;
 }

点击我查看效果

5、flex-flow(flex container)属性

这个属性定义在父元素中,它是flex-direction和flex-wrap这两个属性的缩写版本,但是在这场景下,flex-direction的属性值只能为row或者row-reverse,下面列举3种组合:

  • row wrap :横向顺序布局,页面缩小时换行顺序显示;
  • row wrap-reverse:横向顺序布局,页面缩小时换行反序显示;
  • row-reverse wrap:横向反序布局,页面缩小时换行顺序显示。

还有几种组合方式,大家可以去尝试一下,在这里不全部举出,下面贴出某一段核心代码:

.boxTwo {
    display: flex;
    display: -webkit-flex;
    flex-flow: row-reverse wrap;
}

点击我查看效果

6、justify-content(flex container)属性

这个属性定义在父元素中,它的作用是让子元素以怎样的方式横向布局显示,主要有下面几个属性值:

  • flex-start:这个是默认值,让子元素横向布局,方向是从左往右;
  • flex-end:让子元素横向布局,方向是从右往左;
  • center:让子元素横向居中布局;
  • space-between:让子元素横向布局,每个子元素平均分布在父元素;
  • spack-around:让子元素横向布局,每个子元素平均分布在父元素,并且在两端会有空间。

下面贴出某一段核心代码:

.boxThree {
    display: flex;
    display: -webkit-flex;
    justify-content: center;
}

点击我查看效果

7、align-content(flex container)属性

这个属性定义在父元素中,十分怪异的一个属性,它有以下几个使用的前提:

  • 1、需要父元素有高度,并且不能高于几个子元素高度之和;
  • 2、需要设置父元素中的子元素为纵向布局(column);
  • 3、需要设置父元素中的子元素能换行显示(wrap)。

属性值与justify-content类似,相比只是多了个stretch。下面贴出某段核心代码:

.boxSix {
    display: flex;
    display: -webkit-flex;
    flex-direction: column;
    flex-wrap: wrap;
    align-content: stretch;
    height: 300px;
    width: 800px;
    border: 1px solid #ccc;
}

点击我查看效果

8、align-items(flex container)属性

这个属性定义在父元素中,是一个十分常用的属性,它的作用是改变子元素纵向布局的方式,主要有以下几个属性值:

  • flex-start:让子元素最上、最左布局;
  • flex-end:让子元素最底、最左布局;
  • center:让子元素垂直居中布局;
  • baseline:让子元素根据里面的内容的基线对齐(不理解没关系,请看下方的demo);
  • stretch:这个是默认值,与flex-start值发挥的作用看上去一毛一样,实际上有什么区别我也不太懂。

下面贴出某段核心代码:

.boxFour {
    display: flex;
    display: -webkit-flex;
    align-items: baseline;
    height: 300px;
    width: 800px;
    border: 1px solid brown;
}

点击我查看效果

9、align-sef(flex items)属性

这个属性定义在子元素中,它的作用是改变某个子元素在纵轴方向的布局方式,主要有以下几个属性值:

  • auto:让某个子元素自动布局,很佛性,一般是跟着周围的子元素布局方式走;
  • flex-start:让某个子元素贴着顶部方向布局;
  • flex-end:让某个子元素贴着底部方向布局;
  • center:让某个子元素垂直居中布局;
  • baseline:让某个子元素根据内容基线来布局;
  • strech:这个是默认值,和flex-start值发挥的作用看上去一毛一样。

下面贴出某段核心代码:

.item14 {
    height: 190px;
    font-size: 28px;
    align-self: baseline;
}

点击我查看效果

10、flex-grow(flex-items)属性

这个属性定义在子元素中,是非常实用的一个属性,它的作用是让子元素以几倍宽度拉伸,属性值的类型为number,下面贴出全部代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>flex-grow</title>
    <style>
      .box {
        display: flex;
        display: -webkit-flex;
      }
      .item1, .item2, .item3 {
        background: #FFA500;
        padding: 10px;
        height: 100px;
        margin-right: 10px;
        text-align: center;
        flex: 1;
      }
      .item1 {
        flex-grow: 2;
      }
      .item2 {
        flex-grow: 4;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item1">两倍宽度</div>
      <div class="item2">四倍宽度</div>
      <div class="item3">一倍宽度</div>
    </div>
  </body>
</html>

点击我查看效果

11、flex-shrink(flex items)属性

这个属性定义在子元素中,它的作用就是让子元素在页面缩小时宽度如何变化,属性值为number,默认值是1。当值为2时,说明页面缩小时,该子元素将相对缩小两倍;当值为0时,页面缩小时,该子元素宽度不会发生改变。下面请看全部代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>flex-shrink</title>
    <style>
      .box {
        display: flex;
        display: -webkit-flex;
      }
      .item1, .item2, .item3 {
        background: #FFA500;
        padding: 10px;
        height: 100px;
        width: 400px;
        margin-right: 10px;
        text-align: center;
      }
      .item2 {
        flex-shrink: 0;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item1">flex-shrink</div>
      <div class="item2">flex-shrink</div>
      <div class="item3">flex-shrink</div>
    </div>
  </body>
</html>

点击我查看效果

12、flex-basis(flex items)属性

这个属性定义在子元素中,设置弹性盒元素的初始长度,默认值为auto,下面贴代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>flex-basis</title>
    <style>
      .box {
        display: flex;
        display: -webkit-flex;
      }
      .item1, .item2, .item3 {
        background: #FFA500;
        padding: 10px;
        height: 100px;
        margin-right: 10px;
      }
      .item2 {
        flex-basis: 0;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item1">flex-shrink</div>
      <div class="item2">flex-shrink</div>
      <div class="item3">flex-shrink</div>
    </div>
  </body>
</html>

如果把类名为item2的内容换成别的,你们看看会出现什么情况?点击我查看上述代码效果

13、flex(flex items)属性

这个属性定义在子元素中,是flex-grow、flex-shrink、flex-basis这三个属性的缩写。默认值为“0 1 auto”,下面请看一段代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>flex</title>
    <style>
      .box {
        display: flex;
        display: -webkit-flex;
      }
      .item1, .item2, .item3 {
        background: #FFA500;
        padding: 10px;
        height: 100px;
        margin-right: 10px;
        text-align: center;
        flex: 1;
      }
      .item2 {
        flex: 2 0 auto;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="item1">flex</div>
      <div class="item2">flex-double</div>
      <div class="item3">flex</div>
    </div>
  </body>
</html>

点击我查看效果

尾声

终于把flexbox布局的那些玩意探究的差不多了,真的是累,花了近一整天的时间,不过收获却是巨大的,之前对flexbox布局还是迷迷糊糊的,经过这次的总结和探究,算是进一步深入理解了。对flexbox布局的完全掌握我认为还是得经常将它运用在项目之中,多踩踩兼容的坑,同时也能将它熟记于心。

前端工程师面试指南——工具篇

热更新原理是什么

热更新所使用到的 EventSource 技术,EventSource 不是一个新鲜的技术,它早就随着 H5 规范提出了,正式一点应该叫 Server-sent events,即 SSE。鉴于传统的通过 ajax 轮训获取服务器信息的技术方案已经过时,我们迫切需要一个高效的节省资源的方式去获取服务器信息,一旦服务器资源有更新,能够及时地通知到客户端,从而实时地反馈到用户界面上。EventSource 就是这样的技术,它本质上还是 HTTP,通过 response 流实时推送服务器信息到客户端。

webpack速度优化

参考资料

webpack是怎么打包的,babel又是什么?

  • webpack打包: 把项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
  • babel是将es6、es7、es8等语法转换成浏览器可识别的es5或es3语法。

git push -u 是什么意思

绑定默认提交的远程版本库,加了参数-u后,以后即可直接用git push 代替git push origin master

git rebase解释下

有test和dev两个分支,分别有两个commit,此时执行下列命令:

git checkout test
git rebase dev

以dev为基准将test的提交进行回放,挨个的应用到dev上去,然后test的那些提交就会废弃。 等价于git merge dev。

git merge 和git rebase区别

  • merge不会修改提交历史,rebase会修改提交历史 。
  • rebase只应用于本地没有提交的代码,如果应用到已经提交到远程的分支不要应用,不然会非常的麻烦,git merge 可以应用于远程分支。

VsCode使用指南

安装插件

  • 方法一:Ctrl/Cmd+P (或 Ctrl/Cmd + E) 输入 ext install [插件关键字/名称];
  • 方法二:Ctrl/Cmd+Shift+P (或 F1) 输入 Extensions, 选中 Install Extension然后输入插件名称/关键字;
  • 方法三:不在插件商店的插件, 则可以放置到用户目录下的 .vscode/extensions 文件夹中~ 重启 VS Code 即可生效

一般情况下我们使用方法一来安装插件,比如我们安装一个浏览器打开html文件的插件(open-in-browser),步骤分别是:

ctrl+p
ext install open-in-browser
回车
安装

有个地方需要注意的是,当你把整个项目拖进vscode中,那么右键HTML文件才会出现“open with default browser”

当然也可以使用快捷键在浏览器中打开这个HTML文件,使用方法看介绍即可(在window系统下ctrl+alt+o,当然这个不是一定的,还是得根据安装后的提示信息进行快捷键操作)
image

还有另外一个方法也是挺方便的,直接到管理扩展的地方(左侧5个按钮中的最下那个)去搜索插件进行安装,对于左侧那5个按钮具体的作用看下图
image

更多实用的插件可以点击这里,当然了,还可以点击这里

目前我安装了如下的插件
image
image
image

让vscode终端变成git终端

假如你不知道你的git安装地址在哪,那么可以这么来找:File ==> perferences ==> setting ==> 搜索git.path,那么就能够看到git的安装地址

接着再点击左下角的settings,点击Extensions,然后再点击Edit in settings.json,之后修改文件如下即可
image

image

更多教程请点击这里

创建vue文件模板

首先我们找到关于vue的json配置文件:File ==> perferences ==> user snippets ==> 搜索vue,然后将下面的代码复制粘贴即可

{
  "Print to console": {
    "prefix": "vue",
    "body": [
      "<!-- $0 -->",
      "<template>",
      "  <div></div>",
      "</template>",
      "",
      "<script>",
      "export default {",
      "  data () {",
      "    return {",
      "    };",
      "  },",
      "",
      "  components: {},",
      "",
      "  computed: {},",
      "",
      "  mounted: {},",
      "",
      "  methods: {}",
      "}",
      "",
      "</script>",
      "<style lang='scss' scoped>",
      "</style>"
  ],
    "description": "Log output to console"
  }
}

注:$0 表示你希望生成代码后光标的位置 ; prefix 表示生成对应预设代码的命令。例如我们新建一个名为demo.vue的文件,输入vue按下enter,就会自动生成内容啦。文章源地址传送门

VSCode快捷键

这种东西,在网上有很多参考文章,因此在这我就不自己写了,不过传送门还是贴一下吧。

安装中文版本

插件名称:Chinese Language Pack for Visual Stidio Code

适用于VS Code的中文(简体)语言包新手建议先用中文熟悉,以后上手英文界面也不迟。

工作常用快捷键

  • 1.复制相同字段后的不同字段
export function getName() {}
export function getAge() {}

步骤一:鼠标选中export function
步骤二:ctrl+shift+p  ==> 选择所有
步骤三:移动键盘右键或者左键直到目标字段的首位
步骤四:ctrl+shift+键盘右键或者左键
步骤五:ctrl+c
步骤六:ctrl+v



工作常用插件

  • GitLens — Git supercharged: 查看代码输入者以及时间

前端工程师面试指南——CSS篇

常见的文字溢出隐藏功能实现

@mixin overflow-ellipsis{
    overflow:hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  };
div {
    @include  overflow-ellipsis;
}

底部粘连(stiky footer)布局

在网页设计中,Sticky footers设计是最古老和最常见的效果之一,大多数人都曾经经历过。它可以概括如下:如果页面内容不够长的时候,页脚块粘贴在视窗底部;如果内容足够长时,页脚块会被内容向下推送。下面将详细介绍sticky footer的4种实现方式。

  • 绝对定位: 常见的实现方法是对(.sticky)footer进行绝对定位,假设高度为50px。对父级(.box)进行相对定位,将html、body的高度设置为100%,父级(.box)的最小高度设置为100%,将(.content)内容部分设置padding-bottom为footer的高度,即50px,这里不用margin-bottom是因为会出现margin-bottom传递的情况
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      html,body {
        height: 100%
      }
      body {
        margin: 0
      }
      .box {
        position: relative;
        background-color: lightblue;
        min-height: 100%;
      }
      .content {
        padding-bottom: 50px;
      }
      .sticky {
        position: absolute;
        background-color: lightgreen;
        width: 100%;
        height: 50px;
        bottom: 0;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <main class="content">
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam eos architecto ratione culpa adipisci inventore
        ipsum eum esse, nam aperiam, non tempora perferendis doloribus cumque ducimus quidem consequuntur reprehenderit
        reiciendis!
        ...
      </main>
      <footer class="sticky"></footer>
    </div>
  </body>
</html>
  • calc: 上面的代码中,因为要实现最小高度100%的效果,给html、body都设置为高度100%,不利于代码扩展。下面使用100vh来代替100%,代码会简洁很多。内容部分(.content)设置最小高度为calc(100vh - 50px)即可。
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      html,body {
        margin: 0;
      }
      .content{
        background-color:lightblue;
        min-height: calc(100vh - 50px)
      }
      .sticky{
        background-color:lightgreen;
        height:50px;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <main class="content">
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam eos architecto ratione culpa adipisci inventore
        ipsum eum esse, nam aperiam, non tempora perferendis doloribus cumque ducimus quidem consequuntur reprehenderit
        reiciendis!
        ...
      </main>
      <footer class="sticky"></footer>
    </div>
  </body>
</html>
  • flex: 上面的代码中,如果sticky的底部高度发生了变化,则内容部分的代码也需要进行相应地调整。如果使用flex,则可以更加灵活。为父级(.box)设置flex、上下排列及最小高度为100vh,为内容部分(.content)设置flex:1即可
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      html,body {
        margin: 0;
      }
      .box {
        display:flex;
        flex-flow:column;
        min-height:100vh;
        background-color:lightblue;
      }
      .content {
        flex:1;
      }
      .sticky {
        background-color:lightgreen;
        height:50px;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <main class="content">
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam eos architecto ratione culpa adipisci inventore
        ipsum eum esse, nam aperiam, non tempora perferendis doloribus cumque ducimus quidem consequuntur reprehenderit
        reiciendis!
        ...
      </main>
      <footer class="sticky"></footer>
    </div>
  </body>
</html>
  • grid: 作为最新布局方式的grid当然也可以实现,而且代码更加简洁
<html>
  <head>
    <meta charset="utf-8" />
    <title>grid</title>
    <style>
      html,body {
        margin: 0;
      }
      .box {
        display:grid;
        grid-template-rows:1fr 50px;
        min-height:100vh;
      }
      .content {
        background-color:lightblue;
      }
      .sticky {
        background-color:lightgreen;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <main class="content">
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam eos architecto ratione culpa adipisci inventore
        ipsum eum esse, nam aperiam, non tempora perferendis doloribus cumque ducimus quidem consequuntur reprehenderit
        reiciendis!
        ...
      </main>
      <footer class="sticky"></footer>
    </div>
  </body>
</html>

CSS实现等高布局

  • 利用fill-available
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <style>
      html,body {
        margin: 0;
      }
      .inner{
        width:100px;
        height:-webkit-fill-available;
        margin:0 10px;
        display: inline-block;
        vertical-align: middle;
        background-color: pink;
      }
    </style>
  </head>
  <body>
    <div style="height: 100px;">
      <div class="inner">HTML</div>
      <div class="inner">CSS</div>
      <div class="inner">JS<br>jQyery<br>Vue</div>
    </div>
  </body>
</html>

CSS一些很重要的基础知识

有大牛已经整理好了,详情请点击这里

介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的?如何设置这两种模型

有两种, IE 盒子模型、W3C 盒子模型;

  • 盒模型: 内容(content)、填充(padding)、边界(margin)、 边框(border);
  • 区 别: 这两种盒子模型最主要的区别就是width的包含范围,在标准的盒子模型中,width指content部分的宽度,在IE盒子模型中,width表示content+padding+border这三个部分的宽度,故这使得在计算整个盒子的宽度时存在着差异。
  • 标准盒模型box-sizing: content-box;IE盒模型box-sizing:border-box

CSS选择符有哪些?哪些属性可以继承

  • id选择器( # myid)
  • 类选择器(.myclassname)
  • 标签选择器(div, h1, p)
  • 相邻选择器(h1 + p)
  • 子选择器(ul > li)
  • 后代选择器(li a)
  • 通配符选择器( * )
  • 属性选择器(a[rel = "external"])
  • 伪类选择器(a:hover, li:nth-child)

可继承的样式: font-size font-family color, UL LI DL DD DT;
不可继承的样式: border padding margin width height ;

CSS优先级算法如何计算

优先级就近原则,同权重情况下样式定义最近者为准;载入样式以最后载入的定位为准;

  • 同权重: 内联样式表(标签内部)> 嵌入样式表(当前文件中)> 外部样式表(外部文件中)
  • 不同权重: !important > id > class > tag,其中important 比 内联优先级高

CSS3新增伪类有那些

  • p:first-of-type: 选择属于其父元素的首个 <p> 元素的每个<p>元素。
  • p:last-of-type: 选择属于其父元素的最后 <p> 元素的每个<p>元素。
  • p:only-of-type: 选择属于其父元素唯一的 <p> 元素的每个 <p> 元素。
  • p:only-child: 选择属于其父元素的唯一子元素的每个<p>元素。
  • p:nth-child(2): 选择属于其父元素的第二个子元素的每个 <p> 元素。
  • :checked: 单选框或复选框被选中。
  • :disabled: 控制表单控件的禁用状态。

如何居中div

  • 水平居中:给div设置一个宽度,然后添加margin:0 auto属性
div {
  width: 200px;
  margin: 0 auto;
}
  • 让绝对定位的div居中
div {
  position: absolute;
  width: 300px;
  height: 300px;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  /* 方便看效果 */
  background-color: pink; 
}
  • 水平垂直居中一
 div {
   /* 相对定位或绝对定位均可 */
   position: relative;
   width: 500px;
   height: 300px;
   top: 50%;
   left: 50%;
   /* 外边距为自身宽高的一半 */
   margin: -150px 0 0 -250px;
   /* 方便看效果 */
   background-color: pink;
 }

水平垂直居中二

div {
  /* 相对定位或绝对定位均可 */
  position: absolute;
  width: 500px;
  height: 300px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* 方便看效果 */
  background-color: pink;
}

水平垂直居中三

.container {
  display: flex;
  height: 500px;
  width: 500px;
  border: 1px solid #ccc;
  /* 垂直居中 */
  align-items: center;
  /* 水平居中 */
  justify-content: center;
}
.container div {
  width: 100px;
  height: 100px;
  /* 方便看效果 */
  background-color: pink;
}

display有哪些值?说明他们的作用

  • block: 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。
  • none: 缺省值。象行内元素类型一样显示。
  • inline: 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。
  • inline-block: 默认宽度为内容宽度,可以设置宽高,同行显示。
  • list-item: 象块类型元素一样显示,并添加样式列表标记。
  • table: 此元素会作为块级表格来显示。
  • inherit: 规定应该从父元素继承 display 属性的值。

position的值relative和absolute定位原点

  • absolute: 生成绝对定位的元素,相对于值不为 static的第一个父元素进行定位。
  • fixed: 生成绝对定位的元素,相对于浏览器窗口进行定位。
  • relative: 生成相对定位的元素,相对于其正常位置进行定位。
  • static: 默认值,没有定位,元素出现在正常的流中(忽略 top, bottom, left, right z-index 声明)。
  • inherit: 规定从父元素继承 position 属性的值。

CSS3有哪些新特性

  • 新增各种CSS选择器: (: not(.input):所有 class 不是“input”的节点)
  • 圆角: (border-radius:8px)
  • 多列布局: (multi-column layout)
  • 阴影和反射: (Shadow\Reflect)
  • 文字特效: (text-shadow、)
  • 文字渲染: (Text-decoration)
  • 线性渐变: (gradient)
  • 旋转: (transform)
  • 缩放,定位,倾斜,动画,多背景。例如:transform:\scale(0.85,0.90)\ translate(0px,-30px)\ skew(-9deg,0deg)\Animation:

请解释一下CSS3的Flexbox(弹性盒布局模型),以及适用场景

  • 一个用于页面布局的全新CSS3功能,Flexbox可以把列表放在同一个方向(从上到下排列,从左到右),并让列表能延伸到占用可用的空间。
  • 较为复杂的布局还可以通过嵌套一个伸缩容器(flex container)来实现。
  • 采用Flex布局的元素,称为Flex容器(flex container),简称"容器"。
  • 它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称"项目"。
  • 常规布局是基于块和内联流方向,而Flex布局是基于flex-flow流可以很方便的用来做局中,能对不同屏幕大小自适应。
  • 在布局上有了比以前更加灵活的空间,使用指南

用纯CSS创建一个三角形的原理是什么

把上、左、右三条边框隐藏掉(颜色设为 transparent)

div {
  height: 0;
  width: 0;
  display: block;
  border: transparent solid 20px;
  border-left: #005AA0 solid 20px;
}

一个满屏 品 字布局 如何设计

简单的方式: 上面的div宽100%,下面的两个div分别宽50%,然后用float或者inline使其不换行即可

css多列等高如何实现

利用padding-bottom|margin-bottom正负值相抵;设置父容器设置超出隐藏(overflow:hidden),这样子父容器的高度就还是它里面的列没有设定padding-bottom时的高度,当它里面的任 一列高度增加了,则父容器的高度被撑到里面最高那列的高度,其他比这列矮的列会用它们的padding-bottom补偿这部分高度差。

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>CSS</title>
    <style type="text/css">
      * {
        margin: 0;
        padding: 0;
        font-size: 12px;
      }
      #wrap {
        overflow: hidden;
        width: 670px;
        margin: 10px auto;
        padding-bottom: 10px;
        position: relative;
      }
      .box {
        float: left;
        display: inline;
        margin-top: 10px;
        width: 190px;
        background: #c8c8c8;
        margin-left: 10px;
        padding: 10px;
        padding-bottom: 820px;
        margin-bottom: -800px;
      }
    </style>
  </head>
  <body>
    <div id="wrap">
      <div class="box">
        <h1>CSS实现三列DIV等高布局</h1>
        <p>
          这确实是个很简单的问题,也许你也已经相当熟悉,但很多人还不知道。 下面介绍的技术是一个简捷的小技巧,它一定可以帮助你解决这个头痛的问题。
        </p>
      </div>
      <div class="box">
        <h1>三列DIV等高</h1>
        <p></p>
      </div>
      <div class="box">
        <h1>CSS实现</h1>
        <p></p>
      </div>
    </div>
  </body>
</html>

li与li之间有看不见的空白间隔是什么原因引起的?有什么解决办法

行框的排列会受到中间空白(回车\空格)等的影响,因为空格也属于字符,这些空白也会被应用样式,占据空间,所以会有间隔,把字符大小设为0,就没有空格了。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      ul {
        list-style: none;
        /*解决空格问题*/
        font-size: 0;
      }
      li {
        display: inline-block;
        height: 70px;
        width: 150px;
        line-height: 70px;
        text-align: center;
        border: #005AA0 solid 2px;
        /*解决空格问题*/
        font-size: 16px;
      }
    </style>
  </head>
  <body>
    <ul>
      <li>li标签</li>
      <li>li标签</li>
      <li>li标签</li>
    </ul>
  </body>
</html>

为什么要初始化CSS样式

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。当然,初始化样式会对SEO有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下初始化。

子class选择器设置蓝色,父id选择器设置红色,最终会显示哪个

一定是子元素设置的选择器最优先,要不然页面就乱套了。所以答案是蓝色。

请解释一下为什么需要清除浮动?清除浮动的方式

清除浮动是为了清除使用浮动元素产生的影响。浮动的元素,高度会塌陷,而高度的塌陷使我们页面后面的布局不能正常显示。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      .box {
        width: 510px;
        border: #0000FF solid 1px;
      }
      .box:after {
        content: "";
        visibility: hidden;
        display: block;
        height: 0;
        clear: both;
      }
      .left {
        float: left;
        width: 250px;
        height: 100px;
        background-color: green;
      }
      .right {
        float: left;
        width: 250px;
        height: 100px;
        background-color: red;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="left"></div>
      <div class="right"></div>
    </div>
  </body>
</html>

解析原理:
(1) display:block 使生成的元素以块级元素显示,占满剩余空间
(2)height:0 避免生成内容破坏原有布局的高度
(3) visibility:hidden 使生成的内容不可见,并允许可能被生成内容盖住的内容可以进行点击和交互
(4)通过 content:"."生成内容作为最后一个元素,至于content里面是点还是其他都是可以的,例如oocss里面就有经典的 content:"."有些版本可能content 里面内容为空,一丝冰凉是不推荐这样做的
(5)zoom:1 触发IE hasLayout。

什么是外边距合并?

外边距合并指的是,当两个垂直外边距相遇时,它们将形成一个外边距,合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者。

CSS优化、提高性能的方法有哪些

  • 关键选择器(key selector),选择器的最后面的部分为关键选择器(即用来匹配目标元素的部分);
  • 如果规则拥有 ID 选择器作为其关键选择器,则不要为规则增加标签,过滤掉无关的规则(这样样式系统就不会浪费时间去匹配它们了);
  • 提取项目的通用公有样式,增强可复用性,按模块编写组件;
  • 增强项目的协同开发性、可维护性和可扩展性;
  • 使用预处理工具或构建工具(gulp对css进行语法检查、自动补前缀、打包压缩、自动优雅降级);

浏览器是怎样解析CSS选择器的

样式系统从关键选择器开始匹配,然后左移查找规则选择器的祖先元素。只要选择器的子树一直在工作,样式系统就会持续左移,直到和规则匹配,或者是因为不匹配而放弃该规则。

设置元素浮动后,该元素的display值是多少

自动变成了 display:block

让页面里的字体变清晰,变细用CSS怎么做

-webkit-font-smoothing: antialiased;

什么是CSS 预处理器 / 后处理器

  • 预处理器: LESS、Sass、Stylus,用来预编译Sass或less,增强了css代码的复用性,还有层级、mixin、变量、循环、函数等,具有很方便的UI组件模块化开发能力,极大的提高工作效率。
  • 后处理器: PostCSS,通常被视为在完成的样式表中根据CSS规范处理CSS,让其更有效;目前最常做的是给CSS属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。

em和rem的区别在哪里

  • em是相对长度单位,相对于当前对象内文本的字体尺寸;
  • em是CSS3新增的一个相对单位(root em,根em),这个单位与em有什么区别呢?区别在于使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。

假设高度已知,请写出三栏布局,其中左栏和右栏宽度各为300px,中间自适应

(1)利用浮动

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.float .left {
        float: left;
        width: 300px;
        background: red;
      }
      .layout.float .right {
        float: right;
        width: 300px;
        background: blue;
      }
      .layout.float .center {
        background: yellow;
      }
    </style>
  </head>
  <body>
    <section class="layout float">
      <article class="left-right-center">
        <div class="left"></div>
        <div class="right"></div>
        <div class="center">
          <h1>浮动解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
      </article>
    </section>
  </body>
</html>

(2)利用绝对定位

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.absolute .left-center-right>div {
        position: absolute;
      }
      .layout.absolute .left {
        left: 0;
        width: 300px;
        background: red;
      }
      .layout.absolute .center {
        left: 310px;
        right: 310px;
        background: yellow;
      }
      .layout.absolute .right {
        right: 0;
        width: 300px;
        background: blue;
      }
    </style>
  </head>
  <body>
    <section class="layout absolute">
      <article class="left-center-right">
        <div class="left"></div>
        <div class="center">
          <h1>绝对定位解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
        <div class="right"></div>
      </article>
    </section>
  </body>
</html>

(3)利用flexbox布局

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.flexbox .left-center-right {
        display: flex;
      }
      .layout.flexbox .left {
        width: 300px;
        background: red;
      }
      .layout.flexbox .center {
        flex: 1;
        background: green;
      }
      .layout.flexbox .right {
        width: 300px;
        background: yellow;
      }
    </style>
  </head>
  <body>
    <section class="layout flexbox">
      <article class="left-center-right">
        <div class="left"></div>
        <div class="center">
          <h1>flexbox解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
        <div class="right"></div>
      </article>
    </section>
  </body>
</html>

(4)表格布局法

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Layout</title>
    <style>
      html * {
        margin: 0;
        padding: 0;
      }
      .layout article div {
        min-height: 100px;
      }
      .layout.table .left-center-right {
        width: 100%;
        display: table;
        height: 100px;
      }
      .layout.table .left-center-right>div {
        display: table-cell;
      }
      .layout.table .left {
        width: 300px;
        background: black;
      }
      .layout.table .center {
        background: green;
      }
      .layout.table .right {
        width: 300px;
        background: burlywood;
      }
    </style>
  </head>
  <body>
    <section class="layout table">
      <article class="left-center-right">
        <div class="left"></div>
        <div class="center">
          <h1>表格布局解决方案</h1>
          <p>
            这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分 这是三栏布局中间部分
          </p>
        </div>
        <div class="right"></div>
      </article>
    </section>
  </body>
</html>

什么是BFC?它的渲染规则是什么?如何创建BFC?它的使用场景有哪些

(1)概念:
BFC是Block Formatting Context (块级格式化上下文)的缩写,它是W3C CSS 2.1 规范中的一个概念,是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局,并且与这个区域外部毫不相干。
(2)渲染规则:

  • 内部的box会在垂直方向,一个接一个的放置;
  • box垂直方向的距离由margin决定,属于同一个BFC的两个相邻box的margin会发生重叠;
  • 每个元素的margin box的左边,与包含块border box的左边相接触,即使存在浮动也是如此;
  • BFC的区域不会与float box重叠;
  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素;
  • 计算BFC的高度时,浮动元素也参与计算。

(3)创建BFC:

  • float属性不为none;
  • position属性值为absolute或fixed;
  • display属性值为inline-block,table-cell,table-caption,flex,inline-flex;
  • overflow属性值不为visible。

(4)使用场景:

  • 解决垂直方向边距重叠;
  • 解决和浮动元素的重叠;
  • 清除浮动

CSS中的文档流

普通流就是正常的文档流,在HTML里面的写法就是从上到下,从左到右的排版布局。其中涉及到了块状元素和内联元素。脱离文档流的几个属性:绝对定位(absolute)、固定定位(fixed)、浮动(float)。

CSS中的伪类

  • 概念: 伪类存在的意义是为了通过选择器找到那些不存在与DOM树中的信息以及不能被常规CSS选择器获取到的信息。
  • 拓展: 伪类由一个冒号:开头,冒号后面是伪类的名称和包含在圆括号中的可选参数。任何常规选择器可以再任何位置使用伪类。伪类语法不区别大小写。一些伪类的作用会互斥,另外一些伪类可以同时被同一个元素使用。并且,为了满足用户在操作DOM时产生的DOM结构改变,伪类也可以是动态的。

CSS中的伪元素

  • 概念: 伪元素在DOM树中创建了一些抽象元素,这些抽象元素是不存在于文档语言里的(可以理解为html源码)。比如:document接口不提供访问元素内容的第一个字或者第一行的机制,而伪元素可以使开发者可以提取到这些信息。并且,一些伪元素可以使开发者获取到不存在于源文档中的内容(比如常见的::before,::after)。
  • 拓展: 伪元素的由两个冒号::开头,然后是伪元素的名称。使用两个冒号::是为了区别伪类和伪元素(CSS2中并没有区别)。当然,考虑到兼容性,CSS2中已存的伪元素仍然可以使用一个冒号:的语法,但是CSS3中新增的伪元素必须使用两个冒号::。一个选择器只能使用一个伪元素,并且伪元素必须处于选择器语句的最后。
  • 分类: ::first-letter;::first-line;::before;::after;::selection;(对用户所选取的部分样式改变)

伪元素和伪类的区别

(1)CSS伪类: 用于向某些选择器添加特殊的效果。
(2)CSS伪元素: 用于将特殊的效果添加到某些选择器。伪元素代表了某个元素的子元素,这个子元素虽然在逻辑上存在,但却并不实际存在于文档树中。
(3)总结:

  • 伪类本质上是为了弥补常规CSS选择器的不足,以便获取到更多信息;
  • 伪元素本质上是创建了一个有内容的虚拟容器;
  • CSS3中伪类和伪元素的语法不同;
  • 可以同时使用多个伪类,而只能同时使用一个伪元素。

CSS隐藏元素的几种方法

(1)Opacity: 元素本身依然占据它自己的位置并对网页的布局起作用。它也将响应用户交互;
(2)Visibility: 与 opacity 唯一不同的是它不会响应任何用户交互。此外,元素在读屏软件中也会被隐藏;
(3)Display: display 设为 none 任何对该元素直接打用户交互操作都不可能生效。此外,读屏软件也不会读到元素的内容。这种方式产生的效果就像元素完全不存在;
(4)Position: 不会影响布局,能让元素保持可以操作;

float和display:inline-block;的区别

(1)文档流(Document flow):
浮动元素会脱离文档流,并使得周围元素环绕这个元素。而inline-block元素仍在文档流内。因此设置inline-block不需要清除浮动。当然,周围元素不会环绕这个元素,你也不可能通过清除inline-block就让一个元素跑到下面去。
(2)水平位置(Horizontal position):
很明显你不能通过给父元素设置text- align:center让浮动元素居中。事实上定位类属性设置到父元素上,均不会影响父元素内浮动的元素。但是父元素内元素如果设置了 display:inline-block,则对父元素设置一些定位属性会影响到子元素。(这还是因为浮动元素脱离文档流的关系)。
(3)垂直对齐(Vertical alignment):
inline-block元素沿着默认的基线对齐。浮动元素紧贴顶部。你可以通过vertical属性设置这个默认基线,但对浮动元素这种方法就不行了。这也是我倾向于inline-block的主要原因。
(4)空白(Whitespace):
inline-block包含html空白节点。如果你的html中一系列元素每个元素之间都换行了,当你对这些元素设置inline-block时,这些元素之间就会出现空白。而浮动元素会忽略空白节点,互相紧贴。

关于空白节点的解决方案

(1)删除html中的空白:
不要让元素之间换行,这可能比较蛋疼,但也是一种方法,特别是你元素不多的时候。
(2)使用负边距:
你可以用负边距来补齐空白。但你需要调整font-size,因为空白的宽度与这个属性有关系。
(3)给父元素设置font-size:0:
不管空白多大,由于空白跟font-size的关系,设置这个属性即可把空白的宽度设置为0.在实际使用的时候,你还需要给子元素重新设置font-size。

如何解决图片与文字的不对齐

(1)vertical-align: 最有效的一种方式;
(2)margin: 需要不断调试图片的高度,精确度难以保证;
(3)position: 同样是需要不断调试图片的高度,精确度难以保证。

如何实现点击radio的文字描述控制radio的状态

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <label for="man"></label>
    <input type="radio" id="man" name="sex" checked="checked" />

    <label for="women"><input type="radio" id="women" name="sex" />
    </label>
  </body>
</html>

position有哪些常用定位属性?定位原点是基于哪个位置

(1)relative : 相对定位,没有脱离文档流,依然占有文档空间,它是根据自身原本在文档流中的位置进行偏移;
(2)absolute: 绝对定位,脱离文档流,根据祖先类元素(父类以上)进行定位,而这个祖先类还必须是以position非static方式定位的,如果无父级是position非static定位时是以html作为原点定位。
(3)fixed: 固定定位,脱离文档流,根据浏览器窗口进行定位。

介绍一下box-sizing属性

(1)box-sizing属性: 主要用来控制元素的盒模型的解析模式。默认值是content-box。
(2)content-box属性值: 让元素维持W3C的标准盒模型。元素的宽度/高度由border + padding + content的宽度/高度决定,设置width/height属性指的是content部分的宽/高
(3)border-box属性值: 让元素维持IE传统盒模型(IE6以下版本和IE6~7的怪异模式)。设置width/height属性指的是border + padding + content

解释下浮动和它的工作原理?清除浮动的技巧

(1)原理: 浮动元素脱离文档流,不占据空间,浮动元素碰到包含它的边框或者浮动元素的边框停留。
(2)使用空标签清除浮动: 这种方法是在所有浮动标签后面添加一个空标签定义css clear:both. 弊端就是增加了无意义标签。
(3)使用overflow: 给包含浮动元素的父标签添加css属性 overflow:auto; zoom:1; zoom:1用于兼容IE6。
(4)使用after伪对象清除浮动: 该方法只适用于非IE浏览器。

#parent:after {
  content: ".";
  height: 0;
  visibility: hidden;
  display: block;
  clear: both;
}

浮动元素引起的问题

(1)父元素的高度无法被撑开,影响与父元素同级的元素
(2)与浮动元素同级的非浮动元素(内联元素)会跟随其后
(3)若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构

img的白边是因为什么

原因在于,img标签默认情况下display:inline-block;img在div中的白边就是因为inline-block;造成的,所以此时将img的display设置为block;白边就消失了

px、pt和em、rem的区别是什么

(1)px(pixel)指的是像素,是屏幕上显示数据的最基本的点,表示相对大小。不同分辨率下相同长度的px元素显示会不一样,比如同样是14px大小的字,在1366*768显示屏下会显示的小,在1024*768尺寸的显示器下会相对大点。
(2)pt(point)是印刷行业常用的单位,等于1/72英寸,表示绝对长度。
(3)em是相对长度单位,相对于当前对象内文本的字体尺寸,即em的计算是基于父级元素font-size的。

<body style="font-size:14px">
  <p style="font-size:2em">我这里的字体显示大小是28px(14px*2)</p>
  <div style="font-size:18px">
    <p style="font-size:2em">我这里显示字体大小是36px(18px*2),而不是上面计算的28px</p>
  </div>
</body>

(4)rem是css3新增的一个相对单位,与em的区别在于,它是相对于html根元素的。

<body style="font-size:14px">
  <p style="font-size:2rem">我这里的字体显示大小是28px(14px*2)</p>
  <div style="font-size:18px">
    <p style="font-size:2rem">我这里显示字体大小是28px(14px*2),因为我是根据html根元素的font-size大小进行计算的</p>
  </div>
</body>

谈谈nth-of-type() 选择器

:nth-of-type(n) 选择器匹配属于父元素的特定类型的第 N 个子元素的每个元素,n 可以是数字、关键词或公式。其中,Odd 和 even 是可用于匹配下标是奇数或偶数的子元素的关键词(第一个子元素的下标是 1)。

p:nth-of-type(2) {
  background: #ff0000;
}

p:nth-of-type(odd) {
  background: #ff0000;
}
p:nth-of-type(even) {
  background: #0000ff;
}

p:nth-of-type(3n+0) {
  background: #ff0000;
}

页面重排(Reflow)的概念和触发reflow的条件

(1)概念:
DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow。
(2)触发的条件:

  • 当你增加、删除、修改DOM节点时,会导致reflow或repaint;
  • 当你移动DOM的位置,或是搞个动画的时候;
  • 当你修改CSS样式的时候;
  • 当你resize窗口的时候(移动端没有这个问题),或是滚动的时候;
  • 当你修改网页默认字体的时候。

CSS哪些属性可以继承

  • 字体相关: line-height, font-family, font-size, font-style, font-variant, font-weight, font
  • 文本相关: letter-spacing, text-align, text-indent, text-transform, word-spacing
  • 列表相关: list-style-image, list-style-position, list-style-type, list-style
  • 颜色: color

前端工程师面试指南——Vue篇

v-if和v-show的区别是什么

(1)v-if是条件渲染指令,它根据表达式的真假来删除和插入元素;
(2)v-show也是条件渲染指令,和v-if指令不同的是,使用v-show指令的元素始终会被渲染到HTML,它只是简单地为元素设置CSS的style属性。
(3)总结:v-if 有更高的切换开销,而 v-show 有更高的出事渲染开销.因此,如果需要非常频繁的切换,那么使用v-show好一点;如果在运行时条件不太可能改变,则使用v-if 好点.
(4)点击查看更多

Vue生命周期

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了我们在不同阶段添加自己的代码的机会。

所以,每个Vue实例在被创建之前都要经过一系列的初始化过程,这个过程就是vue的生命周期。更具体的生命周期解释请点击这里

Vue的父子组件如何传值

组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。prop默认是单向绑定:当父组件的属性变化时,将传导给子组件,但是反过来不会。这是为了防止子组件无意修改了父组件的状态。更多知识

v-bind指令和v-on指令

(1)v-bind指令可以在其名称后面带一个参数,中间放一个冒号隔开,这个参数通常是HTML元素的特性(attribute),例如:v-bind:class
(2)v-on指令用于给监听DOM事件,它的用语法和v-bind是类似的,例如监听元素的点击事件:<a v-on:click="doSomething">

v-text和v-html的区别是什么

(1)v-text能够把html标签当做字符给渲染出来;

<-- 结果为TWO TODO<span>Vue</span>-->
<h1 v-text="title"></h1>
data() {
    return {
      title: "TWO TODO<span>Vue</span>",
  }
}

(2)v-html能够把html标签直接渲染出来;

<-- 结果为TWO TODOVue-->
<h1 v-html="title"></h1>
data() {
    return {
      title: "TWO TODO<span>Vue</span>",
  }
}

Vuejs中关于computed、methods、watch的区别是什么

(1)watch和computed都是以Vue的依赖追踪机制为基础的,它们都试图处理这样一件事情:当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动。
(2)对methods:methods里面是用来定义函数的,很显然,它需要手动调用才能执行。而不像watch和computed那样,“自动执行”预先定义的函数。
(3)methods 适合小的、同步的计算,而 watch 对于多任务、异步或者响应数据变化时的开销大的操作是有利的。
(4)点击查看更多

对于mvvm的理解

  • MVVM 是 Model-View-ViewModel 的缩写。

  • Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。

  • View 代表UI 组件,它负责将数据模型转化成UI 展现出来。

  • ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。

  • 在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

  • ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

Vue实现数据双向绑定的原理:Object.defineProperty()

vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

Vue的路由实现:hash模式 和 history模式

  • hash模式: 在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
    特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
  • history模式: history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。

vue路由的钩子函数

首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。

beforeEach主要有3个参数to,from,next:

  • to:route即将进入的目标路由对象,
  • from:route当前导航正要离开的路由
  • next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。

vuex是什么?怎么使用?哪种功能场景使用它?

只用来读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。

在main.js引入store,注入。新建了一个目录store,…… export 。
场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车、图片描述

  • state: Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
  • mutations: mutations定义的方法动态修改Vuex 的 store 中的状态或数据。
  • getters: 类似vue的计算属性,主要用来过滤一些数据。
  • action: actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

前端工程师面试指南——HTTP篇

http中的cookie字段,cookie一般存的是什么,session cookie怎么存在的?

  • Cookie通常也叫做网站cookie,浏览器cookie或者httpcookie,是保存在用户浏览器端的,并在发出http请求时会默认携带的一段文本片段。它可以用来做用户认证,服务器校验等通过文本数据可以处理的问题。
  • Session Cookie这个类型的cookie只在会话期间内有效,即当关闭浏览器的时候,它会被浏览器删除。设置session cookie的办法是:在创建cookie不设置Expires即可。参考

http请求方式有哪些?

  • HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
  • HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
  • 更多请看:HTTP请求方法

http的强缓存和协商缓存

  • 强制缓存: 在缓存数据未失效的情况下,可以直接使用缓存数据;在没有缓存数据的时候,浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应header中。它的缺点就是:生成的是绝对时间,但是客户端时间可以随意修改,会导致误差。
  • 协商缓存: 浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。它的缺点就是:Last-Modified 标注的最后修改时间只能精确到秒,如果有些资源在一秒之内被多次修改的话,他就不能准确标注文件的新鲜度了。如果某些资源会被定期生成,当内容没有变化,但 Last-Modified 却改变了,导致文件没使用缓存有可能存在服务器没有准确获取资源修改时间,或者与代理服务器时间不一致的情形。

https怎么进行加密

  • http是一种属于应用层的协议,简称为超文本传输协议。它有如下的缺点和优点
(1)通信使用明文(不加密),内容可能会被窃听

(2)不验证通信方的身份,因此有可能遭遇伪装

(3)无法证明报文的完整性,所以有可能已遭篡改


优点就是传输速度快。
  • HTTPS 并非是应用层的一种新协议。只是 HTTP 通信接口部分用 SSL (安全套接字层)和TLS (安全传输层协议)代替而已。即添加了加密及认证机制的 HTTP 称为 HTTPS ( HTTP Secure )。也就是说:HTTP + 加密 + 认证 + 完整性保护 = HTTPS。它是使用两把密钥的公开密钥加密。公开密钥加密使用一对非对称的密钥。一把叫做私钥,另一把叫做公钥。私钥不能让其他任何人知道,而公钥则可以随意发布,任何人都可以获得。使用公钥加密方式,发送密文的一方使用对方的公钥进行加密处理,对方收到被加密的信息后,再使用自己的私钥进行解密。利用这种方式,不需要发送用来解密的私钥,也不必担心密钥被攻击者窃听而盗走。

http状态码有那些?分别代表是什么意思

  • 100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
  • 200 OK 正常返回信息
  • 201 Created 请求成功并且服务器创建了新的资源
  • 202 Accepted 服务器已接受请求,但尚未处理
  • 301 Moved Permanently 请求的网页已永久移动到新位置。
  • 302 Found 临时性重定向。
  • 303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
  • 304 Not Modified 自从上次请求后,请求的网页未修改过,也就是协商缓存中返回的状态码。
  • 400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
  • 401 Unauthorized 请求未授权。
  • 403 Forbidden 禁止访问。
  • 404 Not Found 找不到如何与 URI 相匹配的资源。
  • 500 Internal Server Error 最常见的服务器端错误。
  • 503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)

https有几次握手

前端工程师面试指南——性能篇

在前端方面性能有哪些优化

原则: 多使用内存、缓存或者其他方法;减少CPU计算和网络。

加载资源优化:

  • 1、静态资源的压缩合并(require.js、webpack);
  • 2、静态资源缓存(通过连接名称控制缓存);
  • 3、使用CDN让资源加载更快(CDN全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近获得所需内容,解决internet网络拥挤的状况,提高用户访问网站的响应速度。);
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
      <link href="https://cdn.bootcss.bom/bootstrap/..." rel="stylesheet" />
      <script src="https://cdn.bootcss.com/zepto/..."></script>
  </head>
  <body>
  </body>
</html>
  • 4、使用SSR后端渲染;
  • 5、数据直接输出到HTML中。

渲染优化:

  • 1、CSS放前面,JS放后面;
  • 2、图片懒加载;
<!DOCTYPE html>
<html>
  <head>
      <meta charset="utf-8">
      <title></title>
  </head>
  <body>
    <!--事先使用一个小图片替换真正要显示的图片,等真正的图片完全加载后再替换小图片-->
    <img id="img1" src="small.jpg" data-realsrc="readlly.jpg" />
    <script>
      var img1 = document.getElementById('img1');
      img1.src = img1.getAttribute('data-realsrc');
    </script>
  </body>
</html>
  • 3、减少DOM查询,对DOM查询做缓存;
//未缓存DOM查询
var i;
for(i=0;i<document.getElementsByTagName('p').length;i++){
  //执行代码
}

//缓存了DOM查询
var i;
var pList = document.getElementsByTagName('p');
for(i=0;i<pList.length;i++){
  //执行代码
}
  • 4、减少DOM操作,多个操作尽量合并在一起执行;
var listNode = document.getElementById('list');

//要插入10个li标签
var frag = document.createDocumentFragment();
var x,li;
for(x=0;x<10;x++){
  li = document.createElement('li');
  li.innerHTML = 'List item'+x;
  frag.appendChild(li);
}
listNode.appendChild(frag);
  • 5、事件节流;
//让事件延迟执行
var textar = document.getElementById('text')
var timeoutId;
textar.addEventListener('keyup',function(){
  if(timeoutId){
    clearTimeout(timeoutId);
  }
  timeoutId = setTimeout(function(){
    //触发change事件
  },100)
})
  • 6、尽早执行操作,如DOMContentLoaded)。
window.addEventListener('load',function(){
  //页面的全部资源加载完才会执行,包括图片、视频等
})

document.addEventListener('DOMContentLoaded',function(){
  //DOM渲染完即可执行,此时图片、视频还可能没有加载完
})

切图仔手册

好惨啊,沦落为一个切图仔了

切一个背景色透明的图片

  • 选择切片 => 选择移动工具 => 点击选中切片的外面图层右键 => 点击画板 => 取消画板编组 => 点击选中切片的外面图层右键 => 点击各种不需要的图层将其隐藏掉 => 导出切片(大功告成)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.