天天看点

前端面试总结四

1.Canvas和SVG的区别

Canvas和SVG是html5支持的两种可视化技术。基于这两种技术,诞生了很多可视化工具。

Echarts是基于Canvas技术的可视化工具,底层封装了原生的JavaScript的绘图 API。我们很容易联想到另一个同样很优秀的web前端可视化库D3,D3是也最流行的可视化库之一,它被很多其他的表格插件所使用。D3底层基于SVG技术,与Canvas完全不一样,SVG的本质是一个XML 文档。

这两种方式在功能上是等同的,任何一种都可以用另一种来模拟。它们都是有效的图形工具,可用来快速创建在网页中显示的轻型图形;它们都使用 JavaScript 和 HTML;它们都遵守万维网联合会 (W3C) 标准。

Canvas和SVG都允许在浏览器中创建图形,但是它们在根本上是不同的。它们很不相同,他们各有强项和弱点。

Canvas 通过JavaScript来绘制2D图形。Canvas 是逐像素进行渲染的。在 canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。

SVG 是一种使用 XML 描述 2D 图形的语言。SVG 基于 XML,这意味着 SVG DOM 中的每个元素都是可用的。您可以为某个元素附加 JavaScript 事件处理器。在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。

区别:

Canvas 是基于像素的即时模式图形系统最适合较小的表面或较大数量的对象,Canvas不支持鼠标键盘等事件。

SVG 是基于形状的保留模式图形系统,更加适合较大的表面或较小数量的对象。Canvas和SVG在修改方式上还存在着不同。绘制Canvas对象后,不能使用脚本和 CSS 对它进行修改。因为 SVG 对象是文档对象模型的一部分,所以可以随时使用脚本和 CSS 修改它们。

Canvas:

1)依赖分辨率

2)不支持事件处理器

3)弱的文本渲染能力

4)能够以 .png 或 .jpg 格式保存结果图像

5)最适合图像密集型的游戏,其中的许多对象会被频繁重绘

SVG:

1)不依赖分辨率

2)支持事件处理器

3)最适合带有大型渲染区域的应用程序(比如谷歌地图)

4)复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)

5)不适合游戏应用

参考:Canvas和SVG的区别

2.HTML语义化(H5新特性)

语义化是指根据内容的结构化(内容语义化),选择合适的标签(代码语义化),便于开发者阅读和写出更优雅的代码的同时,让浏览器的爬虫和机器很好的解析。

为什么要语义化?

  • 有利于SEO,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重。
  • 语义化的HTML在没有CSS的情况下也能呈现较好的内容结构与代码结构。
  • 方便其他设备的解析
  • 便于团队开发和维护

<section></section>

定义文档中的主体部分的节、段。

<article></article>

一个特殊的section标签,比section有更明确的语义。定义来自外部的一个独立的、完整的内容块,例如什么论坛的文章,博客的文本。

<aside></aside>

用来装载页面中非正文的内容,独立于其他模块。例如广告、成组的链接、侧边栏。

<header></header>

定义文档、页面的页眉。通常是一些引导和导航信息,不局限于整个页面头部,也可以用在内容里。

<footer></footer>

定义了文档、页面的页脚,和header类似。

<nav></nav>

定义了一个链接组组成的导航部分,其中的链接可以链接到其他网页或者当前页面的其他部分。

<hgroup></hgroup>

用于对网页或区段(section)的标题元素(h1~h6)进行组合。

<figure></figure>

用于对元素进行组合。

<figcaption></figcaption>

为figure元素加标题。一般放在figure第一个子元素或者最后一个。

<details></details>

定义元素的细节,用户可以点击查看或者隐藏。

<summary></summary>

和details连用,用来包含details的标题。

<canvas></canvas>

用来进行canvas绘图。

<video></video>

定义视频。

<audio></audio>

定义音频。

<embed></embed>

定义嵌入网页的内容。比如插件。

<source></source>

该标签为媒介元素(比如video、audio)定义媒介元素。

<datalist id='dl'></datalist>

定义可选数据的列表,与input配合使用()可制作输入值的下拉列表。

<mark></mark>

在视觉上向用户展现出那些想要突出的文字。比如搜索结果中向用户高亮显示搜索关键词。

<meter [min/max/low/high/optimum/value]></meter>

度量衡,用红黄绿表示出一个数值所在范围。

<output></output>

定义不同类型的输出,样式与span无异。

<progress></progress>

进度条,运行中的进度。

<time></time>

定义日期或者时间。

<keygen></keygen>

定义加密内容。

<command></command>

定义命令行为。

参考:前端面试基础-html篇之H5新特性

3.常见的浏览器内核

  • Trident( MSHTML ):IE MaxThon TT The World 360 搜狗浏览器
  • Geckos:Netscape6及以上版本 FireFox Mozilla Suite/SeaMonkey
  • Presto:Opera7及以上(Opera内核原为:Presto,现为:Blink)
  • Webkit:Safari Chrome

4.SVG 路径 -

<path>

<path>

元素用于定义一个路径。

下面的命令可用于路径数据:

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Bézier curve
  • T = smooth quadratic Bézier curveto
  • A = elliptical Arc
  • Z = closepath

注意:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。

上面的例子定义了一条路径,它开始于位置150 0,到达位置75 200,然后从那里开始到225 200,最后在150 0关闭路径。

前端面试总结四
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <path d="M150 0 L75 200 L225 200 Z" />
</svg>
           

下面的例子创建了一个二次方贝塞尔曲线,A 和 C 分别是起点和终点,B 是控制点:

前端面试总结四
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
  stroke-width="3" fill="none" />
  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
  stroke-width="3" fill="none" />
  <path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
  fill="none" />
  <path d="M 100 350 q 150 -300 300 0" stroke="blue"
  stroke-width="5" fill="none" />
  <!-- Mark relevant points -->
  <g stroke="black" stroke-width="3" fill="black">
    <circle id="pointA" cx="100" cy="350" r="3" />
    <circle id="pointB" cx="250" cy="50" r="3" />
    <circle id="pointC" cx="400" cy="350" r="3" />
  </g>
  <!-- Label the points -->
  <g font-size="30" font="sans-serif" fill="black" stroke="none"
  text-anchor="middle">
    <text x="100" y="350" dx="-30">A</text>
    <text x="250" y="50" dy="-10">B</text>
    <text x="400" y="350" dx="30">C</text>
  </g>
</svg>
           

参考:SVG

<path>

5.HTML文件里开头的!Doctype有什么作用?

DOCTYPE是什么?

DOCTYPE是document type的简写,它并不是 HTML 标签,也没有结束标签,它是一种标记语言的文档类型声明,即告诉浏览器当前 HTML 是用什么版本编写的。DOCTYPE的声明必须是 HTML 文档的第一行,位于html标签之前。大多数Web文档的顶部都有doctype声明,它是在新建一个文档时,由Web创作软件草率处理的众多细节之一。很少人会去注意 doctype ,但在遵循标准的任何Web文档中,它都是一项必需的元素。doctype会影响代码验证,并决定了浏览器最终如何显示你的 Web文档。

DOCTYPE可声明三种 DTD 类型:严格、过渡以及框架集的 HTML 文档。

超文本严格文档类型定义:HTML Strict DTD

如果需要干净的标记,免于表现层的混乱,请使用此类型。请与层叠样式表配合使用。该 DTD 包含所有 HTML 元素和属性,但不包括展示性的和弃用的元素(比如 font)。不允许框架集(Framesets)

代码为:

DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//en" "http://www.w3. org/TR/html4/strict.dtd"

超文本过渡文档类型定义:HTML Transitional DTD

可包含 W3C 所期望移入样式表的呈现属性和元素。如果您的读者使用了不支持层叠样式表(CSS)的浏览器以至于您不得不使用 HTML 的呈现特性时,请使用此类型。该 DTD 包含所有 HTML 元素和属性,包括展示性的和弃用的元素(比如 font)。不允许框架集(Framesets)。

代码为:

DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"

超文本框架集文档类型定义:Frameset DTD

此类型定义应当被用于带有框架的文档。除 frameset 元素取代了 body 元素之外,Frameset DTD 等同于 Transitional DTD。该 DTD 等同于 HTML 4.01 Transitional,但允许框架集内容。

代码为:

DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"

DOCTYPE的作用是什么?

DOCTYPE声明中指出阅读程序应该用什么规则来解释文档中的标记。在Web文档的情况下,阅读程序通常是浏览器或者校验器这样的一个程序,规则是W3C所发布的一个文档类型定义 DTD 中包含的规则。制作一个符合标准的网页,DOCTYPE声明是不可缺少的,它在Web设计中用来说明你用的XHTML或者HTML是什么版本,如果不做DOCTYPE声明或声明不正确的情况下,将有可能导致你的标识与CSS失效,从而令你网页的布局变乱,造成网页在浏览器中不能正常的显示。我们还可以通过W3C提供的验证工具来检查页面的内容是否符合在DOCTYPE中声明的标准。

DTD是什么

DTD是文档类型定义(Document Type Definition)是一套为了进行程序间的数据交换而建立的关于标记符的语法规则。

它使用一系列合法的元素来定义文档的结构。是SGML的一部分,可被成行地声明于 XML 文档中,也可作为一个外部引用。

通过它,独立的团体可一致地使用某个标准的文档类型定义来交换数据。而您的应用程序也可使用某个标准的文档类型定义来验证从外部接收到的数据。

参考:https://www.jianshu.com/p/ce48b13a4e1e

6.BOM与DOM解释与分析

JavaScript分为 ECMAScript,DOM,BOM。

BOM(Browser Object Model)是指浏览器对象模型,它使 JavaScript 有能力与浏览器进行“对话”。

DOM (Document Object Model)是指文档对象模型,通过它,可以访问HTML文档的所有元素。

BOM:

BOM包含windows(窗口)、navigator(浏览器)、screen(浏览器屏幕)、history(访问历史)、location(地址)等。

(1)windows:

页面一旦加载,就会创建windows对象,无需自行创建。通过该对象可以获取文本框的宽高(windows.innerWidth)、浏览器窗口宽高(windows.outerWidth)、打开关闭新窗口(windows.open("/")、windows.close())等属性

(2)Navigator:

提供浏览器相关信息,包括浏览器名称、版本号、操作系统等等。

(3)screen:

提供用户屏幕相关信息。如高宽等(screen.width)。

(4)history:

记录访问历史。返回上次访问地址:history.back。返回上上次history.back(2),以此类推。

(5)location:

浏览器中的地址栏,可以完成刷新当前页面与放回首页、显示端口号、服务器等信息。

(6)alert弹出框、confirm确认框、prompt输入框

(7)计时器:

前端面试总结四

DOM:

DOM就是把html里的各种数据当做对象处理的一种思路。

(1)通过document.getElementById获取了id=d1的div标签对应的元素节点

然后通过attributes 获取了该节点对应的属性节点

接着通过childNodes获取了内容节点

(2)获取节点:

前端面试总结四

(3)获取节点的属性:

前端面试总结四

(4)DOM的事件:

前端面试总结四

参考:BOM与DOM解释与分析

7.CSS外边距折叠(Margin Collapse)

前端面试总结四

什么是外边距叠加:

W3C对于外边距叠加的定义:

In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.

即,在CSS中,两个或多个毗邻的普通流中的盒子(可能是父子元素,也可能是兄弟元素)在垂直方向上的外边距会发生叠加,这种形成的外边距称之为外边距叠加。

  • 毗邻

    毗邻说明了他们的位置关系,没有被padding、border、clear和line box分隔开。

  • 两个或多个

    两个或多个盒子是指元素之间的相互影响,单个元素不会存在外边距叠加的情况。

  • 垂直方向

    只有垂直方向的外边距会发生外边距叠加。水平方向的外边距不存在叠加的情况。

  • 普通流(in flow)

    啥为普通流?W3C只对out of flow作了定义:

    An element is called out of flow if it is floated, absolutely positioned, or is the root element.An element is called in-flow if it is not out-of-flow.

    从定义中我们可以知道只要不是float、absolutely positioned和root element时就是in flow。

什么时候会发生外边距叠加:

外边距叠加存在两种情况:一是父子外边距叠加;二是兄弟外边距叠加。

  • 都属于普通流的块级盒子且参与到相同的块级格式上下文中
  • 没有被padding、border、clear和line box分隔开
  • 都属于垂直毗邻盒子边缘:

    (1)盒子的top margin和它第一个普通流子元素的top margin

    (2)盒子的bottom margin和它下一个普通流兄弟的top margin

    (3)盒子的bottom margin和它父元素的bottom margin

    (4)盒子的top margin和bottom margin,且没有创建一个新的块级格式上下文,且有被计算为0的min-height,被计算为0或auto的height,且没有普通流子元素

如何避免外边距叠加:

只要破坏上面讲到的四个条件中的任何一个即可:毗邻、两个或多个、普通流和垂直方向。

  • 浮动元素不会与任何元素发生叠加,也包括它的子元素
  • 创建了BFC的元素不会和它的子元素发生外边距叠加
  • 绝对定位元素和其他任何元素之间不发生外边距叠加,也包括它的子元素
  • inline-block元素和其他任何元素之间不发生外边距叠加,也包括它的子元素
  • 普通流中的块级元素的margin-bottom永远和它相邻的下一个块级元素的margin-top叠加,除非相邻的兄弟元素clear
  • 普通流中的块级元素(没有border-top、没有padding-top)的margin-top和它的第一个普通流中的子元素(没有clear)发生margin-top叠加
  • 普通流中的块级元素(height为auto、min-height为0、没有border-bottom、没有padding-bottom)和它的最后一个普通流中的子元素(没有自身发生margin叠加或clear)发生margin-bottom叠加
  • 如果一个元素的min-height为0、没有border、没有padding、高度为0或者auto、不包含子元素,那么它自身的外边距会发生叠加

参考:深入理解CSS外边距折叠(Margin Collapse)

8.伪类和伪元素

伪类:

CSS3给出的定义是:

The pseudo-class concept is introduced to permit selection based on information that lies outside of the document tree or that cannot be expressed using the other simple selectors.

伪类存在的意义是为了通过选择器,格式化DOM树以外的信息以及不能被常规CSS选择器获取到的信息。

伪类的功能有两种:

(1)格式化DOM树以外的信息。

比如:

<a>

标签的:link、:visited 等。这些信息不存在于DOM树中。

(2)不能被常规CSS选择器获取到的信息。

比如:要获取第一个子元素,我们无法用常规的CSS选择器获取,但可以通过 :first-child 来获取到。

伪元素:

CSS3给出的定义如下:

Pseudo-elements create abstractions about the document tree beyond those specified by the document language. For instance, document languages do not offer mechanisms to access the first letter or first line of an element’s content. Pseudo-elements allow authors to refer to this otherwise inaccessible information. Pseudo-elements may also provide authors a way to refer to content that does not exist in the source document (e.g., the ::before and ::after pseudo-elements give access to generated content).

伪元素可以创建一些文档语言无法创建的虚拟元素。比如:文档语言没有一种机制可以描述元素内容的第一个字母或第一行,但伪元素可以做到(::first-letter、::first-line)。同时,伪元素还可以创建源文档不存在的内容,比如使用 ::before 或 ::after。

伪类和伪元素的区别(CSS3下的区别):

伪类其实是弥补了CSS选择器的不足,用来更方便地获取信息。

<ul>
    <li>11111</li>
    <li>22222</li>
</ul>   
           
li:first-child {
    color: red;   
}
// 选择器不能直接选取第一个子元素
// 伪类弥补了选择器的不足
           

而伪元素本质上是创建了一个虚拟容器(元素),我们可以在其中添加内容或样式。

<p>
    <span class="first-letter">H</span>ello, World
</p>
           
.first-letter {
  color: red;
}
           

上面的代码其实就是:

p::first-letter {
  color: red;
}
           

参考:我终于理解了伪类和伪元素

9.跨域资源共享CORS

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

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

两种请求:

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

(1)请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

简单请求:

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
           

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

(1)Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

withCredentials 属性:

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
           

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

非简单请求:

预检请求:

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
           

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。

浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,"预检"请求的头信息包括两个特殊字段。

(1)Access-Control-Request-Method

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

(2)Access-Control-Request-Headers

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

预检请求的回应:

服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
           

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
           

服务器回应的其他CORS相关字段如下。

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
           

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

浏览器的正常请求和回应:

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

下面是"预检"请求之后,浏览器的正常CORS请求。

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
           

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应。

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
           

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

与JSONP比较:

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

参考:跨域资源共享CORS详解

10.Ajax及其底层实现

什么是Ajax?

Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术,是异步刷新技术,用来在当前页面内响应不同的请求内容。

  • Ajax = 异步 JavaScript 和 XML。
  • Ajax 是一种用于创建快速动态网页的技术。
  • 通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新

为什么需要Ajax

 需求:

  有的时候我们需要将本次的响应结构和前面的响应结果内容在同一个页面中展示给用户。

解决:

  1、原始:在后台服务器端将多次响应的内容重新拼接成一个jsp页面,响应,但是这样会造成很多响应内容被重复响应,资源浪费。

  2、使用Ajax技术,实现响应局部内容(异步刷新技术)。

Ajax底层实现:

Ajax访问原理:

  Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。

Ajax的基本使用流程:

  • 创建Ajax引擎对象(XmlHttpRequest、ActiveObject)
  • 复写onreadystatement函数(状态码、响应状态码的使用,操作Dom对象)
  • 发送请求(get/post,参数传递)

Ajax的状态码(readyState)和响应状态码(status):

 Ajax状态码:

  运行Ajax所经历过的几种状态,无论访问是否成功都将响应的步骤,可以理解成为Ajax运行步骤。如:正在发送,正在响应等,由 Ajax对象与服务器交互时所得 ajax.readyState:0,1,2,3,4

  • 0 -(未初始化)还没有调用send()
  • 1 - (载入)已调用send()方法,正在发送请求
  • 2 - (载入完成)send()方法执行完成
  • 3 - (交互)正在解析响应内容
  • 4 - (完成)响应内容解析完成,可以在客户端调用上面的状态,其中“0”状态是在定义后自动具有的状态码值,而对于成功访问的状态(得到信息)我们大多数采用“4”进行判断。

这就是我们在使用Ajax时为什么采用下面的方式判断所获得的信息是否正确的原因。

Ajax响应状态码:

  无论Ajax访问是否成功,由http协议根据所提交的信息,服务器所返回的http头信息代码,该信息使用“ajax.status”所获得(常见的是200成功,404页面找不到)

  总的来说:status体现的是服务器对请求的反馈,而readystate表明客户端与客户的交互状态过程。

Ajax的异步和同步:

  同步的意思是当js代码加载到当前Ajax的时候会把页面里所有的代码停止加载,页面出现假死状态(等待Ajax执行完),当这个Ajax执行完毕后才会继续运行其他代码页面假死状态解除。

  异步则这个Ajax代码运行中的时候其他代码一样可以运行。

   jquery的async:false/true,这个属性默认是true:异步,false:同步。一般使用默认的异步。

Ajax的请求:

 get:

  get是最常见的请求类型,最常用于向服务器查询某些信息,它适用于当url完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存的情况下。

使用get方式发送请求时,数据被追加到open()方法中url的末尾

  

ajax.open(type,url,async)

type:请求方式,url:请求地址,async:异步/同步

  

ajax.open("get","AjaxServlet?username=zhangsan&password=333")

 post:

  使用频率仅次于get的是post请求,通常用于服务器发送应该被保存数据。“post"方法常用于html表单。它在请求主体中包含额外数据且这些数据常存储到服务器上的数据库中。相同url的重复post请求从服务器得到的响应可能不同,同时不应该缓存使用这个方法的请求

  post请求应该把数据作为请求的主体提交,而GET请求传统上不是这样。post请求的主体可以包含非常多的数据,而且格式不限。在open()方法第一个参数的位置传入"post”,就可以初始化一个post请求

  

ajax.open("post","AjaxServlet");

  注意post的方式传数据必须设置请求头,很多人写底层Ajax会漏掉这行代码

  

ajax.setRequestHeader("content-type","application/x-www-form-urlencoded");

  接下来要以适当的格式创建一个字符串,并使用send()方法发送

  

ajax.send("username=zhangsan&password=333");

Ajax的底层实现代码:

测试页面 ajax.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ajax底层实现</title>
<script type="text/javascript" src="js/jquery-3.3.1.min.js" ></script>
<script type="text/javascript" src="js/ajax.js" ></script>
</head>
<body>
<button id='ajaxGet'>ajax技术 get请求</button>
<button id='ajaxPost'>ajax技术 post请求</button>
<hr/>
<span id='span'></span>
</body>
</html>
           

AjaxServlet:

package com.demo.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 功能:ajax底层实现
 * 时间:2018-12-9
 * @author
 *
 */
@WebServlet("/AjaxServlet")
public class AjaxServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//设置请求编码
		request.setCharacterEncoding("utf-8");
		//设置响应编码格式
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		//获取请求信息
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		//处理请求信息
		//响应请求信息
		response.getWriter().write("username:"+username+",password:"+password);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}
           

ajax.js:

$(function() {
	//get请求方式
	$('#ajaxGet').on("click",function() {
		console.log('get');
		//获取ajax引擎对象
		var ajax;
		if (window.XMLHttpRequest) {
			ajax = new XMLHttpRequest();
		} else {//ie比较特殊
			ajax = new ActiveObject("Msxm12.XMLHTTP");
		}
		//复写onreadyStatement函数 当ajax状态值发生改变时 函数就会被触发事件onreadystatechange 
		ajax.onreadystatechange = function() {
			//判断ajax状态值
			if (ajax.readyState == 4) {
				//判断ajax状态码
				if (ajax.status == 200) {
					//获取响应内容(响应内容的格式)
					var result = ajax.responseText;
					//获取元素对象、处理响应内容(js操作dom)
					console.log(result);
					$('#span').text(result);
				}
			}
		}
		//发送get请求
		ajax.open("get","AjaxServlet?username=zhangsan&password=333");
		ajax.send(null);
	});
	//post的请求方式
	$('#ajaxPost').on("click",function() {
		console.log('post');
		//获取ajax引擎对象
		var ajax;
		if (window.XMLHttpRequest) {
			ajax = new XMLHttpRequest();
		} else {//ie比较特殊
			ajax = new ActiveObject("Msxm12.XMLHTTP");
		}
		//复写onreadyStatement函数
		ajax.onreadystatechange = function() {
			//判断ajax状态值
			if (ajax.readyState == 4) {
				//判断ajax状态码
				if (ajax.status == 200) {
					//获取响应内容(响应内容格式)
					var result = ajax.responseText;
					//获取元素对象、处理响应内容(js操作dom)
					console.log(result);
					$('#span').text(result);
				}
			}
		}
		//发送post请求
		ajax.open("post","AjaxServlet");
		//设置请求头
		ajax.setRequestHeader("content-type","application/x-www-form-urlencoded");
		//传递参数
		ajax.send("username=zhangsan&password=333");
	});
})
           

封装自己的Ajax:

测试页面 myAjax.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ajax底层实现</title>
<script type="text/javascript" src="js/jquery-3.3.1.min.js" ></script>
<script type="text/javascript" src="js/myAjax.js" ></script>
<script type="text/javascript" src="js/myAjaxu.js" ></script>
</head>
<body>
<button id='ajaxGet'>ajax技术 get请求</button>
<button id='ajaxPost'>ajax技术 post请求</button>
<hr/>
<span id='span'></span>
</body>
</html>
           

封装好的myAjax.js:

/**
 * method:请求方法,值为get/post
 * url:请求地址
 * data:请求数据,没有值需要传入null,有值则传入字符串数据,格式为"a=1&b=2"
 * deal200:接受一个带有一个形参的js函数对象,形参接收的实参是ajax引擎对象
 * deal404:接受一个带有一个形参的js函数对象(后面还有500等等)
 * 
 */
function myAjax(method, url, data, deal200, deal404, async) {
	console.log("调用success");
	//获取ajax引擎对象
	var ajax = getAjax();
	//复写onreadyStatement函数
	ajax.onreadystatechange = function() {
		//判断ajax状态值
		if (ajax.readyState == 4) {
			//判断ajax状态码
			if (ajax.status == 200) {
				if (deal200) {
					deal200(ajax);
				}
			} else if (ajax.status == 404) {
				if (deal404) {
					deal404(ajax);
				}
			}
		}
	}
	//发送请求
	if("get" == method) {
		ajax.open("get",url+(data == null ? "" : "?")+data,async);
		ajax.send(null);
	} else if ("post" == method) {
		//发送请求
		ajax.open("post",url,async);
		//设置请求头
		ajax.setRequestHeader("content-type","application/x-www-form-urlencoded");
		//参数传递
		ajax.send(data);
	}
};

//获取ajax引擎对象函数
function getAjax() {
	var ajax;
	if (window.XMLHttpRequest) {
		ajax = new XMLHttpRequest();
	} else {//ie比较特殊
		ajax = new ActiveObject("Msxm12.XMLHTTP");
	}
	return ajax;
};
           

测试js myAjax.js:

$(function() {
	//get请求方式
	$('#ajaxGet').on("click",function() {
		//获取请求数据(我这没连接数据库,自己给了两个请求数据)
		var data = "username=zhangsan&password=333";
		myAjax("get","AjaxServlet",data,function(ajax) {
			//获取响应信息
			var result = ajax.responseText;
			//处理响应信息
			$('#span').text(result);
		});
	});
	//post请求方式
	$('#ajaxPost').on("click",function() {
		//获取请求数据(我这没连接数据库,自己给了两个请求数据)
		var data = "username=zhangsan&password=333";
		myAjax("post","AjaxServlet",data,function(ajax){
			//获取响应信息
			var result = ajax.responseText;
			//处理响应信息
			$('#span').text(result);
		});
	});
})
           

参考:什么是Ajax及其底层实现

11.axios

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:

  • 从浏览器中创建 XMLHttpRequest
  • 从 node.js 发出 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防止 CSRF/XSRF

使用:

axios并没有install 方法,所以是不能使用vue.use()方法的。

解决方法有很多种:

  • 结合 vue-axios使用
  • axios 改写为 Vue 的原型属性
  • 结合 Vuex的action

vue-axios使用:

首先在主入口文件main.js中引用

import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios,axios);
           

之后就可以使用了,在组件文件中的methods里去使用了

getdata(){
            this.axios.get('/vue-demo/api/getdata').then((response)=>{
                console.log(response.data)
            }).catch((response)=>{
                console.log(response)
            })
        }
           

axios 改写为 Vue 的原型属性:

首先在主入口文件main.js中引用,之后挂在vue的原型链上

import axios from 'axios'
Vue.prototype.$ajax= axios
           

在组件中使用

this.$ajax.get('api/getNewsList').then((response)=>{
        this.newsList=response.data.data;
      }).catch((response)=>{
        console.log(response);
      })
           

Vuex的action:

在vuex的仓库文件store.js中引用,使用action添加方法

import Vue from 'Vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
const store = new Vuex.Store({
  // 定义状态
  state: {
    user: {
      name: 'xiaoming'
    }
  },
  actions: {
    // 封装一个 ajax 方法
    login (context) {
      axios({
        method: 'post',
        url: '/user',
        data: context.state.user
      })
    }
  }
})
export default store
           

在组件中发送请求的时候,需要使用 this.$store.dispatch

methods: {
  submitForm () {
    this.$store.dispatch('login')
  }
}
           

拦截器:

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });
           

如果你想在稍后移除拦截器,可以这样:

var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
           

与ajax区别

Ajax:

Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。

  • Ajax = 异步 JavaScript 和 XML(标准通用标记语言的子集)。
  • Ajax 是一种用于创建快速动态网页的技术。
  • Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
  • 通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
  • 传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。

参考:Vue基础入门(二)axios简介

12.微信小程序与H5的区别

(1)运行环境不同

传统的HTML5的运行环境是浏览器,包括webview,而微信小程序的运行环境并非完整的浏览器,是微信开发团队基于浏览器内核完全重构的一个内置解析器,针对小程序专门做了优化,配合自己定义的开发语言标准,提升了小程序的性能。

(2)开发成本不同

只在微信中运行,所以不用再去顾虑浏览器兼容性,不用担心生产环境中出现不可预料的奇妙BUG

(3)获取系统级权限不同

系统级权限都可以和微信小程序无缝衔接

(4)应用在生产环境的运行流畅度

长久以来,当HTML5应用面对复杂的业务逻辑或者丰富的页面交互时,它的体验总是不尽人意,需要不断的对项目优化来提升用户体验。但是由于微信小程序运行环境独立

13.小程序的双向绑定和vue哪里不一样

小程序直接this.data的属性是不可以同步到视图的,必须调用:

this.setData({
          noBind:true
})
           

14.新版小程序登录授权

主动登录:

由于APP中有些页面默认需要登录的,如[个人中心]页面,需要登录获取到用户信息,才能继续操作。这样的页面就需要在每次进入页面(onShow)时判断是否授权了。

profile页面

onShow () {
    login(() => {
        do something...
    })
}
           

关于登录授权相关的逻辑都可以封装在handleLogin.js中

handleLogin.js

// 开始login
function login (callback) {
  wx.showLoading()
  wx.login({
    success (res) {
      if (res.code) {
        // 登录成功,获取用户信息
        getUserInfo(res.code, callback)
      } else {
        // 否则弹窗显示,showToast需要封装
        showToast()
      }
    },
    fail () {
      showToast()
    }
  })
}

// 获取用户信息
function getUserInfo (code, callback) {
  wx.getUserInfo({
    // 获取成功,全局存储用户信息,开发者服务器登录
    success (res) {
      // 全局存储用户信息
      store.commit('storeUpdateWxUser', res.userInfo)
      postLogin(code, res.iv, res.encryptedData, callback)
    },
    // 获取失败,弹窗提示一键登录
    fail () {
      wx.hideLoading()
      // 获取用户信息失败,清楚全局存储的登录状态,弹窗提示一键登录
      // 使用token管理登录态的,清楚存储全局的token
      // 使用cookie管理登录态的,可以清楚全局登录状态管理的变量
      store.commit('storeUpdateToken', '')
      // 获取不到用户信息,说明用户没有授权或者取消授权。弹窗提示一键登录,后续会讲
      showLoginModal()
    }
  })
}

// 开发者服务端登录
function postLogin (code, iv, encryptedData, callback) {
  let params = {
    code: code,
    iv: iv,
    encryptedData: encryptedData
  }
  request(apiUrl.postLogin, params, 'post').then((res) => {
    if (res.code == 1) {
      wx.hideLoading()
      // 登录成功,
      // 使用token管理登录态的,存储全局token,用于当做登录态判断,
      // 使用cookie管理登录态的,可以存任意变量当做已登录状态
      store.commit('storeUpdateToken', res.data.token)
      callback && callback()
    } else {
      showToast()
    }
  }).catch((err) => {
    showToast()
  })
}

// 显示toast弹窗
function showToast (content = '登录失败,请稍后再试') {
  wx.showToast({
    title: content,
    icon: 'none'
  })
}
           

到此为止,登录就算完成了。不管使用token还是cookie都可以,都能有正常的登录态了,可以执行后续操作。

整个流程是 wx.login => wx.getUserInfo => 开发者服务器登录postLogin。

调用接口:

某些页面默认不需要登录,但某些用户操作事件是需要登录状态的,所以一者可以判断全局存储的登录状态管理的变量,如果为false,那么直接可以弹窗提示需要一键登录。二者如果全局状态为true,则调用接口看接口返回的code是否是未登录状态(此情况一般来说是登录态过期),未登录的话也弹窗提示需要一键登录。

某页面(需登录的用户操作)

getPlayer () {
    // 判断全局是否有登录状态,如果没有直接弹窗提示一键登录
    isLogin(() => {
        let params = {
            token: this.token
        }
        request(apiUrl.getPlayer, params).then((res) => {
            // TODO: 删除打印
            if (res.code === 1) {
                store.commit('storeUpdateUser', res.data.player_info)
            } else {
                // 获取失败了,如果是code是未登录,则去登录,然后执行回调函数this.getPlayer
                // 如果code不是未登录,直接弹窗报错误信息
                handleError(res, this.getPlayer)
            }
        }).catch((err) => {
            handleError(err)
        })
    })
}
           

handleLogin.js

// 判断是否登录
function isLogin (callback) {
  let token = store.state.token
  if (token) {
    // 如果有全局存储的登录态,暂时认为他是登录状态
    callback && callback()
  } else {
    // 如果没有登录态,弹窗提示一键登录
    showLoginModal()
  }
}

// 接口调用失败处理,
function handleError (res, callback) {
  // 规定-3041和-3042分别代表未登录和登录态失效
  if (res.code == -3041 || res.code == -3042) {
    // 弹窗提示一键登录
    showLoginModal()
  } else if (res.msg) {
    // 弹窗显示错误信息
    showToast(res.msg)
  }
}
           

到此为止,需要登录的用户操作就可以处理了。如果全局登录状态变量为true,先去调用接口,根据返回的信息是否是未登录再处理。

弹窗提示:

由于微信小程序授权的接口wx.getUserInfo和wx.authorize中scope 为 “scope.userInfo” ,新版中调用这两个API是不会主动触发弹出授权窗口的。需要使用

<button open-type="getUserInfo"></button>

方法。

上面代码中多处出现的showLoginModal是用于显示一键登录的。如下:

handleLogin.js

// 显示一键登录的弹窗
function showLoginModal () {
  wx.showModal({
    title: '提示',
    content: '你还未登录,登录后可获得完整体验 ',
    confirmText: '一键登录',
    success (res) {
      // 点击一键登录,去授权页面
      if (res.confirm) {
        wx.navigateTo({
          url: '授权登录页面地址',
        })
      }
    }
  })
}
           

关于授权登录,我们做了一个专门的页面处理,此处的button为

<button type="primary" v-if="canIUse" open-type="getUserInfo" @getuserinfo="getUserInfo">微信登录</button>

。如下:

getUserInfo (e) {
    if (e.target.userInfo) {
        // 点击Button弹窗授权,如果授权了,执行login
        // 因为Login流程中有wx.getUserInfo,此时就可以获取到了
        login(() => {
            // 登录成功后,返回
            wx.navigateBack()
        })
    }
}
           

参考:新版小程序登录授权

15.ES5和ES6的继承

/* es5 创建一个Person 构造函数 */
function Person (name,age) {
    this.name = name
    this.age = age
}
/* 定义原型链上的方法sayholle */
/* 为什么要将方法定义在原型上,定义在原型上的方法,所有的实例对象都共享 
 不会出现没实列一个对象都重新创建一个这个方法 */
Person.prototype.sayholle = function () {
    console.log(this.name+' hello'+ this.age)
}

let person1 = new Person('czcz','23')
person1.sayholle()  //  czcz holle23

/* es6 使用class创建一个对象 */
class Personclass {
    /* 实列对象时默认调用的方法 */
    constructor (name,age) {
        this.name = name
        this.age = age
    }
    /* 定义一个方法,相对构造上述原型链上的方法 */

    sayholle () {
        console.log(this.name+' hello'+ this.age)
    }
}
let person2 = new Personclass('czcz','26')
person2.sayholle()  //  czcz holle23
           

es5中继承的方式:

(1)原型链继承

/* es5原型链继承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}

function Child (sex) {
    this.sex = sex;
}
Child.prototype = new Person();
Child.prototype.hh = 'ddd'
let p = new Child('man')
console.log(p) // 
console.log(new Person());
let p2 = new Child('man')
p2.__proto__.age = '36'
/* 给p2原型上的age赋值,则导致p上的age也改变,父类构造函数上的属性被所有子类共享 */
console.log(p) // 36
/* 缺点,child 新增的属性只能在new person 以后,创建实列时无法向
    父类的构造函数传送参数,因为直接是指定了原型,所有也不能实现多继承
    父类构造函数上的属性被所有子类共享
*/
           

(2)构造函数继承

/* es5构造函数继承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}
function Child (sex,name,age) {
    this.sex = sex
    Person.call(this,name,age)
}

let p = new Child('man','czklove','13')
console.log(p);
/* 
    可以是先多继承,只要执行多个call
    创建实列时能像父类构造函数船体参数
    不会出现父类属性,所有子类构造函数共享
    缺点,
    不能继承父类原型链上的方法,如上面不能掉用sayhello方法
    子类构造函数的实列,原型链上并不存在父类构造函数,
    因为不能继承父类原型链上的函数,所有要继承函数只能定义在父类构造函数上,
    不能达到函数复用
 */
           

(3)组合继承,融合了上面两种方式

/* es5组合继承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}
function Child (sex,name,age) {
    this.sex = sex
    Person.call(this,name,age)
}
Child.prototype = new Person();
/* 重新设置一下constructor 不设置也没有影响,严谨的角度上来说还是设置一下*/
/* 不设置的话,__proto__ 上时没有 constructor */
/* 正常来讲constructor是指向自身的 */
Child.prototype.constructor = Child;
let p = new Child('man','czklove','13')
let p1 = new Child('man','czklove1','16')
p.sayhello(); // czklove holle13
console.log(p);


child {sex: "man", name: "czklove", age: "13"}

age: "13"
name: "czklove"
sex: "man"
__proto__: person

age: undefined
constructor: ƒ Child(sex,name,age)
name: undefined
__proto__: Object



    /*
    组合继承,既能达到对父类属性的继承,也能继承父类原型上的方法
    父类属性继承也不会在所有子类的实列上共享
    唯一缺点,子类原型上有父类构造函数的属性,也就是多了一份属性
    */


console.log(p.__proto__ === Child.prototype) //true
           

(4)优化版的组合继承(寄生组合继承)

/* es5寄生组合继承 */
function Person (name,age) {
    this.name = name
    this.age = age
}
Person.prototype.sayhello = function () {
    console.log(this.name+' hello'+ this.age)
}
function Child (sex,name,age) {
    this.sex = sex
    Person.call(this,name,age)
}
Child.prototype = Object.create(Person.prototype);
Child.prototype.constructor = Child
let p = new Child('man','czklove','13')
p.sayhello(); // czklove holle13
console.log(p);
/*  child {sex: "man", name: "czklove", age: "13"}
    age: "13"
    name: "czklove"
    sex: "man"
    __proto__: person
    constructor: ƒ child(sex,name,age)
    __proto__:
    sayholle: ƒ ()
    constructor: ƒ person(name,age)
    __proto__: Object */
           

原理:

首先对于属性方面,一般是在子构造函数里面调用父构造函数,通过call改变执行上下文,Father.call(this, name); 完成属性继承。

其次对原型上的方法继承,根据原型的向上查找规则,首先创建一个对象(该对象的__proto__指向父构造函数的原型),然后将该对象赋给子构造函数的原型

前端面试总结四

es6 class的继承:

/* esl class */
class Person {
    constructor (name,age) {
        this.name = name
        this.age = age
    }
    sayhello () {
        console.log(this.name+ ' hello '+this.age)
    }
}

class Child extends Person {
    constructor (name,age,sex) {
        /*  执行父类的构造函数 
            子类必须在构造函数中掉用super
            */
        super(name,age)
        /* 使用this一定要在super 之后 */
        this.sex = sex
    }
}

let p = new Child('czklove','23','man')
console.log(p)
/*  child {name: "czklove", age: "23", sex: "man"}
    age: "23"
    name: "czklove"
    sex: "man"
    __proto__: person
    constructor: class child
    __proto__:
    constructor: class person
    syaholle: ƒ syaholle()
    __proto__: Object */
           

原理:

ES6封装了class,extends关键字来实现继承。

子类通过extends继承父类,然后通过在子类中构造函数 super(name); 完成属性继承。注意这句代码需要在构造函数中的第一行。

区别于ES5,这里不再需要修改子类的原型的构造函数指向。

前端面试总结四

ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this)),然后再把原型链继承。

ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法,才可使用this关键字,否则报错。),然后再用子类的构造函数修改this实现继承。

参考:es5继承和es6类和继承

16.ES6中的class类

传统的javascript中只有对象,没有类的概念。它是基于原型的面向对象语言。原型对象特点就是将自身的属性共享给新对象。

如果要生成一个对象实例,需要先定义一个构造函数,然后通过new操作符来完成。构造函数示例:

//函数名和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数)
function Person(name,age) {
    this.name = name;
    this.age=age;
}
Person.prototype.say = function(){
    return "我的名字叫" + this.name+"今年"+this.age+"岁了";
}
var obj=new Person("laotie",88);//通过构造函数创建对象,必须使用new 运算符
console.log(obj.say());//我的名字叫laotie今年88岁了
           

构造函数生成实例的执行过程:

(1)当使用了构造函数,并且new 构造函数(),后台会隐式执行new Object()创建对象;

(2)将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this就代表new Object()出来的对象。

(3)执行构造函数的代码。

(4)返回新对象(后台直接返回);

ES6引入了Class(类)这个概念,通过class关键字可以定义类。该关键字的出现使得其在对象写法上更加清晰,更像是一种面向对象的语言。如果将之前的代码改为ES6的写法就会是这个样子:

class Person{//定义了一个名字为Person的类
    constructor(name,age){//constructor是一个构造方法,用来接收参数
        this.name = name;//this代表的是实例对象
        this.age=age;
    }
    say(){//这是一个类的方法,注意千万不要加上function
        return "我的名字叫" + this.name+"今年"+this.age+"岁了";
    }
}
var obj=new Person("laotie",88);
console.log(obj.say());//我的名字叫laotie今年88岁了
           

注意项:

(1)在类中声明方法的时候,千万不要给该方法加上function关键字

(2)方法之间不要用逗号分隔,否则会报错

由下面代码可以看出类实质上就是一个函数。类自身指向的就是构造函数。所以可以认为ES6中的类其实就是构造函数的另外一种写法!

console.log(typeof Person);//function
console.log(Person===Person.prototype.constructor);//true
           

以下代码说明构造函数的prototype属性,在ES6的类中依然存在着。

console.log(Person.prototype);//输出的结果是一个对象

实际上类的所有方法都定义在类的prototype属性上。代码证明下:

Person.prototype.say=function(){//定义与类中相同名字的方法。成功实现了覆盖!
    return "我是来证明的,你叫" + this.name+"今年"+this.age+"岁了";
}
var obj=new Person("laotie",88);
console.log(obj.say());//我是来证明的,你叫laotie今年88岁了
           

当然也可以通过prototype属性对类添加方法。如下:

Person.prototype.addFn=function(){
    return "我是通过prototype新增加的方法,名字叫addFn";
}
var obj=new Person("laotie",88);
console.log(obj.addFn());//我是通过prototype新增加的方法,名字叫addFn
           

还可以通过Object.assign方法来为对象动态增加方法

Object.assign(Person.prototype,{
    getName:function(){
        return this.name;
    },
    getAge:function(){
        return this.age;
    }
})
var obj=new Person("laotie",88);
console.log(obj.getName());//laotie
console.log(obj.getAge());//88
           

constructor方法是类的构造函数的默认方法,通过new命令生成对象实例时,自动调用该方法。

class Box{
    constructor(){
        console.log("啦啦啦,今天天气好晴朗");//当实例化对象时该行代码会执行。
    }
}
var obj=new Box();
           

constructor方法如果没有显式定义,会隐式生成一个constructor方法。所以即使你没有添加构造函数,构造函数也是存在的。constructor方法默认返回实例对象this,但是也可以指定constructor方法返回一个全新的对象,让返回的实例对象不是该类的实例。

class Desk{
    constructor(){
        this.xixi="我是一只小小小小鸟!哦";
    }
}
class Box{
    constructor(){
       return new Desk();// 这里没有用this哦,直接返回一个全新的对象
    }
}
var obj=new Box();
console.log(obj.xixi);//我是一只小小小小鸟!哦
           

constructor中定义的属性可以称为实例属性(即定义在this对象上),constructor外声明的属性都是定义在原型上的,可以称为原型属性(即定义在class上)。hasOwnProperty()函数用于判断属性是否是实例属性。其结果是一个布尔值, true说明是实例属性,false说明不是实例属性。in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中。

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box=new Box(12,88);
console.log(box.hasOwnProperty("num1"));//true
console.log(box.hasOwnProperty("num2"));//true
console.log(box.hasOwnProperty("sum"));//false
console.log("num1" in box);//true
console.log("num2" in box);//true
console.log("sum" in box);//true
console.log("say" in box);//false
           

类的所有实例共享一个原型对象,它们的原型都是Person.prototype,所以proto属性是相等的

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
//box1与box2都是Box的实例。它们的__proto__都指向Box的prototype
var box1=new Box(12,88);
var box2=new Box(40,60);
console.log(box1.__proto__===box2.__proto__);//true
           

由此,也可以通过proto来为类增加方法。使用实例的proto属性改写原型,会改变Class的原始定义,影响到所有实例,所以不推荐使用!

class Box{
    constructor(num1,num2){
        this.num1 = num1;
        this.num2=num2;
    }
    sum(){
        return num1+num2;
    }
}
var box1=new Box(12,88);
var box2=new Box(40,60);
box1.__proto__.sub=function(){
    return this.num2-this.num1;
}
console.log(box1.sub());//76
console.log(box2.sub());//20
           

class不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。

//ES5可以先使用再定义,存在变量提升
new A();
function A(){

}
//ES6不能先使用再定义,不存在变量提升 会报错
new B();//B is not defined
class B{

}
           

参考:es6中class类的全方面理解(一)

17.Object.create()

Object.create(proto, [propertiesObject])

//方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。

  • proto : 必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null, 对象, 函数的prototype属性 (创建空的对象时需传null , 否则会抛出TypeError异常)。
  • propertiesObject : 可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。

案例说明:

(1)创建对象的方式不同

new Object() 通过构造函数来创建对象, 添加的属性是在自身实例下。

Object.create() es6创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。

// new Object() 方式创建
var a = {  rep : 'apple' }
var b = new Object(a)
console.log(b) // {rep: "apple"}
console.log(b.__proto__) // {}
console.log(b.rep) // {rep: "apple"}

// Object.create() 方式创建
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b)  // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}
           

Object.create()方法创建的对象时,属性是在原型下面的,也可以直接访问 b.rep // {rep: “apple”} ,

此时这个值不是吧b自身的,是它通过原型链proto来访问到b的值。

(2)创建对象属性的性质不同

// 创建一个以另一个空对象为原型,且拥有一个属性p的对象
o = Object.create({}, { p: { value: 42 } })

// 省略了的属性特性默认为false,所以属性p是不可写,不可枚举,不可配置的:
o.p = 24
o.p
//42

o.q = 12
for (var prop in o) {
   console.log(prop)
}
//"q"

delete o.p
//false
           

Object.create() 用第二个参数来创建非空对象的属性描述符默认是为false的,而构造函数或字面量方法创建的对象属性的描述符默认为true。看下图解析:

前端面试总结四

(3)创建空对象时不同

前端面试总结四

当用构造函数或对象字面量方法创建空对象时,对象时有原型属性的,即有_proto_;

当用Object.create()方法创建空对象时,对象是没有原型属性的。

(4)proto 属性

JavaScript 的对象继承是通过原型链实现的。ES6 提供了更多原型对象的操作方法。

__proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前只有浏览器环境必须部署有这个属性,其他运行环境不一定要部署,因此不建议使用这个属性,而是使用下面这些来 Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

Object.create()

描述:该方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__;

格式:Object.create(proto[, propertiesObject])

用法:如果用传统的方法要给一个对象的原型上添加属性和方法,是通过 propt 实现的

var proto = {
    y: 20,
    z: 40,
    showNum(){}
};
var o = Object.create(proto);
           
前端面试总结四

如果是不用Object,create()方法,我们是如何给对象原型添加属性和方法的?

------ 通过构造函数或者类,例如:

//创建一个构造函数或者类
var People = function(){}
People.prototype.y = 20
People.prototype.showNum = function() {}
//通过构造函数创建实例
var p = new People();
console.log(p.__proto__ === People.prototype) // true
           
前端面试总结四

Object.setPrototypeOf

描述:该方法的作用与 proto 相同,用来设置一个对象的 prototype 对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。

格式:Object.setPrototypeOf(object, prototype)

用法:

var proto = {
    y: 20,
    z: 40
};
var o = { x: 10 };
Object.setPrototypeOf(o, proto);
           
前端面试总结四

输出结果中看出,添加的方法是在原型上的。就类似于

Object.getPrototypeOf()

描述:用于读取一个对象的原型对象;

格式:Object.getPrototypeOf(obj);

用法:

Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
           

参考:https://www.jianshu.com/p/28d85bebe599

18.flex:1

flex 是 flex-grow、flex-shrink、flex-basis的缩写。故其取值可以考虑以下情况:

flex 的默认值是以上三个属性值的组合。假设以上三个属性同样取默认值,则 flex 的默认值是 0 1 auto。同理,如下是等同的:

.item {flex: 2333 3222 234px;}
.item {
    flex-grow: 2333;
    flex-shrink: 3222;
    flex-basis: 234px;
}
           

当 flex 取值为 none,则计算值为 0 0 auto,如下是等同的:

.item {flex: none;}
.item {
    flex-grow: 0;
    flex-shrink: 0;
    flex-basis: auto;
}
           

当 flex 取值为 auto,则计算值为 1 1 auto,如下是等同的:

.item {flex: auto;}
.item {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: auto;
}
           

当 flex 取值为一个非负数字,则该数字为 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%,如下是等同的:

.item {flex: 1;}
.item {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 0%;
}
           

当 flex 取值为一个长度或百分比,则视为 flex-basis 值,flex-grow 取 1,flex-shrink 取 1,有如下等同情况(注意 0% 是一个百分比而不是一个非负数字):

.item-1 {flex: 0%;}
.item-1 {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 0%;
}
.item-2 {flex: 24px;}
.item-1 {
    flex-grow: 1;
    flex-shrink: 1;
    flex-basis: 24px;
}
           

当 flex 取值为两个非负数字,则分别视为 flex-grow 和 flex-shrink 的值,flex-basis 取 0%,如下是等同的:

.item {flex: 2 3;}
.item {
    flex-grow: 2;
    flex-shrink: 3;
    flex-basis: 0%;
}
           

当 flex 取值为一个非负数字和一个长度或百分比,则分别视为 flex-grow 和 flex-basis 的值,flex-shrink 取 1,如下是等同的:

.item {flex: 2333 3222px;}
.item {
    flex-grow: 2333;
    flex-shrink: 1;
    flex-basis: 3222px;
}
           

lex-basis 规定的是子元素的基准值。所以是否溢出的计算与此属性息息相关。flex-basis 规定的范围取决于 box-sizing。这里主要讨论以下 flex-basis 的取值情况:

  • auto:首先检索该子元素的主尺寸,如果主尺寸不为 auto,则使用值采取主尺寸之值;如果也是 auto,则使用值为 content。
  • content:指根据该子元素的内容自动布局。有的用户代理没有实现取 content 值,等效的替代方案是 flex-basis 和主尺寸都取 auto。
  • 百分比:根据其包含块(即伸缩父容器)的主尺寸计算。如果包含块的主尺寸未定义(即父容器的主尺寸取决于子元素),则计算结果和设为 auto 一样。

举一个不同的值之间的区别:

<div class="parent">
    <div class="item-1"></div>
    <div class="item-2"></div>
    <div class="item-3"></div>
</div>
 
<style type="text/css">
    .parent {
        display: flex;
        width: 600px;
    }
    .parent > div {
        height: 100px;
    }
    .item-1 {
        width: 140px;
        flex: 2 1 0%;
        background: blue;
    }
    .item-2 {
        width: 100px;
        flex: 2 1 auto;
        background: darkblue;
    }
    .item-3 {
        flex: 1 1 200px;
        background: lightblue;
    }
</style>
           

主轴上父容器总尺寸为 600px

子元素的总基准值是:0% + auto + 200px = 300px,其中

  • 0% 即 0 宽度
  • auto 对应取主尺寸即 100px

故剩余空间为 600px - 300px = 300px

伸缩放大系数之和为: 2 + 2 + 1 = 5

剩余空间分配如下:

  • item-1 和 item-2 各分配 2/5,各得 120px
  • item-3 分配 1/5,得 60px

各项目最终宽度为:

  • item-1 = 0% + 120px = 120px
  • item-2 = auto + 120px = 220px
  • item-3 = 200px + 60px = 260px

当 item-1 基准值取 0% 的时候,是把该项目视为零尺寸的,故即便声明其尺寸为 140px,也并没有什么用,形同虚设

而 item-2 基准值取 auto 的时候,根据规则基准值使用值是主尺寸值即 100px,故这 100px 不会纳入剩余空间

参考:flex:1;详解

19.TCP和UDP

TCP与UDP区别总结:

(1)TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

(2)TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。

(3)UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。

(4)每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

(5)TCP对系统资源要求较多,UDP对系统资源要求较少。

为什么UDP有时比TCP更有优势?

UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。

(1)网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。

(2)TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。

采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。

为什么TCP不适用于实时传输?

TCP影响实时性不是因为握手消耗时间。握手一开始建立完就没事了

一般来说,单位时间内传输的数据流量比较平滑。 TCP依赖滑动窗口进行流量控制,滑动窗口大小是自适应的,影响滑动窗口主要有两个因素,一是网络延时,二是传输速率,滑动窗口的大小与延时成正比,与传输速率也成正比。在给定的网络环境下,延时可以认为是固定的,因此滑动窗口仅与传输速率有关,当传输实时数据时,因为数据流通量比较固定,所以这时TCP上的滑动窗口会处于一个不大不小的固定值,这个值大小恰好保证当前生产的数据实时传输到对方,当出现网络丢包时,按TCP协议(快速恢复),滑动窗口将减少到原来的一半,因此速率立刻减半,此时发送速率将小于数据生产速率,一些数据将滞留在发送端,然后滑动窗口将不断增大,直到积累的数据全部发送完毕。上述过程即为典型的TCP流量抖动过程,对于实时传输影响很大,可能形成较大的突发时延,从用户感观角度来说,就是有时比较流畅,但有时卡(“抖一下”,并且比较严重),因此实时传输通常不使用TCP。

应用场景:

比如普通的会议视频图像,当然首选UDP,毕竟丢几包无所谓。

如果传输文件等,不能丢包,用TCP

udp如何实现可靠性传输

UDP它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。

实现确认机制、重传机制、窗口确认机制。

目前有如下开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT。

基于UDP的数据传输协议(UDP-based Data Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。 顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。

参考:TCP和UDP的区别和优缺点

20.HTTP的长连接和短连接

HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议.

短连接:

浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。

长连接:

当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。

TCP短连接:

client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起 close操作.短连接一般只会在client/server间传递一次读写操作

TCP长连接:

client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

继续阅读