[TOC]
无论从事什么行业,只要做好两件事就够了,一个是你的专业、一个是你的人品,专业决定了你的存在,人品决定了你的人脉,剩下的就是坚持,用善良专业和真诚赢取更多的信任。
HTML、CSS
谈谈你对UTF8字符集的了解 文档流
一、什么是文档流?
那么所谓的文档流(normal flow,也被称为“普通流”),指的是就是元素排版布局过程中,元素会自动从左往右,从上往下地遵守这种流式排列方式。 当浏览器渲染html文档时,从顶部开始渲染,为元素分配所需要的空间,每一个块级元素单独占一行,行内元素则按照顺序被水平渲染直到在当前行遇到了边界,然后换到下一行的起点继续渲染。那么此时就不得不说一下块级元素和行内元素。
二、块级元素与行内元素
- 块级元素:它就应该有自己的宽度和高度。而且它比较霸道,每个块级元素默认占一行高度,一行内添加一个块级元素后一般无法添加其他元素(float浮动后除外),一般作为容器使用,常见的块级元素有:from、select、 textarea、h1-h6 、table 、button 、hr 、p 、ol 、ul等。
- 结合以上内容,块级元素拥有以下特点:
- 1.每个块级元素都是独自占一行。
- 2.元素的高度、宽度、行高和边距都是可以设置的。
- 3.元素的宽度如果不设置的话,默认为父元素的宽度。
- 行内元素:显然,这种元素存在于一行内,且能与别的行内元素共同享有一行。常见的行内元素有:span、input、a、em、strong、b、br、img、select、button等。
- 那么行内元素拥有的特点如下:
- 1.每一个行内元素可以和别的行内元素共享一行,相邻的行内元素会排列在同一行里,直到一行排不下了,才会换行。
- 2.行内元素设置width, height无效(此处有坑,请往下看),宽度随元素的内容而变化。
- 3.行内水平方向的padding-left和padding-right都会产生边距效果,但是竖直方向上的padding-top和padding-bottom都不会产生边距效果。
- **三、替换元素和非替换元素**
- 细心的大家肯定发现了,像<img>、<input>、<select>、<textarea>等,它们也是行内元素呀,明明就可以设置宽高啊,那这里就有问题了。其实并不是所有的行内元素都不能设置宽高的。
- 行内元素也分为两种:替换元素和非替换元素。
- 替换元素:
- 浏览器根据元素的标签和属性,来决定其的具体显示内容的元素,常见的有:<img>、<input>、<select>、<textarea>、<object>。比如浏览器根据<img >标签的src属性显示图片。根据<input>的type属性决定显示输入框还是按钮,它们的宽度和高度是可以设置的。
- 非替换元素:
- 内容直接表现给用户端的元素称为成为非替换元素,常见的有:<span>、<p>、<label>等。例如<span>,它会将开始和结束标签中的内容直接在浏览器上展示出来。
- **四、脱离文档流**
- 所谓脱离文档流,即将元素从普通的布局排版(普通文档流)中脱离出来,其他盒子在定位的时候,会当做没看到它(余生你不必再指教了),两者位置重叠都是可以的,但是依然在DOM树中存在。
- 那么会使元素脱离文档流的情况有哪些呢?
- 1.float产生的浮动
- 使用float脱离文档流时,虽然其他盒子会无视这个元素,但其他盒子内的文本依然会为这个元素让出位置,环绕在该元素的周围。
- 下面是代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .demo1{ width: 200px; height: 200px; border: 5px solid red; float: left; } .demo2{ width: 200px; height: 100px; border: 5px solid green; } </style> </head> <body> <div class="demo1">这是demo1的文本</div> <div class="demo2">这是demo2的文本</div> </body> </html>
- 代码运行效果:
- 
- 2.position:absolute;
- absolute是绝对定位,绝对定位的元素以第一个非static父元素为参照。如果没有非static的父元素,则以body为参照。
- 下面是例子:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .demo1{ width: 200px; height: 200px; border: 5px solid red; } .demo2{ width: 200px; height: 100px; border: 5px solid green; position: absolute; top: 50px;/*给demo2一个绝对定位,并且距离body顶部 50px*/ } .demo3{ width: 200px; height: 200px; border: 5px solid black; } body{ border: 2px solid blue; } </style> </head> <body> <div class="demo1">这是demo1的文本</div> <div class="demo2">这是demo2的文本</div> <div class="demo3">这是demo3的文本</div> </body> </html>
- 代码效果如图:
- 
- 我们可以看到第二个div它相对body顶部向下移动了50px;
- 3.position:fixed;
- 完全脱离文档流,相对于浏览器窗口进行定位,也就是这个div固定在浏览器窗口上了,不论我们怎么拖动滚动条都无法改变它在浏览器窗口的位置。
- 下面是代码:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .demo1{ width: 200px; height: 200px; border: 5px solid red; } .demo2{ width: 200px; height: 100px; border: 5px solid green; position: fixed; right: 50px; } .demo3{ width: 200px; height: 200px; border: 5px solid black; } body{ border: 2px solid blue; } </style> </head> <body> <div class="demo1">这是demo1的文本</div> <div class="demo2">这是demo2的文本</div> <div class="demo3">这是demo3的文本</div> </body> </html>
- 
- 脱离标准文档流的两种方式<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">所谓脱离标准文档流</span><span style="background-color: rgb(255, 255, 255); font-family: SimSun; color: rgb(51, 51, 51); font-size: 16px; white-space: pre-wrap;">就是将元素从普通的布局排版中拿走,其他盒子在定位的时候,会当做脱离文档流的元素不存在而进行定位</span><span style="background-color: rgb(255, 255, 255); color: rgb(51, 51, 51); font-size: 16px; white-space: pre-wrap; font-family: "Microsoft YaHei", arial, "courier new", courier, 宋体, monospace;">。</span>
- 不浮动的盒子会无视浮动的盒子,假使现有两个盒子,一个浮动一个不浮动,则浮动的盒子会覆盖不浮动的盒子。如下代码的结果所示:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>浮动</title> <style type="text/css"> body{ margin:0px; } .first { background-color: #ccc; float:left; width:200px; height:200px; } .second { background-color: blue; width:250px; height:250px; } </style> </head> <body> <div class="first"></div> <div class="second"></div> </body> </html>
- 结果如图:
- 
- 盒子元素会无视浮动的元素,但是盒子元素里面的文字并不会无视浮动元素,如下图所示:
- 
- 可以看到蓝色背景色盒子里的文本注意到了这个浮动元素于是在盒子里右推的形式围绕在浮动盒子的周围。
- 此外,一旦一个元素浮动了,那么他就可以设置宽高,可以并排,无论原来他是块级元素还是行内元素。当浮动元素里面有文字时,浮动元素不会覆盖文字,文字会围绕浮动元素显示。
- 关于清除浮动,为什么要清除浮动呢?举一个自己遇到的例子,代码如下:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "[http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd](http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd)"> <html xmlns="[http://www.w3.org/1999/xhtml](http://www.w3.org/1999/xhtml)"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>混合布局</title> <style> body{ margin:0; padding:0; font-size:10px; font-weight:bold} div{ text-align:center; line-height:50px} .head,.main{ width:200px;margin:0 auto;} .head{ height:100px; background:#F90} .left{ width:80px; height:60px; background:#ccc;float:left;} .right{ width:120px; height:60px;background:#FCC; float:right} .r_sub_left{ width:60px; height:60px; background:#9C3; float:left} .r_sub_right{ width:60px; height:60px; background:#9FC; float:right;} .footer{width:400px; height:50px; background:#9F9;margin:0 auto;} </style> </head> <body> <div class="head">head</div> <div class="main"> <div class="left">left</div> <div class="right"> <div class="r_sub_left">sub_left </div> <div class=" r_sub_right">sub_right </div> </div> </div> <div class="footer">footer</div> </body> </html>
- 运行后看到如下结果:
- 
- 尴尬了,注意到footer的盒子飘到上面去了,只剩下文字孤零零的在下面,这是为什么呢?因为left和right均设置为浮动的状态,而main并没有设置高度,可以想象为一条线在head的下面,这样footer自然无视left和right两个浮动元素然后飘到上面去了,这是我们需要进行float清除,清除的方式有一下几种,如下:清除浮动的方法综合一下答案:
- 一、clear:both(/left/right);
- 二、overflow:hidden;width:100%;
- 三 、:after
- 四、给main设置高度:.main{width:960px; {height:600px};margin:0 auto;}
- 五、:margin:600px 0 0 0;
- 详见:[https://my.oschina.net/leipeng/blog/221125](https://my.oschina.net/leipeng/blog/221125)
- 推荐使用方法一和方法二,在给footer使用overflow的时候,千万不要忘记设置它的宽度。
- 其中clear:both清除浮动 值描述
- left 在左侧不允许浮动元素。
- right 在右侧不允许浮动元素。
- both 在左右两侧均不允许浮动元素。
- none 默认值。允许浮动元素出现在两侧。
- inherit 规定应该从父元素继承 clear 属性的值。
- overflow 属性规定当内容溢出元素框时发生的事情。
- 值描述
- visible 默认值。内容不会被修剪,会呈现在元素框之外。
- hidden 内容会被修剪,并且其余内容是不可见的。
- scroll 内容会被修剪,但是浏览器会显示滚动条以便查看其余的内容。
- auto 如果内容被修剪,则浏览器会显示滚动条以便查看其余的内容。
- inherit 规定应该从父元素继承 overflow 属性的值。
- 脱离文本的第二种方式是绝对定位(position:absolute):相比于float,position:absolute不管是文本还是盒子都会直接无视掉浮动元素,将float:left换为position:absolute后可以看到如下结果:
- 
- 理解脱离文档流:
- 一个帅帅的男生(div1)和一个可爱的女孩纸(div2),他们喜欢着对方,有一天,他们在一起了,从此过上了幸福的生活…..
- 故事怎么可能是这样子的(皮一下)
- 随着时间的推移,在某些事情的影响下(float、fixed、absolute),他们发现对方有很多的缺点,并不是完美的,有一天终于无法忍受对方,“咱们分手吧!”div1说到,balabala,他们分手了(div1脱离了文档流)。“你就当我死了吧”,div1说到。虽然男孩纸div1和女孩纸div2都还在这个世界上过着自己的生活(div1和div2都还存在于DOM树中),但是他们让对方死在了自己的心中(盒子在定位的时候,会当做没看到它)。狗血的故事讲完了。
- 所谓的文档流,就好比如一块块的正方形组成的一个整体,而这些正方形就代表着每个div。当某个div脱离了这个整体,也就代表他脱离了文档流。然后下一个div就会来填补脱离的div的位置。
- 下面是流程图。
- 有四个小朋友在买小卖部排队买糖吃~
- 
- 第一个买完了糖的小朋友脱离了排队的队伍开心的吃糖去。
- 
- 后面的小朋友看前面的小朋友走了,连忙补上防止别人插队。
- 
- 
- 实际上,在html页面中,我们看到的会是这样。
- 
- div2被div1给覆盖了!因为脱离文档流的div1不占据页面的空间了,所以才会留有空间给后面的div补上,当然这也导致了div2给div1覆盖了!
- 目前常见的会影响元素脱离文档流的css属性有:
- ①float浮动。
- ②position的absolute和fixed定位。
- 最后,想知道要怎么解决这种覆盖问题
- 当我们看到div相互层叠覆盖的时候,首先我们想到的是否有div脱离了文档流?其次我们在分析他们是通过什么方法脱离文档流的?
- 目前,常见的脱离文档流的方法有position定位和float浮动两种!
- 1、如果这个div是通过float导致的脱离文档流的话,可以通过上面的div和下面的div之间插入清除浮动。 .clean{ clear:both; } //我是上面的div //我是下面的div
- 2、如果是position绝对定位(absolute)导致某个div脱离了文档流,从而使下面的div(即div2)和上面的div(div1)相互层叠了。那么,该如何解决呢?
- 首先,切忌对下一个div使用position定位来解决问题,不然这就是一个坑!
- ①“替死鬼”法
- 制作多一个div(即div3)来代替div2,使得这个新制作出来的div3来填补这个文档流的缺漏(为脱离文档流的div1填补)。
- 不单单制作出来div3出来后就算了,还要设置他的高度height为div1的高度。这样子就好像恢复如初,回到最初的未改变文档流一样!(嘻嘻~,障眼法~)
input框
input框的23种类型
3大类选择器的作用:
HTML5表单教程之input新增加的六种时间类型
1、Date类型:
如果在之前,我们使用js+css+dom才能实现日历选择日期的效果,在HTML5中,我们只需要设置input为date类型即可,提交表单的时候也不需要我们验证数据了,它已经帮我们实现了。 运行效果如下图:
2、Time类型:
此类型是一个专门用来输入时间的文本框,在提交的时候检查是否输入了有效的时间。 运行效果如下图:
3、DateTime类型:
datetime类型的input元素是专门用来输入UTC日期和实践的文本框,在提交的时候,对日期和时间进行有效的检查。 运行效果如下图:
4、DateTime-Local类型:
此类型与datatime类型差不多,只不过是用来输入本地的日期和时间。 运行效果如下图:
5、Month类型:
month是一种专门输入月份的文本框,在日历中,你只能选择某一个月,不能选择某一天。 运行效果如下图:
6、Week类型:
week是专门用来输入周(星期)的文本框,W后面所跟的数字表示此周是当年的第几个星期。在日历中只能选择一周,同样不能选择某一天。 运行效果如下图:
form表单提交方式
HTML 与 XHTML——二者有什么区别
116,要动态改变层中内容可以使用的方法?
第一种方法:通过改变DIV的innerHTML属性值动态改变页面内容。这种情况适合动态显示的内容较少时,动态显示的内容(如“用户名”不能为空)占据一行的时候比较适合此种方法,使用myDiv.innerHTML=”HTML代码”来动态改变页面内容。 第二种方法:当动态显示的内容较多,并相对固定时,则应该预先制作好DIV内容,然后使用myDiv.style.display=”none/block”,来动态改变层的隐藏或显示,从而实现动态改变页面内容。经过上面的详细分析,这里应该采用第二种方法,先设计好层中的内容,然后使用none/block 属性值来显示或隐藏层,从而实现动态改变页面内容。 利用层的innerHTML改变内容 利用层的innerText改变内容 可以通过设置层的隐藏和显示来实现 可以通过设置层的样式属性display属性来实现
112,什么是 WebGL,它有什么优点?【https://blog.csdn.net/naooomi/article/details/7045017?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-16.base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-16.base 】【https://www.jianshu.com/p/fd4bbf7d09b6 】【】
WebGL就是js和OpenGL2.0的结合,也是3d绘图标准,通过增强对OpenGL的绑定,WebGL可以对html5的canvas进行硬件3D加速渲染 WebGL(全写 Web Graphics Library )是一种 3D 绘图标准,这种绘图技术标准允许把 JavaScript 和 OpenGL ES 2.0 结合在一起,通过增加OpenGL ES 2.0 的一个 JavaScript 绑定, WebGL 可以为 HTML5 Canvas 提供硬件 3D 加速渲染,这样 Web 开发人员就可以借助系统显卡来在浏览器里更流畅地展示 3D 场景和模型了,还能创建复杂的导航和数据视觉化。显然, WebGL 技术标准免去了开发网页专用渲染插件的麻烦,可被用于创建具有复杂 3D 结构的网站页面,甚至可以用来设计 3D 网页游戏等等。 WebGL完美地解决了现有的 Web 交互式三维动画的两个问题: 第一,它通过HTML脚本本身实现 Web 交互式三维动画的制作,无需任何浏览器插件支持 ; 第二,它利用底层的图形硬件加速功能进行的图形渲染,是通过统一的、标准的、跨平台的OpenGL接口实现的。 通俗说WebGL中 canvas 绘图中的 3D 版本。因为原生的 WebGL 很复杂,我们经常会使用一些三方的库,如 three.js 等,这些库多数用于HTML5 游戏开发。
css 动画中 ease,seae-in,ease-in-out,ease-out,效果区别
值 | 描述 linear | 规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))。(匀速) ease | 规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic- bezier(0.25,0.1,0.25,1))(相对于匀速,中间快,两头慢)。 ease-in | 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))(相对于匀速,开始的时候慢,之后快)。 ease-out | 规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))(相对于匀速,开始时快,结束时候间慢,)。 ease-in-out | 规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))(相对于匀速,(开始和结束都慢)两头慢)。 cubic-bezier(n,n,n,n) | 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值。
动画
【前端动画】实现动画的6种方式 通常在前端中,实现动画的方案主要有6种: javascript直接实现; SVG(可伸缩矢量图形); CSS3 transition; CSS3 animation; Canvas动画; requestAnimationFrame; javascript 直接实现动画
其主要思想是通过setInterval或setTimeout方法的回调函数来持续调用改变某个元素的CSS样式以达到元素样式变化的效果。 示例 #rect { width: 200px; height: 200px; background: #ccf; }
3.HTML5 拖放
拖放是一种常见的特性,即抓取对象以后拖到另一个位置。在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放。 设置元素为可拖放 首先,为了使元素可拖动,把 draggable 属性设置为 true : 拖动什么 - ondragstart 和 setData() 放到何处 - ondragover 进行放置 - ondrop
4.HTML5 地理定位
HTML5 Geolocation API 用于获得用户的地理位置。 鉴于该特性可能侵犯用户的隐私,除非用户同意,否则用户位置信息是不可用的。 var x=document.getElementById(“demo”); function getLocation() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(showPosition); } else{x.innerHTML=”该浏览器不支持获取地理位置。”;} } function showPosition(position) { x.innerHTML=”Latitude: “ + position.coords.latitude + “ Longitude: “ + position.coords.longitude; }
5.HTML5 Audio(音频)、Video(视频)
HTML5 规定了在网页上嵌入音频元素的标准,即使用 元素。 您的浏览器不支持 audio 元素。 HTML5 规定了一种通过 video 元素来包含视频的标准方法。
0.7 倍
1.0 倍
1.2 倍
1.5 倍
2.0 倍
您的浏览器不支持Video标签。
6.HTML5 Input 类型
7.HTML5 表单元素
8.HTML5 表单属性
9.HTML5 语义元素
HTML5提供了新的语义元素来明确一个Web页面的不同部分:
10.HTML5 Web 存储
Web Storage DOM API 为Web应用提供了一个能够替代cookie的Javascript解决方案
sessionStorage—客户端数据存储,只能维持在当前会话范围内。
sessionStorage 方法针对一个 session 进行数据存储。当用户关闭浏览器窗口后,数据会被删除。
localStorage—客户端数据存储,能维持在多个会话范围内。
localStorage 对象存储的数据没有时间限制。第二天、第二周或下一年之后,数据依然可用。
对于大量复杂数据结构,一般使用IndexDB
11.HTML5 离线Web应用(应用程序缓存)
HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问。 应用程序缓存为应用带来三个优势:
离线浏览 - 用户可在应用离线时使用它们 速度 - 已缓存资源加载得更快 减少服务器负载 - 浏览器将只从服务器下载更新过或更改过的资源。
HTML5 Cache Manifest 实例 下面的例子展示了带有 cache manifest 的 HTML 文档(供离线浏览): <!DOCTYPE HTML> The content of the document…… Manifest 文件
manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。 manifest 文件可分为三个部分:
CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面
CACHE MANIFEST 2012-02-21 v1.0.0/theme.css /logo.gif /main.js NETWORK: login.php FALLBACK: /html/ /offline.html
12.HTML5 Web Workers
当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。 web worker 是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行。(相当于实现多线程并发)
13.HTML5 SSE
Server-Sent 事件指的是网页自动获取来自服务器的更新。 以前也可能做到这一点,前提是网页不得不询问是否有可用的更新。通过服务器发送事件,更新能够自动到达。 例子:Facebook/Twitter 更新、估价更新、新的博文、赛事结果等。 EventSource 对象用于接收服务器发送事件通知: var source=new EventSource(“demo_sse.php”); source.onmessage=function(event) { document.getElementById(“result”).innerHTML+=event.data + “ “; }; 为了让上面的例子可以运行,您还需要能够发送数据更新的服务器(比如 PHP 和 ASP)。 <?php header(‘Content-Type: text/event-stream’); header(‘Cache-Control: no-cache’); $time = date(‘r’); echo “data: The server time is: {$time}nn”; flush(); ?>
14.HTML5 WebSocket
WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。以下 API 用于创建 WebSocket 对象。
CSS3
CSS3选择器
. class .intro 选择所有class=”intro”的元素 1# id #firstname 选择所有id=”firstname”的元素 1* * 选择所有元素 2element p 选择所有元素 1
element,element div,p 选择所有elementelement div p 选择element >element div>p 选择所有父级是 element +element div+p 选择所有紧接着[attribute ] [target] 选择所有带有target属性元素 2 [attribute =value ] [target=-blank] 选择所有使用target=”-blank”的元素 2 [attribute ~=value ] [title~=flower] 选择标题属性包含单词”flower”的所有元素 2 [attribute |=language ] [lang|=en] 选择一个lang属性的起始值=”EN”的所有元素 2 :link a:link 选择所有未访问链接 1:visited a:visited 选择所有访问过的链接 1:active a:active 选择活动链接 1:hover a:hover 选择鼠标在链接上面时 1:focus input:focus 选择具有焦点的输入元素 2:first-letter p:first-letter 选择每一个元素的第一个字母 1
:first-line p:first-line 选择每一个元素的第一行 1
:first-child p:first-child 指定只有当元素是其父级的第一个子级的样式。 2
:before p:before 在每个元素之前插入内容 2
:after p:after 在每个元素之后插入内容 2
:lang( language ) p:lang(it) 选择一个lang属性的起始值=”it”的所有元素 2
element1 ~element2 p~ul 选择p元素之后的每一个ul元素 3[attribute ^=value ] a[src^=”https”] 选择每一个src属性的值以”https”开头的元素 3 [attribute $=value ] a[src$=”.pdf”] 选择每一个src属性的值以”.pdf”结尾的元素 3 [attribute =value ] a[src =”44lan”] 选择每一个src属性的值包含子字符串”44lan”的元素 3 :first-of-type p:first-of-type 选择每个p元素是其父级的第一个p元素 3:last-of-type p:last-of-type 选择每个p元素是其父级的最后一个p元素 3:only-of-type p:only-of-type 选择每个p元素是其父级的唯一p元素 3:only-child p:only-child 选择每个p元素是其父级的唯一子元素 3:nth-child( n ) p:nth-child(2) 选择每个p元素是其父级的第二个子元素 3:nth-last-child( n ) p:nth-last-child(2) 选择每个p元素的是其父级的第二个子元素,从最后一个子项计数 3:nth-of-type( n ) p:nth-of-type(2) 选择每个p元素是其父级的第二个p元素 3:nth-last-of-type( n ) p:nth-last-of-type(2) 选择每个p元素的是其父级的第二个p元素,从最后一个子项计数 3:last-child p:last-child 选择每个p元素是其父级的最后一个子级。 3:root :root 选择文档的根元素 3:empty p:empty 选择每个没有任何子级的p元素(包括文本节点) 3:target #news:target 选择当前活动的#news元素(包含该锚名称的点击的URL) 3:enabled input:enabled 选择每一个已启用的输入元素 3:disabled input:disabled 选择每一个禁用的输入元素 3:checked input:checked 选择每个选中的输入元素 3:not( selector ) :not(p) 选择每个并非p元素的元素 3::selection ::selection 匹配元素中被用户选中或处于高亮状态的部分 3:out-of-range :out-of-range 匹配值在指定区间之外的input元素 3:in-range :in-range 匹配值在指定区间之内的input元素 3:read-write :read-write 用于匹配可读及可写的元素 3:read-only :read-only 用于匹配设置 “readonly”(只读) 属性的元素 3:optional :optional 用于匹配可选的输入元素 3:required :required 用于匹配设置了 “required” 属性的元素 3:valid :valid 用于匹配输入值为合法的元素 3:invalid :invalid 用于匹配输入值为非法的元素
CSS3 边框(Borders)
用CSS3,你可以创建圆角边框,添加阴影框,并作为边界的形象而不使用设计程序 border-image 设置所有边框图像的速记属性。 3border-radius 一个用于设置所有四个边框- *-半径属性的速记属性 3box-shadow 附加一个或多个下拉框的阴影 3div { border:2px solid; border-radius:25px; box-shadow: 10px 10px 5px #888888; border-image:url(border.png) 30 30 round; }
CSS3 背景
CSS3中包含几个新的背景属性,提供更大背景元素控制。 background-clip 规定背景的绘制区域。 3background-origin 规定背景图片的定位区域。 3background-size 规定背景图片的尺寸。 3div { background:url(img_flwr.gif); background-repeat:no-repeat; background-size:100% 100%; background-origin:content-box; } 多背景 body { background-image:url(img_flwr.gif),url(img_tree.gif); }
CSS3 渐变
CSS3 定义了两种类型的渐变(gradients):
线性渐变(Linear Gradients)- 向下/向上/向左/向右/对角方向
background: linear-gradient(direction, color-stop1, color-stop2, …);
径向渐变(Radial Gradients)- 由它们的中心定义
background: radial-gradient(center, shape size, start-color, …, last-color);
CSS3 文本效果
CSS3 字体
以前CSS3的版本,网页设计师不得不使用用户计算机上已经安装的字体。使用CSS3,网页设计师可以使用他/她喜欢的任何字体。当你发现您要使用的字体文件时,只需简单的将字体文件包含在网站中,它会自动下载给需要的用户。您所选择的字体在新的CSS3版本有关于@font-face规则描述。您”自己的”的字体是在 CSS3 @font-face 规则中定义的。 @font-face { font-family: myFirstFont; src: url(sansation_light.woff); } div { font-family:myFirstFont; }
CSS3 转换和变形
2D新转换属性
2D 转换方法
matrix(n ,n ,n ,n ,n ,n ) 定义 2D 转换,使用六个值的矩阵。 translate(x ,y ) 定义 2D 转换,沿着 X 和 Y 轴移动元素。 translateX(n ) 定义 2D 转换,沿着 X 轴移动元素。 translateY(n ) 定义 2D 转换,沿着 Y 轴移动元素。 scale(x ,y ) 定义 2D 缩放转换,改变元素的宽度和高度。 scaleX(n ) 定义 2D 缩放转换,改变元素的宽度。 scaleY(n ) 定义 2D 缩放转换,改变元素的高度。 rotate(angle ) 定义 2D 旋转,在参数中规定角度。 skew(x-angle ,y-angle ) 定义 2D 倾斜转换,沿着 X 和 Y 轴。 skewX(angle ) 定义 2D 倾斜转换,沿着 X 轴。 skewY(angle ) 定义 2D 倾斜转换,沿着 Y 轴。
3D转换属性
3D 转换方法
matrix3d(n ,n ,n ,n ,n ,n ,n ,n ,n ,n ,n ,n ,n ,n ,n ,n ) 定义 3D 转换,使用 16 个值的 4x4 矩阵。 translate3d(x ,y ,z ) 定义 3D 转化。 translateX(x ) 定义 3D 转化,仅使用用于 X 轴的值。 translateY(y ) 定义 3D 转化,仅使用用于 Y 轴的值。 translateZ(z ) 定义 3D 转化,仅使用用于 Z 轴的值。 scale3d(x ,y ,z ) 定义 3D 缩放转换。 scaleX(x ) 定义 3D 缩放转换,通过给定一个 X 轴的值。 scaleY(y ) 定义 3D 缩放转换,通过给定一个 Y 轴的值。 scaleZ(z ) 定义 3D 缩放转换,通过给定一个 Z 轴的值。 rotate3d(x ,y ,z ,angle ) 定义 3D 旋转。 rotateX(angle ) 定义沿 X 轴的 3D 旋转。 rotateY(angle ) 定义沿 Y 轴的 3D 旋转。 rotateZ(angle ) 定义沿 Z 轴的 3D 旋转。 perspective(n ) 定义 3D 转换元素的透视视图。
CSS3 过渡
过渡属性 下表列出了所有的过渡属性:
div { transition-property: width; transition-duration: 1s; transition-timing-function: linear; transition-delay: 2s; / Safari / -webkit-transition-property:width; -webkit-transition-duration:1s; -webkit-transition-timing-function:linear; -webkit-transition-delay:2s; }
CSS3 动画
要创建CSS3动画,你需要了解@keyframes规则。@keyframes规则是创建动画。 @keyframes规则内指定一个CSS样式和动画将逐步从目前的样式更改为新的样式。 实例 当动画为 25% 及 50% 时改变背景色,然后当动画 100% 完成时再次改变: @keyframes myfirst { 0% {background: red;} 25% {background: yellow;} 50% {background: blue;} 100% {background: green;} } 下面的表格列出了 @keyframes 规则和所有动画属性:
@keyframes 规定动画。 3animation 所有动画属性的简写属性,除了 animation-play-state 属性。 3animation-name 规定 @keyframes 动画的名称。 3animation-duration 规定动画完成一个周期所花费的秒或毫秒。默认是 0。 3animation-timing-function 规定动画的速度曲线。默认是 “ease”。 3animation-delay 规定动画何时开始。默认是 0。 3animation-iteration-count 规定动画被播放的次数。默认是 1。 3animation-direction 规定动画是否在下一周期逆向地播放。默认是 “normal”。 3animation-play-state 规定动画是否正在运行或暂停。默认是 “running”。 3div { animation-name: myfirst; animation-duration: 5s; animation-timing-function: linear; animation-delay: 2s; animation-iteration-count: infinite; animation-direction: alternate; animation-play-state: running; / Safari and Chrome: / -webkit-animation-name: myfirst; -webkit-animation-duration: 5s; -webkit-animation-timing-function: linear; -webkit-animation-delay: 2s; -webkit-animation-iteration-count: infinite; -webkit-animation-direction: alternate; -webkit-animation-play-state: running; }
CSS3 多列
CSS3 盒模型
在 CSS3 中, 增加了一些新的用户界面特性来调整元素尺寸,框尺寸和外边框,主要包括以下用户界面属性:
resize:none | both | horizontal | vertical | inherit box-sizing: content-box | border-box | inherit outline:outline-color outline-style outline-width outine-offset
resize属性指定一个元素是否应该由用户去调整大小。 box-sizing 属性允许您以确切的方式定义适应某个区域的具体内容。 outline-offset 属性对轮廓进行偏移,并在超出边框边缘的位置绘制轮廓。
CSS3伸缩布局盒模型(弹性盒)
CSS3 弹性盒( Flexible Box 或 flexbox),是一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。 引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。 下表列出了在弹性盒子中常用到的属性:
CSS3 多媒体查询
从 CSS 版本 2 开始,就可以通过媒体类型在 CSS 中获得媒体支持。如果您曾经使用过打印样式表,那么您可能已经使用过媒体类型。清单 1 展示了一个示例。 清单 1. 使用媒体类型
清单 2. 媒体查询规则
@media all and (min-width: 800px) { … } @media all 是媒体类型,也就是说,将此 CSS 应用于所有媒体类型。 (min-width:800px) 是包含媒体查询的表达式,如果浏览器的最小宽度为 800 像素,则会告诉浏览器只运用下列 CSS。
清单 3. and 条件
@media (min-width:800px) and (max-width:1200px) and (orientation:portrait) { … }
清单 4. or 关键词
@media (min-width:800px) or (orientation:portrait) { … }
清单 5. 使用 not
@media (not min-width:800px) { … }
16.可以通过哪些方法优化 css3 animation 渲染 44.HTML5 的离线储存怎么使用,工作原理能不能解释一下? 45.浏览器是怎么对 HTML5 的离线储存资源进行管理和加载的呢 47.WEB 标准以及 W3C 标准是什么?理解和认识。 7.HTML5 为什么只写<!DOCTYPE HTML>? 51.你知道多少种 Doctype 文档类型? Doctype 作用? 严格模式与混杂模式如何区分?【严格模式不混杂模式-如何触发这两种模式】它们有何意义? 49.HTML 全局属性(global attribute)有哪些 HEADER标签内一般有什么内容 51.如何在页面上实现一个圆形的可点击区域? 115,请你说说 CSS 有什么特殊性?(优先级、计算特殊值) 60.为什么要初始化 CSS 样式 109,说说你对 HTML5 认识?(是什么,为什么) 14,HTML5 的优点与缺点? 58,html5 有哪些新特性、移除了那些元素?如何处理 HTML5 新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?
有没有关注 HTML5 和 CSS3?如有请简单说一些您对它们的了解情况!【https://www.cnblogs.com/star91/p/5659134.html 】
58.用纯 CSS 创建一个三角形的原理是什么?【https://blog.csdn.net/pengjunlee/article/details/53002553?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-18.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-18.control 】【https://blog.csdn.net/lxcao/article/details/52689313?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control 】 20.css 定位方式【https://blog.csdn.net/weixin_38055381/article/details/81558288 】【https://www.cnblogs.com/demonswang/p/7161290.html 】 153,介绍一下 box-sizing 属性? 62.CSS 里的 visibility 属性有个 collapse 属性值?在不同浏览器下以后什么区别? 118,列出 display 的值并说明他们的作用?
63.display:none 与 visibility:hidden 的区别? 83.display:inline-block 什么时候会显示间隙? 122,block,inline 和 inlinke-block 细节对比? 98.列出 display 的值,说明他们的作用。position 的值, relative 和 absolute 分别是相对于谁进行定位的? 61.absolute 的 containing block 计算方式跟正常流有什么不同? 95.position 的 absolute 与 fixed 共同点与不同点 152,position:absolute 和 float 属性的异同
position:fixed;在 android 下无效怎么处理?
2,position 的值, relative 和 absolute 分别是相对于谁进行定位的? 64.position 跟 display、overflow、float 这些特性相互叠加后会怎么样? 73.margin 和 padding 分别适合什么场景使用? 74.元素竖向的百分比设定是相对于容器的高度吗? 75.全屏滚动的原理是什么?用到了 CSS 的哪些属性?【https://blog.csdn.net/tangdou5682/article/details/52351404?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.essearch_pc_relevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.essearch_pc_relevant 】 79.让页面里的字体变清晰,变细用 CSS 怎么做? 82.li 与 li 之间有看不见的空白间隔是什么原因引起的?有什么解决办法? 85.png、jpg、gif 这些图片格式解释一下,分别什么时候用。有没有了解过 webp? 关于overflow:hidden的作用(溢出隐藏、清除浮动、解决外边距塌陷等等) 87.CSS 属性 overflow 属性定义溢出元素内容区的内容会如何处理? 102.解释下 CSS sprites,以及你要如何在页面或网站中使用它。
一行或多行文本超出隐藏overflow:hidden; //超出的文本隐藏 text-overflow:ellipsis; //溢出用省略号显示 white-space:nowrap; //溢出不换行
前端页面有哪三层构成,分别是什么?作用是什么?
css 的基本语句构成是?
如果让你来制作一个访问量很高的大型网站,你会如何来管理所有 CSS 文件、JS 、图片?
1、列举 W3C 推荐的属性标签,说一下 p 和 img 标签的特点。 常规流布局 块盒 块级格式化上下文 浮动 定位 浮动、定位、弹性、table、Grid 网格布局 :优缺点 页面布局的变通 CSS的position定位 圣杯布局、双飞翼布局、Flex布局和绝对定位布局 H5:div 横向排列的方法。 ul、li导航栏居中的两种办法 22.垂直上下居中的方法
如何居中一个浮动元素?
3、如何让 img 标签在 div 里上下居中 119,如何居中 div 水平居中、 垂直居中、 垂直水平居中 水平垂直居中【https://www.jianshu.com/p/907f99004c3e 】 23.响应式布局原理【https://blog.csdn.net/sinat_17775997/article/details/89087348 】【https://www.jianshu.com/p/d0d29fb7647f 】
有一个高度自适应的 div,里面有两个 div,一个高度 100px,希望另一个填满剩下的高度<!DOCTYPE html>
左边导航右边自适应【https://www.cnblogs.com/vicky123/p/8866548.html 】 2、实现左侧规定宽 200,右侧自适应宽度的布局 三栏布局 三栏布局 弹性布局(display:flex;)属性详解 grip布局【http://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html 】 解释一下Flexbox (弹性盒布局模型)?及适用场景?【https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html 】 display:table的用法 响应布局【https://blog.csdn.net/sinat_17775997/article/details/89087348 】 margin塌陷【https://blog.csdn.net/qq_32381815/article/details/78987828 】【https://blog.csdn.net/shi_1204/article/details/80180224?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.essearch_pc_relevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.essearch_pc_relevant 】 H5标签、语义化标签【http://caibaojian.com/html5/ele.html 】【https://www.cnblogs.com/htzan/p/4516057.html 】 43.语义化的理解
生命周期函数面试题
Vue的核心是什么
Vue是一套构建用户界面的渐进式自底向上增量开发的MWM框架, vue的核心只关注视图层, 核心思想:
数据驱动(视图的内容随着数据的改变而改变) 组件化(可以增加代码的复用性,可维护性,可测试性,提高开发效率,方便重复使用,体现了高内聚低偶合)
vue响应式原理【https://segmentfault.com/a/1190000019700618 】
1.响应式原理
Vue 的响应式原理核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。 在生成vue实例时,为对传入的data进行遍历,使用Object.defineProperty把这些属性转为getter/setter. Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。 每个vue实例都有一个watcher实例,它会在实例渲染时记录这些属性,并在setter触发时重新渲染。
虚拟DOM (Virtaul DOM): 用 js 对象模拟的,保存当前视图内所有 DOM 节点对象基本描述属性和节点间关系的树结构。用 js 对象,描述每个节点,及其父子关系,形成虚拟 DOM 对象树结构。 Vue 无法检测到对象属性的添加或删除 Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value)方法向嵌套对象添加响应式属性。
2.声明响应式属性
由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值。 如果你未在 data 选项中声明 message,Vue 将警告你渲染函数正在试图访问不存在的属性。
3.异步更新队列
vue更新dom时是异步执行的 数据变化、更新是在主线程中同步执行的;在侦听到数据变化时,watcher将数据变更存储到异步队列中,当本次数据变化,即主线成任务执行完毕,异步队列中的任务才会被执行(已去重)。 如果你在js中更新数据后立即去操作DOM,这时候DOM还未更新;vue提供了nextTick接口来处理这样的情况,它的参数是一个回调函数,会在本次DOM更新完成后被调用。 使用方法:
1.在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:Vue.component(‘example’, { template: ‘{{ message }} ‘, data: function () { return { message: ‘未更新’ } }, methods: { updateMessage: function () { this.message = ‘已更新’ console.log(this.$el.textContent) // => ‘未更新’ this.$nextTick(function () { console.log(this.$el.textContent) // => ‘已更新’ }) } } }) 2.因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2016 async/await 语法完成相同的事情:methods: { updateMessage: async function () { this.message = ‘已更新’ console.log(this.$el.textContent) // => ‘未更新’ await this.$nextTick() console.log(this.$el.textContent) // => ‘已更新’ } }
项目中常遇到的关于vue响应式的记录与总结:
因为只要在 data 中声明的基本数据类型的数据,基本不存在数据不响应问题,所以重点介绍数组和对象在vue中的数据响应问题,vue可以检测对象属性的修改,但无法监听数组的所有变动及对象的新增和删除,只能使用数组变异方法及$set方法。
可以看到,arrayMethods 首先继承了 Array,然后对数组中所有能改变数组自身的方法,如 push、pop 等这些方法进行重写。重写后的方法会先执行它们本身原有的逻辑,并对能增加数组长度的 3 个方法 push、unshift、splice 方法做了判断,获取到插入的值,然后把新添加的值变成一个响应式对象,并且再调用 ob.dep.notify() 手动触发依赖通知,这就很好地解释了用 vm.items.splice(newLength) 方法可以检测到变化。。
向响应式的数组或者对象中修改已有的属性的方法
当想要修改对象或者属性,并非新增属性时,一个已经在 data 中声明过的响应式数据,可以直接操作改变,数据改变会经过上图的步骤,触发视图改变。直接obj.xxx = xxx 即可,数组除外,但是后台传过来的 json 数组,数组中嵌套的对象也可以直接修改数组中的对象,因为 Object.defindeProperty 的缺陷导致无法监听数组的变动 ,但始终会深度遍历data中数据,给数组中嵌套的对象添加上 get 和 set 方法,完成对对象的监听。所以数组中嵌套的对象的情况是可以直接修改数组中的对象 ,并且保持响应式。
向响应式的数组或者对象中新增一个响应式的属性的方法this.$set()或者数组变异方法
即使是一个后台传过来的 json 数组,也可以使用this.$set向数组中的其中一个对象中添加一个响应式的属性,例如 this.$set(arr[0], ‘xxx’, xxx) 。或者使用数组变异方法例如splice,更多数组变异方法可以参考vue文档。
data中声明过的数组或者对象,整体替换数组或者对象保持响应式
向响应式的数组和对象替换为新的响应式数据,可直接复制,因为data中声明的数据已经添加了访问器属性setter,当重新赋值一个新的堆内存地址时,该数组或者对象也会被循环遍历添加访问器属性,所以也是有响应式的。
vue无法监听对象的新增和删除,直接通过obj.xxx = xxx新增一个没有的属性,同时修改当前组件的一个响应式的数据,会重新触发当前组件重新render,可以让非响应式数据也保持更新状态(并非响应式) 。
给一个数据添加一个非响应式的数据,例如一个已经在data中声明过的数据obj,obj.xxx=xxx,新增一个原本没有的数据,同时修改组件中一个其他的响应式数据,该obj也会同步更新到最新的数据,另一种情况,当你向一个对象或者数组中同时增加一个响应式和非响应式数据,非响应式数据也会同步更新到页面。 总结:只要触发当前组件重新render,就可以让数据保持更新的状态,例如this.$forceUpdate()。
为什么vue不能监听数组的变化? Object.defindProperty虽然能够实现双向绑定了,但是还是有缺点,只能对对象的属 性进行数据劫持,所以会深度遍历整个对象,不管层级有多深,只要数组中嵌套有对象,就能监听到对象的数据变化无法监听到数组的变化,Proxy就没有这个问题,可以监听整个对象 的数据变化,所以用vue3.0会用Proxy代替definedProperty。 最后实现一个数据双向绑定原理
更深的底层原理还在学习中,完全消化以后会继续分享,嗯,就酱~
请简述你对vue的理解
Vue是一套构建用户界面的渐进式的自底向上增量开发的MWM框架,核心是关注视图层,vue的核心是为了解决数据的绑定问题,为了开发大型单页面应用和组件化,所以vue的核心思想是数据驱动和组件化,这里 也 说 一 下MWM思 想 ,MVVM思想是 模型 视 图 vm是v和m连接的桥梁,当模型层数据修改时,VM层会检测到,并通知视图层进行相应修改
1.vue 优点
1、低耦合。视图(view)可以独立于model变化和修改,一个viewModel可以绑定到不同的”view“上,当view变化的时候,model可以不变,当model变化的时候view也可以不变。 2、可重用性。你可以把一些视图逻辑放在一个viewModel里面,让很多view重用这段视图逻辑。 3、独立开发。开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面设计。 4、可测试。界面素来是比较难测试的,而现在测试可以针对viewModel来写。
vue的整个实现流程?
1、第一步:解析模板成render函数
2、第二步:响应式开始监听
object.defineProperty data属性代理到vm上
3、第三步:首次渲染,显示页面,且绑定依赖
(1)为何要监听get,直接监听set不行吗?
①data中有很多属性,有些被用到,有些可能不被用到(data中没有人访问,就不会用get,如没有{{aaa}}指的就是aaa没有被访问) ②被用到的会走到get,不被用到的不会走到get ③未走到get中的属性,set的时候也无需关心 ④避免不必要的重复渲染
4、第四步:data属性变化,触发rerender
defineProperty, get, set (1)修改属性,被响应式的set监听到 (2)set中执行updateComponent (3)updateComponent重新执行vm._render() (4)生成的vnode和prevVnode,通过Patch进行对比渲染到html
1.什么是 vue 生命周期
一、vue的生命周期是什么
vue每个组件都是独立的,每个组件都有一个属于它的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁 ,这就是一个组件所谓的生命周期。在组件中具体的方法有: beforeCreate created beforeMount mounted ( beforeUpdate updated ) beforeDestroy destroyed 对应的中文就如其字面意思,英文不好的童鞋可以有道翻翻 好了,这里要上图啦~~~
二、vue生命周期的在项目中的执行顺序 … data () { return { rendered: false, } } …
1.beforeCeate(){ console.log(this.rendered); // undefined }
2.created() { console.log(this.$el);//undefined console.log(this.rendered); // false }
3.beforeMount() { console.log(this.$el);//undefined }
4.mounted() { console.log(this.$el); }
5.beforeDestroy(){ console.log(this.$el); console.log(this.rendered); }
6.destroyed() { console.log(this.$el); console.log(this.rendered); }
三、vue中内置的方法 属性和vue生命周期的运行顺序(methods、computed、data、watch、props)
从第一二点可知道data的初始化是在created时已经完成数据观测(data observer),并且诸如methods、computed属性 props等已经初始化;那问题来了, data props computed watch methods他们之间的生成顺序是什么呢? 根据翻看vue源码可知:
props => methods =>data => computed => watch ; 懂了没
四、自己构造的方法与vue生命周期的运行顺序 如show这些
往往我们在开发项目时都经常用到 $refs 来直接访问子组件的方法,但是这样调用的时候可能会导致数据的延迟滞后的问题,则会出现bug。 解决方法则是推荐采取异步回调 的方法,然后传参进去,严格遵守vue的生命周期就可以解决 推荐 es6 的promise。 示例代码:handleAsync () { return new Promise(resolve=>{ const res=””; resolve(res) }) } … async handleShow() { await this.handleAsync().then(res=>{ this.$refs.child.show(res); }) } …
五、总结
vue 的生命周期,总得来说就是实例的创建和销毁这段时间的一个机制吧。也是vue框架的数据间的交互通信。其实现在看来也没那么难,但是vue的源码实现这一套机制那是难得一逼,涉及到复杂的算法如diff算法 ,
Vue的生命周期请简述
vue的生命周期就是vue实例创建到实例销毁的过程。期间会有8个钩子函数的调用。 beforeCreate (创建实例) created (创建完成)、 beforeMount (开始创建模板) mounted (创建完成)、 beforeUpdate ( 开始更新) updated (更新完成)、 beforeDestroy (开始销毁) destroyed (销毁完成)
Vue生命周期一共几个阶段
创建 加载 更新 销毁 Beforecreate 创建前 Created 创建后 Beforemount 加载前 Mounted 加载后 Beforeupdate 更新前 Updated 更新后 Beforedestroy 销毁前 Destroyed 销毁后 页 面 第一次 加 载 会 触 发 beforecreate created beforemount mounted DOM渲染 mounted 周期中就已经完成
2.vue 生命周期的作用是什么
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
说下 vue 生命周期钩子函数?
每个 vue 实例在被创建时都要经过一系列的初始化过程。 所有的生命周期钩子自动绑定 this 上下文到实例中,因此可以在函数中访问数据,对属性和方法进行运算。这意味着不能使用箭头函数来定义一个生命周期方法【这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同】。 vue 的生命周期图:
阶段一:Vue 实例创建阶段
beforeCreate
Vue 实例在内存中刚被创建,this 变量还不能使用,数据对象(data)和方法(methods)未初始化,watcher 中的事件都不能获得到;
created
实例已经在内存中创建好,数据和方法已经初始化完成,但是模板还未编译,页面还是没有内容,还不能对 dom 节点进行操作(此时访问 this.$el 和 this.$refs.xxx 都是undefined)
beforeMounte
找到对应的 template 模板,编译成 render 函数,转换成虚拟 dom,此时模板已经编译完成,数据未挂载到页面,也就是说在这个阶段你可以看到标签间的双花括号,数据还未渲染到页面中;
render : h=>h(App)
在 beforeMounte 之后和 mounted 之前,还有渲染 render 函数,它的作用是把模板渲染成虚拟 dom。
mounted
模板编译好了,虚拟 dom 渲染成真正的 dom 标签,数据渲染到页面,此时 Vue 实例已经创建完毕,如果没有其他操作的话,Vue 实例会静静的躺在内存中,一动不动。 一般会在 mounted 中来渲染从后端获取的数据。(页面初始化时,如果有操作 dom 的事件一般也会放在 mounted 钩子函数中。当然,也可以放在 create 中,前提需使用this.$nextTick(function(){}),在回调函数中操作 dom。)
阶段二:Vue 实例运行阶段
beforeUpdate
数据依赖改变或者用 $forceUpdata 强制刷新时,对象 data 中的数据已经更改(虚拟 dom 已经重新渲染),但是 页面中的值还是原来,未改变,因为此时还未开始渲染 dom;
update
此时 data 中的数据和页面更新完毕,页面已经被重新渲染。 在实际开发中,一般会用监听器 watch 来代替上边 2 个方法,因为 watch 会知道是哪一个数据变化。
阶段三:Vue 实例销毁阶段
beforeDestroy
destroyed
Vue 实例被销毁,观察者、子组件、事件监听被清除(页面数据不会消失,只不过是响应式无效了)。
7.请详细说下你对 vue 生命周期的理解?
一.Vue生命周期简介
二.钩子详解
1.beforeCreate
2.created
实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。 主要应用:调用数据,调用方法,调用异步函数 结果 :
可以看到:created钩子可以获取Vue的data,调用Vue方法,获取原本HTML上的直接加载出来的DOM,但是无法获取到通过挂载模板生成的DOM(例如:v-for循环遍历Vue.list生成li)
3.beforeMount
在挂载开始之前被调用:相关的 render 函数(模板)首次被调用。 例如通过v-for生成的html还没有被挂载到页面上 (接 2created的代码)beforeMount: function () { console.log(‘beforeMount:’,document.getElementsByTagName(‘li’).length); }, 结果 beforeMount: 1
4.mounted
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。 有初始值的DOM渲染,例如我们的初始数据list,渲染出来的li,只有这里才能获取 (接 2created的代码)mounted: function () { console.log(‘mounted:’,document.getElementsByTagName(‘li’).length); }, 结果 mounted: 3 可以看到到这里为止,挂载到实例上了,我们可以获取到li的个数了
5.beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。 当我们更改Vue的任何数据,都会触发该函数beforeUpdate: function () { console.log(‘beforeUpdate 钩子执行…’); console.log(‘beforeUpdate:’+this.message) },
6.updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。 当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。 数据更新就会触发(vue所有的数据只有有更新就会触发),如果想数据一遍就做统一的处理,可以用这个,如果想对不同数据的更新做不同的处理可以用nextTick,或者是watch进行监听updated: function () { console.log(‘updated 钩子执行…’); console.log(‘updated:’,this.message) },
7.beforeDestroy
8.destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
结果:
可以看打到销毁Vue实例时会调用这两个函数
补充$mount
当你vue没有挂在el时,我们可以用$mountvar app = new Vue({ data:{ message:’this is mseeage’, }, }).$mount(‘#app’)
三.钩子的一些实战用法 1.异步函数
这里我们用定时器来做异步函数 结果为:
create: aaaaaaaa mounted: 3 created异步函数: 3 updated: 4
解释:
可以看到因为是在created的钩子中加入异步函数,所以函数的执行顺序为:
ceated钩子,mounted钩子,异步函数,updated钩子(根据事件队列原理,只有在updated后,li才是真的DOM渲染为4个,所以异步函数中获取的li的个数时是没有变化的li的个数)。 因为mounted获取到的是我们在Vue的data中设置初始值渲染的DOM,而我们是在异步函数中变化的list数据,所以mounted获取的li的个数为3。 update函数是只要数据vue绑定的数据变化就会触发,所以最后执行,为4 这是不是意味着可以直接在update函数中操作呢,其实不是,因为update函数是针对vue的所有数据的变化,而我们也有可能会有其他数据的变化。
例如下面的例子://我们利用异步函数改变了两次list,会发现update被触发了2次 created:function(){ //异步获取数据 // 因为是异步,就和我们ajax获取数据一样 setTimeout(()=>{ this.list=[‘111’,’222’,’333’,’444’], console.log(‘created异步:’,document.getElementsByTagName(‘li’).length); },0) setTimeout(()=>{ this.list=[‘快乐大本营’,’脚踏实地’,’300033’,’天天向上’,’好好学习’], console.log(‘created异步:’,document.getElementsByTagName(‘li’).length); },1000) }, mounted: function () { console.log(‘mounted:’,document.getElementsByTagName(‘li’).length); }, updated: function () { console.log(‘updated:’,document.getElementsByTagName(‘li’).length) }, 结果为:
2.Vue.nextTick对异步函数的结果进行操作
我们想要改变数据时,各自触发各自的方法created:function(){ //异步获取数据 // 因为是异步,就和我们ajax获取数据一样 //为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。 setTimeout(()=>{ this.list=[‘111’,’222’,’333’,’444’], console.log(‘created异步:’,document.getElementsByTagName(‘li’).length); this.$nextTick(function(){ console.log(“created$nextTick:”,document.getElementsByTagName(‘li’).length) }); },0) setTimeout(()=>{ this.list=[‘快乐大本营’,’脚踏实地’,’300033’,’天天向上’,’好好学习’], console.log(‘created异步:’,document.getElementsByTagName(‘li’).length); this.$nextTick(function(){ console.log(“created$nextTick:”,document.getElementsByTagName(‘li’).length) }); },1000) }, mounted: function () { console.log(‘mounted:’,document.getElementsByTagName(‘li’).length); }, updated: function () { console.log(‘updated:’,document.getElementsByTagName(‘li’).length) }, 结果:
我们可以看到通过$nextTick我们可以对异步函数的结果进行各自的操作
4.简述每个周期具体适合哪些场景
1、beforeCreate:可以在这加loading事件,在加载实例时触发。 2、created:初始化完成时的事件写在这里,如在这里结束loading,异步请求也适合在这里调用。 3、mounted:挂载元素,获取到dom节点。 4、updated:如果对数据统一处理,在这里写上相应的函数。 5、beforeDestroy:可以做一个确定停止事件的确认框。
6.vue 获取数据在哪个周期函数
看实际情况,一般在 created(或beforeRouter) 里面就可以,如果涉及到需要页面加载完成之后的话就用 mounted。 在created的时候,视图中的html并没有渲染出来,所以此时如果直接去操作html的dom节点,一定找不到相关的元素 而在mounted中,由于此时html已经渲染出来了,所以可以直接操作dom节点,(此时document.getelementById 即可生效了)。 应用
vue中实现先请求数据再渲染dom 在项目中遇到了一个问题,下面是vue template中的代码:
我之前的写法是
这样做的结果是下面取dom的操作,取到的dom都是undefined,也就是没有取到。 原因是并没有按照 请求数据—>渲染dom—>获取dom的顺序执行,实际的执行顺序是 先获取dom,而此时数组option中还是空的,上面的v-for循环也就没有渲染出dom,所以根本取不到(不理解是为什么) 后来我又把请求数据写在了created函数中,把取dom的操作写在mounted函数中,竟然还是先执行取dom的操作(是通过alert的顺序来判断执行的顺序),我也很绝望啊 最后终于找到了解决的办法:
看到一个别人的回答是:“在数据请求的回调中使用nextTick,在nextTick的回调里试试~” 还有一个人的回答是:“如果有依赖dom必须存在的情况,就放到mounted(){this.$nextTick(() => { / code / })}里面”(这种之前我试过,我太好用,不懂为什么) 我把这两种方法综合起来,其实主要是第一种方法,发现好用了!
3.第一次页面加载会触发哪几个钩子
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
DOM 渲染在哪个周期中就已经完成?
5.created 和 mounted 的区别
一、什么是生命周期?
用通俗的语言来说,就是 Vue中实例或者组件从创建到消灭中间经过的一系列过程。 虽然不太严谨,但是也基本上可以理解。 通过一系列实践,现在把所有遇到的问题整理一遍,今天记录一下created和mounted的区别:
二、created和mounted区别?
我们从图中看两个节点:
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。 mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。 其实两者比较好理解,通常created使用的次数多,而mounted通常是在一些插件的使用或者组件的使用中进行操作,比如插件chart.js的使用: var ctx = document.getElementById(ID); 通常会有这一步,而如果你写入组件中,你会发现在created中无法对chart进行一些初始化配置,一定要等这个html渲染完后才可以进行,那么mounted就是不二之选。下面看一个例子(用组件)。
三、例子Vue.component(“demo1”,{ data:function(){ return { name:””, age:””, city:”” } }, template:”“, created:function(){ this.name =”唐浩益” this.age = “12” this.city =”杭州” var x = document.getElementById(“name”)//第一个命令台错误 console.log(x.innerHTML); }, mounted:function(){ var x = document.getElementById(“name”)//第二个命令台输出的结果 console.log(x.innerHTML); } }); var vm = new Vue({ el:”#example1” })
可以看到输出如下:
可以看到都在created赋予初始值的情况下成功渲染出来了。 但是同时看console台如下:
可以看到第一个报了错,实际是因为找不到id,getElementById(ID) 并没有找到元素,原因如下: 在created的时候,视图中的html并没有渲染出来,所以此时如果直接去操作html的dom节点,一定找不到相关的元素 而在mounted中,由于此时html已经渲染出来了,所以可以直接操作dom节点,故输出了结果“唐浩益”。
MVVM
MVC框架:
M-Model : 业务逻辑和实体模型(biz/bean) V-View : 布局文件(XML) C-Controller : 控制器(Activity) 相信大家都熟悉这个框架,这个也是初学者最常用的框架,该框架虽然也是把代码逻辑和UI层分离,但是View层能做的事情还是很少的,很多对于页面的呈现还是交由C实现,这样会导致项目中C的代码臃肿,如果项目小,代码臃肿点还是能接受的,但是随着项目的不断迭代,代码量的增加,你就会没办法忍受该框架开发的项目,这时MVP框架就应运而生。
MVP框架:
M-Model : 业务逻辑和实体模型(biz/bean) V-View : 布局文件(XML)和Activity P-Presenter : 完成View和Model的交互 MVP框架相对于MVC框架做了较大的改变,将Activity当做View使用,代替MVC框架中的C的是P,对比MVC和MVP的模型图可以发现变化最大的是View层和Model层不在直接通信,所有交互的工作都交由Presenter层来解决。既然两者都通过Presenter来通信,为了复用和可拓展性,MVP框架基于接口设计的理念大家自然就可以理解其用意。 但MVP框架也有不足之处:
1.接口过多,一定程度影响了编码效率。 2.业务逻辑抽象到Presenter中,较为复杂的界面Activity代码量依然会很多。 3.导致Presenter的代码量过大。
MVVM框架:
M-Model : 实体模型(biz/bean) V-View : 布局文件(XML) VM-ViewModel : DataBinding所在之处,对外暴露出公共属性,View和Model的绑定器 对比MVP和MVVM模型图可以看出,他们之间区别主要体现在以下两点:
可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。 在Android中,布局里可以进行一个视图逻辑,并且Model发生变化,View也随着发生变化。
低耦合。以前Activity、Fragment中需要把数据填充到View,还要进行一些视图逻辑。现在这些都可在布局中完成(具体代码请看后面) 甚至都不需要再Activity、Fragment去findViewById()。这时候Activity、Fragment只需要做好的逻辑处理就可以了。
说了这么多理论知识,相信大家都有所厌烦了,下面就不来“虚”的了直接来“干”的,大家可能会问MVVM框架在Android怎么样使用? Google在2015年的已经为我们提供DataBinding技术,以便让我们快速实现MVVM框架的实现。下面就详细讲解如何使用DataBinding? 由于本人使用的是AndroidStudio(以下简称AS),所以接下来都是关于AS相关使用规则:
1.检查你的AS版本,要求在1.3.0以上 2.Gradle 版本1.3.0-beta4以上 3.在工程根目录build.gradle文件加入如下配置:dependencies { classpath “com.android.tools.build:gradle:1.3.0-beta4” classpath “com.android.databinding:dataBinder:1.0-rc1” } allprojects { repositories { jcenter() } } 4.在app里的build.gradle文件加入如下配置:apply plugin: ‘com.android.application’ apply plugin: ‘com.android.databinding’ 下面来比较一下布局与之前大家常用的格式的区别:<?xml version=”1.0” encoding=”utf-8”?> 先将根布局改为layout 在布局里引入的model 中的数据类: (还有一种写法将在后面代码中介绍) 设置布局属性值,通过@{}语法: 数据实体类User:public class User { private final String firstName; private final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } } 在Activity中进行数据的绑定:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); User user = new User(“Test”, “User”); binding.setUser(user); } 这时你运行程序就会看到在界面上会显示你设置的测试用户数据,当然你还可以这样做去获取binding:MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater()); 如果你使用的是ListView或者RecyclerView去显示界面,这时候在Items布局中使用Data Binding,在Adapter中你可以这样获取binding:ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); //or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false); 接下来就是事件的实现,在我们以往的使用中对于事件的实现都是android:onClick或者在代码中使用View.setOnClickListener()来实现点击事件,在这里将有一种新的实现方式: 要将事件分配给它的处理程序,使用一个正常的绑定表达式,以值作为调用的方法名称。例如:你的数据对象有两种方法public class MyHandlers { public void onClickFriend(View view) { … } public void onClickEnemy(View view) { … } } 绑定表达式可以为视图指定单击事件监听器<?xml version=”1.0” encoding=”utf-8”?> 由于DataBinding对布局使用改动比较大,下面主要讲解一下布局: ①在布局中import导入
之后在你的布局中通过View控件特性进行对其实现类隐藏和显示操作 可能有人会问如果我导入的类与已导入的类的名字冲突怎么办?那么接下来就会解决这个问题! 这里的“alias”属性就是别名的意思,你可以采用别名的方式解决这个问题。
②当你在布局中引用的变量是一个List集合,需将集合的左”<”使用转义字符输入,如下(想要了解转义字符具体表达形式,请自行查询): ③类型转换 ④当导入的类中存在静态属性和方法时,你也是可以在布局中直接使用 … ⑤属性Variables:
在数据元素中可以使用任意数量的变量元素。每个可变元素描述一个属性,该属性可以设置在布局文件中的绑定表达式中使用的布局上:
⑥自定义Binding类的类名: 在app_package/databinding下生成CustomBinding; 在app_package下生成CustomBinding; 明确指定包名和类名。 ⑦include使用<?xml version=”1.0” encoding=”utf-8”?> ⑧DataBinding数据绑定不支持包括合并元素的直接子元素,例如下面的写法是不被允许的:<?xml version=”1.0” encoding=”utf-8”?>
注意:name.xml 和 contact.xml都必须包含
⑨DataBinding支持的表达式有:数学表达式: + - / *% 字符串拼接 + 逻辑表达式&& || 位操作符 & | ^ 一元操作符 + - ! ~ 位移操作符 >>>>> << 比较操作符 == >< >= <= instanceof 分组操作符 () 字面量 -character, String, numeric, null 强转、方法调用 字段访问 数组访问 [] 三元操作符 ? 聚合判断(Null Coalescing Operator)语法 ‘??’ 例如: ①android:text=”@{String.valueOf(index + 1)}” android:visibility=”@{age < 13 ? View.GONE : View.VISIBLE}” android:transitionName=’@{“image_” + id}’ ②android:text=”@{user.displayName ?? user.lastName}”
上面代码的意思是如果displayName为null,则显示lastName,否则显示displayName;
③android:text=”@{user.displayName != null ? user.displayName : user.lastName}” 集合Collections … android:text=”@{list[index]}” … android:text=”@{sparse[index]}” … android:text=”@{map[key]}” ①String literals(字符串常量)
当使用单引号围绕属性值时,在表达式中使用双引号是很容易的:android:text=’@{map[“firstName”]}’
②也可以使用双引号来环绕属性值。当你这样做的时候,String literals要么用";或反引号():android:text="@{map[firstName`}” android:text=”@{map["firstName"]}” Resources资源:
在DataBinding语法中,可以把resource作为其中的一部分:android:padding=”@{large? @dimen/largePadding : @dimen/smallPadding}” 除了支持dimen,还支持color、string、drawable、anim等。 注意:对mipmap图片资源支持还是有问题,目前只支持drawable。
一些资源要求需明确的类型赋值:
到这里基本的属性使用方法介绍就结束了,下面大家应该对数据变化时,UI如何呈现很迷惑,好了,现在开始讲数据的变化如何让UI的更新? 任何普通的java对象(POJO)可用于数据绑定,但修改一个POJO不会造成UI更新。数据绑定(DataBinding)的真正力量可以通过给你的数据对象在数据改变时通知你来使用。有三种不同的数据变化通知机制,Observable objects, observable fields, and observable collections。 下面来逐个讲解: Observable Objects
一个实现可观察到的接口的类,将允许绑定到绑定一个单一的监听器绑定到一个绑定对象,以监听该对象上的所有属性的更改;观察到的接口有一个机制来添加和删除监听器,但通知是由开发人员来进行的。为使开发更容易,一个基类,baseobservable,是为了实现监听器注册机制。数据类实现者仍然是负责通知时的性能变化。这是通过分配一个绑定注释getter和setter进行通知:private static class User extends BaseObservable { private String firstName; private String lastName; @Bindable public String getFirstName() { return this.firstName; } @Bindable public String getLastName() { return this.lastName; } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); } } 注意:BR类自动生成的 好了,现在你就会发现当通过set方法改变数据后,UI就会自动更新!
ObservableFieldsprivate static class User { public final ObservableField firstName = new ObservableField<>(); public final ObservableField lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt(); }
在代码中设置数据:user.firstName.set(“Google”); int age = user.age.get();
Observable CollectionsObservableArrayMap user = new ObservableArrayMap<>(); user.put(“firstName”, “Google”); user.put(“lastName”, “Inc.”); user.put(“age”, 17); 布局中使用: … 如果集合的key是Integer,可以使用ObservableArrayList代替ObservableArrayMap:ObservableArrayList user = new ObservableArrayList<>(); user.add(“Google”); user.add(“Inc.”); user.add(17);在布局中使用: …
什么是MVVM?MVVM 是Model-View-ViewModel的缩写。 要编写可维护的前端代码绝非易事。我们已经用MVC模式通过koa实现了后端数据、模板页面和控制器的分离,但是,对于前端来说,还不够。 这里有童鞋会问,不是讲Node后端开发吗?怎么又回到前端开发了? 对于一个全栈开发工程师来说,懂前端才会开发出更好的后端程序(不懂前端的后端工程师会设计出非常难用的API),懂后端才会开发出更好的前端程序。程序设计的基本思想在前后端都是通用的,两者并无本质的区别。这和“不想当厨子的裁缝不是好司机”是一个道理。 当我们用Node.js有了一整套后端开发模型后,我们对前端开发也会有新的认识。由于前端开发混合了HTML、CSS和JavaScript,而且页面众多,所以,代码的组织和维护难度其实更加复杂,这就是MVVM出现的原因。 在了解MVVM之前,我们先回顾一下前端发展的历史。 在上个世纪的1989年,欧洲核子研究中心的物理学家Tim Berners-Lee发明了超文本标记语言(HyperText Markup Language),简称HTML,并在1993年成为互联网草案。从此,互联网开始迅速商业化,诞生了一大批商业网站。 最早的HTML页面是完全静态的网页,它们是预先编写好的存放在Web服务器上的html文件。浏览器请求某个URL时,Web服务器把对应的html文件扔给浏览器,就可以显示html文件的内容了。 如果要针对不同的用户显示不同的页面,显然不可能给成千上万的用户准备好成千上万的不同的html文件,所以,服务器就需要针对不同的用户,动态生成不同的html文件。一个最直接的想法就是利用C、C++这些编程语言,直接向浏览器输出拼接后的字符串。这种技术被称为CGI:Common Gateway Interface。 很显然,像新浪首页这样的复杂的HTML是不可能通过拼字符串得到的。于是,人们又发现,其实拼字符串的时候,大多数字符串都是HTML片段,是不变的,变化的只有少数和用户相关的数据,所以,又出现了新的创建动态HTML的方式:ASP、JSP和PHP——分别由微软、SUN和开源社区开发。 在ASP中,一个asp文件就是一个HTML,但是,需要替换的变量用特殊的<%=var%>标记出来了,再配合循环、条件判断,创建动态HTML就比CGI要容易得多。 但是,一旦浏览器显示了一个HTML页面,要更新页面内容,唯一的方法就是重新向服务器获取一份新的HTML内容。如果浏览器想要自己修改HTML页面的内容,就需要等到1995年年底,JavaScript被引入到浏览器。 有了JavaScript后,浏览器就可以运行JavaScript,然后,对页面进行一些修改。JavaScript还可以通过修改HTML的DOM结构和CSS来实现一些动画效果,而这些功能没法通过服务器完成,必须在浏览器实现。 用JavaScript在浏览器中操作HTML,经历了若干发展阶段:
第一阶段,直接用JavaScript操作DOM节点,使用浏览器提供的原生API:var dom = document.getElementById(‘name’); dom.innerHTML = ‘Homer’; dom.style.color = ‘red’; 第二阶段,由于原生API不好用,还要考虑浏览器兼容性,jQuery横空出世,以简洁的API迅速俘获了前端开发者的芳心:$(‘#name’).text(‘Homer’).css(‘color’, ‘red’); 第三阶段,MVC模式,需要服务器端配合,JavaScript可以在前端修改服务器渲染后的数据。
现在,随着前端页面越来越复杂,用户对于交互性要求也越来越高,想要写出Gmail这样的页面,仅仅用jQuery是远远不够的。MVVM模型应运而生。 MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。 把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。 ViewModel如何编写?需要用JavaScript编写一个通用的ViewModel,这样,就可以复用整个MVVM模型了。 一个MVVM框架和jQuery操作DOM相比有什么区别? 我们先看用jQuery实现的修改两个DOM节点的例子: Hello, Bart !
You are 12 .
Hello, Bart ! You are 12 . 用jQuery修改name和age节点的内容: ‘use strict’;var name = ‘Homer’; var age = 51; $(‘#name’).text(name); $(‘#age’).text(age); // 执行代码并观察页面变化 (no output) 如果我们使用MVVM框架来实现同样的功能,我们首先并不关心DOM的结构,而是关心数据如何存储。最简单的数据存储方式是使用JavaScript对象:var person = { name: ‘Bart’, age: 12 }; 我们把变量person看作Model,把HTML某些DOM节点看作View,并假定它们之间被关联起来了。 要把显示的name从Bart改为Homer,把显示的age从12改为51,我们并不操作DOM,而是直接修改JavaScript对象: Hello, Homer ! You are 51 . ‘use strict’;person.name = ‘Homer’; person.age = 51; // 执行代码并观察页面变化 (no output) 执行上面的代码,我们惊讶地发现,改变JavaScript对象的状态,会导致DOM结构作出对应的变化!这让我们的关注点从如何操作DOM变成了如何更新JavaScript对象的状态,而操作JavaScript对象比DOM简单多了! 这就是MVVM的设计思想:关注Model的变化,让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来!
对MVC 、MVVM、MVP的理解【https://www.kancloud.cn/small-four/asdasdasdasd/1007502 】 1.mvvm 框架是什么?
MVC简介
MVC是Model-View-Controler的简称
Model——即模型。模型一般都有很好的可复用性,统一管理一些我们需要使用的数据。 View——就是存放视图使用的。 Controller——控制器它负责处理View和Model的事件。
MVVM简介 MVC框架一目了然,也非常好理解,随着App应用功能的强大Controller的负担越来越大因此在MVC的基础上繁衍出了MVVM框架。
ViewModel: 相比较于MVC新引入的视图模型。是视图显示逻辑、验证逻辑、网络请求等代码存放的地方。 现实开发中是找到一个合适的框架时使用,并不局限于哪一种,下面举一个简单的例子,在ViewModel里面处理业务逻辑,旨在讲解MVVM框架,不用与工作,当我们处理复杂的业务逻辑的时候可以优先选择MVVM框架。
看图简单的逻辑,下面上代码: User.h和User.m文件#import @interface User : NSObject @property (nonatomic,copy) NSString *userName; @property (nonatomic,assign) NSInteger userId; @end#import “User.h” @implementation User - (id)init{ self = [superinit]; if (self) { self.userName =@””; self.userId = 20; } returnself; } @end UserViewModel.h和UserViewModel.m 文件#import @class User; @interface UserViewModel : NSObject @property (nonatomic,strong) User user; @property (nonatomic,strong) NSString userName; @end#import “UserViewModel.h” #import “User.h” @implementation UserViewModel - (id)init{ self = [superinit]; if (self) { //在这里处理业务逻辑 _user = [[Useralloc]init]; if (_user.userName.length > 0) { _userName =_user.userName; }else { _userName = [NSStringstringWithFormat:@”简书%ld”, (long)_user.userId]; } } returnself; } @end ViewController.m文件 ViewController - (void)viewDidLoad { [superviewDidLoad]; _userLabel = [[UILabelalloc]initWithFrame:CGRectMake(10, 199, 200, 50)]; _userLabel.backgroundColor = [UIColorredColor]; _userViewModel = [[UserViewModelalloc]init]; _userLabel.text =_userViewModel.userName;//显示 [self.viewaddSubview:_userLabel]; // Do any additional setup after loading the view, typically from a nib. } ViewController.m文件#import “ViewController.h” #import “UserViewModel.h” @interface ViewController () @property (nonatomic,strong) UILabel userLabel; @property (nonatomic,strong) UserViewModel userViewModel; @end@implementation ViewController - (void)viewDidLoad { [superviewDidLoad]; _userLabel = [[UILabelalloc]initWithFrame:CGRectMake(10, 199, 200, 50)]; _userLabel.backgroundColor = [UIColorredColor]; _userViewModel = [[UserViewModelalloc]init]; _userLabel.text =_userViewModel.userName;//显示 [self.viewaddSubview:_userLabel]; // Do any additional setup after loading the view, typically from a nib. } 这就是简单的MVVM框架使用,工作中要灵活应用,并不局限于哪一种。
说说你对MVC和MVVM的理解
MVC:
View 传送指令到 Controller Controller 完成业务逻辑后,要求 Model 改变状态 Model 将新的数据发送到 View,用户得到反馈 所有通信都是单向的。
MVVM:
组成部分Model、View、ViewModel View:UI界面 ViewModel:它是View的抽象,负责View与Model之间信息转换,将View的Command传送到Model; Model:数据访问层
vue 路由面试题
什么是RESTful API?怎么使用?
是一个api的标准,无状态请求。请求的路由地址是固定的, 如果是tp5则先路由配置中把资源路由配置好。标准有:.get .post .put .delete
vue路由的实现/ vue路由的原理?
1、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 依赖的页面。”
2.vue-router 是什么?它有哪些组件 //路由声明式跳转 ,active-class是标签被点击时的样式 //渲染路由的容器 //缓存组件
vue-router路由,通俗来讲主要是来实现页面的跳转,通过设置不同的path,向服务器发送的不同的请求,获取不同的资源。 路由中有三个基本的概念 route, routes, router。
1, route,它是一条路由,由这个英文单词也可以看出来,它是单数, Home按钮 => home内容, 这是一条route, about按钮 => about 内容, 这是另一条路由。 2, routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}] 3, router 是一个机制,相当于一个管理者 ,它来管理路由。因为routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router 就起作用了,它到routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。 4,客户端中的路由,实际上就是dom 元素的显示和隐藏。当页面中显示home 内容的时候,about 中的内容全部隐藏,反之也是一样。客户端路由有两种实现方式:基于hash 和基于html5 history api.
路由跳转等都需要vue-router
6.Vue-router 路由有哪些模式?
一般有两种模式:
hash 模式:后面的 hash 值的变化,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变化会触发 hashchange 事件。 history 模式:利用了 HTML5 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
5.vue-router 有哪几种导航钩子?它们有哪些参数?
3种。 1、全局导航钩子:
(1)前置守卫:跳转前进行拦截。
router.beforeEach(to, from, next)
(2)后置钩子
router.afterEach((to, from) => {})
2、组件内的钩子
beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
3、单独路由独享组件。
beforeEnter: (to, from ,next)const router = new VueRouter({ routes: [ { path: ‘/foo’, component: Foo, beforeEnter: (to, from, next) => { // … } } ]
vue-router导航钩子实现的原理?【https://www.cnblogs.com/tiedaweishao/p/9144531.html 】
前端路由是直接找到与地址匹配的一个组件或对象并将其渲染出来。改变浏览器地址而不向服务器发出请求有两种方式:
在地址中加入#以欺骗浏览器,地址的改变是由于正在进行页内导航
使用H5的window.history功能,使用URL的Hash来模拟一个完整的URL。
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
目录结构
和流程相关的主要需要关注点的就是 components、history 目录以及 create-matcher.js、create-route-map.js、index.js、install.js。下面就从 basic 应用入口开始来分析 vue-router 的整个流程。import Vue from ‘vue’ import VueRouter from ‘vue-router’ // 1. 插件 // 安装 and 组件 // 且给当前应用下所有的组件都注入 $router and $route 对象 Vue.use(VueRouter) // 2. 定义各个路由下使用的组件,简称路由组件 const Home = { template: ‘home’ } const Foo = { template: ‘foo’ } const Bar = { template: ‘bar’ } // 3. 创建 VueRouter 实例 router const router = new VueRouter({ mode: ‘history’, base: __dirname, routes: [ { path: ‘/‘, component: Home }, { path: ‘/foo’, component: Foo }, { path: ‘/bar’, component: Bar } ] }) // 4. 创建 启动应用 // 一定要确认注入了 router // 在中将会渲染路由组件 new Vue({ router, template: <div id="app"> <h1>Basic</h1> <ul> <li><router-link to="/">/</router-link></li> <li><router-link to="/foo">/foo</router-link></li> <li><router-link to="/bar">/bar</router-link></li> <router-link tag="li" to="/bar">/bar</router-link> </ul> <router-view class="view"></router-view> </div> }).$mount(‘#app’) 作为插件
上边代码中关键的第 1 步,利用 Vue.js 提供的插件机制 .use(plugin) 来安装 VueRouter,而这个插件机制则会调用该 plugin 对象的 install 方法(当然如果该 plugin 没有该方法的话会把 plugin 自身作为函数来调用);下边来看下 vue-router 这个插件具体的实现部分。 VueRouter 对象是在 src/index.js 中暴露出来的,这个对象有一个静态的 install 方法:/ @flow / // 导入 install 模块 import { install } from ‘./install’ // … import { inBrowser, supportsHistory } from ‘./util/dom’ // … export default class VueRouter { // … } // 赋值 install VueRouter.install = install // 自动使用插件 if (inBrowser && window.Vue) { window.Vue.use(VueRouter) } 可以看到这是一个 Vue.js 插件的经典写法,给插件对象增加 install 方法用来安装插件具体逻辑,同时在最后判断下如果是在浏览器环境且存在 window.Vue 的话就会自动使用插件。 install 在这里是一个单独的模块,继续来看同级下的 src/install.js 的主要逻辑:// router-view router-link 组件 import View from ‘./components/view’ import Link from ‘./components/link’ // export 一个 Vue 引用 export let _Vue // 安装函数 export function install (Vue) { if (install.installed) return install.installed = true // 赋值私有 Vue 引用 _Vue = Vue // 注入 $router $route Object.defineProperty(Vue.prototype, ‘$router’, { get () { return this.$root._router } }) Object.defineProperty(Vue.prototype, ‘$route’, { get () { return this.$root._route } }) // beforeCreate mixin Vue.mixin({ beforeCreate () { // 判断是否有 router if (this.$options.router) { // 赋值 _router this._router = this.$options.router // 初始化 init this._router.init(this) // 定义响应式的 _route 对象 Vue.util.defineReactive(this, ‘_route’, this._router.history.current) } } }) // 注册组件 Vue.component(‘router-view’, View) Vue.component(‘router-link’, Link) // … } 这里就会有一些疑问了? · 为啥要 export 一个 Vue 引用?插件在打包的时候是肯定不希望把 vue 作为一个依赖包打进去的,但是呢又希望使用 Vue 对象本身的一些方法,此时就可以采用上边类似的做法,在 install 的时候把这个变量赋值 Vue ,这样就可以在其他地方使用 Vue 的一些方法而不必引入 vue 依赖包(前提是保证 install 后才会使用)。 · 通过给 Vue.prototype 定义 $router、$route 属性就可以把他们注入到所有组件中吗?在 Vue.js 中所有的组件都是被扩展的 Vue 实例,也就意味着所有的组件都可以访问到这个实例原型上定义的属性。 beforeCreate mixin 这个在后边创建 Vue 实例的时候再细说。
实例化 VueRouter
在入口文件中,首先要实例化一个 VueRouter ,然后将其传入 Vue 实例的 options 中。现在继续来看在 src/index.js 中暴露出来的 VueRouter 类:// … import { createMatcher } from ‘./create-matcher’ // … export default class VueRouter { // … constructor (options: RouterOptions = {}) { this.app = null this.options = options this.beforeHooks = [] this.afterHooks = [] // 创建 match 匹配函数 this.match = createMatcher(options.routes || []) // 根据 mode 实例化具体的 History let mode = options.mode || ‘hash’ this.fallback = mode === ‘history’ && !supportsHistory if (this.fallback) { mode = ‘hash’ } if (!inBrowser) { mode = ‘abstract’ } this.mode = mode switch (mode) { case ‘history’: this.history = new HTML5History(this, options.base) break case ‘hash’: this.history = new HashHistory(this, options.base, this.fallback) break case ‘abstract’: this.history = new AbstractHistory(this) break default: assert(false, invalid mode: ${mode}) } } // … } 里边包含了重要的一步:创建 match 匹配函数。
match 匹配函数
匹配函数是由 src/create-matcher.js 中的 createMatcher 创建的:/ @flow / import Regexp from ‘path-to-regexp’ // … import { createRouteMap } from ‘./create-route-map’ // … export function createMatcher (routes: Array): Matcher { // 创建路由 map const { pathMap, nameMap } = createRouteMap(routes) // 匹配函数 function match ( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location ): Route { // … } function redirect ( record: RouteRecord, location: Location ): Route { // … } function alias ( record: RouteRecord, location: Location, matchAs: string ): Route { // … } function _createRoute ( record: ?RouteRecord, location: Location, redirectedFrom?: Location ): Route { if (record && record.redirect) { return redirect(record, redirectedFrom || location) } if (record && record.matchAs) { return alias(record, location, record.matchAs) } return createRoute(record, location, redirectedFrom) } // 返回 return match } // … 具体逻辑后续再具体分析,现在只需要理解为根据传入的 routes 配置生成对应的路由 map,然后直接返回了 match 匹配函数。 继续来看 src/create-route-map.js 中的 createRouteMap 函数:/ @flow / import { assert, warn } from ‘./util/warn’ import { cleanPath } from ‘./util/path’ // 创建路由 map export function createRouteMap (routes: Array): { pathMap: Dictionary, nameMap: Dictionary } { // path 路由 map const pathMap: Dictionary = Object.create(null) // name 路由 map const nameMap: Dictionary = Object.create(null) // 遍历路由配置对象 增加 路由记录 routes.forEach(route => { addRouteRecord(pathMap, nameMap, route) }) return { pathMap, nameMap } } // 增加 路由记录 函数 function addRouteRecord ( pathMap: Dictionary, nameMap: Dictionary, route: RouteConfig, parent?: RouteRecord, matchAs?: string ) { // 获取 path 、name const { path, name } = route assert(path != null, "path" is required in a route configuration.) // 路由记录 对象 const record: RouteRecord = { path: normalizePath(path, parent), components: route.components || { default: route.component }, instances: {}, name, parent, matchAs, redirect: route.redirect, beforeEnter: route.beforeEnter, meta: route.meta || {} } // 嵌套子路由 则递归增加 记录 if (route.children) { // … route.children.forEach(child => { addRouteRecord(pathMap, nameMap, child, record) }) } // 处理别名 alias 逻辑 增加对应的 记录 if (route.alias !== undefined) { if (Array.isArray(route.alias)) { route.alias.forEach(alias => { addRouteRecord(pathMap, nameMap, { path: alias }, parent, record.path) }) } else { addRouteRecord(pathMap, nameMap, { path: route.alias }, parent, record.path) } } // 更新 path map pathMap[record.path] = record // 更新 name map if (name) { if (!nameMap[name]) { nameMap[name] = record } else { warn(false, Duplicate named routes definition: { name: "${name}", path: "${record.path}" }) } } } function normalizePath (path: string, parent?: RouteRecord): string { path = path.replace(/\/$/, ‘’) if (path[0] === ‘/‘) return path if (parent == null) return path return cleanPath(`${pa 可以看出主要做的事情就是根据用户路由配置对象生成普通的根据 path 来对应的路由记录以及根据 name 来对应的路由记录的 map,方便后续匹配对应。
实例化 History
这也是很重要的一步,所有的 History 类都是在 src/history/ 目录下,现在呢不需要关心具体的每种 History 的具体实现上差异,只需要知道他们都是继承自 src/history/base.js 中的 History 类的:/ @flow / // … import { inBrowser } from ‘../util/dom’ import { runQueue } from ‘../util/async’ import { START, isSameRoute } from ‘../util/route’ // 这里从之前分析过的 install.js 中 export _Vue import { _Vue } from ‘../install’ export class History { // … constructor (router: VueRouter, base: ?string) { this.router = router this.base = normalizeBase(base) // start with a route object that stands for “nowhere” this.current = START this.pending = null } // … } // 得到 base 值 function normalizeBase (base: ?string): string { if (!base) { if (inBrowser) { // respect tag const baseEl = document.querySelector(‘base’) base = baseEl ? baseEl.getAttribute(‘href’) : ‘/‘ } else { base = ‘/‘ } } // make sure there’s the starting slash if (base.charAt(0) !== ‘/‘) { base = ‘/‘ + base } // remove trailing slash return base.replace(/\/$/, ‘’) } // … 实例化完了 VueRouter,下边就该看看 Vue 实例了。
实例化 Vue new Vue({ router, template: <div id="app"> <h1>Basic</h1> <ul> <li><router-link to="/">/</router-link></li> <li><router-link to="/foo">/foo</router-link></li> <li><router-link to="/bar">/bar</router-link></li> <router-link tag="li" to="/bar">/bar</router-link> </ul> <router-view class="view"></router-view> </div> }).$mount(‘#app’)
options 中传入了 router,以及模板;还记得上边没具体分析的 beforeCreate mixin 吗,此时创建一个 Vue 实例,对应的 beforeCreate 钩子就会被调用:// … Vue.mixin({ beforeCreate () { // 判断是否有 router if (this.$options.router) { // 赋值 _router this._router = this.$options.router // 初始化 init this._router.init(this) // 定义响应式的 _route 对象 Vue.util.defineReactive(this, ‘_route’, this._router.history.current) } } }) 具体来说,首先判断实例化时 options 是否包含 router,如果包含也就意味着是一个带有路由配置的实例被创建了,此时才有必要继续初始化路由相关逻辑。然后给当前实例赋值_router,这样在访问原型上的 $router 的时候就可以得到 router 了。 下边来看里边两个关键:router.init 和 定义响应式的 _route 对象。
router.init
然后来看 router 的 init 方法就干了哪些事情,依旧是在 src/index.js 中:/ @flow / import { install } from ‘./install’ import { createMatcher } from ‘./create-matcher’ import { HashHistory, getHash } from ‘./history/hash’ import { HTML5History, getLocation } from ‘./history/html5’ import { AbstractHistory } from ‘./history/abstract’ import { inBrowser, supportsHistory } from ‘./util/dom’ import { assert } from ‘./util/warn’ export default class VueRouter { // … init (app: any / Vue component instance /) { // … this.app = app const history = this.history if (history instanceof HTML5History) { history.transitionTo(getLocation(history.base)) } else if (history instanceof HashHistory) { history.transitionTo(getHash(), () => { window.addEventListener(‘hashchange’, () => { history.onHashChange() }) }) } history.listen(route => { this.app._route = route }) } // … } // … 可以看到初始化主要就是给 app 赋值,针对于 HTML5History 和 HashHistory 特殊处理,因为在这两种模式下才有可能存在进入时候的不是默认页,需要根据当前浏览器地址栏里的 path 或者 hash 来激活对应的路由,此时就是通过调用 transitionTo 来达到目的;而且此时还有个注意点是针对于 HashHistory 有特殊处理,为什么不直接在初始化 HashHistory 的时候监听 hashchange 事件呢?这个是为了修复vuejs/vue-router#725这个 bug 而这样做的,简要来说就是说如果在 beforeEnter 这样的钩子函数中是异步的话,beforeEnter 钩子就会被触发两次,原因是因为在初始化的时候如果此时的 hash 值不是以 / 开头的话就会补上 #/,这个过程会触发 hashchange 事件,所以会再走一次生命周期钩子,也就意味着会再次调用 beforeEnter 钩子函数。 来看看这个具体的 transitionTo 方法的大概逻辑,在 src/history/base.js 中:/ @flow / import type VueRouter from ‘../index’ import { warn } from ‘../util/warn’ import { inBrowser } from ‘../util/dom’ import { runQueue } from ‘../util/async’ import { START, isSameRoute } from ‘../util/route’ import { _Vue } from ‘../install’ export class History { // … transitionTo (location: RawLocation, cb?: Function) { // 调用 match 得到匹配的 route 对象 const route = this.router.match(location, this.current) // 确认过渡 this.confirmTransition(route, () => { // 更新当前 route 对象 this.updateRoute(route) cb && cb(route) // 子类实现的更新url地址 // 对于 hash 模式的话 就是更新 hash 的值 // 对于 history 模式的话 就是利用 pushstate / replacestate 来更新 // 浏览器地址 this.ensureURL() }) } // 确认过渡 confirmTransition (route: Route, cb: Function) { const current = this.current // 如果是相同 直接返回 if (isSameRoute(route, current)) { this.ensureURL() return } // 交叉比对当前路由的路由记录和现在的这个路由的路由记录 // 以便能准确得到父子路由更新的情况下可以确切的知道 // 哪些组件需要更新 哪些不需要更新 const { deactivated, activated } = resolveQueue(this.current.matched, route.matched) // 整个切换周期的队列 const queue: Array = [].concat( // leave 的钩子 extractLeaveGuards(deactivated), // 全局 router before hooks this.router.beforeHooks, // 将要更新的路由的 beforeEnter 钩子 activated.map(m => m.beforeEnter), // 异步组件 resolveAsyncComponents(activated) ) this.pending = route 每一个队列执行的 iterator 函数 const iterator = (hook: NavigationGuard, next) => { // 确保期间还是当前路由 if (this.pending !== route) return hook(route, current, (to: any) => { if (to === false) { // next(false) -> abort navigation, ensure current URL this.ensureURL(true) } else if (typeof to === ‘string’ || typeof to === ‘object’) { // next(‘/‘) or next({ path: ‘/‘ }) -> redirect this.push(to) } else { // confirm transition and pass on the value next(to) } }) } // 执行队列 runQueue(queue, iterator, () => { const postEnterCbs = [] // 组件内的钩子 const enterGuards = extractEnterGuards(activated, postEnterCbs, () => { return this.current === route }) // 在上次的队列执行完成后再执行组件内的钩子 // 因为需要等异步组件以及是OK的情况下才能执行 runQueue(enterGuards, iterator, () => { // 确保期间还是当前路由 if (this.pending === route) { this.pending = null cb(route) this.router.app.$nextTick(() => { postEnterCbs.forEach(cb => cb()) }) } }) }) } // 更新当前 route 对象 updateRoute (route: Route) { const prev = this.current this.current = route // 注意 cb 的值 // 每次更新都会调用 下边需要用到! this.cb && this.cb(route) // 执行 after hooks 回调 this.router.afterHooks.forEach(hook => { hook && hook(route, prev) }) } } // … 可以看到整个过程就是执行约定的各种钩子以及处理异步组件问题,这里有一些具体函数具体细节被忽略掉了(后续会具体分析)但是不影响具体理解这个流程。但是需要注意一个概念:路由记录,每一个路由 route 对象都对应有一个 matched 属性,它对应的就是路由记录,他的具体含义在调用 match() 中有处理;通过之前的分析可以知道这个 match 是在 src/create-matcher.js 中的:// … import { createRoute } from ‘./util/route’ import { createRouteMap } from ‘./create-route-map’ // … export function createMatcher (routes: Array): Matcher { const { pathMap, nameMap } = createRouteMap(routes) // 关键的 match function match ( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location ): Route { const location = normalizeLocation(raw, currentRoute) const { name } = location // 命名路由处理 if (name) { // nameMap[name] = 路由记录 const record = nameMap[name] const paramNames = getParams(record.path) // … if (record) { location.path = fillParams(record.path, location.params, named route "${name}") // 创建 route return _createRoute(record, location, redirectedFrom) } } else if (location.path) { // 普通路由处理 location.params = {} for (const path in pathMap) { if (matchRoute(path, location.params, location.path)) { // 匹配成功 创建route // pathMap[path] = 路由记录 return _createRoute(pathMap[path], location, redirectedFrom) } } } // no match return _createRoute(null, location) } // … // 创建路由 function _createRoute ( record: ?RouteRecord, location: Location, redirectedFrom?: Location ): Route { // 重定向和别名逻辑 if (record && record.redirect) { return redirect(record, redirectedFrom || location) } if (record && record.matchAs) { return alias(record, location, record.matchAs) } // 创建路由对象 return createRoute(record, location, redirectedFrom) } return match } // … 路由记录在分析 match 匹配函数那里以及分析过了,这里还需要了解下创建路由对象的 createRoute,存在于 src/util/route.js 中:// … export function createRoute ( record: ?RouteRecord, location: Location, redirectedFrom?: Location ): Route { // 可以看到就是一个被冻结的普通对象 const route: Route = { name: location.name || (record && record.name), meta: (record && record.meta) || {}, path: location.path || ‘/‘, hash: location.hash || ‘’, query: location.query || {}, params: location.params || {}, fullPath: getFullPath(location), // 根据记录层级的得到所有匹配的 路由记录 matched: record ? formatMatch(record) : [] } if (redirectedFrom) { route.redirectedFrom = getFullPath(redirectedFrom) } return Object.freeze(route) } // … function formatMatch (record: ?RouteRecord): Array { const res = [] while (record) { res.unshift(record) record = record.parent } return res } // … 回到之前看的 init,最后调用了 history.listen 方法:history.listen(route => { this.app._route = route }) listen 方法很简单就是设置下当前历史对象的 cb 的值, 在之前分析 transitionTo 的时候已经知道在 history 更新完毕的时候调用下这个 cb。然后看这里设置的这个函数的作用就是更新下当前应用实例的 _route 的值,更新这个有什么用呢?请看下段落的分析。
defineReactive 定义 _route
继续回到 beforeCreate 钩子函数中,在最后通过 Vue 的工具方法给当前应用实例定义了一个响应式的 _route 属性,值就是获取的 this._router.history.current,也就是当前 history 实例的当前活动路由对象。给应用实例定义了这么一个响应式的属性值也就意味着如果该属性值发生了变化,就会触发更新机制,继而调用应用实例的 render 重新渲染。还记得上一段结尾留下的疑问,也就是 history 每次更新成功后都会去更新应用实例的 _route 的值,也就意味着一旦 history 发生改变就会触发更新机制调用应用实例的 render 方法进行重新渲染。
router-link 和 router-view 组件new Vue({ router, template: <div id="app"> <h1>Basic</h1> <ul> <li><router-link to="/">/</router-link></li> <li><router-link to="/foo">/foo</router-link></li> <li><router-link to="/bar">/bar</router-link></li> <router-link tag="li" to="/bar">/bar</router-link> </ul> <router-view class="view"></router-view> </div> }).$mount(‘#app’)
可以看到这个实例的 template 中包含了两个自定义组件:router-link 和 router-view。 router-view 组件
router-view 组件比较简单,所以这里就先来分析它,他是在源码的 src/components/view.js 中定义的:export default { name: ‘router-view’, functional: true, // 功能组件 纯粹渲染 props: { name: { type: String, default: ‘default’ // 默认default 默认命名视图的name } }, render (h, { props, children, parent, data }) { // 解决嵌套深度问题 data.routerView = true // route 对象 const route = parent.$route // 缓存 const cache = parent._routerViewCache || (parent._routerViewCache = {}) let depth = 0 let inactive = false // 当前组件的深度 while (parent) { if (parent.$vnode && parent.$vnode.data.routerView) { depth++ } 处理 keepalive 逻辑 if (parent._inactive) { inactive = true } parent = parent.$parent } data.routerViewDepth = depth // 得到相匹配的当前组件层级的 路由记录 const matched = route.matched[depth] if (!matched) { return h() } // 得到要渲染组件 const name = props.name const component = inactive ? cache[name] : (cache[name] = matched.components[name]) if (!inactive) { // 非 keepalive 模式下 每次都需要设置钩子 // 进而更新(赋值&销毁)匹配了的实例元素 const hooks = data.hook || (data.hook = {}) hooks.init = vnode => { matched.instances[name] = vnode.child } hooks.prepatch = (oldVnode, vnode) => { matched.instances[name] = vnode.child } hooks.destroy = vnode => { if (matched.instances[name] === vnode.child) { matched.instances[name] = undefined } } } // 调用 createElement 函数 渲染匹配的组件 return h(component, data, children) } } 可以看到逻辑还是比较简单的,拿到匹配的组件进行渲染就可以了。
router-link 组件
再来看看导航链接组件,他在源码的 src/components/link.js 中定义的:// … import { createRoute, isSameRoute, isIncludedRoute } from ‘../util/route’ // … export default { name: ‘router-link’, props: { // 传入的组件属性们 to: { // 目标路由的链接 type: toTypes, required: true }, // 创建的html标签 tag: { type: String, default: ‘a’ }, // 完整模式,如果为 true 那么也就意味着 // 绝对相等的路由才会增加 activeClass // 否则是包含关系 exact: Boolean, // 在当前(相对)路径附加路径 append: Boolean, // 如果为 true 则调用 router.replace() 做替换历史操作 replace: Boolean, // 链接激活时使用的 CSS 类名 activeClass: String }, render (h: Function) { // 得到 router 实例以及当前激活的 route 对象 const router = this.$router const current = this.$route const to = normalizeLocation(this.to, current, this.append) // 根据当前目标链接和当前激活的 route匹配结果 const resolved = router.match(to, current) const fullPath = resolved.redirectedFrom || resolved.fullPath const base = router.history.base // 创建的 href const href = createHref(base, fullPath, router.mode) const classes = {} // 激活class 优先当前组件上获取 要么就是 router 配置的 linkActiveClass // 默认 router-link-active const activeClass = this.activeClass || router.options.linkActiveClass || ‘router-link-active’ // 相比较目标 // 因为有命名路由 所有不一定有path const compareTarget = to.path ? createRoute(null, to) : resolved // 如果严格模式的话 就判断是否是相同路由(path query params hash) // 否则就走包含逻辑(path包含,query包含 hash为空或者相同) classes[activeClass] = this.exact ? isSameRoute(current, compareTarget) : isIncludedRoute(current, compareTarget) // 事件绑定 const on = { click: (e) => { // 忽略带有功能键的点击 if (e.metaKey || e.ctrlKey || e.shiftKey) return // 已阻止的返回 if (e.defaultPrevented) return // 右击 if (e.button !== 0) return // target="_blank" 忽略 const target = e.target.getAttribute(‘target’) if (/\b_blank\b/i.test(target)) return // 阻止默认行为 防止跳转 e.preventDefault() if (this.replace) { // replace 逻辑 router.replace(to) } else { // push 逻辑 router.push(to) } } } // 创建元素需要附加的数据们 const data: any = { class: classes } if (this.tag === ‘a’) { data.on = on data.attrs = { href } } else { // 找到第一个 给予这个元素事件绑定和href属性 const a = findAnchor(this.$slots.default) if (a) { // in case the is a static node a.isStatic = false const extend = _Vue.util.extend const aData = a.data = extend({}, a.data) aData.on = on const aAttrs = a.data.attrs = extend({}, a.data.attrs) aAttrs.href = href } else { // 没有 的话就给当前元素自身绑定时间 data.on = on } } // 创建元素 return h(this.tag, data, this.$slots.default) } } function findAnchor (children) { if (children) { let child for (let i = 0; i < children.length; i++) { child = children[i] if (child.tag === ‘a’) { return child } if (child.children && (child = findAnchor(child.children))) { return child } } } } function createHref (base, fullPath, mode) { var path = mode === ‘hash’ ? ‘/#’ + fullPath : fullPath return base ? cleanPath(base + path) : path } 可以看出 router-link 组件就是在其点击的时候根据设置的 to 的值去调用 router 的 push 或者 replace 来更新路由的,同时呢,会检查自身是否和当前路由匹配(严格匹配和包含匹配)来决定自身的 activeClass 是否添加。
6.$route 和 $router 的区别
1.router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。
举例:history对象 $router.push({path:’home’});本质是向history栈中添加一个路由,在我们看来是 切换路由,但本质是在添加一个history记录 方法:
$router.replace({path:’home’});//替换路由,没有历史记录
2.route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
我们可以从vue devtools中看到每个路由对象的不同
这两个不同的结构可以看出两者的区别,他们的一些属性是不同的。 $route.path
字符串,等于当前路由对象的路径,会被解析为绝对路径,如 “/home/news” 。 this.$router.push({path:/user/${userId}}) 这样传递参数的话,配置路由的时候需要在path上加参数path:user/:userId。 这种接收参数的方式是this.$route.params.userId。
$route.params
$route.query
对象,包含路由中查询参数的键值对。例如,对于 /home/news/detail/01?favorite=yes ,会得到$route.query.favorite == ‘yes’ 。
query传参是针对path的,params传参是针对name的。。接收参数的方式都差不多。。this.$route.query.和this.$route.params. 注意这只是跳转url,跳转到这个url显示什么组件,得配置路由。router跳转和标签跳转,规则差不多。 展示上的话:
注意:如果提供了path,params将会被忽略,但是query不属于这种情况。。。 如果使用完整路径和query传参,刷新页面时不会造成路由传参的参数丢失。 这个vue官方文档讲的很详细。
$route.router
$route.matched
数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
$route.name
当前路径的名字,如果没有使用具名路径,则名字为空。
$route.path, $route.params, $route.name, $route.query这几个属性很容易理解,看示例就能知道它们代表的含义
有时候配置路由时path有时候会加 ‘/‘ 有时候不加,例如path:’name’和path:’/name’。区别其实官方文档说了,我当时没仔细看,导致这个问题还困扰了我很久。
意思就是以 / 开头的会被当做路径,就不会一直嵌套之前的路径。
4.怎么定义 vue-router 的动态路由? 怎么获取传过来的值
在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id
怎么定义vue-router的动态路由以及如何获取传过来的动态参数?
1、直接修改地址栏中的路由地址。 2、声明式(标签跳转):通过router-link实现跳转注册 3、编程式(js跳转):通过js的编程方式this.$router.push(‘/myLogin’);
8.vue-router 传参
vue-router传递参数分为两大类
编程式的导航 router.push
字符串
字符串的方式是直接将路由地址以字符串的方式来跳转,这种方式很简单但是不能传递参数:this.$router.push(“home”);
对象
想要传递参数主要就是以对象的方式来写,分为两种方式:命名路由、查询参数,下面分别说明两种方式的用法和注意事项。
命名路由
命名路由的前提就是在注册路由的地方需要给路由命名如:
命名路由传递参数需要使用params来传递,这里一定要注意使用params不是query。目标 页面接收传递参数时使用params 特别注意:命名路由这种方式传递的参数,如果在目标页面刷新是会出错的 使用方法如下:this.$router.push({ name: ‘news’, params: { userId: 123 }}) 代码如下:
{{ msg }} click here to news page 接受传递的参数: this is the news page.the transform param is {{this.$route.params.userId}}
运行效果如下:
查询参数
查询参数其实就是在路由地址后面带上参数和传统的url参数一致的,传递参数使用query而且必须配合path来传递参数而不能用name,目标页面接收传递的参数使用query。 注意:和name配对的是params,和path配对的是query 使用方法如下:this.$router.push({ path: ‘/news’, query: { userId: 123 }}); 代码如下:
{{ msg }} click here to news page 接收参数如下: this is the news page.the transform param is {{this.$route.query.userId}}
运行效果如下:
声明式的导航
声明式的导航和编程式的一样,这里就不在过多介绍,给几个例子大家对照编程式理解,例子如下: 字符串click to news page 命名路由click to news page 运行效果如下:
查询参数click to news page 运行效果如下:
最后总结:路由传递参数和传统传递参数是一样的,命名路由类似表单提交而查询就是url传递,在vue项目中基本上掌握了这两种传递参数就能应付大部分应用了,最后总结为以下两点:
1.命名路由搭配params,刷新页面参数会丢失 2.查询参数搭配query,刷新页面数据不会丢失 3.接受参数使用this.$router后面就是搭配路由的名称就能获取到参数的值
tip: 用params传参,F5强制刷新参数会被清空,用query,由于参数适用路径传参的所以F5强制刷新也不会被清空。(传参强烈建议适用string) 也可以选用sessionstorage/localstorage/cookie存储,可以参考我的另一边文章: sessionstorage、localstorage与cookie params:参数不会显示到路径上
1:配置路径rutesexport default new Router({ routes: [ { path: ‘/testVueRouter’, name: ‘TestVueRouter’, component: TestVueRouter }, { path: ‘/testVueRouterTo’, // 一定要写name,params必须用name来识别路径 name: ‘TestVueRouterTo’, component: TestVueRouterTo } ] }) 2:传递参数:用$router 3:接受参数:用$route,少个r,注意啦
query:最好也用name来识别,保持与params一致性,好记了,路径传参
1:路径配置(跟params一样,代码不写了) 2:传递参数页 3:接受参数
图片区:
下面我们采用path: ‘/testVueRouterTo’
1:query(成功)
2:params:(不成功)
这是由于query与params传参机制不一样,造成的差异,如果要隐藏参数用params,如果强制刷新不被清除用query
第一种:使用router的name属性也就是params来传递参数 这个方法有一个bug就是当你传参过去的时候,再次刷新页面时参数就会丢失。解决方法下边会说到。
step:1,首先需要在router/index.js里边配置每个页面的路径,name属性,看例子:import Vue from ‘vue’ import Router from ‘vue-router’ const import = require(‘./_import ‘ + process.env.NODE_ENV) Vue.use(Router) export const constantRouterMap = [{ path: ‘/login/:userId/:id’, name:’Message’, //就是要在路由配置里边配置这个属性,用来知道你要跳转到那个页面的名字 /** 如果想做到页面刷新,参数不丢失,就必须在path后面加上这个参数 但是这样做的话就会导致参数显示在url的后面,(在这一点上)跟query没什么区别了。 多个参数也可以一直往后边追加 */ component: _import(‘login/index’), hidden: true }, { path: ‘’, component: Layout, redirect: ‘dashboard’, icon: ‘dashboard’, hidden: true, noDropDown: true, children: [{ path: ‘dashboard’, name: ‘首页’, component: _import(‘main/index’), meta: { title: ‘dashboard’, icon: ‘dashboard’, noCache: true } }] } ] export default new Router({ routes: constantRouterMap }) step:2,在传值页面的写法://用这种方法传参,必须这么些,不能写path,否则你在取参数的时候this.$router.params.userId就是undefined.这是因为,params只能用name来引入路由, this.$router.push({ name:”‘Message’”,//这个name就是你刚刚配置在router里边的name params:{ userId:”10011” } }) step:3,在取值页面的写法:切记,再取参数的时候一定是this.route不是this.router切记。
this.$route.params.userId
第二种:使用query来传递参数
step:1,在传值页面的写法:this.$router.push({ path:”/login”,//这个path就是你在router/index.js里边配置的路径 query:{ userId:”10011” } }) step:2,在取值页面的写法:第一种: this.$router.currentRoute.query.userId 第二种: 这种方法再取参数的时候一定是this.$route不是this.$router,切记。 this.$route.query.userId
第三种:使用vue里的标签来传递参数
step:1,在传值页面的写法: step:2,在取值页面的写法:同第二种。
其实,router-link也可以使用name的方法传参同样,这种方法也需要在router/index.js里边配置每个页面的路径,name属性 name:’Message’, //就是要在路由配置里边配置这个属性,用来知道你要跳转到那个页面的名字 Hi页面1 取参方法:this.$route.params.userId
9.vue-router 的两种模式【https://blog.csdn.net/caoxinhui521/article/details/77688512?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-10.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-10.no_search_link 】
vue-router 有 3 种路由模式:hash、history、abstract,对应的源码如下所示:switch (mode) { case ‘history’: this.history = new HTML5History(this, options.base) break case ‘hash’: this.history = new HashHistory(this, options.base, this.fallback) break case ‘abstract’: this.history = new AbstractHistory(this, options.base) break default: if (process.env.NODE_ENV !== ‘production’) { assert(false, invalid mode: ${mode}) } } 其中,3 种路由模式的说明如下:
hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器; history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式; abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
Vue Router 是Vue官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。vue-router 默认 hash 模式,还有一种是history模式。 hash模式
hash模式的工作原理是hashchange事件,可以在window监听hash的变化。我们在url后面随便添加一个#xx触发这个事件。 window.onhashchange = function(event){ console.log(event); } 打印出来的结果如下:
可以看到里边有两个属性newURL和oldURL。可以通过模拟改变hsh的值,动态页面数据。
尽管浏览器没有请求服务器,但是页面状态和url已经关联起来了,这就是所谓的前端路由,单页应用的标配。
history模式
把window.history对象打印出来可以看到里边提供的方法和记录长度
前进,后退,跳转操作方法: history.go(-3);//后退3次 history.go(2);//前进2次 history.go(0);//刷新当前页面 history.back(); //后退 history.forward(); //前进
HTML5新增的API
A)history.pushState(data, title [, url]):往历史记录堆栈顶部添加一条记录; data会在onpopstate事件触发时作为
参数传递过去;title为页面标题,当前所有浏览器都会 忽略此参数;url为页面地址,可选,缺省为当前页地址;
B)history.replaceState(data, title [, url]) :更改当前的历史记录,参数同上; C)history.state:用于存储以上方法的data数据,不同浏览器的读写权限不一样; D)window.onpopstate:响应pushState或replaceState的调用;有了这几个新的API,针对支持的浏览器,
我们可以构建用户体验更好的应用了。就像刚提到的Facebook相册,虽然是AJAX的方式,但用户可以直接复 制页面地址分享给好友, 如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。const router = new VueRouter({ mode: ‘history’, routes: […] }) 当你使用 history 模式时,URL 就像正常的 url,例如 http://www.yongcun.wang/tclass,也好看! 不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问http://www.yongcun.wang/tclass就会返回 404,这就不好看了。 所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。 给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。const router = new VueRouter({ mode: ‘history’, routes: [ { path: ‘*’, component: NotFoundComponent } ] })
7.vue-router 响应路由参数的变化
在vue项目中,假使我们在同一个路由下,只是改变路由后面的参数值,如果不监听路由参数值的变化,页面无数据刷新,需手动刷新浏览器,这样做就不是我们的预期效果。
举例:当前路由为 /pjthome?pjtid=123456mounted: function () { this.pjtid = this.$route.query.pjtid this.pjtdetail() },
在页面pjtdetail()方法中,需要用到pjtid这个参数,假如在同一页面有相似项目切换,只是pjtid发生变化,在切换时,并未重新加载数据,原因是跟vue的生命周期有关,具体该解决这个问题,添加路由监听即可。
exp:watch: { $route(){ this.pjtid = this.$route.query.pjtid }, pjtid() { this.pjtdetail() }, } 解决。
10.vue-router 实现路由懒加载( 动态加载路由 )
1 . vue异步组件技术 ==== 异步加载
vue-router配置路由 , 使用vue的异步组件技术 , 可以实现按需加载 . 但是,这种情况下一个组件生成一个js文件/ vue异步组件技术 / { path: ‘/home’, name: ‘home’, component: resolve => require([‘@/components/home’],resolve) }, { path: ‘/index’, name: ‘Index’, component: resolve => require([‘@/components/index’],resolve) }, { path: ‘/about’, name: ‘about’, component: resolve => require([‘@/components/about’],resolve) }
非懒加载:
懒加载
2.组件懒加载方案二 路由懒加载(使用import)
const 组件名=() => import(‘组件路径’); // 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。 /* const Home = () => import(‘@/components/home’) const Index = () => import(‘@/components/index’) const About = () => import(‘@/components/about’) */ // 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。 把组件按组分块
const Home = () => import(/ webpackChunkName: ‘ImportFuncDemo’ / ‘@/components/home’) const Index = () => import(/ webpackChunkName: ‘ImportFuncDemo’ / ‘@/components/index’) const About = () => import(/ webpackChunkName: ‘ImportFuncDemo’ / ‘@/components/about’)
{ path: ‘/about’, component: About }, { path: ‘/index’, component: Index }, { path: ‘/home’, component: Home }
3.webpack提供的require.ensure()
vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。 这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。 / 组件懒加载方案三: webpack提供的require.ensure() / { path: ‘/home’, name: ‘home’, component: r => require.ensure([], () => r(require(‘@/components/home’)), ‘demo’) }, { path: ‘/index’, name: ‘Index’, component: r => require.ensure([], () => r(require(‘@/components/index’)), ‘demo’) }, { path: ‘/about’, name: ‘about’, component: r => require.ensure([], () => r(require(‘@/components/about’)), ‘demo-01’) } // r就是resolve const list = r => require.ensure([], () => r(require(‘../components/list/list’)), ‘list’); // 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载const router = new Router({ routes: [ { path: ‘/list/blog’, component: list, name: ‘blog’ } ] })
3.active-class 是哪个组件的属性?
vue-router模块的router-link 组件。
嵌套路由怎么定义?
嵌套路由顾名思义就是路由的多层嵌套。一级路由里面使用children数组配置子路由,就是嵌套路由。
vue 常见面试题
35 引进组件的步骤
第一步
第二步
第三步
第四步
在vue的项目开发过程中,基本都是基于组件化开发项目,总结下使用组件的几个点: 一、@符号的使用
在vue项目中 @ 符号代表的是根目录,即 src 目录。
二、组件的放置位置
在项目中,公用的组件放置在 components 目录下,项目组件新建 views 目录来存放:
三、组件的引和使用方法 1、第一种引入和使用方法:
import navs from ‘@/views/nav/index’ 使用组件:
components:{ ‘v-nav’:navs}
模板中使用组件:
2、第二种引入和使用方法
import navs from ‘@/views/nav/index’ import indexList from ‘./index-list’ 使用组件:
components: { navs,indexList },
模板中使用:
3、第三种使用方法
组件目录:
引入方式:
import BackToTop from ‘@/components/BackToTop’
使用组件:
components: { BackToTop },
使用组件: 其实没什么,只是要注意下组件大小写命名的写法。
Vue中组件怎么传值
正向:父传子 父组件把要传递的数据绑定在属性上,发送,子组件通过props接收 逆向:子传父 子组件通过this.$emit (自定义事件名,要发送的数据),父组件设置一个监听事件来接收,然后拿到数据 兄弟:eventbus 中央事件总线 通过Vuex
49.请说下封装 vue 组件的过程?
vue组件的定义
组件(Component)是Vue.js最强大的功能之一 组件可以扩展HTML元素,封装可重用代码 在较高层面上,组件是自定义元素,Vue.js的编译器为他添加特殊功能 有些情况下,组件也可以表现用 is 特性进行了扩展的原生的HTML元素 所有的Vue组件同时也都是Vue实例,所以可以接受相同的选项对象(除了一些根级特有的选项),并提供相同的生命周期钩子
vue组件的功能
1)能够把页面抽象成多个相对独立的模块 2)实现代码重用,提高开发效率和代码质量,使得代码易于维护
Vue组件封装过程
首先,使用Vue.extend()创建一个组件 然后,使用Vue.component()方法注册组件 接着,如果子组件需要数据,可以在props中接受定义 最后,子组件修改好数据之后,想把数据传递给父组件,可以使用emit()方法
组件使用流程详细介绍
1、组件创建—-有3中方法,extend()
2、注册组件——有2中方法,全局注册,局部注册
A1、全局注册:一次注册( 调用Vue.component( 组件名称,为组件创建时定义的变量 ) ),可在多个Vue实例中使用。
我们先用全局注册,注册上面例子中创建的myCom组件Vue.component(‘my-com’,myCom)
A2、全局注册语法糖:不需要创建直接注册的写法Vue.component(‘my-com’,{ ‘template’:’这是我的组件
‘ })
‘my-com’为给组件自定义的名字,在使用时会用到,后面myCom对应的就是上面构建的组件变量。
A3、如果是用template及script标签构建的组件,第二个参数就改为它们标签上的id值Vue.component(‘my-com’,{ template: ‘#myCom’ }) B1、局部注册:只能在注册该组件的实例中使用,一处注册,一处使用var app = new Vue({ el: ‘#app’, components: { ‘my-com’: myCom } }) B2、局部注册语法糖:var app = new Vue({ el: ‘#app’, components: { ‘my-com’: { template: ‘这是我的组件
‘ } } }) B3、及
显示效果:
可以看到,全局注册的组件在实例app1和实例app2中都可以被调用。
B、局部注册:将创建的组件注册到实例app1下<!DOCTYPE html>
可以看到只渲染了app1实例下的组件,app2实例虽然调用了该组件,但是因为这个组件没有在其内部注册,也没有全局注册,所以报错说找不到该组件。
C、template 和script标签创建组件<!DOCTYPE html>
这是template标签构建的组件
5、异步组件 6、Vue中的props数据流
通过在注册组件中申明需要使用的props,然后通过props中与模板中传入的对应的属性名,去取用这些值<!DOCTYPE html>
注意:
A、props取值的方式 B、在template选项属性中,可以写驼峰命名法,也可以写短横线命名法
在HTML(模板)中,只能写短横线命名法 原因:vue组件的模板可以放在两个地方 a、Vue组件的template选项属性中,作为模板字符串 b、放在.html中[ 用script template标签创建的组件 ],作为HTML 问题在于HTML不区分大小写,所以在vue注册组件中通用的驼峰命名法,不适用于HTML中的Vue模板,在HTML中写入props属性,必须写短横线命名法(把原来props属性中的每个prop大写换成小写,并且在前面加“-”) 将6中的
改成
显示效果,第二个没有显示
异步组件的实现原理;异步组件的3种实现方式—-工厂函数、Promise、高级函数 异步组件实现的本质是2次渲染,先渲染成注释节点,当组件加载成功后,在通过forceRender重新渲染 高级异步组件可以通过简单的配置实现loading resolve reject timeout 4种状态
53 vue 封装通用组件
一.数据从父组件传入
为了解耦,子组件本身就不能生成数据。即使生成了,也只能在组件内部运作,不能传递出去,下面是在一些较复杂的场景中,对props传递的参数加一些验证,也是方便如果是数据类型不符合可以直接抛出异常。props: { tableData: { type: Array, required: true // 必传 }, titleName: String, needNum: [String, Number], // 两个类型都可以传 isEdit: { type: Boolean, default: false // 默认false } } props传入参数,不建议对它进行操作,如果要操作,请先在子组件深拷贝。如果你是用JSON.stringify, JSON.parse方法深拷贝需注意:JSON.parse(JSON.stringify(obj))我们一般用来深拷贝,其过程说白了 就是利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象, 如果使用JSON.parse(JSON.stringify(obj))拷贝应注意以下几点: 1、如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象; 2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象; 3、如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失; 4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null 5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor 6、如果对象中存在循环引用的情况也无法正确实现深拷贝;
二.在父组件处理事件
比如某些子组件的click事件,避免高耦合,逻辑最好放在父组件中,子组件只是一个承载体。// 子组件 proflistchangeSort( sortObj ) { this.$emit(‘sortChange’, sortObj ) } // 父组件 这样既降低耦合,保证子组件中数据和逻辑不会混乱。
三.slot的应用
现在有一个需求,在同一个子组件中,我在不同的场景需要用到不同的按钮,那么在封装组件的时候就不用去写按钮,只用在合适的未知留一个slot,把按钮的位置留出来,然后再父组件中写入:// 子组件
// 父组件 按钮1 这样一个具名插槽灵活地解决了不同场景同一组件不同配置的问题。
四.统一管理各个组件公共样式
这里以less为例:
var.less里面可以装所有的公共样式:
然后各个子页面引进来就可以用,可以方便的做到样式统一和日后样式全局维护。涉及的变量,继承,嵌套,混合,可以在我的第一篇简书(整理之后的less知识点梳理)查看。
这里提供两种vue封装共用组件的方法 方法一:
main.js中import ListItem from ‘./components/list.vue’//封装共用组件方法一 Vue.component(‘ListItem’,ListItem)
方法二:
新建vue文件的时候再建个相应的js文件。 component.jsimport child from ‘./component.vue’ export default child.install = function (Vue) { Vue.component(child.name ,child) }
main.js中import child from ‘./components/component/component.js’//封装共用组件方法二Vue.use(child)
通过上面的两种方法定义公共组件后都可以直接和这样调用组件了,无需在每个vue文件中important组件了。 说说方法二吧根据官方文档提供的api
vue使用install定义安装全局组件需要install和use这两个api来配合才能完成。我更喜欢第一种但是今天看了公司的代码认识到了第二种方法,也发现了vue提供了不少提高生产效率的api往后会继续深入去学习这些api。 同时也解决了一个困惑很长时间的问题,element ui中的message这个组件不需要vue.use安装直接就能用,因为element源码中直接将这个组件赋值到vue的prototype上了,其他的组件都只export 暴露出install方法所以其他组件需要vue.use安装
50.vue 各种组件通信方法(父子 子父 兄弟 爷孙 毫无关系的组件)【https://www.cnblogs.com/dream111/p/13520206.html 】
(1)props / $emit 适用 父子组件通信
这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。
(2)ref 与 $parent / $children 适用 父子组件通信
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 $parent / $children:访问父 / 子实例
(3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
(4)$attrs/$listeners 适用于 隔代组件通信
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind=”$attrs” 传入内部组件。通常配合 inheritAttrs 选项一起使用。 $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件
(5)provide / inject 适用于 隔代组件通信
祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
(6)Vuex 适用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。
1、props(父组件传给子组件)
新建父组件father.vue
注意标记的地方,同时不要忘了引入子组件跟注册子组件哟 新建子组件child.vue
在子组件中使用props接收,就可以拿来使用了。 传递多个,在props添加就行了,使用数组的形式,比如[‘msg’,‘msgd’]这样的形式,当然也要在父组件加多一个:msgd=“msgd”;命名不一定重复,对应就行,个人建议一样好一点。也可以使用对象的形式接收。props: { logo: String, own: String, name: String, state: String, remark: String, },
2、$emit(子组件传给父组件)
依旧在子组件中,跟上面的代码写在一起了,注意区分
$emit():第一个参数是触发的方法,第二个参数是要传给父组件的值 父组件以@+$emit里面的方法receive(这里必须子组件名字一样)的形式接收
childmsg就是子组件传过来的值,注意上面handleReceive($event)中的$event,若是写成其他诸如handleReceive(message)之类的传参会报错。
3、EventBus ($emit / $on)(父子,兄弟,隔代组件)
我这里还是拿father跟child两个组件做示例,以下示例对兄弟组件、隔代组件也能成立。 首先先建立一个中间站bus.js(名字随便取);代码如下import Vue from ‘vue’ export default new Vue() 接着在父组件引入bus.js
在father使用$emit触发传值,在child通过$on接收值(反过来也一样,看你是谁传给谁),接下来是child组件
定义自己组件内的全局变量来接收从father组件传过来的值,效果图如下
4、ref 与 $parent / $children(父子组件)
(1)ref属性本来是vue中用来操作元素的,类似document.getElemnetById,他在这里还有一种用法,就是可以用来传值,看father.vue、child.vue代码
看父组件mounted打印出来的东西this.$refs.child
我们可以看到是一个对象,红色框圈出的就是子组件data里面的属性。 我们在father.vue里就可以通过this.$refs.child.msg this.$refs.child.childmsg 取到子组件的值。
(2)在child.vue打印this.$parent
可以看到也是一个对象,我们一样能通过this.$parent.fathermsg 取到值 在父组件中打印出this.$children,注意这是一个数组对象了
取值 this.$children[0].childmsg this.$children[0].msg $parent / $children使用条件比较苛刻,如果是在好几个父组件或者好几个子组件中使用,可能取不到对应的值。
5、$attrs/$listeners(隔代)
(1)$attrs
father.vue
child.vue
在子组件中只接收了父组件传过来的bar值 this.$attrs可以打印出 父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。就是说只要在子组件中没有给prop特性绑定的都能够通过$attrs获取
(2)$listeners
vue文档是这样说的:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件——在创建更高层次的组件时非常有用。 大致意思就是说使用v-on绑定一个事件时,不加.native修饰符的话,父组件可以通过子组件绑定v-on=”$listeners”,然后将孙组件的值传给父组件。(如果理解有误希望大佬可以纠正) 示例:通过child.vue绑定v-on=”$listeners”,然后让grandson.vue传值给father.vue。father.vue监听grandson触发的事件 child.vue 绑定v-on=”$listeners”
grandson.vue触发,注意这里click事件未加修饰符.native(即非@click.native=””)
father.vue监听
现在可以看到把值传过来了,控制台也能打印出来。 **–如果在grandson click事件中添加修饰符.native变成 @click.native=””handleListener,则无法传值。
6、vuex
父子组件、兄弟组件之间组件传值是怎么传的?【https://blog.csdn.net/wangjia55/article/details/89246530 】
1、父组件与子组件传值
父组件传给子组件:子组件通过props方法接受数据; 子组件传给父组件:$emit 方法传递参数
2、非父子组件间的数据传递,兄弟组件传值eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。$emit 传值,$on() 接收值。项目比较小时,用这个比较合适。 3、整个状态存储:vuex
2.vue 父组件向子组件传递数据?
父子组件传参:
1.父传子:通过props属性实现;子组件要做类型检测; 2.子传父:
子组件this.$emit触发父组件监听的方法;$emit第二个参数为:向父组件传递的数据 父组件监听子组件触发的事件,然后调用绑定的方法;
非父子组件传参:
路由传值:和编程式导航中,均可在query/params中传值,在子组件中:this.$route.query 通过$parent $children方法调用层级关系的组件内部的数据和方法:this.$parent.$data.id 获取父元素data中的id,但是容易造成代码耦合性太强,难以维护 eventBus:在全局定义一个eventBus
window.eventBus = new Vue( )或者Vue.eventBus = new Vue( ) 在需要传递参数的组件中定义一个emit发送需要传递的值:eventBus.$emit(‘name’,id) 在需要接受参数的组件中,用on接受该值:eventBus.$on(‘name’,(val) => {…}) 注意:使用完后要在beforeDestroy( )中关闭这个eventBus eventBus.$off(‘name’)
本地存储:localStorage或者sessionStorage,setItem存储value,getItem获取value 状态管理 Vuex
3.子组件向父组件传递事件
1、Vue父组件向子组件传递事件/调用事件 调用子组件中的方法
2.子组件调用父组件事件
调用父组件的方法
子组件通过this.$emit()派发事件,父组件利用v-on对事件进行监听,实现参数的传递
3.两平等组件间的调用@{ ViewBag.Title = “Index”; }
new一个调度器来Event来完成,在mounted中监听事件,另一个组件中调用Event.$emit来调用此事件完成调度。
keep-alive 组件有什么作用?
keep-alive 是 vue 的内置组件,而这个组件的作用就是能够缓存不活动的组件。一般情况下,组件进行切换的时候,默认是会进行销毁的,如果我们有需求,在某个组件切换后不进行销毁,而是保存之前的状态,那么就可以利用 keep-alive 来实现。 在 keep-alive 上有两个属性,可以对字符串或正则表达式进行匹配,匹配到的组件会被缓存。 include 值为字符串或者正则表达式匹配的组件 name 会被缓存。(缓存匹配到的组件) exclude 值为字符串或正则表达式匹配的组件 name 不会被缓存。(排除匹配到的组件) 其拥有两个独立的生命周期钩子函数 actived 和 deactived,使用 keep-alive 包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactived 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
vue 组件中 data 为什么必须是一个函数
如果 data 是一个对象,当复用组件时,因为 data 都会指向同一个引用类型地址,其中一个组件的 data 一旦发生修改,则其他重用的组件中的 data 也会被一并修改。 如果 data 是一个返回对象的函数,因为每次重用组件时返回的都是一个新对象,引用地址不同,便不会出现如上问题。
Vue注册一个全局组件
Vue.component (“组件名称” {对象 template 组件的内容
})
Vue页面之间的数据传递
在应用复杂时,推荐使用vue官网推荐的vuex,以下讨论简单SPA中的组件间传值。 一、路由传值
路由对象如下图所示:
在跳转页面的时候,在js代码中的操作如下,在标签中使用标签this.$router.push({ name: ‘routePage’, query/params: { routeParams: params } }) 需要注意的是,实用params去传值的时候,在页面刷新时,参数会消失,用query则不会有这个问题。 这样使用起来很方便,但url会变得很长,而且如果不是使用路由跳转的界面无法使用。
二、通过$parent,$chlidren等方法调取用层级关系的组件内的数据和方法
通过下面的方法调用:this.$parent.$data.id //获取父元素data中的id this.$children.$data.id //获取父元素data中的id 这样用起来比较灵活,但是容易造成代码耦合性太强,导致维护困难
三、通过eventBus传递数据
使用前可以在全局定义一个eventBuswindow.eventBus = new Vue(); 在需要传递参数的组件中,定义一个emit发送需要传递的值,键名可以自己定义(可以为对象)eventBus.$emit(‘eventBusName’, id); 在需要接受参数的组件重,用on接受该值(或对象)//val即为传递过来的值 eventBus.$on(‘eventBusName’, function(val) { console.log(val) }) 最后记住要在beforeDestroy()中关闭这个eventBuseventBus.$off(‘eventBusName’);
同级传参的两种方式 1.query穿参,或者params传参
使用 this.$router.push({path: ‘/‘, query: {参数名: ‘参数值’}) this.$router.push({name: ‘/‘, params: {参数名: ‘参数值’}) 注意1: 使用params时不能使用path 注意2:实用params去传值的时候,在页面刷新时,参数会消失,用query则不会有这个问题。
栗子:由A向B 跳转 在A列表跳转页//点击事件 goToSDetails:function (id) { this.$router.push({ path:’./release’, query:{ nameId:this.list[id].nameCn}, }) }, B详情页created:function(){ this.getParams(); }, watch: { // 监测路由变化,只要变化了就调用获取路由参数方法将数据存储本组件即可 ‘$route’: ‘getParams’ }, methods:{ getParams:function(){ // 取到路由带过来的参数 var routerParams = this.$route.query.nameId // 将数据放在当前组件的数据内 console.log(“传来的参数==”+routerParams) this.textareText = routerParams }, } 二,下面也可以,不过还没试,先记录下来:
三、通过设置 Session Storage缓存的形式进行传递(这是摘抄别人的,自己记录一下)
①两个组件A和B,在A组件中设置缓存orderData const orderData = { ‘orderId’ : 123 , ‘price’ : 88 } sessionStorage . setItem (‘缓存名称’ , JSON . stringify ( orderData )) ②B组件就可以获取在A中设置的缓存了 const dataB = JSON . parse ( sessionStorage . getItem (‘缓存名称’ )) 此时 dataB 就是数据 orderData 朋友们可以百度下 Session Storage(程序退出销毁) 和 Local Storage(长期保存) 的区别。
1、使用query传值—地址栏可见
比如从a.vue跳转至b.vue,传name=‘jack’,代码如下: this.$router.push({ path: “/result”, query: { name: ‘jack’ } });
在b.vue通过地址栏的url 进行接收参数; 我是在created这个函数里面进行接收的,var name = this.$route.query.name;就可以接收到name这个参数了;
2、使用params传值—地址栏不可见
这个方式要注意一点,在请求的时候需要写上在router下面index.js里面的name, 可以和query一同传值; this.$router.push({ path: “/result”, name: “Result”, query: { name: ‘name’ }, params: { usersitelist: ‘userlist’ } }); params传值在地址栏不可见,但是取值方式是和query一样的,我也是在created函数里面进行取值的; var usersitelist = this.$route.params.usersitelist; 这样就可以取到了。
21 组件间的通信
前言
组件是 vue.js 最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。 针对不同的使用场景,如何选择行之有效的通信方式?这是我们所要探讨的主题。本文总结了 vue 组件间通信的几种方式,如 props、$emit/$on、vuex、$parent / $children、$attrs/$listeners和 provide/inject,以通俗易懂的实例讲述这其中的差别及使用场景,希望对小伙伴有些许帮助。 本文的代码请猛戳github 博客 ,纸上得来终觉浅,大家动手多敲敲代码!
方法一、props/$emit
父组件 A 通过 props 的方式向子组件 B 传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
父组件向子组件传值
接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件 Users.vue 中如何获取父组件 App.vue 中的数据 users:[“Henry”,”Bucky”,”Emily”]//App.vue父组件 //前者自定义名称便于子组件调用,后者要传递数据名
$on 监听了自定义事件 data-a 和 data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。
方法三、vuex
简要介绍 Vuex 原理
Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。
简要介绍各模块在流程中的功能:
Vue Components:Vue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。 dispatch:操作行为触发方法,是唯一能执行 action 的方法。 actions:操作行为处理模块,由组件中的$store.dispatch(‘action 名称’, data1)来触发。然后由 commit()来触发 mutation 的调用 , 间接更新 state 。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。 commit:状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。 mutations:状态改变操作方法,由 actions 中的commit(‘mutation 名称’)来触发 。是 Vuex 修改 state 的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。 state:页面状态管理容器对象。集中存储 Vue components 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。 getters:state 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。
Vuex 与 localStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面,刷新之后,如果 localStorage 里有保存的数据,取出来再替换 store 里的 state。 let defaultCity = “上海” try { // 用户关闭了本地存储功能,此时在外层加个try…catch if (!defaultCity){ defaultCity = JSON.parse(window.localStorage.getItem(‘defaultCity’)) } }catch(e){} export default new Vuex.Store({ state: { city: defaultCity }, mutations: { changeCity(state, city) { state.city = city try { window.localStorage.setItem(‘defaultCity’, JSON.stringify(state.city)); // 数据改变的时候把数据拷贝一份保存到localStorage里面 } catch (e) {} } } }) 这里需要注意的是:由于 vuex 里,我们保存的状态,都是数组,而 localStorage 只支持字符串,所以需要用 JSON 转换:JSON.stringify(state.subscribeList); // array -> string JSON.parse(window.localStorage.getItem(“subscribeList”)); // string -> array
方法四、$attrs/$listeners
简介
多级组件嵌套需要传递数据时,通常使用的方法是通过 vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此 Vue2.4 版本提供了另一种方法——$attrs/$listeners
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。通常配合 interitAttrs 选项一起使用。 $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件
接下来我们看个跨级通信的例子:// index.vue
浪里行舟 // childCom1.vue foo: {{ foo }}
childCom1的$attrs: {{ $attrs }}
// childCom2.vue boo: {{ boo }}
childCom2: {{ $attrs }}
// childCom3.vue
如上图所示$attrs表示没有继承数据的对象,格式为{属性名:属性值}。Vue2.4 提供了$attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。 简单来说:$attrs与$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。
方法五、provide/inject
简介
Vue2.2.0 新增 API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效 。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系 。
举个例子
假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件// A.vue export default { provide: { name: ‘浪里行舟’ } } // B.vue export default { inject: [‘name’], mounted () { console.log(this.name ); // 浪里行舟 } } 可以看到,在 A.vue 里,我们设置了一个 provide: name ,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。 需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的 ——vue 官方文档 所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。
provide 与 inject 怎么实现数据响应式
一般来说,有两种办法:
provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如 props,methods 使用 2.6 最新 API Vue.observable 优化响应式 provide(推荐)
我们来看个例子:孙组件 D、E 和 F 获取 A 组件传递过来的 color 值,并能实现数据响应式变化,即 A 组件的 color 变化后,组件 D、E、F 不会跟着变(核心代码如下:)// A 组件
A 组件 改变color …… data() { return { color: “blue” }; }, // provide() { // return { //theme: { //color: this.color //这种方式绑定的数据并不是可响应的 // } // 即A组件的color变化后,组件D、E、F不会跟着变 // }; // }, provide() { return { theme: this//方法一:提供祖先组件的实例 }; }, methods: { changeColor(color) { if (color) { this.color = color; } else { this.color = this.color === “blue” ? “red” : “blue”; } } } // 方法二:使用2.6最新API Vue.observable 优化响应式 provide // provide() { // this.theme = Vue.observable({ // color: “blue” // }); // return { // theme: this.theme // }; // }, // methods: { // changeColor(color) { // if (color) { // this.theme.color = color; // } else { // this.theme.color = this.theme.color === “blue” ? “red” : “blue”; // } // } // } // F 组件
F 组件
虽说 provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!
方法六、$parent / $children与 ref
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例 $parent / $children:访问父 / 子实例 需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:// component-a 子组件 export default { data () { return { title: ‘Vue.js’ } }, methods: { sayHello () { window.alert(‘Hello’); } } } // 父组件 不过,这两种方法的弊端是,无法在跨级或兄弟间通信 。// parent.vue 我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案。
总结 常见使用场景可以分为三类:
父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
兄弟通信:
跨级通信:
Bus;Vuex;provide / inject API、$attrs/$listeners
给大家推荐一个好用的 BUG 监控工具 Fundebug,欢迎免费试用!
实现表单输入和应用状态之间的双向绑定。
$emit和$listeners通信的异同
相同点:均可实现子组件向父组件传递消息 差异点:
$emit更加符合单向数据流,子组件仅发出通知,由父组件监听做出改变;而$listeners则是在子组件中直接使用了父组件的方法。 调试工具可以监听到子组件$emit的事件,但无法监听到$listeners中的方法调用。(想想为什么) 由于$listeners中可以获得传递过来的方法,因此调用方法可以得到其返回值。但$emit仅仅是向父组件发出通知,无法知晓父组件处理的结果
对于上述中的第三点,可以在$emit中传递回调函数来解决
5.如何让 CSS 只在当前组件中起作用
6. 的作用是什么?
</keep-alive包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。
Vue数据绑定的几种方式
1 .单向绑定 双大括号 {{}} html 内字符串绑定 2 .v-bind 绑定 html 属性绑定 3 .双向绑定 v-model 4 .一次性绑定 v-once 依赖于 v-model
17.v-on 可以监听多个方法吗【https://blog.csdn.net/qq_42238554/article/details/86592295?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-4.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-4.no_search_link 】
Vue双向绑定的原理
Vue中双向数据绑定是如何实现的? 1 .vue.js则是采用数据劫持结合发布者-订阅者模式的方式。 2 .通过 Object.defineProperty()来劫持各个属性的 setter , getter . 3 .在数据变动时发布消息给订阅者,触发相应的监听回调。我们先来看Object.defineProperty。这个方法:var obj = {}; Object.defineProperty(obj, ‘name’, { get: function() { console.log(‘我被获取了 ‘) return val; }, set: function (newVal) { console.log(,我被设置了 ,) } ) obj.name = “fei’; //在给obj设置name属性的时候,触发了 set这个方法 var val = obj.name ; //得到obj的name属性,会触发get方法 Vue双向绑定就是:数据变化更新视图,视图变化更新数据 Vue数据双向绑定是通过数据劫持和观察者模式来实现的, 数据劫持,object.defineproperty它的目的是:当给属性赋值的时候, 程序可以感知到,就可以控制改变属性值 观察者模式,当属性发生改变的时候,使用该数据的地方也发生改变
Vue双数据绑定过程中,这边数据改变怎么通知另一边改变
数据劫持和观察者模式 Vue数据双向绑定是通过数据劫持和观察者模式来实现的, 数据劫持,object.defineproperty它的目的是:当给属性赋值的时候, 程序可以感知到,就可以控制属性值的有效范围,可以改变其他属性的值 观察者模式它的目的是当属性发生改变的时候,使用该数据的地方也发生改变
23.Vue 中双向数据绑定是如何实现的
1、Vue实现数据双向绑定的原理:Object.defineProperty() 2、vue实现数据双向绑定主要步骤:
(1)需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter。 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。 (2)compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。 (3)Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
(4)MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
3、js实现简单的双向绑定
跨组件双向数据绑定
7.如何获取 dom
DOM 是一个树形结构,操作一个DOM节点,实际上就是这几个操作:更新、删除、添加、遍历 在操作DOM节点之前,需要通过各种方式先拿到这个DOM节点,常用的方法有: 一、通过元素类型的方法来操作:
document.getElementById();//id名,在实际开发中较少使用,选择器中多用class id一般只用在顶级层存在 不能太过依赖id document.getElementsByTagName();//标签名 document.getElementsByClassName();//类名 document.getElementsByName();//name属性值,一般不用 document.querySelector();//css选择符模式,返回与该模式匹配的第一个元素,结果为一个元素;如果没找到匹配的元素,则返回null document.querySelectorAll()//css选择符模式,返回与该模式匹配的所有元素,结果为一个类数组 注意:
前缀为document,意思是在document节点下调用这些方法,当然也可以在其他的元素节点下调用,如: jquery
querySelector()和querySelectorAll()方法,最后两个为静态的,不是实时的,保存的是当时的状态,是一个副本,即:在以后的代码中通过方法使所选元素发生了变化,但该值依然不会改变,因此使用有局限性,一般不用,除非就想得到副本
举例如下: jquery
(3)常见错误解决【question1:】 ‘err’ is defined but never used (no-unused-vars) 这个问题,是由于 vue 项目安装了 ESLint 。 暴力解决:直接关闭 ESLint 在 package.json 文件中 添加 “rules”: { “generator-star-spacing”: “off”, “no-tabs”:”off”, “no-unused-vars”:”off”, “no-console”:”off”, “no-irregular-whitespace”:”off”, “no-debugger”: “off” }
3、解决跨域问题
(1)step1:配置 baseURL (2)step2:修改配置文件(修改后要重启服务)
vue 3.0 通过 vue.config.js 文件 修改配置(若没有,则直接在项目路径下新建即可)。【vue.config.js】 module.exports = { devServer: { proxy: { ‘/api’: { // 此处的写法,目的是为了 将 /api 替换成 https://www.baidu.com/ target: ‘https://www.baidu.com/‘, // 允许跨域 changeOrigin: true, ws: true, pathRewrite: { ‘^/api’: ‘’ } } } } }
(3)step3:修改 axios 使用方式【App.vue】 TestAxios
重启服务后,点击按钮,可以成功访问。
13.v-modal 的使用
14.scss 的安装以及使用
1.安装npm install sass-loader node-sass —save-dev 2.在build文件夹下的webpack.base.conf.js的rules里面添加配置{ test: /.sass$/, loaders: [‘style’, ‘css’, ‘sass’] },
3.vue组件引入
scss是什么?在vue-cli中的安装使用步骤是什么?有哪几大特性?
1、css的预编译。 2、使用步骤:
(1)先装css-loader、node-loader、sass-loader等加载器模块。 (2)build目录找到webpack.base.config.js,extends属性中加一个扩展.scss (3)在同一个文件,配置一个module属性。 (4)在组件的style标签上加上lang属性,如lang=“scss”
3、特性:
(1)可以使用变量,如($变量名称=值)。 (2)可以用混合器,混入@mixin可以传变量。 (3)可以嵌套。 (4)继承@extend不可以传变量,相同样式直接继承,不会造成代码冗余。基类未被继承时,也会被编译成css代码。
Vue 中 computed 和 watch 有什么区别?使用场景?
计算属性 computed: 支持缓存,只有依赖数据发生变化时,才会重新进行计算函数; 计算属性内不支持异步操作; 计算属性的函数中都有一个 get(默认具有,获取计算属性)和 set(手动添加,设置计算属性)方法; 计算属性是自动监听依赖值的变化,从而动态返回内容。 侦听属性 watch: 不支持缓存,只要数据发生变化,就会执行侦听函数; 侦听属性内支持异步操作; 侦听属性的值可以是一个对象,接收 handler 回调,deep,immediate 三个属性; 监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些其他事情。
methods, computed, watch 区别?
methods:简单处理 computed:数据在模板 watch:组件变化,做数据处理
18.$nextTick 的使用【什么时候用到vue.nextTick()?】
1、created()钩子函数中,进行dom操作,要放在vue.nextTick()回调函数中。
原因:created()钩子函数执行的时候,dom其实并未进行任何渲染。
2、mounted钩子函数中,在数据变化中要执行某个操作,而这个操作需要随数据改变而改变数据结构。
原因:设置数据改变,vm.someData = ‘new value’, dom并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时,才会进行必要的dom更新。此时如果想要根据更新的dom状态去做某些事情时,就会出现问题。 为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) ,这样回调函数在 DOM 更新完成后就会调用。
20.vue 事件对象的使用
一、event 对象 (一)事件的 event 对象
你说你是搞前端的,那么你肯定就知道事件,知道事件,你就肯定知道 event 对象吧?各种的库、框架多少都有针对 event 对象的处理。比如 jquery,通过它内部进行一定的封装,我们开发的时候,就无需关注 event 对象的部分兼容性问题。最典型的,如果我们要阻止默认事件,在 chrome 等浏览器中,我们可能要写一个:event.preventDefault(); 而在 IE 中,我们则需要写:event.returnValue = false; 多亏了 jquery ,跨浏览器的实现,我们统一只需要写:event.preventDefault(); 兼容?jquery 内部帮我们搞定了。类似的还有比如阻止事件冒泡以以及事件绑定(addEventListener / attachEvent)等,简单到很多的后端都会使用 $(‘xxx’).bind(…),这不是我们今天的重点,我们往下看。
(二)vue 中的 event 对象
我们知道,相比于 jquery,vue 的事件绑定可以显得更加直观和便捷,我们只需要在模板上添加一个 v-on 指令(还可以简写为 @),即可完成类似于 $(‘xxx’).bind 的效果,少了一个利用选择器查询元素的操作。我们知道,jquery 中,event 对象会被默认当做实参传入到处理函数中,如下:$(‘body’).bind(‘click’, function (event) { console.log(typeof event); // object }); 这里直接就获取到了 event 对象,那么问题来了,vue 中呢? click me
… var app = new Vue({ el: ‘#app’, methods: { click(event) { console.log(typeof event); // object } } }); 这里的实现方式看起来和 jquery 是一致的啊,但是实际上,vue 比 jquery 要要复杂得多,jquery 官方也明确的说,v-on 不简单是 addEventListener 的语法糖。在 jquery 中,我们传入到 bind 方法中的回调,只能是一个函数表类型的变量或者一个匿名函数,传递的时候,还不能执行它(在后面加上一堆圆括号),否则就变成了取这一个函数的返回值作为事件回调。而我们知道,vue 的 v-on 指令接受的值可以是函数执行的形式,比如 v-on:click=”click(233)” 。这里我们可以传递任何需要传递的参数,甚至可以不传递参数: click me
… var app = new Vue({ el: ‘#app’, methods: { click(event) { console.log(typeof event); // undefined } } }); 咦?我的 event 对象呢?怎么不见了?打印看看 arguments.length 也是 0,说明这时候确实没有实参被传入进来。T_T,那我们如果既需要传递参数,又需要用到 event 对象,这个该怎么办呢?
(三)$event
翻看 vue 文档,不难发现,其实我们可以通过将一个特殊变量 $event 传入到回调中解决这个问题: click me
… var app = new Vue({ el: ‘#app’, methods: { click(event, val) { console.log(typeof event); // object } } }); 好吧,这样看起来就正常了。 简单总结来说:
使用不带圆括号的形式,event 对象将被自动当做实参传入; 使用带圆括号的形式,我们需要使用 $event 变量显式传入 event 对象。
二、乌龙
前面都算是铺垫吧,现在真正的乌龙来了。 翻看小伙伴儿的代码,偶然看到了类似下面的代码: click me
… var app = new Vue({ el: ‘#app’, methods: { click(val) { console.log(typeof event); // object } } }); 看到这一段代码,我的内心是崩溃的,丢进 chrome 里面一跑,尼玛还真可以,打印 arguments.length,也是正常的 1。尼玛!这是什么鬼?毁三观啊? 既没有传入实参,也没有接收的形参,这个 event 对象的来源,要么是上级作用链,要么。。。是全局作用域。。。全局的,不禁想到了 window.event。再次上 MDN 确认了一下,果然,window.event,ie 和 chrome 都在 window 对象上有这样一个属性:
事件对象<!DOCTYPE html>
事件冒泡<!DOCTYPE html> 阻止事件默认行为<!DOCTYPE html>
22.渐进式框架的理解
引言
Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。如下图所示,这里包含了Vue的所有部件,在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件
Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。如下图所示,这里包含了Vue的所有部件,在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念
具体理解
在我看来,渐进式代表的含义是:主张最少。 每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。 比如说,Angular,它两个版本都是强主张的,如果你用它,必须接受以下东西: 所以Angular是带有比较强的排它性的,如果你的应用不是从头开始,而是要不断考虑是否跟其他东西集成,这些主张会带来一些困扰。 比如React,它也有一定程度的主张,它的主张主要是函数式编程的理念,比如说,你需要知道什么是副作用,什么是纯函数,如何隔离副作用。它的侵入性看似没有Angular那么强,主要因为它是软性侵入。 Vue可能有些方面是不如React,不如Angular,但它是渐进的,没有强主张,你可以在原有大系统的上面,把一两个组件改用它实现,当jQuery用;也可以整个用它全家桶开发,当Angular用;还可以用它的视图,搭配你自己设计的整个下层用。你可以在底层数据逻辑的地方用OO和设计模式的那套理念,也可以函数式,都可以,它只是个轻量视图而已,只做了自己该做的事,没有做不该做的事,仅此而已。
渐进式的含义,我的理解是:没有多做职责之外的事。
24.单页面应用和多页面应用区别及优缺点
单页面应用(SPA),通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。 多页面(MPA),就是指一个应用中有多个页面,页面跳转时是整页刷新 单页面的优点:
1,用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小 2,前后端分离 3,页面效果会比较炫酷(比如切换页面内容时的专场动画)
单页面缺点:
1,不利于seo 2,导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理) 3,初次加载时耗时多 4,页面复杂度提高很多
查找资料过程中感觉掘金上一个博主总结的很好,拿到这里借鉴下:
25.vue 中过滤器有什么作用及详解【https://blog.csdn.net/qappleh/article/details/89639948?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link 】【https://blog.csdn.net/qq_42778001/article/details/95613371?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link 】
使用方式: {{ message | formatDate }}
那么,我们该如何去写过滤器呢?接下来我们假定一种场景,后端返回的一个 时间列表大概是长下边这样的:[ {time: 1533527037000}, {time: 1533527037000}, {time: 1533527037000} ] 可以看到这个数组中有三个数据,分别是三个时间戳,如果没有过滤器,我们只能用普通的方式,循环这个数组,将每一项的时间戳转换为具体的事件。但是,vue提供的过滤器可以帮我们很好的解决这种问题。接下来我们看如何去使用过滤器解决。 首先定义过滤器有两种方式,第一种是全局过滤器,我们可以直接在vue对象上使用filter方法注册过滤器,这种全局注册的过滤器在任何一个组件内都可以使用。第二种则是组件内部的过滤器,注册组件内部过滤器则只能在当前组件内使用,接下来我们使用这两种方式注册过滤器函数。 全局过滤器:import Vue from ‘vue’; Vue.filter(‘formatTime’, function (val) { const date = new Date(val); const hour = date.getHours(); const minutes = date.getMinutes(); const seconds = date.getSeconds(); return ${hour} : ${minutes} : ${seconds}; }); 局部组件过滤器(局部过滤器直接在组件内写即可):export default { name: ‘FilterDemo’, / 局部过滤器 / filters: { / 格式化时间戳 / formatDate (val) { const date = new Date(val); const year = date.getFullYear(); const month = date.getMonth() > 9 ? date.getMonth() + 1 : 0${date.getMonth() + 1}; const day = date.getDate() > 9 ? date.getDate() + 1 : 0${date.getDate() + 1}; return ${year}-${month}-${day}; console.log(val); } }, data () { return { } } } ok,接下来我们看下如何来使用: vue过滤器: {{ 1533527037000 | formatDate }}
{{ 1533527037000 | formatTime }}
在组件里,按照文章开头说的两种方式即可,注意组件内注册的过滤器(这里是formatDate)只能在当前组件内部使用。 在Vue中filter过滤器是一个非常强大的功能。 个人觉得称它为加工车间会更加贴切一些。 过滤器可以用来筛选出符合条件的,丢弃不符合条件的; 加工车间既可以筛选,又可以对筛选出来的进行加工。 一、filter的作用是:对值进行筛选加工。 二、使用的地方有两个位置,和两个方式。
1、{{ msg | filterA }}双括号插值内。 2、{{ msg }} v-bind绑定的值的地方。(msg为需要filter处理的值,filterA为过滤器。)方式 3、{{ msg | filterA }}单个使用。 4、{{ msg | filterA| filterB }}多个连用。
三、过滤器的制作方法:new Vue({ filters:{ //过滤器一:(使用时没有参数,即{{msg|filterA}}) filterA(value){ return “¥”+value } } }) //添加filters属性,该属性内的函数就是过滤器。其中value就是{{msg|filterA}}中的msg。 new Vue({ filters:{ //过滤器二:(使用时有参数,即{{ msg | filterA( arg1, arg2, arg3…. )}}) filterA (value , …args){//其中msg为filterA中的第一个参数value。 for(arg in args{ console.log(arg) value+=arg } return value } }, filterB (value , …args){ for(arg in args{ console.log(arg) value+=arg } return value } } }) (使用时有参数,即{{ msg | filterA( arg1, arg2, arg3…. ) | filterB( arg1, arg2, arg3…. )}}) 此时msg为filterA的第一个参数,filterA执行完后的返回值为filterB的第一个参数,以后也是依次类推。
v-text 与{{}}与 v-html 区别
{{}}将数据解析为纯文本,不能显示输出html v-html可以渲染输出html v-text将数据解析为纯文本,不能输出真正的html,与花括号号的区别是在页面加载时不显示双福舌号 v-text 指令:
作用:操作网页元素中的纯文本内容。{{}}是他的另外一种写法
v-text 与{{}}区别:
v-text与{{}}等价,{{}}叫模板插值,v-text叫指令。 有一点区别就是,在渲染的数据比较多的时候,可能会把大括号显示出来,俗称屏幕闪动:
Vue 中 v-if 和 v-show 有什么区别?
v-if 是真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的 :如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。 v-if 在进行切换时,会直接对标签进行创建或销毁,不显示的标签不会加载在 DOM 树中。 v-show 在进行切换时,会对标签的 display 属性进行切换,通过 display 不显示来隐藏元素。 一般来说,v-if 的性能开销会比 v-show 大,切换频繁的标签更适合使用 v-show。 v-if能够控制是否生成vnode,也就间接控制了是否生成对应的dom。当v-if为true时,会生成对应的vnode,并生成对应的dom元素;当其为false时,不会生成对应的vnode,自然不会生成任何的dom元素。 v-show始终会生成vnode,也就间接导致了始终生成dom。它只是控制dom的display属性,当v-show为true时,不做任何处理;当其为false时,生成的dom的display属性为none。 使用v-if可以有效的减少树的节点和渲染量,但也会导致树的不稳定;而使用v-show可以保持树的稳定,但不能减少树的节点和渲染量。 因此,在实际开发中,显示状态变化频繁的情况下应该使用v-show,以保持树的稳定;显示状态变化较少时应该使用v-if,以减少树的节点和渲染量
为什么避免v-if和v-for一起使用?
1、当vue处理指令时,当v-for,v-if处于同一节点时,v-for比v-if具有更高的优先级。这意味着v-if将分别重复运行于每个v-for循环中。 2、解决:可以将v-if放在一个包装元素内。
4.v-show 和 v-if 指令的共同点和不同点
1、相同点:
v-show 和 v-if 都能控制元素的显示和隐藏。
2、不同点:
2.1)实现本质方法不同
v-show 本质就是通过设置 css 中的 display 设置为 none,控制隐藏 v-if 是动态的向 DOM 树内添加或者删除 DOM 元素
2.2)编译的区别
v-show 其实就是在控制 css v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件
2.3)编译的条件
v-show 都会编译,初始值为 false,只是将 display 设为 none,但它也编译了 v-if 初始值为 false,就不会编译了
2.4)性能比较
v-show 只编译一次,后面其实就是控制 css,而 v-if 不停的销毁和创建,故 v-show 性能更好。
3、注意点:
因为 v-show 实际是操作 display:” “或者 none,当 css 本身有 display:none 时,vshow 无法让显示
4、总结(适用场景):
如果要频繁切换某节点时,使用 v-show(无论 true 或者 false 初始都会进行渲染,此后通过 css 来控制显示隐藏,因此切换开销比较小,初始开销较大),如果不需要频繁切换某节点时,使用 v-if(因为懒加载,初始为 false 时,不会渲染,但是因为它是通过添加和删除dom 元素来控制显示和隐藏的,因此初始渲染开销较小,切换开销比较大)
26.v-if 和 v-for 的优先级
v-for和v-if不应该一起使用,必要情况下应该替换成computed属性。原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。 错误写法 {{ user.name }} 如上情况,即使100个user中之需要使用一个数据,也会循环整个数组。 正确写法computed: { activeUsers: function () { return this.users.filter(function (user) { return user.isActive }) } }
10.为什么使用 key
当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。 key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。Vue 的 diff 过程可以概括为:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。具体有无 key 的 diff 过程,可以查看作者写的另一篇详解虚拟 DOM 的文章《深入剖析:Vue核心之虚拟DOM 》 所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速 更准确 :因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。更快速 :利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map }
27.assets 和 static 的区别
相同点:资源在html中使用,都是可以的。 不同点:使用assets下面的资源,在js中使用的话,路径要经过webpack中file-loader编译,路径不能直接写。 而static中的文件,不会经过编译。项目在经过打包后,会生成dist文件夹,static中的文件只是复制一遍而已。简单来说,static中建议放一些外部第三方,自己的放到assets,别人的放到static中。 且必须使用绝对路径来引用这些文件。 这是通过在 config.js 文件中的 build.assetsPublicPath 和 build.assetsSubDirectory 链接来确定的 注意 :任何放在 static 中的文件需要以绝对路径的形式引用:/static/[filename]assets中的文件会经过webpack打包,重新编译,推荐该方式。 注意:
1、static中的文件,是不会经过编译的,打包后会生成dist文件夹,static中的文件只是复制一面。 因此,static中建议放一些外部第三方,自己的文件放在assets,别人的放在static中 2、若把图片放在assets和static中,html页面中都可以使用; 但是在动态绑定中,assets路径的图片会加载失败, 因为webpack使用的是 commenJS 规范,必须使用require才可以 如果把图片放在assets与static中,html页面可以使用;但在动态绑定中,assets路径的图片会加载失败,因为webpack使用的是commenJS规范,必须使用require才可以,具体代码如下:
html jsdata (){ return { assetsURL: require(‘../../assets/11.png’), staticURL: ‘../../../static/11.png’ } } 效果图
说出几种 vue 当中的指令和它的用法?
v-for:循环数组,对象字符串,数字 v-on:绑定事件监听 v-bind:动态绑定一个或者多个属性 v-model:表单控件或者组件上创建双向绑定 v-if v-else v-else-if 条件渲染 v-show 根据表达式真假,切换元素的display v-html 更新元素的 innerhtml v-text 更新元素的 textcontent v-pre 跳过这个元素和子元素的编译过程 v-clock 这个指令保持在元素上知道关联实例结束编译 v-once 只渲染一次 顶栏容器 侧栏容器 主要内容容器 底栏容器 Dropdown 下拉菜单 下拉按钮 下拉菜单 下拉项 Table 表格 Tabs 标签页 Form 表单 Pagination 分页 Message 消息提示
32.自定义指令详解
1、自定义指令:使用Vue.directive(id,definition)注册全局自定义指令,使用组件的directives选项注册局部自定义指令。 2、钩子函数:
指令定义函数提供了几个钩子函数(可选): bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。 inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。 update:第一次是紧跟在 bind 之后调用,获得的参数是绑定的初始值,之后被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。 componentUpdated:被绑定元素所在模板完成一次更新周期时调用。 unbind:只调用一次, 指令与元素解绑时调用。
3、钩子函数的参数:(el, binding, vnode, oldVnode)
el:指令所绑定的元素,可以用来直接操作 DOM 。 binding:一个对象,包含以下属性
name:指令名,不包含v-的前缀; value:指令的绑定值;例如:v-my-directive=”1+1”,value的值是2; oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子函数中可用,无论值是否改变都可用; expression:绑定值的字符串形式;例如:v-my-directive=”1+1”,expression的值是’1+1’; arg:传给指令的参数;例如:v-my-directive:foo,arg的值为 ‘foo’; modifiers:一个包含修饰符的对象;例如:v-my-directive.a.b,modifiers的值为{‘a’:true,’b’:true}
vnode:Vue编译的生成虚拟节点; oldVnode:上一次的虚拟节点,仅在update和componentUpdated钩子函数中可用。
Vue.directive(‘demo’, { bind: function (el, binding, vnode) { console.log(‘bind’); var s = JSON.stringify el.innerHTML = ‘name: ‘ + s(binding.name ) + ‘ ‘ + ‘value: ‘ + s(binding.value) + ‘ ‘ + ‘expression: ‘ + s(binding.expression) + ‘ ‘ + ‘argument: ‘ + s(binding.arg) + ‘ ‘ + ‘modifiers: ‘ + s(binding.modifiers) + ‘ ‘ + ‘vnode keys: ‘ + Object.keys(vnode).join(‘, ‘) } }); new Vue({ el: ‘#app’, data: { message: ‘hello!’ } });
4、函数简写:大多数情况下,我们可能想在 bind 和 update 钩子上做重复动作,并且不想关心其它的钩子函数。可以这样写:Vue.directive(‘color-swatch’, function (el, binding) { el.style.backgroundColor = binding.value }) 5、对象字面量:如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法类型的 Javascript 表达式。
Vue.directive(‘demo’, function (el, binding) { console.log(binding.value.color) // => “white” console.log(binding.value.text) // => “hello!” })
例子解析: 更新 卸载 安装
a、页面加载时:bind inserted b、更新组件:update componentUpdated c、卸载组件:unbind d、重新安装组件:bind inserted 注意区别:bind与inserted:bind时父节点为null,inserted时父节点存在;update与componentUpdated:update是数据更新前,componentUpdated是数据更新后。
6.最后上一个实际开发的指令封装实现
基本思路import store from ‘@/store’ export default { inserted(el, binding, vnode) { const { value } = binding const roles = store.state.permission.pagePermission if (value && typeof value === ‘string’ && value.length > 0) { const hasPermission = roles.some(role => { return role.permission == value }) if (!hasPermission) { el.parentNode && el.parentNode.removeChild(el) } } else { throw new Error(need roles! Like v-permission="'button'") } } } 向外暴露api import permission from ‘./permission’ const install = function(Vue) { Vue.directive(‘permission’, permission) } if (window.Vue) { window[‘permission’] = permission Vue.use(install) // eslint-disable-line } permission.install = install export default permission 向外暴露apiimport permission from ‘./permission’ const install = function(Vue) { Vue.directive(‘permission’, permission) } if (window.Vue) { window[‘permission’] = permission Vue.use(install) // eslint-disable-line } permission.install = install export default permission
自定义指令传参 指令
在vue官网中,常用指令有v-model和v-bind,但是,如果我们需要对DOM元素进行底层操作,就需要用到自定义指令。 今天主要讲到传参的2种方式。
环境
vue2.3.3 node6.11.2 webpack2.6.1
传参方式
在main.js中定义一个指令。Vue.directive(‘zoom’, { bind: function (el, binding, vnode) { console.log(binding) }, update: function (el) { }, unbind: function (el) { } }) 使用自定义指令,在任意一个html元素中,使用指令。
方式一:v-zoom:{a:1,b:2} 对于{a:1,b:2},在binding属性中,以字符串的形式传递,故可以写[1,2]、true等数据格式,但是最后拿到的都是字符串类型。 方式二:v-zoom=”{width: 800, height: 1000}” 等号后面的{width: 800, height: 1000},可以在binding的value属性中获取的数据的类型没有变化, 可以直接使用。width后面的数字,可以用当前组件的参数替换。
自定义指令(v-check、v-focus)的方法有哪些?它有哪些钩子函数?它有哪些钩子函数参数? 29.vue 常用的修饰符
一、修饰符是什么 表单修饰符
在我们填写表单的时候用得最多的是input标签,指令用得最多的是v-model 关于表单的修饰符有如下:
事件修饰符
在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。 为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。 …
…
注意:
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
事件修饰符是对事件捕获以及目标进行了处理,有如下修饰符:
stop
阻止了事件冒泡,相当于调用了event.stopPropagation方法 ok
//只输出1
prevent
阻止了事件的默认行为,相当于调用了event.preventDefault方法
self
只当在 event.target 是当前元素自身时触发处理函数…
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击
once
capture
使事件触发从包含这个元素的顶层开始往下触发 // 输出结构: 1 2 4 3
passive
在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll事件整了一个.lazy修饰符 …
不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。 passive 会告诉浏览器你不想阻止事件的默认行为
native
让组件变成像html内置标签那样监听根元素的原生事件,否则组件上使用 v-on 只会监听自定义事件 使用.native修饰符来操作普通HTML标签是会令事件失效的
按键修饰符
v-bind修饰符
v-bind修饰符主要是为属性进行操作,用来分别有如下:
async
能对props进行一个双向绑定//父组件 //子组件 this.$emit(‘update:myMessage’,params); 以上这种方法相当于以下的简写//父亲组件 func(e){ this.bar = e; } //子组件js func2(){ this.$emit(‘update:myMessage’,params); } 使用async需要注意以下两点:
使用sync的时候,子组件传递的事件名格式必须为update:value,其中value必须与子组件中props中声明的名称完全一致 注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的
props
camel
将命名变为驼峰命名法,如将view-Box属性名转换为 viewBox
三、应用场景
根据每一个修饰符的功能,我们可以得到以下修饰符的应用场景:
.stop:阻止事件冒泡 .native:绑定原生事件 .once:事件只执行一次 .self :将事件绑定在自身身上,相当于阻止事件冒泡 .prevent:阻止默认事件 .caption:用于事件捕获 .once:只触发一次 .keyCode:监听特定键盘按下 .right:右键
列举Vue中的事件修饰符
Vue.js为v-on提供了事件修饰符。 修饰符是由点开头的指令后缀来表示的。 .stop 阻止事件继续传播 .prevent 阻止默认事件 .capture 使用捕获模式 .self 只当事件在该元素本身(而不是子元素)触发时触发回调 .once 事件只会触发一次
30.数组更新检测
变异方法 (mutation method)
Vue 将被侦听的数组的变异方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
push() pop() shift() unshift() splice() sort() reverse()
你可以打开控制台,然后对前面例子的items数组尝试调用变异方法。比如example1.items.push({ message: ‘Baz’ })。
替换数组
变异方法,顾名思义,会改变调用了这些方法的原始数组。相比之下,也有非变异 (non-mutating method) 方法,例如filter()、concat()和slice()。它们不会改变原始数组,而总是返回一个新数组 。当使用非变异方法时,可以用新数组替换旧数组:example1.items = example1.items.filter(function (item) { return item.message.match(/Foo/) }) 你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
注意事项
由于 JavaScript 的限制,Vue不能 检测以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如:vm.items.length = newLength
举个例子:var vm = new Vue({ data: { items: [‘a’, ‘b’, ‘c’] } }) vm.items[1] = ‘x’ // 不是响应性的 vm.items.length = 2 // 不是响应性的 为了解决第一类问题,以下两种方式都可以实现和vm.items[indexOfItem] = newValue相同的效果,同时也将在响应式系统内触发状态更新:// Vue.set Vue.set(vm.items, indexOfItem, newValue) // Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue) 你也可以使用vm.$set 实例方法,该方法是全局方法Vue.set的一个别名:vm.$set(vm.items, indexOfItem, newValue) 为了解决第二类问题,你可以使用splice:vm.items.splice(newLength)
对象变更检测注意事项
还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除 :var vm = new Vue({ data: { a: 1 } }) // vm.a 现在是响应式的 vm.b = 2 // vm.b 不是响应式的 对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用Vue.set(object, propertyName, value)方法向嵌套对象添加响应式属性。例如,对于:var vm = new Vue({ data: { userProfile: { name: ‘Anika’ } } }) 你可以添加一个新的age属性到嵌套的userProfile对象:Vue.set(vm.userProfile, ‘age’, 27) 你还可以使用vm.$set实例方法,它只是全局Vue.set的别名:vm.$set(vm.userProfile, ‘age’, 27) 有时你可能需要为已有对象赋值多个新属性,比如使用Object.assign()或_.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:Object.assign(vm.userProfile, { age: 27, favoriteColor: ‘Vue Green’ }) 你应该这样做:vm.userProfile = Object.assign({}, vm.userProfile, { age: 27, favoriteColor: ‘Vue Green’ })
最近在项目中,遇到一个问题,VueJS在递归组件时候,更改数组数据中的值,列表不进行重新渲染,查文档只解决了一部分,网上也没有相关的解决方法,在此做一个总结。 首先贴两段demo代码 第一段app.vue 第二段 treeMenu.vue {{ model.menuName }}
场景二:控制按钮的显示隐藏,下图为vue和jquery两种操作的代码,我们从中可以看出vue只需要控制属性isShow的值为true和false即可,而jquery则还是需要操作dom元素控制按钮的显示和隐藏
vue:<!DOCTYPE html> jquery:<!DOCTYPE html> 输出结果:
4.总结:内容讲的比较浅,主要就是分析一下vue和jquey对比的区别,上面两个例子只是做了一个简单的说明,然而vue能解决的问题远比这些要多的多,复杂的多。
vue适用的场景:复杂数据操作的后台页面,表单填写页面 jquery适用的场景:比如说一些html5的动画页面,一些需要js来操作页面样式的页面 然而二者也是可以结合起来一起使用的,vue侧重数据绑定,jquery侧重样式操作,动画效果等,则会更加高效率的完成业务需求
附上公司前端目录结构,感兴趣的可以分享代码给大家看看
src代码目录包含assets静态文件,components vue组件文件,plugins 插件文件(包含登录操作,http请求操作,过滤器,加解密操作,公共方法等),router 路由文件,store vuex文件,app.js vue相关配置,index.html主页面
build目录为webpack打包文件,dist目录为打包后生成的文件,node_modules 引用的外部组件
请说出 vue.cli 项目中 src 目录每个文件夹和文件的用法?
assets文件夹是放静态资源;components是放组件;router是定义路由相关的配置;view视图;app.vue是一个应用主组件;main.js是入口文件
36.Vue-cli 打包命令是什么?打包后悔导致路径问题,应该在哪里修改
问题1:使用vue-cli 3.x开发的项目,开发的时候顺利无比,一旦打包上线各种问题就来了。首先是资源里面报各种各样的请求错误。
解决思路:在src文件夹同级目录下创建vue.config.js文件
在vue.config.js中重写打包后的基础路径为当前目录module.exports = { //… baseUrl: ‘./‘ //… }
问题2:重新打包之后上传服务器,发现访问是不会报错了,但是请求的资源(如index.html)这些又报304问题,整个页面是一个白屏,没有任何自己的开发的东西
解决思路:查看各种资料后得出结论,应该是路由的加载模式的问题,因为vue-router的模式不同,导致服务器不能够识别,解决方案就是在 router.js 中 更改路由器的访问模式。
将history模式更改为vue项目默认的hash模式即可
再次打包上传服务器,访问成功
命令行输入:npm run build
打包出来后项目中就会多了一个文件夹dist,这就是我们打包过后的项目。
第一个问题,文件引用路径。我们直接运行打包后的文件夹中的index.html文件,会看到网页一片空白,f12调试,全是css,js路径引用错误的问题。
解决:到config文件夹中打开index.js文件。 文件里面有两个assetsPublicPath属性,更改第一个,也就是更改build里面的assetsPublicPath属性:
assetsPublicPath属性作用是指定编译发布的根目录,‘/’指的是项目的根目录 ,’./’指的是当前目录。 改好之后重新打包项目,运行index.html文件,我们可以看到没有报错了。但是router-view里面的内容却出不来了。
第二个问题:router-view中的内容显示不出来。 路由history模式。
这个坑是当你使用了路由之后,在没有后端配合的情况下就手贱打开路由history模式的时候 ,打包出来的文件也会是一片空白的情况, 很多人踩这个坑的时候花了很多时间,网上的教程基本上都是说的第一个坑,这个坑很少有人提起。
解决:// mode: ‘history’,//将这个模式关闭就好 这里并不是说不能打开这个模式,这个模式需要后端设置的配合,详情可以看:路由文档
第三个问题 就是背景图片引用资源错误
此时通过img标签引入的图片显示正常,是因为img为html标签,他的路径是由index.html开始访问的,他走static/img/‘图片名’是能正确访问到图片的 但是app.css访问static/img/‘图片名’是访问错误的,因为在css目录下并没有static目录。所以此时需要先回退两层到根节点处才可以正确获取到图片。 具体办法是:
打开build/utils.js,在图中相应位置加入红框内容,其中值可能会有不同,若不同,自己配置成相应的即可。
vue打包命令
首先要保证你的环境中有node.js nodejs.org 去这个网站下载 安装好了之后,cmd 》 node -v 查看是否安装成功 1.如果在你的本地环境中第一次安装vue要全局安装vue-clinpm install —global vue-cli 2.创建一个基于 webpack 模板的新项目,首先cd到要安装项目的目录vue init webpack my-project 3.安装依赖,这个执行命令,可以用第二步执行完成后提示的命令cd my-project //切换到安装目录 npm install //安装依赖,新版本不用 npm run dev //将vue项目跑起来 4.安装路由npm install vue-router —save-dev 5.安装resource:要进行http的请求要安装的npm install vue-resource —save-dev
下载好了之后要引用,main.js中添加上下面两句import VueResource from ‘vue-resource’ Vue.use(VueResource)
6.打包
先将config/index.js中第二个assetsPublicPath: ‘/‘,改成assetsPublicPath: ‘./‘,加一个点 npm run build
1、打包命令是npm run build,这个命令实际上是在package.json中,scripts中build所对应的命令;
2、创建一个prod.server.js,这个文件不是必须的,这个文件的用处是在打包完毕之后,通过启动node.js本地服务来访问打包完成的静态文件,不需要的同学可以忽略这一点,
prod.server.js文件代码示例:let express = require(‘express’); let config = require(‘./config/index’); // let axios = require(‘axios’); let app = express(); let apiRoutes = express.Router(); app.use(‘/api’, apiRoutes); app.use(express.static(‘./dist’)); let port = process.env.PORT || config.build.port; module.exports = app.listen(port, (err) => { if (err){ console.error(err); return; } console.log(‘Listening at: http://localhost :’+port+’\n’); });
3、在index.html中使用scrip标签引入的js和使用link引入的css文件,全部改为在main.js中直接import;我目前main.js的代码示例:// The Vue build version to load with the import command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from ‘vue’ import App from ‘./App’ import router from ‘./router’ import iView from ‘iview’ import ‘iview/dist/styles/iview.css’ import VueAwesomeSwiper from ‘vue-awesome-swiper’ import MuseUI from ‘muse-ui’ import ‘muse-ui/dist/muse-ui.css’ import ‘src/base/css/libs/museui/muse-ui-fonts.css’ import ‘src/base/css/libs/museui/muse-ui-icons.css’ import VueResource from ‘vue-resource’ import ‘src/base/js/libs/waves/waves.min.js’ import ‘src/base/css/libs/waves/waves.min.css’ import $ from ‘jquery’ Vue.use(VueResource); Vue.use(iView); Vue.use(VueAwesomeSwiper); Vue.use(MuseUI); Vue.config.productionTip = false / eslint-disable no-new / new Vue({ el: ‘#app’, router, template: ‘ ‘, components: { App } }) 4、图片的相对路径问题,要引用相对路径下的图片,首先是在在config/index.js中,将build.assetsPublicPath改为’’,原来是’/‘,
5、使用iview开发的话,打包之后,直接打开index.html之后会报错,有两个字体文件引入失败,但是我这里是没有手动引入这两个文件的,最后百度到解决办法是,在webpack.prod.conf.js中设置module.rules中的extract为false;详情见这个issue:https://github.com/iview/iview/issues/515
一、终端运行命令 npm run build 二、打包成功的标志与项目的改变,如下图:
3、点击index.html,通过浏览器运行,出现以下报错,如图:
四、那么应该如何修改呢?
具体步骤如下:
1、查看package.js文件的scripts命令 2、打开webpack.dev.conf.js文件,找到publicPath: config.dev.assetsPublicPath,按Ctrl点击,跳转到index.js文件 3、其中dev是开发环境,build是构建版本,找到build下面的assetsPublicPath: ‘/‘,然后修改为assetsPublicPath: ‘./‘,即“/”前加点。 4、终端运行 npm run build 即可。 此时点击index.html,通过浏览器运行便,会发现动态绑定的static的图片找不到,故static必须使用绝对路径。将图片路径修改为绝对路径,至此,打包完成。
7.Vue-cli 项目中 assets 和 static 文件夹有什么区别?
两者都是用于存放项目中所使用的静态资源文件的文件夹。其区别在于: assets 中的文件在运行 npm run build 的时候会打包,简单来说就是会被压缩体积,代码格式化之类的。打包之后也会放到 static 中。static 中的文件则不会被打包。
vue.cli中怎样使用自定义的组件?有遇到过哪些问题吗?
第一步:在components目录新建你的组件文件(smithButton.vue),script一定要export default { 第二步:在需要用的页面(组件)中导入:importsmithButton from ‘../components/smithButton.vue’ 第三步:注入到vue的子组件的components属性上面,components:{smithButton} 第四步:在template视图view中使用,<smith-button> </smith-button> 问题有:smithButton命名,使用的时候则smith-button。
37.三大框架的对比
组织方式
Angular采用MVC的数据划分,而Vue和React采用模块化方案。
数据绑定
模板指令
Angular的模板是最强大的,除了自带的丰富的模板指令,还可以通过自定义的指令定义模板,调用的时候只需要一个指令名称就够了。 React模板就是JSX,JSX语法相当于一个变量,相当灵活。没有什么问题是一坨js解决不了的,如果有,那就用两坨。
模板
Vue模板借鉴了Angular的模板指令,但是没那么复杂。它的特点就是简洁易用。
自由度
Angular本身是一个大而全的框架,它对模块模板路由都有很多的要求,因此自由度比较小。 React是UI层框架,JSX模板相当于js,写起来自由度非常大,相当于原生的js。 Vue追求的是灵活,简单,但还是受到一些经典Web技术的限制,相对React自由度小一些,但其实也比较自由。
路由
Angular路由是自带的,而Vue和React是借助别的路由工具。 React使用React-router,Vue使用Vue-router,Angular中是静态的路由,而React4.x开始使用动态路由,Vue2.2之后也可以使用addRoutes来创建动态路由。
其他维度对比
React与Vue
相同点
使用 Virtual DOM,有较高的运行速度 提供组件化功能 可使用mobx与vuex进行状态管理,响应式、依赖追踪
react和vue进行对比:二者都采用的是virtual DOM的方式去渲染组件。
(1)语法
react推崇的是JSX语法,在学习react之前还要去出不了解JSX语法。简单说是HTML CSS in JavaScript。 vue与之不同的是语法更友好,简单说是ALL IN Vue。可以像之前写HTML5一样去写vue,webpack+vue-loader的单文件格式,再搭配前端模块化,大的小的都能hold住,less等等的也都可以使用,这个语法还是非常爽的~
(2)数据流
理论上,react要采用单向数据流,但是真正在开发的时候,还是可以通过callback等方式来进行子组件对父组件传值,不会有任何影响。但是引入flux概念,有了redux,强制将state抽取出来集中去管理。虽然更难去掌握,但是也让react的状态管理机制更加完善。 vue的数据流默认也是单向的,但是可以使用v-model去进行双向的数据流绑定操作。vue的在应用大型页面的时候也会有这样的state管理的问题,所以有了vuex。
(3)使用场景
借用尤雨溪大佬的话:
使用场景上来说:React 配合严格的 Flux 架构,适合超大规模多人协作的复杂项目。理论上 Vue 配合类似架构也可以胜任这样的用例,但缺少类似 Flux 这样的官方架构。小快灵的项目上,Vue 和 React 的选择更多是开发风格的偏好。对于需要对 DOM 进行很多自定义操作的项目,Vue 的灵活性优于 React。
(4)render
react是通过递归的方式去遍历DOM树,实现diff;而vue是自带检查机制。 react应用中,为了避免不要的子组件重渲染,需要部分手动实现shouldComponentUpdate。而这一点vue是更加出色的,组件的依赖是在渲染过程中自动追踪的。有兴趣的可以去打印一下vue中data的值,都会有一个不可枚举属性“ob ”。这个属性就是相当于追踪属性,精确的知道哪个组件确实需要被渲染。可以理解为每个组件都是有react的shouldComponentUpdate。 参考知乎vue和react使用场景和深度有什么不同
个人还是很喜欢vue和尤雨溪大佬的。 搬运的新资料 关于Vue和React区别
React
子组件重复渲染问题需要手动优化 可以使用redux进行状态管理,函数式、不可变、模式化,时间旅行 可使用JSX,完全的javascript能力 更繁荣的社区生态 优点:速度快、跨浏览器兼容、单向数据流、兼容性好 缺点:并不是一个完整的框架,需要加上ReactRouter和Flux才能完成。 虚拟DOM操作,设置state值render页面。 在vue的官网中有提到,跟其他框架的不同,可以去仔细了解一下。vue对比其他框架 官网的涉及到的东西比较深,也没有那么浅显易懂,所以想通过自己的理解再去总结。
Vue
可使用JSX,但推荐使用模版语言而不是JSX 学习曲线平缓 优点:更轻量,单页面,简单易学 缺点:不支持IE8 轻量级。每一个组件自带shouldComponentUpdate
Angular
特点
完善的MV*框架,包含模板,数据双向绑定,路由,模块化,服务,过滤器,依赖注入等所有功能 Typescript 脏检查,对脏数据的检查就是脏检查,比较UI和后台的数据是否一致 优点:模块化功能强大、自定义directive非常灵活、双向数据绑定,依赖注入 缺点:比较笨重,学习成本高,不兼容IE6/7 数据双向绑定,模板功能强大。依赖注入和自定义directive非常灵活。 学习路线长,框架偏重。 优缺点详见:angularJS在实际开发中有哪些优缺点?
比较
MVVM 列表渲染的初始化几乎一定比 Virtual DOM 慢,因为创建 ViewModel / scope 实例比起 Virtual DOM 来说要昂贵很多 大而全大框架,学习成本高
react和angular对比,同样都是Model Driven View
(1)语法
react采用的JSX语法,angular推崇使用typescript,也可以直接用js写。配合上ts还是很爽,后端的也能很快习惯ts的开发。react更注重的是在view层,用state的改变去re-render页面。angular是双向绑定,更加注重的是model层,更加擅长对数据的处理和业务逻辑。
39.delete 和 Vue.delete 删除数组的区别
delete 操作符:删除对象的 property。如果删除数组中的某项元素,并不会改变数组长度,原来的空位会变成 undefined Vue.delete 删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制,但是你应该很少会使用它。
41.Vue-router 跳转和 location.href 有什么区别
vue-router与location.href的用法区别
①vue-router使用pushState进行路由更新,静态跳转,页面不会重新加载;location.href会触发浏览器,页面重新加载一次 ②vue-router使用diff算法,实现按需加载,减少dom操作 ③vue-router是路由跳转或同一个页面跳转;location.href是不同页面间跳转; ④vue-router是异步加载this.$nextTick(()=>{获取url});location.href是同步加载
其他用法 this.$router.push({path:’/fillinformation’, query: {applicationNo: this.applicationNo,contractNo:this.contractNo}}) } this.$route.query.applicationNo//页面跳转后获取携带参数applicationNo参数 //此用法参数会展示在跳转地址上—-图一 this.$router.push({ name: ‘clientdetail’, params: { clientCode: clientCode, clientType: clientType } }) this.$route.params.clientCode//页面跳转后获取携带参数clientCode //此用法参数不会展示在跳转地址—图二
使用location.href实现页面div块的快速定位location.href=’#divClass’ //,通过事件直接跳转到该dev
location.href可直接获取当前路径 parent.location.href跳转至上一层页面 top.location.href跳转至最外层页面
vue slot
什么是插槽? 插槽(Slot)是Vue提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性。插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制 slot官方介绍 Props:
Usage:
元素作为组件模板之中的内容分发插槽。 元素自身将被替换。
官方demo < slot > 元素 Shadow DOM 使用 元素将不同的 DOM 树组合在一起。Slot 是组件内部的占位符,用户可以使用自己的标记来填充。 通过定义一个或多个 slot,您可将外部标记引入到组件的 shadow DOM 中进行渲染。 这相当于您在说“在此处渲染用户的标记”。 注:Slot 是为网络组件创建“声明性 API”的一种方法。它们混入到用户的 DOM 中,帮助对整个组件进行渲染,从而将不同的 DOM 树组合在一起。 个人理解slot是对组件的扩展,通过slot插槽向组件内部指定位置传递内容,通过slot可以父子传参;是“占坑”,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中< slot >位置),当插槽也就是坑< slot name=”mySlot”>有命名时,组件标签中使用属性slot=”mySlot”的元素就会替换该对应位置内容; Slot出现时为了解决什么问题呢? 正常情况下,< Child>< span style=”color:red;”>hello world</ span></ Child>在组件标签Child中的span标签会被组件模板template内容替换掉,当想让组件标签Child中内容传递给组件时需要使用slot插槽; 好多人不知道这个说的是什么?简单讲就是没有一个标志性的标签 < Child>< span style=”color:red;”>hello world</ span></ Child>是不起作用的。 只会显示Child的template的内容,不管template里面还有没有内容,也就是页面上被Child组件模板template内容替换掉了。<!DOCTYPE html> hello world
这是标题
这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容
具体怎么使用slot呢?(重点) 事先准备了一个demo<!DOCTYPE html>
这是标题
这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容
单个或多个匿名插槽 这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容
结果和初始demo一样,也可以把内容放到slot标签里,不过也就没啥意义了 这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是在slot上添加了样式
这是标题
这是在slot上添加了样式 我是boboy
结果会输出两遍—这是内容….+这是slot上添加了样式+我是boboy
注意:只有匿名插槽,若child标签有内容,看模板组件中有几个slot就渲染几次内容, 且slot标签添加样式无效。拥有命名的插槽不能被不含slot属性的标签内容替换,会显示slot的默认值(具名slot具有对应性);
单个或多个具名插槽 这是页脚
这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容这是内容
这是页脚
作用域插槽
44.vue 遇到的坑,如何解决的?
使用keep-alive包裹的组件/路由,打开一次后created只会执行一次,有两种情况,一、如果要重新渲染部分数据,可以在activated中做处理;二、路由/组件重新重新created,可以使用官方推荐的:key=”key” ,然后去改变key的值,组件就会重新挂载了 beforeRouteEnter中的next函数的执行时间是在组件mounted之后,因此需要在此处处理的数据要注意了 网页刷新时vuex数据会丢失,需配合localStorage或sessionStorage使用,把必须数据先存后取 对于权限及不确定路由,可以使用addRoutes(),可以避免抖动 熟练使用es6的数组map、find、filter等方法,对解构赋值、class继承、promise,及es7中的async和await 使用computed替代watch,computed依赖于data属性的更改,是有缓存的 通过props传递的值,不要在子组件去更改。开发中,如果直接更改props,一、基本类型的值会报错,二、引用类型的值不会报错,但是不好去追溯数据的更改,很多人不太注意引用类型,可通过computed或watch去更改 在data里调用methods的方法,可以在data里定义let self = this,然后在使用self.xx()进行调用
45.Vue 里面 router-link 在电脑上有用,在安卓上没反应怎么解决? 46.Vue2 中注册在 router-link 上事件无效解决方法 47.RouterLink 在 IE 和 Firefox 中不起作用(路由不跳转)的问题 51.params 和 query 的区别
vue mock 数据
43.你们 vue 项目是打包了一个 js 文件,一个 css 文件,还是有多个文件?【https://blog.csdn.net/lx376693576/article/details/54911340 】 40.SPA 首屏加载慢如何解决 54.vue 初始化页面闪动问题 55.vue 禁止弹窗后的屏幕滚动methods : { //禁止滚动 stop(){ var mo=function(e){e.preventDefault();}; document.body.style.overflow=‘hidden‘; document.addEventListener(“touchmove”,mo,false);//禁止页面滑动 }, /取消滑动限制 / move(){ var mo=function(e){e.preventDefault();}; document.body.style.overflow=‘‘;//出现滚动条 document.removeEventListener(“touchmove”,mo,false); } } 56.vue 更新数组时触发视图更新的方法
vue 如何引进本地背景图片
57.vue 常用的 UI 组件库【https://www.jianshu.com/p/398a3b2e535f/ 】
vue 如何引进 sass
vue如何实现按需加载配合webpack配置? 60.vue 修改打包后静态资源路径的修改 谈谈Virtual Dom(虚拟DOM)的意义及原理
vuex 常见面试题
vuex 是什么?怎么使用?哪种功能场景使用它? vuex原理,状态管理库 vuex的优势 你是怎么认识vuex的? Vuex 流程 2.vuex 有哪几种属性 3.不使用 Vuex 会带来什么问题 5.vuex 一个例子方法 6.Vuex 中如何异步修改状态 Vuex怎么请求异步数据 在哪个生命周期内调用异步请求? Vuex那个是同步、哪个是异步? 4.Vue.js 中 ajax 请求代码应该写在组件的 methods 中还是 vuex 的 actions 中? 7.Vuex 中 actions 和 mutations 的区别 Vuex中 action 如何提交给 mutation 的 vue 项目实战 使用过 Vue SSR 吗?说说 SSR? 什么是计算属性 请阐述 vue 的 diff 算法
实 现DOM 字 符 串 转 虚 拟DOM 对 象( 不 能 用 DOM 相 关 的 api )【】<!DOCTYPE html>