飞道的博客

前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs)

265人阅读  评论(0)

前端面试+学习笔记(HTML+CSS+JavaScript+ES6+Vue+NodeJs)

一. HTML

1. 盒子模型

  • 是什么:每个元素被表示为一个矩形的盒子,有四个部分组成:内容(content)、内边距(padding)、边框(border)、外边距(margin)。它在页面中所占的实际大小(宽高)是content+padding+border+margin之和。

  • 盒模型有两种:标准盒模型(W3C盒模型)、IE盒模型。

  • 两种盒模型的区别:标准盒模型内容大小就是content大小、而IE盒模型内容大小则是content+padding+border总的大小。

  • **怎么设置两种盒模型:**通过设置box-sizing属性为content-box(默认值:标准盒模型)、border-box(IE盒模型)。

  • JS怎么获取和设置box的宽高

  • box-sizing使用场景:若设置子元素的margin或border时可能会撑破父元素的尺寸,就需要使用box-sizing:border-box来将border包含进元素的尺寸中。

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

  • **link属于XHTML标签,**除了加载CSS外,还能用于定义RSS(简易信息聚合,是一种基于XML标准,在互联网上被广泛采用的内容包装和投递协议)
    • rel连接属性等作用;
    • @import是CSS提供的;
    • 只能用于加载CSS
  • 页面被加载时,link会同时被加载;而@import引用的CSS会等到页面被加载完成后再加载

  • link是XHTML标签,没有兼容问题;而@import只有在IE5以上才能被识别

  • link支持使用JavaScript控制DOM修改样式;而@import不支持。

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

  • **行内元素:**a,b,span,img,input,strong,label,button,select,textarea,em
  • **块级元素:**div,ul(无序列表),ol,li,dl(自定义列表),dt(自定义列表项),dd(自定义列表项的定义),p,h1-h6,blockquote(块引用)
  • 空元素(void):即没有内容的HTML元素。br(换行),hr(水平分割线),meta,link,input,img

5. src 和 herf 的区别

  • href是指向网络资源所在位置,建立和当前(锚点)或当前文档(链接)之间的连接,用于超链接。
  • src执行外部资源的位置,指向的内容会嵌入到文档中当前标签所在位置,在请求src资源时会将其指向的资源下载并应用到文档中。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕。(性能优化

6. 为什么CSS样式放在头部,JS脚本放在底部

  • 浏览器为了更好的用户体验,渲染引擎会尝试尽快在屏幕上显示内容,不会等到所有的HTML元素解析之后在构建和布局DOM树,所以部分内容将被解析并显示。
  • 前端一般主要关心首屏的渲染速度,这也是为什么要提倡“图片懒加载”的原因。
  • **其实外部的js和CSS文件时并行下载的。**随着JS技术的发展,JS也开始承担起页面的渲染工作了。如果JS加载需要很长时间,会影响用户体验。所以需要将JS区分为承担页面渲染工作的JS和承担事件处理的JS。渲染页面的JS放在前面,事务处理的JS放在后面。

7.常用浏览器,内核

  • Trident内核:(国产的绝大部分浏览器)IE,360,搜狗

    • **Gecko内核:**Firefox,NetScape6及以上

    • **Presto内核:**Opera7及以上

    • Webkit内核:(国产大部分双核浏览器其中一核)Safari(苹果),Chrome

  • 浏览器内核:主要分成两部分:渲染引擎和JS引擎。最开始渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎

  • **渲染引擎:**负责取得网页内容(HTML,XML,图像等)、整理讯息(加入CSS等),以及计算网页的显示方式,后会输出至显示器或打印机。

  • JS引擎:解析和执行JavaScript来实现网页的动态效果。

8. DOCTYPE作用?严格模式与混杂模式 , 标准模式和怪异模式

  • 声明位于HTML文档中的第一行,处于标签之前,告知浏览器的解析器用什么文档标准解析这个文档。
  • 严格模式下,排版和JS以浏览器支持的最高标准运行;**混杂模式下,**页面以宽松向后兼容的方式显示

  • **如何触发混杂模式:**DOCTYPE不存在或格式不正确,会导致文档以混合模式呈现

  • **标准模式(standards mode)**是指浏览器按照W3C标准解析执行代码;**怪异模式(quirks mode)**则是使用浏览器自己的方式解析执行代码。

  • 浏览器解析时到底使用何种模式,与网页中的DTD声明(文档类型定义,DOCTYPE相关)有关,忽略DTD声明,将使网页进入怪异模式。

9.优雅降级和渐进增强

  • 渐进增强(progressive enhancement):针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。

  • 优雅降级(graceful degradation):一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

  • 区别

    • 优雅降级是**从复杂的现状开始,并试图减少用户体验的供给;渐进增强则是从一个非常基础的,**能够起作用的版本开始,并不断扩充,以适应未来环境的需要。
  • 渐进增强观点认为应该关注于内容本身,这使得渐进增强成为一种更为合理的设计范例;优雅降级观点认为应该针对那些最高级、最完善的浏览器来设计网站

10. 对HTML语义化的理解

  • 用正确的标签做正确的事情

  • HTML语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析

  • 即使在没有样式CSS情况下也以一种文档格式显示,并且是易于阅读的

  • 搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO

  • 使阅读源代码的人更容易将网站分块,便于阅读维护理解

二. CSS

1.CSS选择符,优先级

  • **选择符:**id(ID);class(类);element(标签);element element(后代);element>element(子);

    • element,element(群组);element+element(相邻同胞);伪类(:link,:visited,:active,:hover,:focus:first-child,:lang(language));

    • 伪元素(:first-letter,:first-line,:before,:after);属性选择器

  • **可继承的选择符:**主要是文本方面的可继承,盒模型相关的属性基本没有继承特性。font-size,font-family,color,ul

  • **不可继承的选择符:**border,padding,margin,width,height

  • **优先级:**同权重下样式定义最近者高。!important>内联样式 即定义在HTML标签内的样式,(1000)>id(100)>class/伪类/属性(10)>伪元素/element(1)

  • **CSS引入伪类和伪元素的原因:**用来修饰DOM树以外的部分。

    • 伪类用于当已有元素处于某个状态时,为其添加对应的样式,这个状态根据用户行为而动态变化。
    • 伪类的操作对象是DOM树中已有的元素。
    • 伪元素用于创建一些不在DOM树中的元素,并为其添加样式
    • 伪类和伪元素的区别在于有没有创建一个DOM树之外的元素

2. 外边距重叠(collapsing margins)/margin坍塌

  • **是什么:**相邻的两个或多个普通流中的块元素,如果它们设置了外边距,那么在垂直方向上,外边距会发生重叠,以绝对值大的那个为最终结果显示在页面上,即最终的外边距等于发生层叠的外边距中绝对值较大者。

  • **最终外边距:**margin全为正(取最大值)、margin全为负(取绝对值最大的负数)、margin有正有负(分别取正数最大值a,负数的最大绝对值b,a-b)

  • **外边距重叠的应用:**几个段落一起布局,第一个段落的上外边距正常显示,下外边距与第二个段落的上外边距重叠。

  • 防止外边距重叠:创建BFC元素。

  • 不会发生外边距重叠的情况:行内元素、浮动元素、绝对定位元素之间的外边距都不会叠加。

3. BFC(Block Formatting Context,块级格式化上下文)

  • **是什么:**决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。简言之,就是一个特殊的块,内部的元素和外部的元素不会相互影响。BFC内的盒子会在垂直方向上一个接一个地放置,垂直方向上也会发生外边距重叠。

  • **应用场景:**自适应布局(BFC不与float box重叠)、清除浮动(计算BFC的高度时,内部的浮动元素也被计算在内)、防止外边距重叠

  • 如何触发BFC:float属性(不为none)、overflow属性(不为visible)、position属性(absolute,fixed)、display属性(inline-block,table-cell,table-caption,flex,inline-flex)。

4.元素的position属性

  • **定义:**规定元素的定位类型。

  • **正常文档流:**指的是没有用CSS样式去控制的HTML文档结构,代码的顺序就是网页展示的顺序。

  • **脱离文档流:**指的是元素所显示的位置和文档代码不一致。

  • **static:**默认值。没有定位,元素出现在正常的文档流中。

    • **relative:**生成相对定位的元素,相对于其在正常文档流中的位置进行定位(不脱离文档流)。

    • **absolute:**生成绝对定位的元素,相对于static定位以外的最近父级元素进行定位,即相对于其直接父级元素(脱离文档流)。absolute较少直接单独使用在正常的文档流中,主要运行于进行了相对定位的元素框架层里面,相对该层的左上点进行偏移。

    • **fixed:**生成固定定位元素,相对于浏览器窗口进行定位。

    • **inherit:**从父元素继承position属性的值。

  • **z-index属性:**使用了relative、absolute、fixed三种定位后,都会使正常的文档流发生一定程度的改变,造成元素出现重叠的情形。为了能让重叠的元素有序的显示出来,需要在定位的相关元素加上z-index属性。其值是一个整数值,默认为0,数值越大表示拥有的优先级越高,该属性只对使用了定位的元素有效。

5.元素的display属性

  • **定义:**规定元素应该生成的框的类型

  • 常用属性值:

    • **inline:**默认值。元素会被显示为内联元素。

    • **none:**元素不会被显示。

    • **block:**元素将显示为块级元素。

    • **inline-block:**行内块元素,即元素像行内元素一样显示,内容像块元素一样显示。

    • **list-item:**元素像块元素一样显示,并添加样式列表标记。

    • **table:**元素会作为块级表格来显示。

    • **table-caption:**元素会作为一个表格标题显示。

    • **inherit:**从父元素继承display属性。

  • display属性值inline和block的区别:

    • block元素会独占一行,默认情况下,block元素宽度自动填满父级元素的宽度;

    • block元素可以设置width、height属性,即使设置了宽度,仍然是独占一行;

    • block元素可以设置margin和padding属性;

    • inline元素不会独占一行,多个相邻的行内元素会排列在同一行里面,其宽度随元素的内容而变化;

    • inline元素设置width、height无效;

    • inline元素的margin和padding属性在水平方向上能产生边距效果,垂直方向不会产生边距效果。

  • display:inline-block元素显示间隙

    • inline-block水平呈现的元素之间,HTML元素标签换行显示或标签之间有空格的情况下会有间距

    • 消除办法:移除标签之间的空格;

      使用margin-left或margin-right取负值;

      对父元素设置font-size为0,然后对元素的font-size初始化;

      对父元素设置letter-spacing(字符间距)为负值,然后设置元素的letter-spacing为0;

      对父元素设置word-spacing(单词间距)为负值,然后设置元素的word-spacing为0。

7.overflow属性

  • **定义:**规定当内容溢出元素框时发生的事情

  • **visible:**默认值。内容不会被修剪,会呈现在元素框之外

    • **hidden:**内容会被修剪,并且其余内容不可见

    • **scroll:**内容被修剪,但浏览器会显示滚动条以便查看其余内容

    • **auto:**如果内容被修剪,则浏览器会显示滚动条以便查看其余内容

    • inherit:从父元素继承overflow属性的值

8. 初始化CSS样式

  • **为什么要初始化CSS样式:**因为浏览器的兼容问题,不同浏览器对有些标签的默认值时不同的,如果没有对CSS初始化往往会出现浏览器之间页面显示差异。

  • 最简单的方法:*{margin:0;padding:0;}

  • **初始化CSS的缺点:**对SEO(搜索引擎优化)有一定的影响。

  • SEO:Search Engine Optimization,搜索引擎的优化。SEO具体是指通过网站结构调整、网站内容建设、网站代码优化以及站外优化,使网站满足搜索引擎的收录排名需求,提高网站在搜索引擎中关键字的排名,从而吸引精准用户进入网站,获得免费流量,产生直接销售或品牌推广。

  • **什么是CSS Hack:**一般来说针对不同的浏览器写不同的CSS,就是CSS Hack。

9.CSS属性cursor?

  • cursor属性规定要显示的鼠标的光标类型。

  • 常用取值:pointer(手),crosshair(十字线),default(箭头),auto(浏览器设置的光标)

HTML5

一. HTML5 新特性

1. HTML5新特性:主要是关于图像、位置、存储、多任务等功能的增加。包括:

  • 绘画canvas(通过脚本实现绘画)

  • 用于媒介回放的video和audio元素

  • 本地离线存储localStorage、sessionStorage

  • 语义化更好的内容元素:article、footer、header、nav、section

  • 表单元素:datalist(规定输入域的选项列表)、output(用于不同元素的输出)、keygen(提供一种验证用户的可靠方法)

  • input类型:color、date、month、week、number、email(检测是否为一个email格式的地址)、range(滑动条)、search、url、tel(输入电话号码,-time选择时间)

2. HTML5新标签的浏览器兼容问题:当在页面中使用HTML5新标签时,可能会得到三种不同的结果:

  • 新标签被当做错误处理并被忽略,在DOM构建时会当做这个标签不存在

  • 新标签被当做错误处理,在DOM构建时,这个新标签会被构造成行内元素

  • 新标签被识别成HTML5标签,然后用DOM节点对齐进行替换

3.解决兼容性问题:

  • **实现标签被识别。**通过document.createElement(tagName)即可让浏览器识别新标签,浏览器支持新标签后,还可以为其添加CSS样式

  • JavaScript解决方案:

    • 使用html5shim。在中调用以下代码(也可下载到本地后调用):
    <!--[if It IE 9]>
        <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    
    • 使用kill IE6
    <!--[if It IE 6]>
    <script 			    src="http://letskillie6.googlecode.com/svn/trunk/letskillie6.an_CN.pack.js"></script>
    <![endif]-->
    

4. 如何区分HTML和HTML5:

  • DOCTYPE声明

  • 新增的元素

5. HTML5移除的元素:

  • **纯表现的元素:**big,center,font,strike(删除线),u(下划线),s(删除线)

  • **对可用性产生负面影响的元素:**frame,frameset,noframes

6.iframe的缺点

  • 会阻塞主页面的onload事件

  • 搜索引擎的检索程序无法解读这种页面,不利于SEO

二. cookie和localStorage的区别

1.共同点:cookie、sessionStorage和localStorage都是由浏览器存储在本地的数据。

区别:

  • cookie是网站为了标识用户身份而存储在用户本地终端上的数据(通常经过加密),数据始终在同源的http请求中携带,即在浏览器和服务器之间来回传递;localStorage不会自动把数据发给服务器,尽在本地保存

  • cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下,存储大小也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据(如会话标识);localStorage也有存储大小的限制,但比cookie大很多,可以达到5M或更大。

  • cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭localStorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;sessionStorage当前浏览器窗口关闭之后自动删除

  • localStorage支持**事件通知机制,**可以将数据更新的通知发送给监听者,API接口使用更方便;cookie的原生接口不友好,需要程序员自己封装

  • **localStorage如何删除数据:**localStorage对象可以将数据长期保存在客户端,除非人为清除,提供了以下几个方法:

    • **存储:**localStorage.setItem(key,value) 如果key存在,更新value

    • **获取:**localStorage.getItem(key) 如果key不存在,则返回null

    • **删除:**localStorage.removeItem(key) 一旦删除,key对应的数据将会全部删除

  • 全部清除localStorage.clear()使用removeItem逐个删除太麻烦,可以使用**clear,**执行的结果是清除所有的localStorage对象保存的数据

  • localStorage存储的数据是不能跨浏览器共用的,一个浏览器只能读取各自浏览器的数据。

CSS3

一. CSS3新特性

1. 新特性

  • 边框:border-radius(圆角)、box-shadow(阴影)、border-image(边框图片)

  • 背景:background-size(背景图片的尺寸)、background-origin(背景图片的定位区域)

  • 文本效果:text-shadow(文本阴影)、word-wrap(文本换行)

  • 转换和变形:transform(包括2D,3D转换,rotate(angle),translate(x,y),scale(x,y))

  • 过渡:transition

  • 动画:animation

  • 多列:column-count(元素被分隔的列数)、column-gap(列之间的间隔)、column-rule(洌之间的宽度,样式,颜色规则)

  • 用户界面:resize(规定是否可由用户调整元素尺寸)、box-sizing(以确切的方式适应某个区域的具体内容)、outline-offset(对轮廓进行偏移)

2. 新增伪类:

  • element:before(在元素之前添加内容) element:after(在元素之后添加内容)

  • element:first-of-type、element:last-of-type、element:only-of-type、element:only-child、element:nth-child(n)(第n个)

  • :checked、:disabled、:enabled

3. 四个锚点伪类的设置问题:

  • **问题描述:**超链接访问后hover样式就不出现了,被点击访问过的超链接样式不再具有hover和active

  • **解决办法:**爱恨原则LoVe/HAte。改变CSS属性的排列顺序,L-V-H-A 即a:link{} a:visited() a:hover{} a:active{}

4.transition、transform和animation的区别

  • transform是指转换,可以将元素移动、旋转、倾斜、拉伸。没有变化的过程。而transitionanimation都加上了时间属性,所以能产生动画效果
  • transition是指过渡,一般由行为(hover等)触发;而animation则是自动触发
  • transition只能设置头尾,所有样式属性一起变化;animation可以设定每一帧的样式和时间,且可以循环播放。

5.rgba和opacity的区别?

  1. rgba和opacity都能实现透明效果,但最大的不同在于opacity作用于元素本身以及元素内的所有内容,而rgba只作用于元素本身,子元素不会继承透明效果。

  2. **rgba是CSS3的属性,**用法说明:rgba(R,G,B,A),参数说明R(红色值。正整数|百分数),G(绿色值。正整数|百分数),B(蓝色值。正整数|百分比),A(Alpha透明度。0(透明)~1)。IE6-8不支持rgba模式,可以使用IE滤镜处理:

  • filter:progid:DXImageTransform.Microsoft.Gradient(startColorstr=#AARRGGBBAA,endColorstr=#AARRGGBBAA);其中AA,RR,GG,BB分别表示Alpha,R,G,B,取值为00-FF。
  1. opacity也是CSS3的属性,用法说明:opacity:value 其中value取值0(透明)~1。对于IE6-8,可以用IE滤镜处理:
    • filter:alpha(opacity=50); /对应于opacity:0.5;

浮动

一. 清除浮动

  • 父级div定义height

  • 结尾处加空div标签,样式clear:both

  • 父级div定义伪类:after和zoom

    • 通过CSS伪元素在容器的内部元素最后添加了一个看不见的空格"020"或点".",并且赋予clear属性来清除浮动。需要注意的是为了IE6和IE7浏览器,要给clearfix这个class添加一条zoom:1;触发haslayout。
  • 父级div定义overflow:hidden(同时还要定义width或zoom:1,不能定义height)

  • 父级div定义overflow:auto(同时还要定义width或zoom:1,不能定义height)

  • 父级也浮动,需要定义width(不推荐)

  • 父级div定义display:table(不推荐)

  • 结尾处加br标签,样式clear:both(父元素div定义zoom:1,不推荐)

二. 属性clear取值

1. 定义:规定元素的那一侧不允许其他浮动元素

2. 取值

  • none:(默认值)。允许浮动元素

  • left:在左侧不允许浮动元素

  • right:在右侧不允许浮动元素

  • both:在左右侧均不允许浮动元素

  • inherit:从父元素继承clear属性

三. 属性zoom取值

1. 定义:设置或检索对象的缩放比例

2.取值

  • normal:(默认值),使用对象的实际尺寸

  • :用浮点数来定义缩放比例,不允许负值

  • :用百分比来定义缩放比例,不允许负值

新的注意点:

一. 重排(reflow) 与重绘(repaint)

  1. 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排

  2. 重绘是一个元素外观的改变所触发的浏览器行为(例如改变visibility,outline,background等属性),浏览器会根据元素的新属性重新绘制,是元素呈现新的外观。

  3. **重排时更明显的一种改变,可以理解为渲染树需要重新计算。**常见的触发重排的操作:

    • DOM元素的几何属性变化

    • DOM树的结构变化(例如节点的增减、移动)

    • 获取某些属性(例如offsetTop,offsetLeft,offsetHeight,offsetWidth,clientWidth,clientHeight等)

    • 改变元素的一些样式(例如调整浏览器窗口大小)

  4. 重绘不会带来重新布局,并不一定伴随着重排。

  5. 在实践中,应该尽量减少重排次数和缩小重排的影响范围。有以下几种方法:

    • 将多次改变样式属性的操作合并成一次操作

    • 将需要多次重排的元素,position属性设为absolute或fixed,使其脱离文档流,这样它的变化就不会影响到其他元素

    • 在内存中多次操作节点,完成后再添加到文档中去

    • 如果要对一个元素进行复杂的操作,可以将其display属性设置为none使其隐藏,待操作完成后再显示

    • 在需要经常获取那些引起浏览器重排的属性值时,要缓存到变量

如何在网页中添加空格?

​ 在HTML代码中输入&nbsp;

二. 如何在网页中显示代码?

  1. 对于单行代码,使用标签<code>代码</code>

  2. 对于多行代码,使用标签<pre></pre> (被包围在pre元素中的文本通常会保留空格和换行符)

26.使用mailto在网页中链接Email地址?

(1)a标签有一个作用是可以链接Email地址,使用mailto能让访问者便捷想网站管理者发送电子邮件

(2)如果mailto后面同时又多个参数的话,第一个参数必须以?开头,后面的参数每一个都以&分隔

三. form表单当前页面无刷新提交?

  • 使用target属性取值为iframe元素的name属性值。具体如下:
    • 在当前页面建一个iframe并隐藏(display:none)
      给这个iframe取名(name=“id_iframe”)
      设置form表单的target属性(target=“id_iframe”)
      提交表单,就是无刷新

iframe标签

一. 优点

  • iframe能够把嵌入的页面展示出来,如果有多个网页引用iframe,只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷
  • 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用
  • 重载页面时不需要重载整个页面,只需要重载页面中的一个框架页,减少了数据的传输,增加了网页的下载速度
  • 方便制作导航栏

二. 缺点

  • 会产生很多页面,不利于管理
  • 浏览器的前进/后退按钮无效
  • 无法被一些搜索引擎索引到,现在搜索引擎爬虫还不能很好的处理iframe中的内容,所以不利于SEO
  • 多数小型的移动设备无法显示框架,兼容性差
  • 多框架的页面会增加服务器的http请求,对于大型网站是不可取的
  • 综上,目前框架中的所有优点完全可以使用Ajax实现,因此不推荐使用框架

ES6

一、问:ES6是什么,为什么要学习它,不学习ES6会怎么样?

答: ES6是新一代的JS语言标准,对分JS语言核心内容做了升级优化,规范了JS使用标准,新增了JS原生方法,使得JS使用更加规范,更加优雅,更适合大型应用的开发。学习ES6是成为专业前端正规军的必经之路。不学习ES6也可以写代码打鬼子,但是最多只能当个游击队长。

二、问:ES5、ES6和ES2015有什么区别?

答: ES2015特指在2015年发布的新一代JS语言标准,ES6泛指下一代JS语言标准,包含ES2015、ES2016、ES2017、ES2018等。现阶段在绝大部分场景下,ES2015默认等同ES6。ES5泛指上一代语言标准。ES2015可以理解为ES5和ES6的时间分界线。

三、问:babel是什么,有什么作用?

答:babel是一个 ES6 转码器,可以将 ES6 代码转为 ES5 代码,以便兼容那些还没支持ES6的平台。

四、问:let有什么用,有了var为什么还要用let?

答: 在ES6之前,声明变量只能用var,var方式声明变量其实是很不合理的,准确的说,是因为ES5里面没有块级作用域是很不合理的,甚至可以说是一个语言层面的bug(这也是很多c++、java开发人员看不懂,也瞧不起JS语言的劣势之一)。没有块级作用域回来带很多难以理解的问题,比如for循环var变量泄露,变量覆盖等问题。let 声明的变量拥有自己的块级作用域,且修复了var声明变量带来的变量提升问题

五、问:举一些ES6对String字符串类型做的常用升级优化?

答:

1、优化部分:

ES6新增了字符串模板,在拼接大段字符串时,用反斜杠(`)取代以往的字符串相加的形式,能保留所有空格和换行,使得字符串拼接看起来更加直观,更加优雅。

2、升级部分:

ES6在String原型上新增了includes()方法,用于取代传统的只能用indexOf查找包含字符的方法(indexOf返回-1表示没查到不如includes方法返回false更明确,语义更清晰), 此外还新增了startsWith(), endsWith(), padStart(),padEnd(),repeat()等方法,可方便的用于查找,补全字符串。

六、问:举一些ES6对Array数组类型做的常用升级优化?

答:

1、优化部分:

a. 数组解构赋值。ES6可以直接以let [a,b,c] = [1,2,3]形式进行变量赋值,在声明较多变量时,不用再写很多let(var),且映射关系清晰,且支持赋默认值。

b. 扩展运算符。ES6新增的扩展运算符(…)(重要),可以轻松的实现数组和松散序列的相互转化,可以取代arguments对象和apply方法,轻松获取未知参数个数情况下的参数集合。(尤其是在ES5中,arguments并不是一个真正的数组,而是一个类数组的对象,但是扩展运算符的逆运算却可以返回一个真正的数组)。扩展运算符还可以轻松方便的实现数组的复制和解构赋值(let a = [2,3,4]; let b = [...a])。

2、升级部分:

ES6在Array原型上新增了find()方法,用于取代传统的只能用indexOf查找包含数组项目的方法,且修复了indexOf查找不到NaN的bug([NaN].indexOf(NaN) === -1).此外还新增了copyWithin(), includes(), fill(),flat()等方法,可方便的用于字符串的查找,补全,转换等。

七、问:举一些ES6对Number数字类型做的常用升级优化?

答:

1、优化部分:

ES6在Number原型上新增了isFinite(), isNaN()方法,用来取代传统的全局isFinite(), isNaN()方法检测数值是否有限、是否是NaN。ES5的isFinite(), isNaN()方法都会先将非数值类型的参数转化为Number类型再做判断,这其实是不合理的,最造成isNaN('NaN') === true的奇怪行为–'NaN’是一个字符串,但是isNaN却说这就是NaN。而Number.isFinite()和Number.isNaN()则不会有此类问题(Number.isNaN('NaN') === false)。(isFinite()同上)

2、升级部分:

ES6在Math对象上新增了Math.cbrt(),trunc(),hypot()等等较多的科学计数法运算方法,可以更加全面的进行立方根、求和立方根等等科学计算。

八、问:举一些ES6对Object类型做的常用升级优化?(重要)

答:

1、优化部分:

a. 对象属性变量式声明。ES6可以直接以变量形式声明对象属性或者方法,。比传统的键值对形式声明更加简洁,更加方便,语义更加清晰。

let [apple, orange] = ['red appe', 'yellow orange'];
let myFruits = {apple, orange};    // let myFruits = {apple: 'red appe', orange: 'yellow orange'};
复制代码

尤其在对象解构赋值(见优化部分b.)或者模块输出变量时,这种写法的好处体现的最为明显:

let {keys, values, entries} = Object;
let MyOwnMethods = {keys, values, entries}; // let MyOwnMethods = {keys: keys, values: values, entries: entries}
复制代码

可以看到属性变量式声明属性看起来更加简洁明了。方法也可以采用简洁写法:

let es5Fun = {
    method: function(){}
}; 
let es6Fun = {
    method(){}
}
复制代码

b. 对象的解构赋值。 ES6对象也可以像数组解构赋值那样,进行变量的解构赋值:

let {apple, orange} = {apple: 'red appe', orange: 'yellow orange'};
复制代码

c. 对象的扩展运算符(…)。 ES6对象的扩展运算符和数组扩展运算符用法本质上差别不大,毕竟数组也就是特殊的对象。对象的扩展运算符一个最常用也最好用的用处就在于可以轻松的取出一个目标对象内部全部或者部分的可遍历属性,从而进行对象的合并和分解。

let {apple, orange, ...otherFruits} = {apple: 'red apple', orange: 'yellow orange', grape: 'purple grape', peach: 'sweet peach'}; 
// otherFruits  {grape: 'purple grape', peach: 'sweet peach'}
// 注意: 对象的扩展运算符用在解构赋值时,扩展运算符只能用在最有一个参数(otherFruits后面不能再跟其他参数)
let moreFruits = {watermelon: 'nice watermelon'};
let allFruits = {apple, orange, ...otherFruits, ...moreFruits};
复制代码

d. super 关键字。ES6在Class类里新增了类似this的关键字super。同this总是指向当前函数所在的对象不同,super关键字总是指向当前函数所在对象的原型对象。

2、升级部分:

a. ES6在Object原型上新增了is()方法,做两个目标对象的相等比较,用来完善’=‘方法。’='方法中NaN === NaN //false其实是不合理的,Object.is修复了这个小bug。(Object.is(NaN, NaN) // true)

b. ES6在Object原型上新增了assign()方法,用于对象新增属性或者多个对象合并。

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
复制代码

注意: assign合并的对象target只能合并source1、source2中的自身属性,并不会合并source1、source2中的继承属性,也不会合并不可枚举的属性,且无法正确复制get和set属性(会直接执行get/set函数,取return的值)。

c. ES6在Object原型上新增了getOwnPropertyDescriptors()方法,此方法增强了ES5中getOwnPropertyDescriptor()方法,可以获取指定对象所有自身属性的描述对象。结合defineProperties()方法,可以完美复制对象,包括复制get和set属性。

d. ES6在Object原型上新增了getPrototypeOf()和setPrototypeOf()方法,用来获取或设置当前对象的prototype对象。这个方法存在的意义在于,ES5中获取设置prototype对像是通过__proto__属性来实现的,然而__proto__属性并不是ES规范中的明文规定的属性,只是浏览器各大产商“私自”加上去的属性,只不过因为适用范围广而被默认使用了,再非浏览器环境中并不一定就可以使用,所以为了稳妥起见,获取或设置当前对象的prototype对象时,都应该采用ES6新增的标准用法。

d. ES6在Object原型上还新增了Object.keys(),Object.values(),Object.entries()方法,用来获取对象的所有键、所有值和所有键值对数组。

九、问:举一些ES6对Function函数类型做的常用升级优化?(重要)

答:

1、优化部分:

a. 箭头函数**(核心)**。箭头函数是ES6核心的升级项之一,箭头函数里没有自己的this,这改变了以往JS函数中最让人难以理解的this运行机制。主要优化点:

Ⅰ. 箭头函数内的this指向的是函数定义时所在的对象,而不是函数执行时所在的对象。ES5函数里的this总是指向函数执行时所在的对象,这使得在很多情况下this的指向变得很难理解,尤其是非严格模式情况下,this有时候会指向全局对象,这甚至也可以归结为语言层面的bug之一。ES6的箭头函数优化了这一点,它的内部没有自己的this,这也就导致了this总是指向上一层的this,如果上一层还是箭头函数,则继续向上指,直到指向到有自己this的函数为止,并作为自己的this。

Ⅱ. 箭头函数不能用作构造函数,因为它没有自己的this,无法实例化。

Ⅲ. 也是因为箭头函数没有自己的this,所以箭头函数 内也不存在arguments对象。(可以用扩展运算符代替)

b. 函数默认赋值。ES6之前,函数的形参是无法给默认值得,只能在函数内部通过变通方法实现。ES6以更简洁更明确的方式进行函数默认赋值。

function es6Fuc (x, y = 'default') {
    console.log(x, y);
}
es6Fuc(4) // 4, default
复制代码

2、升级部分:

ES6新增了双冒号运算符,用来取代以往的bind,call,和apply。(浏览器暂不支持,Babel已经支持转码)

foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
复制代码

十、问:Symbol是什么,有什么作用?

答: Symbol是ES6引入的第七种原始数据类型(说法不准确,应该是第七种数据类型,Object不是原始数据类型之一,已更正),所有Symbol()生成的值都是独一无二的,可以从根本上解决对象属性太多导致属性名冲突覆盖的问题。对象中Symbol()属性不能被for…in遍历,但是也不是私有属性。

十一、问:Set是什么,有什么作用?

答: Set是ES6引入的一种类似Array的新的数据结构,Set实例的成员类似于数组item成员,区别是Set实例的成员都是唯一,不重复的。这个特性可以轻松地实现数组去重。

十二、问:Map是什么,有什么作用?

答: Map是ES6引入的一种类似Object的新的数据结构,Map可以理解为是Object的超集,打破了以传统键值对形式定义对象,对象的key不再局限于字符串,也可以是Object。可以更加全面的描述对象的属性。

十三、问:Proxy是什么,有什么作用?

答: Proxy是ES6新增的一个构造函数,可以理解为JS语言的一个代理,用来改变JS默认的一些语言行为,包括拦截默认的get/set等底层方法,使得JS的使用自由度更高,可以最大限度的满足开发者的需求。比如通过拦截对象的get/set方法,可以轻松地定制自己想要的key或者value。下面的例子可以看到,随便定义一个myOwnObj的key,都可以变成自己想要的函数。

function createMyOwnObj() {
	//想把所有的key都变成函数,或者Promise,或者anything
	return new Proxy({}, {
		get(target, propKey, receiver) {
			return new Promise((resolve, reject) => {
				setTimeout(() => {
					let randomBoolean = Math.random() > 0.5;
					let Message;
					if (randomBoolean) {
						Message = `你的${propKey}运气不错,成功了`;
						resolve(Message);
					} else {
						Message = `你的${propKey}运气不行,失败了`;
						reject(Message);
					}
				}, 1000);
			});
		}
	});
}

let myOwnObj = createMyOwnObj();

myOwnObj.hahaha.then(result => {
	console.log(result) //你的hahaha运气不错,成功了
}).catch(error => {
	console.log(error) //你的hahaha运气不行,失败了
})

myOwnObj.wuwuwu.then(result => {
	console.log(result) //你的wuwuwu运气不错,成功了
}).catch(error => {
	console.log(error) //你的wuwuwu运气不行,失败了
})
复制代码

十四、问:Reflect是什么,有什么作用?

答: Reflect是ES6引入的一个新的对象,他的主要作用有两点,一是将原生的一些零散分布在Object、Function或者全局函数里的方法(如apply、delete、get、set等等),统一整合到Reflect上,这样可以更加方便更加统一的管理一些原生API。其次就是因为Proxy可以改写默认的原生API,如果一旦原生API别改写可能就找不到了,所以Reflect也可以起到备份原生API的作用,使得即使原生API被改写了之后,也可以在被改写之后的API用上默认的API。

十五、问:Promise是什么,有什么作用?

答: Promise是ES6引入的一个新的对象,他的主要作用是用来解决JS异步机制里,回调机制产生的“回调地狱”。它并不是什么突破性的API,只是封装了异步回调形式,使得异步回调可以写的更加优雅,可读性更高,而且可以链式调用。

十六、问:Iterator是什么,有什么作用?(重要)

答: Iterator是ES6中一个很重要概念,它并不是对象,也不是任何一种数据类型。因为ES6新增了Set、Map类型,他们和Array、Object类型很像,Array、Object都是可以遍历的,但是Set、Map都不能用for循环遍历,解决这个问题有两种方案,一种是为Set、Map单独新增一个用来遍历的API,另一种是为Set、Map、Array、Object新增一个统一的遍历API,显然,第二种更好,ES6也就顺其自然的需要一种设计标准,来统一所有可遍历类型的遍历方式。Iterator正是这样一种标准。或者说是一种规范理念。

就好像JavaScript是ECMAScript标准的一种具体实现一样,Iterator标准的具体实现是Iterator遍历器。Iterator标准规定,所有部署了key值为[Symbol.iterator],且[Symbol.iterator]的value是标准的Iterator接口函数(标准的Iterator接口函数: 该函数必须返回一个对象,且对象中包含next方法,且执行next()能返回包含value/done属性的Iterator对象)的对象,都称之为可遍历对象,next()后返回的Iterator对象也就是Iterator遍历器。

//obj就是可遍历的,因为它遵循了Iterator标准,且包含[Symbol.iterator]方法,方法函数也符合标准的Iterator接口规范。
//obj.[Symbol.iterator]() 就是Iterator遍历器
let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};
复制代码

ES6给Set、Map、Array、String都加上了[Symbol.iterator]方法,且[Symbol.iterator]方法函数也符合标准的Iterator接口规范,所以Set、Map、Array、String默认都是可以遍历的。

//Array
let array = ['red', 'green', 'blue'];
array[Symbol.iterator]() //Iterator遍历器
array[Symbol.iterator]().next() //{value: "red", done: false}

//String
let string = '1122334455';
string[Symbol.iterator]() //Iterator遍历器
string[Symbol.iterator]().next() //{value: "1", done: false}

//set
let set = new Set(['red', 'green', 'blue']);
set[Symbol.iterator]() //Iterator遍历器
set[Symbol.iterator]().next() //{value: "red", done: false}

//Map
let map = new Map();
let obj= {map: 'map'};
map.set(obj, 'mapValue');
map[Symbol.iterator]().next()  {value: Array(2), done: false}
复制代码

十七、问:for…in 和for…of有什么区别?

答: 如果看到问题十六,那么就很好回答。问题十六提到了ES6统一了遍历标准,制定了可遍历对象,那么用什么方法去遍历呢?答案就是用for…of。ES6规定,有所部署了载了Iterator接口的对象(可遍历对象)都可以通过for…of去遍历,而for…in仅仅可以遍历对象。

这也就意味着,数组也可以用for…of遍历,这极大地方便了数组的取值,且避免了很多程序用for…in去遍历数组的恶习。

上面提到的扩展运算符本质上也就是for…of循环的一种实现。

十八、Generator函数是什么,有什么作用?

答: 如果说JavaScript是ECMAScript标准的一种具体实现、Iterator遍历器是Iterator的具体实现,那么Generator函数可以说是Iterator接口的具体实现方式。

执行Generator函数会返回一个遍历器对象,每一次Generator函数里面的yield都相当一次遍历器对象的next()方法,并且可以通过next(value)方法传入自定义的value,来改变Generator函数的行为。

Generator函数可以通过配合Thunk 函数更轻松更优雅的实现异步编程和控制流管理。

十九、async函数是什么,有什么作用?

答: async函数可以理解为内置自动执行器的Generator函数语法糖,它配合ES6的Promise近乎完美的实现了异步编程解决方案。

二十、Class、extends是什么,有什么作用?

答: ES6 的class可以看作只是一个ES5生成实例对象的构造函数的语法糖。它参考了java语言,定义了一个类的概念,让对象原型写法更加清晰,对象实例化更像是一种面向对象编程。Class类可以通过extends实现继承。它和ES5构造函数的不同点:

a. 类的内部定义的所有方法,都是不可枚举的。

///ES5
function ES5Fun (x, y) {
	this.x = x;
	this.y = y;
}
ES5Fun.prototype.toString = function () {
	 return '(' + this.x + ', ' + this.y + ')';
}
var p = new ES5Fun(1, 3);
p.toString();
Object.keys(ES5Fun.prototype); //['toString']

//ES6
class ES6Fun {
	constructor (x, y) {
		this.x = x;
		this.y = y;
	}
	toString () {
		return '(' + this.x + ', ' + this.y + ')';
	}
}

Object.keys(ES6Fun.prototype); //[]
复制代码

b.ES6的class类必须用new命令操作,而ES5的构造函数不用new也可以执行。

c.ES6的class类不存在变量提升,必须先定义class之后才能实例化,不像ES5中可以将构造函数写在实例化之后。

d.ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。

二十一、module、export、import是什么,有什么作用?

答: module、export、import是ES6用来统一前端模块化方案的设计思路和实现方案。export、import的出现统一了前端模块化的实现方案,整合规范了浏览器/服务端的模块化方法,用来取代传统的AMD/CMD、requireJS、seaJS、commondJS等等一系列前端模块不同的实现方案,使前端模块化更加统一规范,JS也能更加能实现大型的应用程序开发。

import引入的模块是静态加载(编译阶段加载)而不是动态加载(运行时加载)。

import引入export导出的接口值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。

二十二、日常前端代码开发中,有哪些值得用ES6去改进的编程优化或者规范?

答:

1、常用箭头函数来取代var self = this;的做法。

2、常用let取代var命令。

3、常用数组/对象的结构赋值来命名变量,结构更清晰,语义更明确,可读性更好。

4、在长字符串多变量组合场合,用模板字符串来取代字符串累加,能取得更好地效果和阅读体验。

5、用Class类取代传统的构造函数,来生成实例化对象。

6、在大型应用开发中,要保持module模块化开发思维,分清模块之间的关系,常用import、export方法。

VUE

一、对于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的生命周期

beforeCreate(创建前) 在数据观测和初始化事件还未开始
created(创建后) 完成数据观测,属性和方法的运算,初始化事件, e l 属 性 还 没 有 显 示 出 来 ∗ ∗ b e f o r e M o u n t ∗ ∗ ( 载 入 前 ) 在 挂 载 开 始 之 前 被 调 用 , 相 关 的 r e n d e r 函 数 首 次 被 调 用 。 实 例 已 完 成 以 下 的 配 置 : 编 译 模 板 , 把 d a t a 里 面 的 数 据 和 模 板 生 成 h t m l 。 注 意 此 时 还 没 有 挂 载 h t m l 到 页 面 上 。 ∗ ∗ m o u n t e d ∗ ∗ ( 载 入 后 ) 在 e l 被 新 创 建 的 v m . el属性还没有显示出来 **beforeMount**(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。 **mounted**(载入后) 在el 被新创建的 vm. elbeforeMountrenderdatahtmlhtmlmountedelvm.el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
1.什么是vue生命周期?
答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

2.vue生命周期的作用是什么?
答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

3.vue生命周期总共有几个阶段?
答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

4.第一次页面加载会触发哪几个钩子?
答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。

5.DOM 渲染在 哪个周期中就已经完成?
答:DOM 渲染在 mounted 中就已经完成了。

三、 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变更双向绑定效果。

js实现简单的双向绑定

<body>
    <div id="app">
    <input type="text" id="txt">
    <p id="show"></p>
</div>
</body>
<script type="text/javascript">
    var obj = {}
    Object.defineProperty(obj, 'txt', {
        get: function () {
            return obj
        },
        set: function (newValue) {
            document.getElementById('txt').value = newValue
            document.getElementById('show').innerHTML = newValue
        }
    })
    document.addEventListener('keyup', function (e) {
        obj.txt = e.target.value
    })
</script>

四、Vue组件间的参数传递

1.父组件与子组件传值
父组件传给子组件:子组件通过props方法接受数据;
子组件传给父组件:$emit方法传递参数
2.非父子组件间的数据传递,兄弟组件传值
eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道。)

五、Vue的路由实现:hash模式 和 history模式

**hash模式:**在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

**history模式:**history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

六、Vue与Angular以及React的区别?

(版本在不断更新,以下的区别有可能不是很正确。我工作中只用到vue,对angular和react不怎么熟)
1.与AngularJS的区别
相同点:
都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。

不同点:
AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。

2.与React的区别
相同点:
React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。
不同点:
React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。

七、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 。
场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biCZCx5s-1611887935896)(C:\Users\zjx\Desktop\桌面文件\web面试\Vuepng.png)]

state
Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutations
mutations定义的方法动态修改Vuex 的 store 中的状态或数据。
getters
类似vue的计算属性,主要用来过滤一些数据。
action
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

const store = new Vuex.Store({ //store实例
      state: {
         count: 0
             },
      mutations: {                
         increment (state) {
          state.count++
         }
          },
      actions: { 
         increment (context) {
          context.commit('increment')
   }
 }
})

modules
项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
 }
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
 }

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
})

九、vue-cli如何新增自定义指令?

1.创建局部指令

var app = new Vue({
    el: '#app',
    data: {    
    },
    // 创建指令(可以多个)
    directives: {
        // 指令名称
        dir1: {
            inserted(el) {
                // 指令中第一个参数是当前使用指令的DOM
                console.log(el);
                console.log(arguments);
                // 对DOM进行操作
                el.style.width = '200px';
                el.style.height = '200px';
                el.style.background = '#000';
            }
        }
    }
})

2.全局指令

Vue.directive('dir2', {
    inserted(el) {
        console.log(el);
    }
})

3.指令的使用

<div id="app">
    <div v-dir1></div>
    <div v-dir2></div>
</div>

十、vue如何自定义一个过滤器?

html代码:

<div id="app">
     <input type="text" v-model="msg" />
     {
  {msg| capitalize }}
</div>

JS代码:

var vm=new Vue({
    el:"#app",
    data:{
        msg:''
    },
    filters: {
      capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
})

全局定义过滤器

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

过滤器接收表达式的值 (msg) 作为第一个参数。capitalize 过滤器将会收到 msg的值作为第一个参数。

十一、对keep-alive 的了解?

keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。

使用方法

<keep-alive include='include_components' exclude='exclude_components'>
  <component>
    <!-- 该组件是否缓存取决于include和exclude属性 -->
  </component>
</keep-alive>

参数解释
include - 字符串或正则表达式,只有名称匹配的组件会被缓存
exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存
include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。

使用示例

<!-- 逗号分隔字符串,只有组件a与b被缓存。 -->
<keep-alive include="a,b">
  <component></component>
</keep-alive>

<!-- 正则表达式 (需要使用 v-bind,符合匹配规则的都会被缓存) -->
<keep-alive :include="/a|b/">
  <component></component>
</keep-alive>

<!-- Array (需要使用 v-bind,被包含的都会被缓存) -->
<keep-alive :include="['a', 'b']">
  <component></component>
</keep-alive>

十二、一句话就能回答的面试题

1.css只在当前组件起作用
答:在style标签中写入scoped即可 例如:

2.v-if 和 v-show 区别
答:v-if按照条件是否渲染,v-show是display的block或none;

3. r o u t e 和 route和 routerouter的区别
答:$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。

4.vue.js的两个核心是什么?
答:数据驱动、组件系统

5.vue几种常用的指令
答:v-for 、 v-if 、v-bind、v-on、v-show、v-else

6.vue常用的修饰符?
答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用

7.v-on 可以绑定多个方法吗?
答:可以

8.vue中 key 值的作用?
答:当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。

9.什么是vue的计算属性?
答:在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。好处:①使得数据处理结构清晰;②依赖于数据,数据更新,处理结果自动更新;③计算属性内部this指向vm实例;④在template调用时,直接写计算属性名即可;⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。

10.vue等单页面应用及其优缺点
答:优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。
缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。

11.怎么定义 vue-router 的动态路由? 怎么获取传过来的值
答:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。

算法面试

1. 字符串回文判断

思路:回文是指把相同的词汇或句子,在下文中调换位置或颠倒过来,产生首尾回环的情景,叫做回文,也叫回环。

将一个字符串首尾倒序排列,如果与原字符串相等,则这个字符串回文。

<script type="text/javascript">
	var str1 = 'abcdefgh';
	var str2 = 'abcdcba';
	function plalindrome(str){
   
		return str == str.split('').reverse().join('');
	}
	console.log(plalindrome(str1));//false
	console.log(plalindrome(str2));//true
</script>

2. 数组去重

思路:利用indexOf()a方法,在遍历原数组,若里面的元素第一次出现,则放在数组arr1中,遍历完之后,arr1中存放的是无重复的新数组

<script type="text/javascript">
	var arr = [2,4,2,2,5,6,7,8,9,9,9];
	function unique(arr){
   
		var arr1 = [];
		for (var i = 0;i < arr.length;i ++){
   
			if(arr1.indexOf(arr[i]) == -1){
   
				arr1.push(arr[i]);
			}
		}
		return arr1;
	}
	console.log(unique(arr));//[2, 4, 5, 6, 7, 8, 9] 
</script>

3. 统计一个字符串中出现最多的字母

思路:在另外一个数组存放原数组每个元素出现的位置次数,且次数跟存放不重复数组的下标对应,然后取出最多的次数,对应的下标就是不重复数组里面那个出现次数最多的元素的下标

<script type="text/javascript">
	var str1 = "jhadfgskjfajhdewqe";
	var arr1 = str1.split('');
	console.log(arr1);
	function MostUnit(){
   
		var arrA = [];
		var arrB = [];
		for(var i = 0 ;i <arr1.length; i ++){
   
			if(arrA.indexOf(arr1[i])==-1){
   
				arrA.push(arr1[i]);
				arrB.push(1);
			}else {
   
				arrB[arrA.indexOf(arr1[i])] ++;
			}
		}
		console.log(arrB)
		console.log(arrA[arrB.indexOf(Math.max.apply(Math,arrB))]);
	}
	MostUnit();//j
</script>

4. 冒泡排序:

  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
<script type="text/javascript">
	var arr1 = [2,3,45,64,321,3,21,321,31,999];
	function bubbleSort(arr) {
   
		for(var i = 0 ;i < arr1.length-1 ;i ++){
   
			for(var j = 0; j < arr1.length - i - 1 ;j ++){
   
				if(arr[j]>arr[j+1]) {
   
	                let tem = arr[j];
	                arr[j] = arr[j+1];
	                arr[j+1] = tem;
        		}
			}
		}
		return arr;
	}
	console.log(bubbleSort(arr1));//[2, 3, 3, 21, 31, 45, 64, 321, 321, 999]
</script>

5. 快速排序:

思路:算法参考某个元素值,将小于它的值,放到左数组中,大于它的值的元素就放到右数组中,然后递归进行上一次左右数组的操作,返回合并的数组就是已经排好顺序的数组了。

<script type="text/javascript">
	var arr1 = [1,4,765,86,53,87,53,32,6,64,2,3,767,34,1,4,35,6];
	function quickSort(arr){
   
		if(arr.length <= 1){
   
			return arr;
		}
		var leftArr = [];
	    var rightArr = [];
	    var q = arr[0];
	    for(var i = 1;i < arr.length; i++) {
   
	        if(arr[i]>q) {
   
	            rightArr.push(arr[i]);
	        }else{
   
	            leftArr.push(arr[i]);
	        }
		}
		return [].concat(quickSort(leftArr),[q],quickSort(rightArr));
	}
	console.log(quickSort(arr1));//[1,4,765,86,53,87,53,32,6,64,2,3,767,34,1,4,35,6]
</script>

6. 不利用第三方变量的情况下交换两个变量的值

思路:利用两个元素的差值进行计算

<script type="text/javascript">
		var a = 10;
		var b = 12;
		function swap (a,b) {
			b = b - a;
			a = a + b;
			b = a - b;
			return [a,b]
		}
		console.log(swap(a,b));
</script>

7. 求一个数组中最大数和最小数的差值

<script type="text/javascript">
	var arr1 = [2,44,3,-12,43,5,8,67,54,32,-211];
	var max = Math.max.apply(Math,arr1);
	var min = Math.min.apply(Math,arr1);
	console.log(max-min);//278
</script>

8. 生成指定长度的随机字符串

思路:charAt()方法,获取元素下标

<script type="text/javascript">
	function randomString(n){
   
		var str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz9876543210"
		var str2 = "";
		for (var i = 0; i < n ; i ++){
   
			str2 += str1.charAt(Math.floor(Math.random()*str1.length));
		}
		return str2;
	}
	console.log(randomString(5));
</script>

9. 获取一个DOM节点下面包含某个class名的所有节点

<div id="text">
	<div class="cs"></div>
	<div class="as"></div>
	<p class="cs"></p>
</div>
<script type="text/javascript">
  function getClass(node,classname) {
    
   if(node.getElementsByClassName) {
   
    return node.getElementsByClassName(classname);
 //如果存在该标签 就返回
   } else {
   
    var elems = node.getElementsByTagName(node),
      defualt = [];
    for (var i = 0; i < elems.length; i++) {
   
 //遍历所有标签
     if(elems[i].className.indexOf(classname) != -1) {
   
 //查找相应类名的标签
      defualt[defualt.length] = elems[i];
     }
    }
    return defualt;
   }
  }
   var text = document.getElementById('text'),
    cs = getClass(text,'cs');
     console.log(cs);//[div.cs, p.cs]
</script>

10. 二叉树

一说到二叉树我们肯定会问,什么是二叉树,二叉树是个啥子东东,拿来有啥子用嘛,我们为啥子要学习它嘛? 如果当初你在学习二叉树的时候你没有问过自己这些问题,那么你对它的了解也仅仅也只是了解。那我们现在来说说什么是二叉树,二叉树就是一种数据结构, 它的组织关系就像是自然界中的树一样。官方语言的定义是:是一个有限元素的集合,该集合或者为空、或者由一个称为根的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。至于为啥子要学习它,妈妈总是说,孩子,等你长大了就明白了。

11. 二叉树的性质

性质1:二叉树第i层上的节点数目最多为2i-1(i≥1);

性质2:深度为k的二叉树至多有2k-1个结点(k≥1)。

性质3: 在任意-棵二叉树中,若叶子结点(即度为0的结点)的个数为n0,度为1的结点数为n1,度为2的结点数为n2,则no=n2+1。

12. 二叉树的存储结构与构建

二叉树的存储方式有两种,一种顺序存储,比如:

var binaryTree = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘h’, ‘i’]; 这就是一颗二叉树,假设binaryTree[i]是二叉树的一个节点,那么它的左孩子节点 leftChild = binaryTree[i2+1]那  么相应的右孩子节点 rightChild = binaryTree[i2+2]; 一般情况下顺序存储的这种结构用的较少,另外一种存储方式就是链式存储,下面我会用代码来详细描述二叉树式  结构的构建与存储方式,构建二叉树也有两种方式一种是递归方式构建,这种很简单,另一种是非递归方法构建,这种呢相对于前一种复杂一点点,不过也不用担心,我在  代码中加上详细的注释,一步一步的走下去。我们现在就以26个英文字母来构建二叉树

var charecters = [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’, ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’];

在构建二叉树之前我们会用到一个节点对象,节点对象如下:(注意:关于javascript的面向对象,原型,语法特点我会放在javascript语言知识点这个系列)

/*
 *二叉树的节点对象
 */
function Node() {
   
    this.text = '';           //节点的文本
    this.leftChild = null;    //节点的左孩子引用
    this.rightChild = null;   //节点右孩子引用
}

13. 递归构建二叉树

在构建好二叉树节点之后我们紧接着用递归来构建二叉树

 var charecters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
            function buildTree(node, i) {
   
                var leftIndex = 2*i+1,                          //左孩子节点的索引
                    rightIndex = 2*i+2;                         //右孩子节点的索引
                if(leftIndex < charecters.length) {
                //判断索引的长度是否超过了charecters数组的大小
                    var childNode = new Node();                 //创建一个新的节点对象
                    childNode.text = charecters[leftIndex];     //给节点赋值
                    node.leftChild = childNode;                 //给当前节点node加入左孩子节点
                    buildTree(childNode, leftIndex);            //递归创建左孩子
                }
                if(rightIndex < charecters.length) {
               //下面注释参照上面的构建左孩子的节点
                    var childNode = new Node();
                    childNode.text = charecters[rightIndex];
                    node.rightChild = childNode;
                    buildTree(childNode, rightIndex);
                }
            }
            //下面构造二叉树
            var node = new Node();
            node.text = charecters[0];
            buildTree(node, 0);   //索引i是从0开始构建

14. 非递归构建二叉树

下面是以非递归方式构建二叉树:

var root;
            function createBinaryTree() {
   
                var len = charecters.length,                //数组的长度
                    index = 0,                              //索引从0开始
                    nodes = new Array();                    //创建一个临时数组,用于存放二叉树节点
                //循环创建二叉树节点存放到数组中
                for (var i = 0 ; i < charecters.length ; i++) {
   
                    var node = new Node();
                    node.text = charecters[i];
                    nodes.push(node);
                }
                //循环建立二叉树子节点的引用
                while(index < len) {
   
                    var leftIndex = 2*index+1,              //当前节点左孩子索引
                        rightIndex = 2*index+2;             //当前节点右孩子索引
                    //给当前节点添加左孩子
                    nodes[index].leftChild = nodes[leftIndex];
                    //给当前节点添加右孩子
                    nodes[index].rightChild = nodes[rightIndex];
                    index++;
                }
                root = nodes[0];
            }

15.二叉树的三种遍历

好了,现在我们已经成功构建了二叉树的链式结构,在构建了二叉树的链式结构后我们进入二叉树的最基本的遍历了,遍历有三种最基本的遍历,我不说想必大家都知道,先序遍历,中序遍历和后续遍历。虽然这三种遍历递归方式都比较简单,但非递归方式就不是那么容易了,当时我在实现的时候都卡了半天,真的是说起来容易做起来难啊,在实现遍历前我们首先要来实现的是栈,因为在非递归遍历的时候会用到栈,那到底什么是栈呢,这里我就简单介绍下吧,有兴趣的朋友可以去维基百科有权威的定义,栈和队列也是一种数据结构,栈存放数据的时候是先进先出,而队列是先进后出。

16.实现栈的对象

下面用javascript来实现栈的对象

function Stack() {
   
                var stack = new Array();               //存放栈的数组
                //压栈
                this.push = function(o) {
   
                    stack.push(o);
                };
                //出栈
                this.pop = function() {
   
                    var o = stack[stack.length-1];
                    stack.splice(stack.length-1, 1);
                    return o;
                };
                //检查栈是否为空
                this.isEmpty = function() {
   
                    if(stack.length <= 0) {
   
                        return true;
                    }
                    else {
   
                        return false;
                    }
                };
            }
            //使用方式如下
            var stack = new Stack();
            stack.push(1);       //现在栈中有一个元素
            stack.isEmpty();     //false , 栈不为空
            alert(stack.pop());  //出栈, 打印1
            stack.isEmpty();     //true, 此时栈为空,因为在调用了stack.pop()之后元素出栈了,所以为空

17. 先序遍历

在实现了栈对象以后我们首先来进行先序遍历的递归方式

 function firstIteration(node) {
   
                if(node.leftChild) {
                      //判断当前节点是否有左孩子
                    firstIteration(node.leftChild);    //递归左孩子
                }
                if(node.rightChild) {
                     //判断当前节点是否有右孩子
                    firstIteration(node.rightChild);   //递归右孩子
                }
            }
            //递归遍历二叉树
            firstIteration(root);

18. 先序遍历的非递归方式

上面的代码大家可以在firstIteration()方法中加个alert()函数来验证是否正确。那么下面就要说说先序遍历的非递归方式,遍历思想是这样的:先访问根节点在访问左节   点, 最后访问右节点。从根节点一直往下访问找左孩子节点,直到最后一个左孩子节点(将这条路径保存到栈中),然后再访问最后一个左孩子的兄弟节点(右孩子节点),之后回溯到上一层(将栈中的元素取出 就是出栈),又开始从该节点(回溯到上一层的节点)一直往下访问找左孩子节点… 直到栈中的元素为空,循环结束。

function notFirstIteration(node) {
   
                var stack = new Stack(),                 //开辟一个新的栈对象
                    resultText = '';                     //存放非递归遍历之后的字母顺序
                stack.push(root);                        //这个root在上面非递归方式构建二叉树的时候已经构建好的
 
                var node = root;
                resultText += node.text;
                while(!stack.isEmpty()) {
   
                    while(node.leftChild) {
                 //判断当前节点是否有左孩子节点
                        node = node.leftChild;           //取当前节点的左孩子节点
                        resultText += node.text;         //访问当前节点
                        stack.push(node);                //将当前节点压入栈中
                    }
                    stack.pop();                         //出栈
                    node = stack.pop().rightChild;       //访问当前节点的兄弟节点(右孩子节点)
                    if(node) {
                              //当前节点的兄弟节点不为空
                        resultText += node.text;         //访问当前节点
                        stack.push(node);                //将当前节点压入栈中
                    }
                    else {
                                  //当前节点的兄弟节点为空   
                        node = stack.pop();              //在回溯到上一层
                    }
                }
            }
            //非递归先序遍历
            notFirstIteration(root);

19.中序遍历

只要把思路理清楚了现实起来其实还是挺容易的,只要我们熟悉了一种二叉树的非递归遍历方式,其他几种非递归方式就容易多了,照着葫芦画瓢,下面是中序遍历的递归  方式,中序遍历的思想是:先访问左孩子节点,在访问根节点,最后访问右节点

 var strText = "";
            function secondIteration(node) {
   
                //访问左节点
                if(node.leftChild) {
                           
                    if(node.leftChild.leftChild) {
             
                        secondIteration(node.leftChild);
                    }
                    else {
   
                        strText += node.leftChild.text;
                    }
                }
                //访问根节点
                strText += node.text;
                //访问右节点
                if(node.rightChild) {
   
                    if(node.rightChild.leftChild) {
   
                        secondIteration(node.rightChild);
                    }
                    else {
   
                        strText += node.rightChild.text;
                    }
                }
            }
            secondIteration(root);
            alert(strText);

20. 中序遍历的非递归方式

思想是:1. 从根节点一直往下找左孩子节点,直到找到最后一个左孩子节点(用栈将此路径保存,但不访问)2.访问最后一个左孩子节点,然后再  访问根节点(要先弹出栈,就是在栈中取上一层节点)3.在访问当前节点(最后一个左孩子节点)的兄弟节点(右孩子节点),这里要注意如果兄弟节点是一个叶节点就直  接访问,否则是兄弟节点是一颗子树的话不能马上访问,要先来重复 1, 2,3步骤, 直到栈为空,循环结束

function notSecondIteration() {
   
                var resultText = '',
                    stack = new Stack(),
                    node = root;
                stack.push(node);
                
                while(!stack.isEmpty()) {
   
                    //从根节点一直往下找左孩子节点直到最后一个左孩子节点,然后保存在栈中
                    while(node.leftChild) {
   
                        node = node.leftChild;
                        stack.push(node);
                    }
                    //弹出栈
                    var tempNode = stack.pop();
                    //访问临时节点
                    resultText += tempNode.text;
                    if(tempNode.rightChild) {
   
                        node = tempNode.rightChild;
                        stack.push(node);
                    }
                }
                alert(resultText);
            }

21. 后续遍历

最后就还剩下一种遍历方式,二叉树的后续遍历,后续遍历的思想是:先访问左孩子节点,然后在访问右孩子节点,最后访问根节点

22. 后续遍历的递归方式

var strText = '';
            function lastIteration(node) {
   
                //首先访问左孩子节点
                if(node.leftChild) {
   
                    if(node.leftChild.leftChild) {
   
                        lastIteration(node.leftChild);
                    }
                    else {
   
                        strText += node.leftChild.text;
                    }
                }
                //然后再访问右孩子节点
                if(node.rightChild) {
   
                    if(node.rightChild.rightChild) {
   
                        lastIteration(node.rightChild);
                    }
                    else {
   
                        strText += node.rightChild.text;
                    }
                }
                //最后访问根节点
                strText += node.text;
            }
            //中序递归遍历
            lastIteration(root);
            alert(strText);

23.后续非递归遍历

后续非递归遍历的思想是:1.从根节点一直往下找左孩子节点,直到最后一个左孩子节点(将路径保存到栈中,但不访问)2.弹出栈访问最后一个左孩子节点 3.进入最后一  个左孩子节点的兄弟节点,如果兄弟节点是叶节点就访问它,否则将该节点重复 1, 2步骤, 直到栈中的元素为空,循环结束。3.访问根节点

 function notLastIteration() {
   
                var strText = '',
                    stack = new Stack();
                    nodo = root;
                stack.push(node);
 
                while(!stack.isEmpty()) {
   
                    while(node.leftChild) {
   
                        node = node.leftChild;
                        stack.push(node);
                    }
 
                    //弹出栈
                    var tempNode = stack.pop();
                    //访问左孩子节点
                    strText += tempNode.text;
                    //访问右孩子节点
                    if(tempNode.rightChild) {
   
                        if(tempNode.rightChild.leftChild || tempNode.rightChild.rightChild) {
    //判断最后一个左孩子节点的兄弟节点是否为页节点
                            stack.push(tempNode.rightChild);
                        }
                        else {
   
                            strText += tempNode.rightChild.text;
                        }
                    }
                }
                alert(strText);
            }

24. 已知前序和中序构建二叉树

  <script>
        let qianxu = [ 1,2,4,7,3,5,6,8 ];
        let zhongxu = [ 4,7,2,1,5,3,8,6 ];
        function TreeNode( val ){
   
            this.val = val;
            this.left = null;
            this.right = null;
        }
        function rebuildTree(qianxu,zhongxu){
   
            if (qianxu[0]){
   
            // 1. 根据找到的根节点( 前序序列的第一个元素一定是根节点 )
            let rootVal = qianxu[0];
            //  2. 找到根节点和中序序列,找到树的左子树和右子树
            // 根节点在中序序列中的位置
            let index = zhongxu.indexOf( rootVal );
            // 前序序列:左子树 qianxu(1,index),右子树qianxu(index+1,最后)
            // 中序序列:左子树 zhongxu(0,index-1),右子树 zhongxu(index+1,最后)
            let leftTree = rebuildTree(qianxu.slice(1,index+1),zhongxu.slice(0,index));
            let rightTree = rebuildTree(qianxu.slice(index+1),zhongxu.slice(index+1));
            let root = new TreeNode(rootVal);
            root.right = rightTree;
            root.left = leftTree;
            return root;
            }
        }

        console.log(rebuildTree(qianxu,zhongxu));
    </script>

25. 栈转化成队列

  <script>
       let stack1 = [];
       let stack2 = [];
       function push(node){
   
           stack1.push(node);
       }
       function pop (){
   
            while(stack1.length){
   
                stack2.push(stack1.pop());
            }   
            let popVal = stack2.pop();
            while(stack2.length){
   
                stack1.push(stack2.pop());
            }   
            return popVal;
       }
       /**
        * 1,2,3,4,5 入队
        * 出队操作 1
        * 入队 6
        * 出队操作2
        * 出队操作3
       */
      push(1);
      push(2);
      push(3);
      push(4);
      push(5);
      console.log(pop());
    </script>

26.跳台阶(典型递归)

  • 一只青蛙一次可以跳上一级台阶,也可以跳上两级
  • 求该青蛙上一个n级的台阶总共有多少中跳法(向后次序不同算不同结果)
<script>
        function jumpFloor(n){
   
            if(n==1) return 1;
            else if(n==2) return 2;
            return jumpFloor(n-1)+jumpFloor(n-2);
        }
        console.log(jumpFloor(3));
    </script>

  • 算法优化用空间换时间;(加缓存)(记忆化递归)
<script>
        // 记忆化递归
        let cache = [,1,2];
        function jumpFloor2(n){
   
            if(cache[n] !==undefined) return cache[n];
            return cache[n] = jumpFloor(n-1)+jumpFloor(n-2);
        }
        console.log(jumpFloor2(3));
    </script>

  • 一只青蛙一次可以跳上一级台阶,也可以跳上两级…它也可以跳上n级;
  • 求该青蛙上一个n级的台阶总共有多少中跳法(向后次序不同算不同结果)
  • f(n)=f(n-1)+f(n-2)+…+f(2)+f(1)+1
<script>
       let cache = [,1,2];
       function jumpFloor(n){
   
            if(cache[n] !== undefined) return cache[n];
            cache[n] = 1;
            for(let i = n-1;i >=1;i--){
   
               cache[n] += jumpFloor(i); 
            }
            return cache[n];
       }
       console.log(jumpFloor(10));
    </script>

27. 反转链表

  • 写一个函数,输入一个链表,反转练表后,输出新链表的表头。
  <script>
      function Node(val){
          this.val = val;
          this.next = null;  
      }
 function creatList(arr){
        let head = new Node(arr[0]);
        let tail = head;
        for(let i =1;i<=arr.length-1;i++){
            tail.next = new Node(arr[i]);
            tail = tail.next;
        }
        return head;
  }

  let list = creatList([1,2,3,4,5]);
//   console.log(list);

  function reverseList (head){
      let arr = [];
      let p = head;
      while(p){
          arr.push(p.val);
          p = p.next;
      }

      p = head;
      while(p){
          p.val = arr.pop(p.val);
          p = p.next;
      }
      return head;
  }
  console.log(reverseList(list));
</script>

28. 字典树

需求:
10 创建一个字典树,在字典树中查找是否包含某个单词
11
12 单词序列:
13 and
14 about
15 as
16 boy
17 by
18 because
19 as
20
21 查找:
22 close false
23 an false
24 as true
25 boy true
26
27 字典树是什么
28 字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
30 字典树的作用
31 统计,排序和保存大量的字符串
33 字典树的特点
34 1、字典树的节点存储的是单词的字符(字母)
35 2、为了表示一个单词是否出现,我们可以给单词的最后的字符加上标记
36 3、字典树中表示一个单词用的是一条链
37 4、字典树的根节点没有什么意义
39 字典树的操作
40 1、把单词插入到字典树里面去
41 2、在字典树中查找单词
45 1、把单词插入到字典树里面去
46 算法步骤:
47 去跟节点下面去找这个单词的第一个字符是否出现,
48 如果没出现,就创建,然后走这条路,
49 如果出现了,就直接走这条路
50 (在这个过程里面,单词的第一个字符就被消耗掉了)
52 算法:
53 递归
56 2、在字典树中查找单词
57 算法:
58 递归
60 算法步骤:
61 查找单词的第一个字符是否在根节点的子节点中,如果出现了,就接着往下找
62 如果没出现,直接return false
63 在单词找完后,如果标记大于1,表示单词出现过,就return true,
64 否则return false


NodeJS

一:Node 好处: 处理高并发 事件驱动 轻量 要用于搭建高性能的web服务器,

1. 它是一个Javascript运行环境

2. 依赖于Chrome V8引擎进行代码解释

3. 事件驱动

4. 非阻塞I/O

5. 轻量、可伸缩,适于实时数据交互应用

6. 单进程,单线程

二:Express 和 koa的区别?

异步 摆脱回调地域

对response 和request进行了封装 content

Express主要基于Connect中间件框架,功能丰富,随取随用,并且框架自身封装了大量便利的功能,比如路由、视图处理等等。而koa主要基于co中间件框架,框架自身并没集成太多功能,大部分功能需要用户自行require中间件去解决,但是由于其基于ES6 generator特性的中间件机制,解决了长期诟病的“callback hell”和麻烦的错误处理的问题,大受开发者欢迎。

三:事件驱动模型和事件循环:

事件驱动模型:当服务端收到请求时,就把它关闭 然后处理下一个请求 当第一个请求处理完毕后 就放回处理队列 当达到队列开头 将结果返回给用户 好处:高效 扩展性强 因为服务端一直接受请求 不等待任何读写操作

事件循环:查看队列里面是否有队列里面有待处理的 如果有 交给主线程执行

四:Redis:

使用场景:支持string、list、set、zset和hash类型数据。

  1. 配合关系型数据库做高速缓存
  • 缓存高频次访问的数据,降低数据库io
  • 分布式架构,做session共享
  1. 可以持久化特定数据。
  • 利用zset类型可以存储排行榜
  • 利用list的自然时间排序存储最新n个数据

五:mysql 和mongodb的区别

mysql 关系型数据库 mongodb是非关系数据库(主要)

六:MySQL索引
七:闭包应该注意的地方
八:进程和线程

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

进程是线程的容器

十:mysql存储引擎 和区别

InnoDB存储引擎:事务型数据库首选,支持事务安全表(ACID),支持行锁定和外键 是mysql 5.5之后的默认引擎

MyISAM 存储引擎:不支持事务和外键,访问速度较快,是mysql5.5 之前的默认引擎

MEMORY: 保存在内存中的数据表 ,每个memory表对应一个磁盘文件。格式是.frm 访问速度很快 缺点是:mysql服务关闭,数据丢失,另外对数据表大小有限制。

十一:如何判断一个字符串是另一个字符串的子串

indexof es6:include startWith endWith
十二:单点登录
十三:oauth2.0
十四:type of 和instance of 区别
十五:pm2 restart 和reload的区别(配置文件的重载 重启)
十六:MySQL 读写分离
十七:pm2如何查看指定三个项目的日志
十八:深拷贝 浅拷贝
十九:路由机制
二十:MySQL 批量更新
二十一:登录流程
二十二:cookie 和session
二十三:基本数据类型 引用数据类型 区别

二十四:防止sql 注入

1.使用escape() 对传入参数进行编码

2.使用connection.query ()的查询参数占位符

3.使用escapeId()编码SQL查询标识符

4.使用mysql.format()转义参数:

二十五:require()模块加载机制

先判断是否存在文件缓存区中,存在直接导入,没有的话,在判断是否是原生模块,

如果是原生模块,再看是否在原生模块缓存区中,如果有直接导入,没有的话加载原生模块,缓存原生模块,在导入

如果不是原生模块,先查找文件模块,根据扩展名载入文件模块,缓存文件模块,在导入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rujkgYx7-1611887935898)(C:\Users\zjx\Desktop\桌面文件\web面试\1.jpg)]

第1题, 什么是nodejs?我们在哪里使用它?

Nodejs是服务器端的一门技术。它是基于Google V8 JavaScript引擎而开发的。用来开发可扩展的服务端程序。

第2题,为什么要使用node js?

nodejs会让我们的编程工作变得简单,它主要包含如下几点几个好处:

执行快速。

永远不会阻滞。

JavaScript是通用的编程语言。

异步处理机制。

避免并行所带来的问题。

第3题,nodejs有哪些特点?

是单线程的,但是有很高的可扩展性,使用JavaScript作为主流编程语言。使用的是异步处理机制和事件驱动。处理高效。

第4题, Set immediate和set time out 区别在哪里?

Set immediate就是马上执行的意思。Set time out, 时间参数传为0,也想获得同样的功能。只不过前者要快一些。

第5题,如何更新nodejs的版本?

npm install npm -g

第6题,为什么nodejs是单线程的?

Nodejs使用的是单线程没错,但是通过异步处理的方式,可以处理大量的数据吞吐量,从而有更好的性能和扩可扩展性。

第7题,什么是回调函数?

回调函数是指用一个函数作为参数传入另一个函数,这个函数会被在某个时机调用。

第8题, 什么叫做回调地狱?

回调地狱是由嵌套的回调函数导致的。这样的机制会导致有些函数无法到达,并且很难维护。

第9题,如何阻止回调地狱?

有三种方法, 对每个错误都要处理到, 保证代码的贯通, 程序代码模块化。

第10题,解释一下repl的作用?

Read evaluate print loop, 用于测试,调试和实验用。

第11题,API函数的类型有哪些?

有两种,

一种是阻滞型函数。阻滞型函数会等待操作完成以后再进行下一步。

另外一种是非阻滞型函数。这种函数使用回调函数来处理当前函数获取的结果。

第12题,回调函数的第1个参数是什么?

通常是错误对象。如果这个参数为空,表示没有错误。

第13题,NPM的作用是什么?

Node package manager, 主要有两个功能。

它是一个网端模块的存储介质。

它的另一个作用是安装程序依赖和版本管理。

第14题,nodejs和ajax的区别是什么?

Nodejs和ajax也就是asynchronous JavaScript and xml,都是通过JavaScript来表现的,但是他们的目的截然不同。

Ajax是设计用来动态的更新页面的某个区域,从而不需要更新整个页面。

Nodejs是用来开发客户服务器类型应用的。

第15题,解释一下nodejs中chaining.

Chaining是指从一个数据流到另一个数据流的链接,从而实现多个流操作。

第16题,什么是streams?解释一下有哪些类型?

流的概念是不间断的,它可以不间断的从某个地方读取数据,或者向某个地方写入数据。

有4种类型的流数据。可读,可写。既可读,又可写,转化。

第17题,退出代码是什么?有哪些退出代码?

退出代码是指中断nodejs运行时返回的代码。

有这么几种unused, uncaught fatal exception, fatal error, non function internal exception handler, internal exception handler run time failure,internal JavaScript evaluation failure.

第18题, 什么是globals?

有三个global的关键字。

Global代表的是最上层的命名空间,用来管理所有其他的全局对象。

Process 是一个全局对象,可以把异步函数转化成异步回调, 它可以在任何地方被访问,它主要是用来返回系统的应用信息和环境信息.

Buffer, 是用来处理二进制数据的类.

第19题, Angular js和node js的区别是什么?

Angular js是网络应用开发框架,而nodejs是一个实时系统。

第20题, 为什么统一的风格儿非常重要,有什么工具可以保证这一点?

统一的风格可以让所有的组成员按照一种规矩来写代码。工具有Standard和eslint.

第21题, 用什么方法来处理没有被处理的异常?

在应用和node js之间使用domain来处理这样的异常。

第22题, Node js是如何支持多处理器平台的?

Cluster模块是用来支持这方面的。它可以允许多个nodejs工作进程运行在相同的端口上。

第23题, 如何配置开发模式和生产模式的环境?

首先有一个配置文件,然后通过环境变量参数来获取对应的配置内容。

第24题, nodejs中跟时间相关的函数有哪些?

Set time out, clear time out.

Set interval, clear interval.

Set immediate, clear immediate.

Process.nextTick.

第25题, 解释一下什么是reactor pattern。

Reactor pattern主要是非阻滞的i/o操作。提供一个回调函数来关联io操作。io请求完成以后会不会提交给demultiplexer, 这是一个通知接口用来处理并发性的非阻滞的io操作,这个功能是通过查询一个event loop来实现的.

第26题,lts版本是什么意思?

也就是long term support版本。至少会被支持18个月。使用的是偶数来标识。这种版本有稳定性和安全性的保证。

第27题,你为什么需要把express APP和server分开?

分开以后方便维护以及测试,在测试某个模块的时候,尤其是APP模块的时候,你不需要去对网络方面的连接配置做工作。

第28题,next tick和setImmediate的区别是什么?

Next tick会等待当前的event执行完成或者下一轮儿事件循环到达再执行。

Set immediate, 会在下一轮的事件循环中,执行回调并且返回当前的循环来做读写操作.

第29题,apply, call和bind有什么区别?

参考答案:三者都可以把一个函数应用到其他对象上,注意不是自身对象.apply,call是直接执行函数调用,bind是绑定,执行需要再次调用.apply和call的区别是apply接受数组作为参数,而call是接受逗号分隔的无限多个参数列表,

代码演示

    function Person() {
    }
    Person.prototype.sayName() { alert(this.name); }

    var obj = {name: 'michaelqin'}; // 注意这是一个普通对象,它不是Person的实例
    1) apply
    Person.prototype.sayName.apply(obj, [param1, param2, param3]);

    2) call
    Person.prototype.sayName.call(obj, param1, param2, param3);

    3) bind
    var sn = Person.prototype.sayName.bind(obj);
    sn([param1, param2, param3]); // bind需要先绑定,再执行
    sn(param1, param2, param3); // bind需要先绑定,再执行

二)、node全局对象

3. node有哪些核心模块?

参考答案: EventEmitter, Stream, FS, Net和全局对象

1. node有哪些全局对象?

参考答案: process, console, Buffer

2. process有哪些常用方法?

参考答案: process.stdin, process.stdout, process.stderr, process.on, process.env, process.argv, process.arch, process.platform, process.exit

3. console有哪些常用方法?

参考答案: console.log/console.info, console.error/console.warning, console.time/console.timeEnd, console.trace, console.table

4. node有哪些定时功能?

参考答案: setTimeout/clearTimeout, setInterval/clearInterval, setImmediate/clearImmediate, process.nextTick

5. node中的事件循环是什么样子的?
    总体上执行顺序是:process.nextTick >> setImmidate >> setTimeout/SetInterval 看官网吧:[https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/)
6. node中的Buffer如何应用?

参考答案: Buffer是用来处理二进制数据的,比如图片,mp3,数据库文件等.Buffer支持各种编码解码,二进制字符串互转.

三)、EventEmitter

1. 什么是EventEmitter?

参考答案: EventEmitter是node中一个实现观察者模式的类,主要功能是监听和发射消息,用于处理多模块交互问题.

2. 如何实现一个EventEmitter?

参考答案: 主要分三步:定义一个子类,调用构造函数,继承EventEmitter

代码演示

    var util = require('util');
    var EventEmitter = require('events').EventEmitter;

    function MyEmitter() {
        EventEmitter.call(this);
    } // 构造函数

    util.inherits(MyEmitter, EventEmitter); // 继承

    var em = new MyEmitter();
    em.on('hello', function(data) {
        console.log('收到事件hello的数据:', data);
    }); // 接收事件,并打印到控制台
    em.emit('hello', 'EventEmitter传递消息真方便!');
3. EventEmitter有哪些典型应用?

参考答案:

    1. 模块间传递消息
    1. 回调函数内外传递消息
    1. 处理流数据,因为流是在EventEmitter基础上实现的.
    1. 观察者模式发射触发机制相关应用
4. 怎么捕获EventEmitter的错误事件?

参考答案: 监听error事件即可.如果有多个EventEmitter,也可以用domain来统一处理错误事件.

代码演示

    var domain = require('domain');
    var myDomain = domain.create();
    myDomain.on('error', function(err){
        console.log('domain接收到的错误事件:', err);
    }); // 接收事件并打印
    myDomain.run(function(){
        var emitter1 = new MyEmitter();
        emitter1.emit('error', '错误事件来自emitter1');
        emitter2 = new MyEmitter();
        emitter2.emit('error', '错误事件来自emitter2');
    });
5. EventEmitter中的newListenser事件有什么用处?

参考答案: newListener可以用来做事件机制的反射,特殊应用,事件管理等.当任何on事件添加到EventEmitter时,就会触发newListener事件,基于这种模式,我们可以做很多自定义处理.

代码演示

var emitter3 = new MyEmitter();
emitter3.on('newListener', function(name, listener) {
    console.log("新事件的名字:", name);
    console.log("新事件的代码:", listener);
    setTimeout(function(){ console.log("我是自定义延时处理机制"); }, 1000);
});
emitter3.on('hello', function(){
    console.log('hello node');
});

四)、Stream

1. 什么是Stream?

参考答案: stream是基于事件EventEmitter的数据管理模式.由各种不同的抽象接口组成,主要包括可写,可读,可读写,可转换等几种类型.

2. Stream有什么好处?

参考答案: 非阻塞式数据处理提升效率,片断处理节省内存,管道处理方便可扩展等.

3. Stream有哪些典型应用?

参考答案: 文件,网络,数据转换,音频视频等.

4. 怎么捕获Stream的错误事件?

参考答案: 监听error事件,方法同EventEmitter.

5. 有哪些常用Stream,分别什么时候使用?

参考答案: Readable为可被读流,在作为输入数据源时使用;Writable为可被写流,在作为输出源时使用;Duplex为可读写流,它作为输出源接受被写入,同时又作为输入源被后面的流读出.Transform机制和Duplex一样,都是双向流,区别时Transfrom只需要实现一个函数_transfrom(chunk, encoding, callback);而Duplex需要分别实现_read(size)函数和_write(chunk, encoding, callback)函数.

6. 实现一个Writable Stream?

参考答案: 三步走:1)构造函数call Writable 2) 继承Writable 3) 实现_write(chunk, encoding, callback)函数

代码演示

var Writable = require('stream').Writable;
var util = require('util');

function MyWritable(options) {
    Writable.call(this, options);
} // 构造函数
util.inherits(MyWritable, Writable); // 继承自Writable
MyWritable.prototype._write = function(chunk, encoding, callback) {
    console.log("被写入的数据是:", chunk.toString()); // 此处可对写入的数据进行处理
    callback();
};

process.stdin.pipe(new MyWritable()); // stdin作为输入源,MyWritable作为输出源   

五)、文件系统

1. 内置的fs模块架构是什么样子的?

参考答案: fs模块主要由下面几部分组成: 1) POSIX文件Wrapper,对应于操作系统的原生文件操作 2) 文件流 fs.createReadStream和fs.createWriteStream 3) 同步文件读写,fs.readFileSync和fs.writeFileSync 4) 异步文件读写, fs.readFile和fs.writeFile

2. 读写一个文件有多少种方法?

参考答案: 总体来说有四种: 1) POSIX式低层读写 2) 流式读写 3) 同步文件读写 4) 异步文件读写

3. 怎么读取json配置文件?

参考答案: 主要有两种方式,第一种是利用node内置的require(‘data.json’)机制,直接得到js对象; 第二种是读入文件入内容,然后用JSON.parse(content)转换成js对象.二者的区别是require机制情况下,如果多个模块都加载了同一个json文件,那么其中一个改变了js对象,其它跟着改变,这是由node模块的缓存机制造成的,只有一个js模块对象; 第二种方式则可以随意改变加载后的js变量,而且各模块互不影响,因为他们都是独立的,是多个js对象.

4. fs.watch和fs.watchFile有什么区别,怎么应用?

参考答案: 二者主要用来监听文件变动.fs.watch利用操作系统原生机制来监听,可能不适用网络文件系统; fs.watchFile则是定期检查文件状态变更,适用于网络文件系统,但是相比fs.watch有些慢,因为不是实时机制.

六)、网络

1. node的网络模块架构是什么样子的?

参考答案: node全面支持各种网络服务器和客户端,包括tcp, http/https, tcp, udp, dns, tls/ssl等.

2. node是怎样支持https,tls的?

参考答案: 主要实现以下几个步骤即可: 1) openssl生成公钥私钥 2) 服务器或客户端使用https替代http 3) 服务器或客户端加载公钥私钥证书

3. 实现一个简单的http服务器?

参考答案: 经典又很没毛意义的一个题目.思路是加载http模块,创建服务器,监听端口.

代码演示

    var http = require('http'); // 加载http模块

    http.createServer(function(req, res) {
        res.writeHead(200, {'Content-Type': 'text/html'}); // 200代表状态成功, 文档类型是给浏览器识别用的
        res.write('<meta charset="UTF-8"> <h1>我是标题啊!</h1> <font color="red">这么原生,初级的服务器,下辈子能用着吗?!</font>'); // 返回给客户端的html数据
        res.end(); // 结束输出流
    }).listen(3000); // 绑定3ooo, 查看效果请访问 http://localhost:3000

七)、child-process

1. 为什么需要child-process?

参考答案: node是异步非阻塞的,这对高并发非常有效.可是我们还有其它一些常用需求,比如和操作系统shell命令交互,调用可执行文件,创建子进程进行阻塞式访问或高CPU计算等,child-process就是为满足这些需求而生的.child-process顾名思义,就是把node阻塞的工作交给子进程去做.

2. exec,execFile,spawn和fork都是做什么用的?

参考答案: exec可以用操作系统原生的方式执行各种命令,如管道 cat ab.txt | grep hello; execFile是执行一个文件; spawn是流式和操作系统进行交互; fork是两个node程序(javascript)之间时行交互.

3. 实现一个简单的命令行交互程序?

参考答案: 那就用spawn吧.

代码演示

    var cp = require('child_process');

    var child = cp.spawn('echo', ['你好', "钩子"]); // 执行命令
    child.stdout.pipe(process.stdout); // child.stdout是输入流,process.stdout是输出流
    // 这句的意思是将子进程的输出作为当前程序的输入流,然后重定向到当前程序的标准输出,即控制台
4. 两个node程序之间怎样交互?

参考答案: 用fork嘛,上面讲过了.原理是子程序用process.on, process.send,父程序里用child.on,child.send进行交互.
代码演示

    1) fork-parent.js
    var cp = require('child_process');
    var child = cp.fork('./fork-child.js');
    child.on('message', function(msg){
        console.log('老爸从儿子接受到数据:', msg);
    });
    child.send('我是你爸爸,送关怀来了!');

    2) fork-child.js
    process.on('message', function(msg){
        console.log("儿子从老爸接收到的数据:", msg);
        process.send("我不要关怀,我要银民币!");
    });
5. 怎样让一个js文件变得像linux命令一样可执行?

参考答案: 1) 在myCommand.js文件头部加入 #!/usr/bin/env node 2) chmod命令把js文件改为可执行即可 3) 进入文件目录,命令行输入myComand就是相当于node myComand.js了

6. child-process和process的stdin,stdout,stderror是一样的吗?

参考答案: 概念都是一样的,输入,输出,错误,都是流.区别是在父程序眼里,子程序的stdout是输入流,stdin是输出流.

九、node高级话题(异步,部署,性能调优,异常调试等)

1. node中的异步和同步怎么理解

参考答案: node是单线程的,异步是通过一次次的循环事件队列来实现的.同步则是说阻塞式的IO,这在高并发环境会是一个很大的性能问题,所以同步一般只在基础框架的启动时使用,用来加载配置文件,初始化程序什么的.

2. 有哪些方法可以进行异步流程的控制?

参考答案: 1) 多层嵌套回调 2) 为每一个回调写单独的函数,函数里边再回调 3) 用第三方框架比方async, q, promise等

3. 怎样绑定node程序到80端口?

参考答案: 多种方式 1) sudo 2) apache/nginx代理 3) 用操作系统的firewall iptables进行端口重定向

4. 有哪些方法可以让node程序遇到错误后自动重启?

参考答案: 1) runit 2) forever 3) nohup npm start &

5. 怎样充分利用多个CPU?

参考答案: 一个CPU运行一个node实例

6. 怎样调节node执行单元的内存大小?

参考答案: 用–max-old-space-size 和 --max-new-space-size 来设置 v8 使用内存的上限

7. 程序总是崩溃,怎样找出问题在哪里?

参考答案: 1) node --prof 查看哪些函数调用次数多 2) memwatch和heapdump获得内存快照进行对比,查找内存溢出

8. 有哪些常用方法可以防止程序崩溃?

参考答案: 1) try-catch-finally 2) EventEmitter/Stream error事件处理 3) domain统一控制 4) jshint静态检查 5) jasmine/mocha进行单元测试

9. 怎样调试node程序?

参考答案: node --debug app.js 和node-inspector

10. 如何捕获NodeJS中的错误,有几种方法? 参考答案: 1) 监听错误事件req.on(‘error’, function(){}), 适用EventEmitter存在的情况; 2) Promise.then.catch(error),适用Promise存在的情况 3) try-catch,适用async-await和js运行时异常,比如undefined object

十、 常用知名第三方类库(Async, Express等)

1. async都有哪些常用方法,分别是怎么用?

参考答案: async是一个js类库,它的目的是解决js中异常流程难以控制的问题.async不仅适用在node.js里,浏览器中也可以使用.

  • 1). async.parallel并行执行完多个函数后,调用结束函数
    async.parallel([
        function(){ ... },
        function(){ ... }
    ], callback);
  • 2). async.series串行执行完多个函数后,调用结束函数
    async.series([
        function(){ ... },
        function(){ ... }
    ]);
  • 3). async.waterfall依次执行多个函数,后一个函数以前面函数的结果作为输入参数
    async.waterfall([
        function(callback) {
            callback(null, 'one', 'two');
        },
        function(arg1, arg2, callback) {
          // arg1 now equals 'one' and arg2 now equals 'two'
            callback(null, 'three');
        },
        function(arg1, callback) {
            // arg1 now equals 'three'
            callback(null, 'done');
        }
    ], function (err, result) {
        // result now equals 'done'
    });
  • 4). async.map异步执行多个数组,返回结果数组
    async.map(['file1','file2','file3'], fs.stat, function(err, results){
        // results is now an array of stats for each file
    });
  • 5). async.filter异步过滤多个数组,返回结果数组
    async.filter(['file1','file2','file3'], fs.exists, function(results){
        // results now equals an array of the existing files
    });
2. express项目的目录大致是什么样子的

参考答案: app.js, package.json, bin/www, public, routes, views.

3. express常用函数

参考答案: express.Router路由组件,app.get路由定向,app.configure配置,app.set设定参数,app.use使用中间件

4. express中如何获取路由的参数

参考答案: /users/:name使用req.params.name来获取; req.body.username则是获得表单传入参数username; express路由支持常用通配符 ?, +, *, and ()

5. express response有哪些常用方法

参考答案: res.download() 弹出文件下载
res.end() 结束response
res.json() 返回json
res.jsonp() 返回jsonp
res.redirect() 重定向请求
res.render() 渲染模板
res.send() 返回多种形式数据
res.sendFile 返回文件
res.sendStatus() 返回状态

十一、其它相关后端常用技术(MongoDB, Redis, Apache, Nginx等)

1. mongodb有哪些常用优化措施

参考答案: 类似传统数据库,索引和分区.

2. mongoose是什么?有支持哪些特性?

参考答案: mongoose是mongodb的文档映射模型.主要由Schema, Model和Instance三个方面组成.Schema就是定义数据类型,Model就是把Schema和js类绑定到一起,Instance就是一个对象实例.常见mongoose操作有,save, update, find. findOne, findById, static方法等.

3. redis支持哪些功能

参考答案: set/get, mset/hset/hmset/hmget/hgetall/hkeys, sadd/smembers, publish/subscribe, expire

4. redis最简单的应用

参考答案:

    var redis = require("redis"),
        client = redis.createClient();

    client.set("foo_rand000000000000", "some fantastic value");
    client.get("foo_rand000000000000", function (err, reply) {
        console.log(reply.toString());
    });
    client.end();
5. apache,nginx有什么区别?

参考答案: 二者都是代理服务器,功能类似.apache应用简单,相当广泛.nginx在分布式,静态转发方面比较有优势.

HTTP状态码

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误

转载:https://blog.csdn.net/qq_44891434/article/details/113371490
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场