offer收割机-Web前端面试宝典【精编版-3】

image

在线访问手册:
https://hanxueqing.github.io/Web-Front-end-Interview-Q-A/
github地址:
https://github.com/Hanxueqing/Web-Front-end-Interview-Q-A

前端构建集成工具(打包)

什么是前端集成解决方案?

FIS(Front-end Integrated Solution)是专为解决前端开发中自动化工具、性能优化、模块化框架、开发规范、代码部署、开发流程等问题的工具框架。

前端集成解决方案解决了前端哪些问题?

  1. 开发团队代码风格不统一,如何强制开发规范。
  2. 前期开发的组件库如何维护和使用
  3. 如何模块化前端项目
  4. 服务器部署前必须压缩,检查流程如何简化,流程如何完善。

你使用过哪些前端构建集成?你用过什么打包工具?

  1. Gulp

gulp是工具链、构建工具,可以配合各种插件做js压缩,css压缩,less编译,替代手工实现自动化工作

(1)构建工具

(2)自动化

(3)提高效率用

  1. webpack

webpack是文件打包工具,可以把项目的各种js文、css文件等打包合并成一个或多个文件,主要用于模块化方案,预编译模块的方案

(1)打包工具

(2)模块化识别

(3)编译模块代码方案用

webpack打包

一款模块化打包工具,webpack是基于配置的,通过配置一些选项来让webpack执行打包任务

webpack在打包的时候,依靠依赖关系图,在打包的时候要告知webpack两个概念:入口和出口

plugins:在webpack编译用的是loader,但是有一些loader无法完成的任务,交由插件(plugin)来完成,插件的时候需要在配置项中配置plugins选项,值是数组,可以放入多个插件使用,而一般的插件都是一个构造器,我们,我们只需在plugins数组中放入该插件的实例即可。

loader:在webpack中专门有一些东西用来编译文件、处理文件,这些东西就叫loader。

webpack都用过哪些loader?

url-loader 可以将css中引入的图片(背景图)、js中生成的img图片处理一下,生成到打包目录里

url-loader/file-loader 将图片转成base64

html-withimg-loader 可以将html中img标签引入的img图片打包到打包目录

css-loader 可以将引入到js中的css代码给抽离出来

style-loader 可以将抽离出来的css代码放入到style标签中

sass-loader/less-loader sass/less预编译

postcss-loader 兼容前缀

babel-loader 将es6转成es5转成大部分浏览器可以识别的语法

vue-loader 把vue组件转换成js模块

为何要转译此模块?

可以动态的渲染一些数据,对三个标签做了优化
<template>  写虚拟dom
<script>  写es6语法
<style>   默认可以用scss语法,提供了作用域
并且开发阶段提供了热加载功能

注意:webpack中loader的使用是从后往前的

webpack都用过哪些plugins?

  • html-webpack-plugin:这个插件可以选择是否依据模板来生成一个打包好的html文件,在里面可以配置、title、template、filename、minify等选项。

  • optimize-css-assets-webpack-plugin:压缩css插件

  • extract-text-webpack-plugin:样式合并

  • webpack.optimize.UglifyJsPlugin:js合并

webpack入口属性

  • entry 入口文件

  • output出口文件

Gulp打包

gulp:基于流的前端自动化构建工具,基于流的任务式工具.pipe()

gulp的特点:自动化 基于流 插件很多

Gulp 的特点:

* 自动化 - Gulp 为你的工作流而服务,自动运行那些费事费力任务。

* 平台透明 - Gulp 被集成到各种 IDE 中,并且除了 NodeJS 之外,其他如 PHP、.NET、Java 平台都可以使用 Gulp。

* 强大生态系统 - 你可以使用 npm 上 2000+ 的插件来构造你的工作流。

* 简单 - Gulp 只提供几个 API,这可以很快地学习和上手。

Gulp 原生API

在进阶Gulp时,必须熟悉API中的四个方法的使用,在这里简单概括一下,更多信息查阅API

  1. gulp.src(globs[, options])

    返回符合匹配规则的虚拟文件对象流(Vinyl files)。

  2. gulp.dest(path[, options])

    用来指定要生成的文件的目录,目录路径为path。

  3. gulp.task(name[, deps], fn)

    定义一个流任务,任务名为name。

  4. gulp.watch(glob[, opts], tasks)

    监视文件的变化,执行操作。

Gulp插件

gulp-scss 编译css文件(注意:windows下使用 gulp-sass)

gulp-connect 来启动一个服务器

gulp-concat 合并js文件

gulp-uglify js文件压缩

gulp-rename重命名

gulp-minify-css 压缩css

gulp-babel 将es6代码转成es5

webpack和gulp有什么区别

  • gulp是基于流的构建工具:all in one的打包模式,输出一个js文件和一个css文件,优点是减少http请求,万金油方案。gulp强调的是前端开发的工作流程,我们可以通过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),然后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。

    PS:简单说就一个Task Runner。

  • webpack是模块化管理工具:all in js,使用webpack可以对模块进行压缩、预处理、打包、按需加载等。webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。

    PS:webpack is a module bundle

虽然都是前端自动化构建工具,但看他们的定位就知道不是对等的。

gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。

webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。

webpack和gulp有什么关系

Gulp和webpack在定义和用法上来说都不冲突,可以结合来使用。

gulp应该与grunt比较,而webpack应该与browserify(网上太多资料就这么说,这么说是没有错,不过单单这样一句话并不能让人清晰明了)。

gulp与webpack上是互补的,还是可替换的,取决于你项目的需求。如果只是个vue或react的单页应用,webpack也就够用;如果webpack某些功能使用起来麻烦甚至没有(雪碧图就没有),那就可以结合gulp一起用。

Gulp应该和Grunt比较

Gulp / Grunt 是一种工具,能够优化前端工作流程。比如自动刷新页面、combo、压缩css、js、编译less等等。简单来说,就是使用Gulp/Grunt,然后配置你需要的插件,就可以把以前需要手工做的事情让它帮你做了。

devDependencies和dependencies的区别

区别是:
dependencies 程序正常运行需要的包。
devDependencies 是开发需要的包,比如 一些单元测试的包之类的。
一个node package有两种依赖,一种是dependencies一种是devDependencies,
其中前者依赖的项该是正常运行该包时所需要的依赖项,
而后者则是开发的时候需要的依赖项,像一些进行单元测试之类的包。
如果将包下载下来在包的根目录里运行npm install默认会安装两种依赖,如果只是单纯的使用这个包而不需要进行一些改动测试之类的,可以使用npm install --production,只安装dependencies而不安装devDependencies。
如果是通过以下命令进行安装npm install packagename那么只会安装。dependencies,如果想要安装devDependencies,需要输入。npm install packagename --dev。

参考文章:

gulp与webpack的区别

https://www.cnblogs.com/lovesong/p/6413546.html

前端集成解决方案(webpack、gulp)

https://blog.csdn.net/qishuixian/article/details/79453343

CSS预处理器

什么是CSS预处理器?

CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加了一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行编码工作。

通俗的说,“CSS 预处理器用一种专门的编程语言,进行 Web 页面样式设计,然后再编译成正常的 CSS 文件,以供项目使用。CSS 预处理器为 CSS 增加一些编程的特性,无需考虑浏览器的兼容性问题”,例如你可以在 CSS 中使用变量、简单的逻辑程序、函数(如右侧代码编辑器中就使用了变量$color)等等在编程语言中的一些基本特性,可以让你的 CSS 更加简洁、适应性更强、可读性更佳,更易于代码的维护等诸多好处。

都有哪些常用的CSS预处理器?

SCSS、LESS、Stylus

Sass预编译的特性

  • 它使用自己语法并编译为可读的CSS

  • 可以在更少的时间内轻松的编写CSS代码

  • 是一个开源的预处理器,被解析为CSS

  • 可以兼容所有的CSS版本

为什么要用sass?

  • 它是预处理语言,它为CSS提供缩进语法(它自己的语法)

  • 它允许更有效的编写代码和易于维护

  • 它使用可重复使用的方法,逻辑语句和一些内置的函数

  • 它提供了比平面CSS好的结构格式和文档样式

Sass和Scss有什么区别?

Sass 和 SCSS 其实是同一种东西,我们平时都称之为 Sass,两者之间不同之处有以下两点:

文件扩展名不同,Sass 是以“.sass后缀为扩展名,而 SCSS 是以“.scss”后缀为扩展名;

语法书写方式不同,Sass 是以严格的缩进式语法规则来书写,不带大括号({})和分号(;),而 SCSS 的语法书写和我们的 CSS 语法书写方式非常类似。

Sass语法

(1)声明变量

普通变量:$美元符号+变量名称:变量值,定义之后可以在全局范围内使用

默认变量:sass 的默认变量仅需要在值后面加上*!default 即可。sass 的默认变量一般是用来设置默认值,然后根据需求来覆盖的,覆盖的方式也很简单,只需要重新声明下变量即可

特殊变量:一般情况下,我们定义的变量都是属性值,可以直接使用,但是如果变量作为属性或者其他的特殊情况下,必须使用#{$variable}的形式进行调用。

#{$variable} 就是取值的一种特殊形式,符合特殊用法。

全局变量:全局变量——在变量的后面加上[!global]即可声明全局变量。

(2)sass嵌套-选择器嵌套

SASS 中的嵌套主要说的是选择器嵌套和属性嵌套两种方式,正常项目中通常使用的都是选择器嵌套方案

【注】在嵌套的过程中,如果需要用到父元素,在 SASS 中通过&符号引用父属性

(3)sass嵌套-属性嵌套

嵌套属性——不常用

所谓属性嵌套,是指某些属性拥有同样的单词开头,如:border-left,border-color

都是以 border 开头的,所以就出现了属性嵌套语法

(4)sass混合-Mixin

sass 中可以通过@mixin 声明混合,可以传递参数,参数名称以$开始,多个参数之间使用

逗号分隔,@mixin 的混合代码块由@include 来调用

混合能使我们重用一整段sass代码,同时也可以给其传递参数。

定义一个混合,需用到@mixin关键字,后面跟自己定义的名字,若需要传参,则在名字之后加一对单括号(),同时在里面定义参数变量。通过调用@include关键字,来调用这段混合。

(5)sass继承拓展-@extend

在 SASS 中,通过继承/扩展来减少重复代码,可以让一个选择器去继承另一个选择中所有

的样式。

继承某个样式的同时,也会继承样式的扩展。

(6)Partitials和@import

Partials 是用来定义公共样式或者组件的样式的,专门用于被其他的 scss 文件 import进行使用的,在 SCSS 文件中引入指令@import 在引入 Partials 文件时,不需要添加下划线。详细参考案例代码。

(7)sass注释

SASS 中提供了三种注释

多行注释 在编译输出的 css 文件中会保留,压缩输出格式中不会保留 --style compressed

/*

* 多行注释

*/

单行注释 在输出 css 文件时不保留

​ // 单行注释

强制注释 在多行注释的开头,添加感叹号!表示强制保留

/*!

* 强制注释

*/

混合样式,怎么调用,调用需不需要携带参数

无参数混合

image
image
image

版本控制工具

SVN优缺点(集中式版本管理控制工具)

优点:

1、 管理方便,逻辑明确,符合一般人思维习惯。

2、 易于管理,集中式服务器更能保证安全性。

3、 代码一致性非常高。

4、 适合开发人数不多的项目开发。

缺点:

1、 服务器压力太大,数据库容量暴增。

2、 如果不能连接到服务器上,基本上不可以工作,看上面第二步,如果服务器不能连接上,就不能提交,还原,对比等等。

3、 不适合开源开发(开发人数非常非常多,但是Google app engine就是用svn的)。但是一般集中式管理的有非常明确的权限管理机制(例如分支访问限制),可以实现分层管理,从而很好的解决开发人数众多的问题。

Git优缺点(分布式版本管理控制工具)

优点:

1、适合分布式开发,强调个体。

2、公共服务器压力和数据量都不会太大。

3、速度快、灵活。

4、任意两个开发者之间可以很容易的解决冲突。

5、可以离线工作。

缺点:

1、学习周期相对而言比较长。

2、不符合常规思维。

3、代码保密性差,一旦开发者把整个库克隆下来就可以完全公开所有代码和版本信息。

GIT常用命令

git add * /文件名 将想要快照的内容写入缓存区

git commit -m "当前提交的日志"

git push -u origin master 提交代码

git status 查看当前工作区提交状态

git diff 比对 暂存区和工作区版本的区别

git checkout 切换分支命令

git reset --hard 版本号 恢复到指定的版本

GIT解决冲突

Git在push时如果版本比服务器上的旧,会提示先进行pull。问题是pull时如果服务器上的版本与你本地的版本在源文件修改上有冲突,那么在解决冲突前push都会失败。用git status可以查看冲突文件。

接下来用git diff指令查看具体哪里起冲突

这里还有一些其他指令,在冲突规模比较大的时候可以很方便的确认哪里不对。
git diff --ours:看本体分支对源文件的改动
git diff --theirs:看服务器分支对源文件的改动
git diff --base:看双方对源文件的改动,base和不加base的区别就是base选项会现实双方改动中即使不冲突的部分,默认diff则只会显示冲突部分。

参考:

Git 冲突的解决方法

https://www.jianshu.com/p/9382a0e3402a

git冲突解决的方法

https://www.cnblogs.com/nicknailo/p/9044238.html

GIT和SVN的区别

最核心的区别Git是分布式的,而Svn不是分布的

Git把内容按元数据方式存储,而SVN是按文件

Git的内容的完整性要优于SVN: GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

SVN的特点是简单,只是需要一个放代码的地方时用是OK的。

Git的特点版本控制可以不依赖网络做任何事情,对分支和合并有更好的支持(当然这是开发者最关心的地方),不过想各位能更好使用它,需要花点时间尝试下

SVN和Git对比结果

  1. git是分布式的scm,svn是集中式的。(最核心)

  2. git是每个历史版本都存储完整的文件,便于恢复,svn是存储差异文件,历史版本不可恢复。(核心)

  3. git可离线完成大部分操作,svn则不能。

  4. git有着更优雅的分支和合并实现。

  5. git有着更强的撤销修改和修改历史版本的能力

  6. git速度更快,效率更高。

基于以上区别,git有了很明显的优势,特别在于它具有的本地仓库。

JQuery

JQuery中获取网页元素的方法

//$的数据类型是一个函数
//alert(typeof $); //function

//$("#div1").css("background-color","red"); //id是div1
//$(".box").css("background-color","blue"); //classname是box
//$("ul .box").css("background-color","blue");//ul下的classname为box
//$("div").css("background-color","green");//div标签
//$("[name=hello]").css("background-color","orange");//name为hello
//$("div[id=div1]").css("background-color","orange");

JQuery中的工具方法

• type() 输出当前常量/变量的数据类型

• trim() 删除字符串的首尾空格

• inArray() 查找某一个元素,在数组中的下标

• proxy() 功能类似bind,预设this

• noConflict() 给$起一个别名

• parseJSON() 功能类似JSON.parse()

• $.makeArray() 将伪数组转成数组。

JQuery插件方法

$.extend() 拓展工具方法

$.fn.extend() 拓展JQ的方法

设计思想:高内聚低耦合

谈一下Jquery中的bind、live、delegate、on的区别?

on()方法-1.9版本整合了之前的三种方式的新事件绑定机制

.on( events [, selector ] [, data ], handler(eventObject) )

  • 使用.bind()方法是很浪费资源的,因为它要匹配选择器中的每一项并且挨个设置相同的事件处理程序
  • 建议停止使用.live()方法,因为它已经被弃用了,由于他有很多的问题
  • .delegate()方法“很划算”用来处理性能和响应动态添加元素的时候
  • 新的.on()方法主要是可以实现.bind() .live() 甚至 .delegate()的功能
  • 建议使用.on()方法,如果你的项目使用了1.7+的jQuery的话

参考:jQuery方法区别:click() bind() live() delegate()区别

https://www.cnblogs.com/zagelover/articles/2840762.html

关于jquery的事件委托-bind,live,delegate,on的区别

https://blog.csdn.net/qq_42164670/article/details/83450066

拖拽的三剑客

mousedown:记录被拖拽物体和鼠标按下位置相对距离

mousemove:让拖拽物体跟随鼠标去走,保持按下相对距离

mouseup:结束拖拽

Window.onload与$(document).ready(function(){})的区别是什么?

Window.onload:页面中只会出现一次,页面中所有元素都必须加载完毕才会去执行 。 $(document).ready()是JQ中的,可以执行多次 并且可以简写为:

$(function(){}) ,不需要等页面中所有元素加载,只需要dom挂载进来就可以执行回调函数。

1、执行时间上的区别:window.onload必须等到页面内(包括图片的)所有元素加载到浏览器中后才能执行。而$(document).ready(function(){})是DOM结构加载完毕后就会执行。
2、编写个数不同:window.onload不能同时写多个,如果有多个window.onload,则只有最后一个会执行,它会把前面的都覆盖掉。$(document).ready(function(){})则不同,它可以编写多个,并且每一个都会执行。
3、简写方法:window.onload没有简写的方法,$(document).ready(function(){})可以简写为$(function(){})。
另外:由于在$(document).ready()方法内注册的事件,只要DOM就绪就会被执行,因此可能此时元素的关联文件未下载完,例如与图片有关的HTML下载完毕,并且已经解析为DOM树了,但很有可能图片还未加载完毕,所以例如图片的高度和宽度这样的属性此时不一定有效。
要解决这个问题,可以使用JQuery中另一个关于页面加载的方法---load()方法。load()方法会在元素的onload事件中绑定一个处理函数。如果处理函数绑定在元素上,则会在元素的内容加载完毕后触发。如:$(window).load(function(){})=====window.onload = function(){}

Jquery中如何将数组转化为json字符串,然后再转化回来?

var arr = [{usernname:"张三",age:10}]
console.log($.parseJSON(JSON.stringify(arr)));

正则表达式

电话号码

function checkPhone(){ 
    var phone = document.getElementById('phone').value;
    if(!(/^1(3|4|5|6|7|8|9)\d{9}$/.test(phone))){ 
        alert("手机号码有误,请重填");  
        return false; 
    } 
}

邮箱号码

^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$

Vue—渐进式 JavaScript 框架

Vue相关概念

Vue的诞生

Vue是一个前端js框架,由尤雨溪开发,是个人项目,目前由饿了么ued团队进行维护。Vue近几年来特别的受关注,三年前的时候angularJS霸占前端JS框架市场很长时间,接着react框架横空出世,因为它有一个特性是虚拟DOM,从性能上碾轧angularJS,这个时候,vue1.0悄悄的问世了,它的优雅,轻便也吸引了一部分用户,开始收到关注,16年中旬,VUE2.0问世,这个时候vue不管从性能上,还是从成本上都隐隐超过了react,火的一塌糊涂,这个时候,angular开发团队也开发了angular2.0版本,并且更名为angular,吸收了react、vue的优点,加上angular本身的特点,也吸引到很多用户,目前已经迭代到5.0了。

注意下Vue的诞生时间,面试官如果问你是从什么时候开始接触并且使用Vue的,你如果回答用了5、6年了那场面就十分尴尬了。

Vue渐进式框架的理解?

在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统(components)、客户端路由(vue-router)、大规模状态管理(vuex)来构建一个完整的框架。更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念。

拓展:Vue2.0 中,“渐进式框架”和“自底向上增量开发的设计”这两个概念是什么?

https://www.zhihu.com/question/51907207

虚拟DOM

什么是虚拟DOM?

我们知道操作DOM的代价是昂贵的,所以vue2.0采用了虚拟DOM来代替对真实DOM的操作,最后通过某种机制来完成对真实DOM的更新,渲染视图。

所谓的虚拟DOM,其实就是用JS来模拟DOM结构,把DOM的变化操作放在JS层来做,尽量减少对DOM的操作 (个人认为主要是因为操作JS比操作DOM快了不知道多少倍,JS运行效率高)。然后对比前后两次的虚拟DOM的变化,只重新渲染变化了的部分,而没有变化的部分则不会重新渲染。

必须要注意一点的是:JS模拟的DOM结构并没有模拟所有DOM节点上的属性、方法(因为DOM节点本身的属性非常多,这也是DOM操作耗性能的一个点),而是只模拟了一部分和数据操作相关的属性和方法。

Virual DOM是用JS对象记录一个dom节点的副本,当dom发生更改时候,先用虚拟dom进行diff,算出最小差异,然后再修改真实dom。
当用传统的方式操作DOM的时候,浏览器会从构建DOM树开始从头到尾执行一遍流程,效率很低。而虚拟DOM是用javascript对象表示的,而操作javascript是很简便高效的。虚拟DOM和真正的DOM有一层映射关系,很多需要操作DOM的地方都会去操作虚拟DOM,最后统一一次更新DOM,因而可以提高性能。

虚拟DOM的缺点

  1. 代码更多,体积更大
  2. 内存占用增大
  3. 小量的单一的dom修改使用虚拟dom成本反而更高,不如直接修改真实dom快

VUE中虚拟dom操作流程

  1. 在内存中构建虚拟dom树

  2. 将内存中虚拟dom树渲染成真实dom结构

  3. 数据改变的时候,将之前的虚拟dom树结合新的数据生成新的虚拟dom树

  4. 将此次生成好的虚拟dom树和上一次的虚拟dom树进行一次比对(diff算法进行比对)

  5. 会将对比出来的差异进行重新渲染

参考:vue2.0的虚拟DOM渲染思路分析

https://www.jb51.net/article/145319.htm

v-for循环渲染为什么要设置key值?虚拟DOM与key值的关系?

(1)跟diff算法有关:如果在两个元素之间插入新元素,如果没有key的话,就需要把原位置的元素卸载了,把新元素插进来,然后依次卸载,会打乱后续元素的排列规则,如果有key值,只需要插入到对应位置即可,不会改变其他元素的走向。

(2)为了减免一些出错问题:例如在数组中,本来第一个是选中的,这时候我们再去添加新元素,如果没有key的话,那么新添加进来的元素就会被选中,加上key就是为了避免出现这样的问题。

参考:Vue 虚拟DOM与key属性

https://blog.csdn.net/weixin_42695446/article/details/84680213

虚拟DOM的Diff算法

虚拟DOM中,在DOM的状态发生变化时,虚拟DOM会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。

在Diff算法中,只平层的比较前后两棵DOM树的节点,没有进行深度的遍历。

computed计算属性跟watch监听的区别

computed计算属性是根据现有数据生成一个新的数据,并且两者会产生关联,建立永久缓存。当无关数据变化的时候,他不会重新计算,而是直接从缓存里面取之前的值。

watch监听依赖是单个的,他每次监听只能监听一个变量的改变。

参考:浅谈VUE虚拟dom

https://blog.csdn.net/mrliber/article/details/79036828

设计模式

VUE的设计模式MVVM

vue中采用了mvvm的设计模式,是从mvc/mvp演变过来的,mvvm主要解决了mvc反馈不及时的问题,或者实现了自动同步的功能。

也就是说model层改变的时候,我们不需要手动取更改dom。而vm帮助我们实现了这个效果,改变属性后该属性对应view会自动更新,view与model之间没有必然的联系,靠vm将两者进行关联。

MVC设计模式(单向通信)

image
  • 视图(View):用户界面。
  • 控制器(Controller):业务逻辑
  • 模型(Model):数据保存
  1. View 传送指令到 Controller
  2. Controller 完成业务逻辑后,要求 Model 改变状态
  3. Model 将新的数据发送到 View,用户得到反馈

MVP设计模式

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

image
  1. 各部分之间的通信,都是双向的。
  2. View 与 Model 不发生联系,都通过 Presenter 传递。
  3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

MVVM设计模式

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

image

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。AngularEmber 都采用这种模式。

参考:MVC,MVP 和 MVVM 的图示

http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html

MVC、MVP、MVVM三种区别及适用场合

https://blog.csdn.net/victoryzn/article/details/78392128

MVVM解决了什么问题

mvvm就是为了解决mvc反馈不及时的问题

MVVM与MVC的区别

MVVM实现了View和Model的自动同步,也就是当Model属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变(双向数据绑定原理)。

为什么model层数据更改的时候,vm就可以知道数据改变了呢?(vue的mvm框架的双向绑定原理是什么)

双向数据绑定:
当视图改变更新模型层
当模型层改变更新视图层

在Vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。Vue采用数据劫持&发布-订阅模式的方式,vue在创建vm的时候,会将数据配置在实例当中,然后通过Object.defineProperty对数据进行操作,为数据动态添加了getter与setter方法,当获取数据的时候会触发对应的getter方法,当设置数据的时候会触发对应的setter方法,从而进一步触发vm的watcher方法,然后数据更改,vm则会进一步触发视图更新操作。

参考:剖析Vue原理&实现双向绑定MVVM

https://segmentfault.com/a/1190000006599500

双向数据绑定的实现

要实现Vue中的双向数据绑定,大致可以划分三个模块:Observer、Compile、Watcher

  • Observer 数据监听器
    负责对数据对象的所有属性进行监听(数据劫持),监听到数据发生变化后通知订阅者。
  • Compiler 指令解析器
    扫描模板,并对指令进行 解析,然后绑定指定事件。
  • Watcher 订阅者
    关联Observer和Compile,能够订阅并收到属性变动的通知,执行指令绑定的相应操作,更新视图。
let data = {
    message:"hello world!"
}
let vm = new  Vue({
    el:"#app",
    data
});
// console.log(data);
// console.log(vm.$data);
//Object.defineProperty  es5提供的  ie8不支持 原理
let _data={

}
let middle = 123;
Object.defineProperty(_data,"msg",{ //给某个对象赋了一个属性 属性中提供了get方法和set方法
    get(){
        return middle; 调用_data.msg相当于调用get方法 直接返回123
    },
    set(val){
        // _data.msg = val;
        middle = val; 设置完之后middle=4
    }
});
console.log(_data.msg);//获取属性的时候,就会执行getter方法
_data.msg = 4;//设置属性的时候,就会执行setter方法 相当于给这个对象设置msg属性为4
console.log(_data.msg);

this.$nextTick()

用swiper时,有没有出现图片划不动的情况,原因是什么?你怎么解决的?

【VUE】swiper实例化后会出现划不动现象,产生的原因是本来这个地方是没有swiper-slide这个数据的,后续我们发送了ajax请求他才会动态生成swiperslide,banners数据立马改变了,它内部会生成新的虚拟dom和上一次虚拟dom结构作对比,然后产生新的真实dom,这个过程需要时间,但是我们立马实例化了,所以等到真实dom渲染完成后实例化早就结束了。解决方法就是,我们必须要等到因为数据改变了引发新的真实dom渲染完成后才会执行的操作,就可以避免这样的问题。

所以我们需要把实例化的过程写在this.$nextTick的回调函数中,在这个函数里面进行的操作就是等到数据更新而引发的页面当中新的虚拟dom所渲染成的真实dom真正渲染出来之后才执行,简单来说就是等到页面全部渲染完成后。

created(){
        this.$http.get("/api/v2/movie/in_theaters",{
            params:{
                count:6
            }
        }).then(res=>{
            console.log(res)
            this.banners = res.data.subjects
            //问题:异步请求 数据改变了 产生新的虚拟dom 需要与上一次虚拟dom结构对比 diff算法更新差异对比需要一定时间 数据渲染完成后直接实例化 就可能导致虚拟dom对比完成之后生成新的虚拟dom 这个实例化的代码早就执行完毕了
            this.$nextTick(()=>{//这个方法的作用就是 数据更改引发新的虚拟dom更新完毕 生成真实dom后 才会进入此函数的回调函数中 所以在这个回调函数中就可以拿到因数据改变而更新生成的真实dom
                new Swiper(".home-banner",{
                    loop:true
                })
            })
        })

this.$nextTick() 主要作用是等数据改变引发dom重新渲染完成之后才会执行

可以在实例化配置项中进行如下配置 (自己了解一下,但是面试官一般就想让你说this.$nextTick()这个知识点)

observer:true,
observeParents:true//修改swiper子元素的话,会自动帮助我们初始化swiper

生命周期

【初始化阶段(4个)】

(1)beforeCreate

此钩子函数不能获取到数据,dom元素也没有渲染出来,此钩子函数不会用来做什么事情。

(2)created

此钩子函数,数据已经挂载了,但是dom节点还是没有渲染出来,在这个钩子函数里面,如果同步更改数据的话,不会影响运行中钩子函数的执行。可以用来发送ajax请求,也可以做一些初始化事件的相关操作。

(3)beforeMount

代表dom节点马上要被渲染出来了,但是还没有真正的渲染出来,此钩子函数跟created钩子函数基本一样,也可以做一些初始化数据的配置。

(4)mounted

是生命周期初始化阶段的最后一个钩子函数,数据已经挂载完毕了,真实dom也可以获取到了。

【运行中阶段(2个)】

(5)beforeUpdate

运行中钩子函数beforeUpdate默认是不会执行的,当数据更改的时候,才会执行。数据更新的时候,先调用beforeUpdate,然后数据更新引发视图渲染完成之后,再会执行updated。运行时beforeUpdate这个钩子函数获取的数据还是更新之前的数据(获取的是更新前的dom内容),在这个钩子函数里面,千万不能对数据进行更改,会造成死循环。

(6)updated

这个钩子函数获取的数据是更新后的数据,生成新的虚拟dom,跟上一次的虚拟dom结构进行比较,比较出来差异(diff算法)后再渲染真实dom,当数据引发dom重新渲染的时候,在updated钩子函数里面就可以获取最新的真实dom了。

【销毁阶段(2个)】

(7)beforeDestroy

切换路由的时候,组件就会被销毁了,销毁之前执行beforeDestroy。在这个钩子函数里面,我们可以做一些善后的操作,例如可以清空一下全局的定时器(created钩子函数绑定的初始化阶段的事件)、清除事件绑定。

(8)destoryed

组件销毁后执行destroyed,销毁后组件的双向数据绑定、事件监听watcher相关的都被移除掉了,但是组件的真实dom结构还是存在在页面中的。

官网这张生命周期流程图要熟练掌握,在面试官问到的时候最好能将这张图画出来,针对每个函数的功能做详细解答,整个过程至少保持在3-5分钟。如果面试官很有耐心,还想继续听你说下去,可以拓展keep-alive标签的active和deactive这两个生命周期函数,属于加分项。

image

组件

什么是组件?

WEB中的组件其实就是页面组成的一部分,好比是电脑中的每一个元件(如硬盘、键盘、鼠标),它是一个具有独立的逻辑和功能或界面,同时又能根据规定的接口规则进行相互融合,变成一个完整的应用。
页面就是由一个个类似这样的部分组成的,比如导航、列表、弹窗、下拉菜单。页面只不过是这些组件的容器,组件自由组合形成功能完整的界面,当不需要某个组件,或者想要替换某个组件时,可以随时进行替换和删除,而不影响整个应用的运行。
【注】前端组件化的核心思想就是将一个巨大复杂的东西拆分成粒度合理的小东西。

页面中能被复用的内容都被称之为组件,你所能看到的一些结构,像头部Header、Footer、Banner都可以被封装成一个组件去复用,组件就是集成了html、css、js、image的一个聚合体。

使用组件的好处?

(1)提高开发效率
(2)方便重复使用
(3)简化调试步骤
(4)提升整个项目的可维护性
(5)便于协同开发

组件的特性

高内聚,低耦合

为什么要封装组件?

(1)解耦

(2)提升组件复用性

组件封装的案例

<div id="demo">
    <v-header></v-header>
</div>

<template id="header">
    <div>
        {{msg}}<input/>
    </div>
</template>

var Header = {
    template:"#header",
    data(){
        return {
           msg:"v-header的msg!!!" 
        }
    }
};

new Vue({
    el:"#demo",
    components:{
        "v-header":Header
    }
});

组件中的data是什么类型?为什么是一个函数?

实例中的data是什么类型?

为什么实例中的data是一个对象,组件中的data是一个函数?

为了防止组件与组件之间的数据共享,让作用域独立,data是函数内部返回一个对象,让每个组件或者实例可以维护一份被返回对象的独立的拷贝。组件可以被复用,但是数据不能共享,每个组件管理自己的数据更新,不能影响其他组件的数据。

参考:data必须是一个函数(VUE官网)

https://vue.docschina.org/v2/guide/components.html#data-%E5%BF%85%E9%A1%BB%E6%98%AF%E4%B8%80%E4%B8%AA%E5%87%BD%E6%95%B0

组件的 data 选项必须是一个函数,以便每个实例都可以维护「函数返回的数据对象」的彼此独立的数据副本。

image

vue组件传值

父子组件通信

1、父子组件通过prop传递数据

父组件可以将一条数据传递给子组件,这条数据可以是动态的,父组件的数据更改的时候,子组件接收的也会变化

子组件被动的接收父组件的数据,子组件不要再更改这条数据了。

组件实例的作用域是孤立的,父组件不能直接使用子组件的数据,子组件也不能直接使用父组件的数据。

父组件在调用子组件的时候给子组件传递数据:

<template id="father">
        <div class="father">
            <p>我是父组件,这是我的fMsg:{{fMsg}}</p>
            <input type = "text" v-model = "fMsg">
            <hr>
            <son msg = "你好"></son>
        </div>
    </template>

父组件给子组件传递数据的时候,子组件需要利用props的属性来确定自己的预期数据,如果儿子没有通过props属性接受传递过来的数据,则数据会以自定义属性的方式,放在儿子最外层的根元素上面。

image

子组件通过props来接受父组件传递过来的数据,并且通过{{msg}}使用

components:{
            son:{
                template:"<div>我是son子组件!这是父组件传递给我的msg:{{msg}}</div>",
                //接收父组件传递来的属性  msg
                props:["msg"]
            }
        }
image

2、父组件通过v-bind指令传递自身变量给子组件

我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件。

<template id="father">
        <div class="father">
            <p>我是父组件,这是我的fMsg:{{fMsg}}</p>
            <input type = "text" v-model = "fMsg">
            <hr>
            <!-- <son msg = "你好"></son> -->
            <son :msg = "fMsg"></son>
        </div>
    </template>

如果如果父组件传递属性给子组件的时候键名有'-'

<son :f-msg = "fMsg"></son>

子组件接收、使用的时候写成小驼峰的模式

components:{
            son:{
                template:"<div>我是son子组件!这是父组件传递给我的msg:{{fMsg}}</div>",
                //接收父组件传递来的属性  msg
                props:["fMsg"]
            }
        }

3、父子组件依靠应用类型的地址传递共享数据

单向数据流

Prop 是单向绑定的:当父组件的属性变化时,将传递给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。

<template id="father">
        <div class="father">
            <input type = "text" v-model = "message">
            <hr>
            <son :message = "message"></son>
        </div>
    </template>

    <template id = "son">
        <div>
            <p>这是子组件</p>
            <input type = "text" v-model = "message"></input>
        </div>
    </template>

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

image

所以如果我们想实现父子间的数据共享,依靠的就是应用类型的地址传递,应将message写成对象的形式,传递的时候将对象传递给子组件,子组件引用的时候使用对象的value值。

<template id="father">
        <div class="father">
            <input type = "text" v-model = "message.value">
            <hr>
            <!-- 传递的时候将对象传递给子组件 -->
            <son :message = "message"></son>
        </div>
    </template>

    <template id = "son">
        <div>
            <p>这是子组件</p>
            <!-- 引用的时候使用对象的value值 -->
            <input type = "text" v-model = "message.value"></input>
        </div>
    </template>

这时候更改父组件的value值,子组件的数据同步更改,子组件修改value值的时候也同步修改了父组件的数据。这是因为不管是子组件还是父组件,我们操作的都是同一个对象,父组件直接把引用类型的地址传递给子组件,子组件没有直接修改对象,只是更改了里面的属性值。

父组件如果将一个引用类型的动态数据传递给子组件的时候,数据会变成双向控制的,子组件改数据的时候父组件也能接收到数据变化,因为子组件改的时候不是在改数据(地址),而是在改数据里的内容,也就是说引用类型数据的地址始终没有变化,不算改父组件数据。

注意:在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。 message:{val:""}

父子间数据共享(双向控制),基本不会使用,违背了单向数据流(父=》子)

4、viewmodel关系链

在组件间可以用过ref形成ref链,组件还拥有一个关系链($parent),通过这两种链;理论来说,任意的两个组件都可以互相访问,互相进行通信。

$parent:父组件

$children:子组件

$root:根组件

image

当子组件在set方法中修改父组件传递过来的值时,系统会报错,因为子组件不能修改父组件的数据。

Vue.component("bbb",{
        template:"#bbb",
        props:["msg"],
        computed:{
            /* ownMessage(){
                return this.msg;
            } */
            ownMessage:{
                get(){
                    return this.msg;
                },
                set(val){
                    this.msg = val //系统报错:子组件不能更改父组件传递的数据
                }
            }
        }
    })
image

所以这时候要使用$parent,让父组件自己更改自己的数据

set(val){
                    // this.msg = val //系统报错:子组件不能更改父组件传递的数据
                    // console.log(this)
                    // 相当于父组件自己更改了msg数据
                    this.$parent.msg = val;
                }

5、父组件通过ref标记获取子组件的数据

父组件在调用子组件的时候使用ref做标记

<template id="aaa">
        <div>
            <button @click = "get">点击获取bbb数据</button>
            <!-- 组件间不仅可以用过$root/$parent/$children来获取对应关系的组件,父组件还可以主动的通过ref为子组件做标记 -->
            <bbb ref = "b"></bbb>
        </div>
    </template>

父组件的this属性上有$refs标记,通过refs标记拿到子组件

image
// 通过ref标记更改子组件的数据
// this.$refs.b.message = "哈哈"

组件间不仅可以用过$parent/children/root来获取对应关系的组件,父组件还可以主动的通过ref为子组件做标记 也可以给dom做标记,也会形成ref链,也可以交互.

<button ref="btn" @click="get">get</button>
<bbb ref="b></bbb>  
image

注意多个子组件标记的是同一个键名,获取到的应该是一个数组

<bbb ref = "b" v-for = "(item,index) in 3" :key = "index"></bbb>
image
// 通过下标修改对应的数值
this.$refs.b[0].message = "哈哈"

子父组件通信

1、子组件通过父组件传递的方法来更改父组件的数据

父组件可以将更改自身数据的方法传递给子组件,子组件调用这个方法的时候,就可以给父组件传递数据,父组件被动的接收子组件的数据。

子组件声明一条自身的msg

Vue.component("son",{
        template:"#son",
        // 子组件接收父组件传递过来的方法
        props:["change"],
        data(){
            return{
                msg:"我是子组件"
            }
        }
    })

父组件先声明一条自己的数据

data(){
            return{
                // 父组件先声明一条自己的数据
                parentMsg:""
            }
        }

再写一个可以更改自身数据的方法

methods:{
            // 写一个可以更改自身数据的方法
            change(msg){
                this.parentMsg = msg
            }
        }

将写好的change方法传递给子组件

<template id="father">
        <div>
            <p>这是父组件</p>
            <p>子组件传递过来的值是:{{parentMsg}}</p>
            <hr>
            <!-- 调用子组件的时候,将更改自身数据的方法传递给子组件 -->
            <son :change = "change"></son>
        </div>
    </template>

子组件通过props接收父组件传递过来的change方法

props:["change"]

给p标签添加点击事件,点击即触发change方法,同时将自身的msg传递给父组件,相当于父组件的change方法被执行。

<template id="son">
        <div>
            <p>子组件说:{{msg}}</p>
            <p @click = "change(msg)">点击我触发父亲的change方法</p> 
        </div>
    </template>

父组件可以在页面中渲染子组件传递过来的数据

<p>子组件传递过来的值是:{{parentMsg}}</p>

2、通过自定义事件实现子父通信

每一个组件或者实例都会有自定义事件,和触发事件的能力,父组件给子组件绑定一个自定义事件,这个事件的处理程序却是父组件的一个方法,当子组件触发这个事件的时候,相当于父组件的方法被执行。

父组件想获取子组件的数据时,在调用子组件的时候给子组件绑定一个自定义事件change-event

<template id="father">
        <div>
            <p>这是父组件</p>
            <p>子组件传递过来的值是:{{parentMsg}}</p>
            <hr>
            <!-- 给子组件绑定一个自定义事件 -->
            <son @change-event = "change"></son>
        </div>
    </template>

在子组件中定义一个点击事件,点击p标签执行changeWord方法

<p @click = "changeWord">点击我触发父亲的change方法</p> 

在方法中编写changeWord方法,通过this.$emit来触发绑定在自己身上的自定义事件,第一个参数为事件名称change-event,第二个参数为触发这个函数的时候给他传递的数值:自身的msg。

methods:{
            changeWord(){
                //触发自身绑定的change事件
                this.$emit("change-event",this.msg)//第一个参数为触发事件的名字,第二个参数为触发这个函数的时候给他传递的数值
            }
        }

一旦触发绑定在自身上的自定义事件,相当于父组件的change方法被执行。

兄弟组件通信

1、通过viewmodel关系链

定义哥哥组件,给哥哥组件添加一个点击事件,点击触发hitLittle方法

<template id = "big-brother">
        <div>
            <p>我是哥哥</p>
            <button @click = "hitLittle">打弟弟</button>
        </div>
    </template>

定义弟弟组件,给弟弟组件添加一个p标签,由crying数据控制其显示与隐藏

<template id="little-brother">
        <div>
            <p>我是弟弟</p>
            <p v-if = "crying">呜呜呜</p>
        </div>
    </template>

在弟弟组件的data中声明crying数据,默认为false

Vue.component("little-brother",{
        template:"#little-brother",
        data(){
            return{
                crying:false
            }
        }
    })

在哥哥组件的methods中定义hitLittle方法,通过viewmodel关系链更改弟弟组件中的crying方法

 Vue.component("big-brother",{
        template:"#big-brother",
        methods:{
            hitLittle(){
                //在兄弟组件之间的通信,可以采用关系链和ref链去使用,解决兄弟之间通信问题。
                this.$parent.$children[1].crying = true;//让littel改变自身的crying状态
            }
        }
    })

2、viewmodel关系链+ref链

在弟弟组件中添加ref标记

<little-brother ref = "little"></little-brother>

在哥哥组件的hitLittle方法中通过viewmodel和ref链配合使用更改弟弟组件中的crying数据

hitLittle(){
                //在兄弟组件之间的通信,可以采用关系链和ref链去使用,解决兄弟之间通信问题。
                // this.$parent.$children[1].crying = true;//让littel改变自身的crying状态
                
                //viewmodel链和ref链配合使用
                this.$parent.$refs.little.crying = true;
            }

3、eventbus事件总线

创建一个空的实例

var angle = new Vue();

弟弟组件自己定义一个更改自身状态的方法

methods:{
            cry(){
                this.crying = true
            }
        }

在mounted生命周期函数中绑定一个自定义事件,第一个参数为自定义事件名,第二个函数为需要处理的函数

mounted(){
            // 绑定一个自定义事件,第一个参数为自定义事件名,第二个函数为需要处理的函数
            angle.$on("hit-little",this.cry)
        }

在哥哥组件中触发自定义事件

hitLittle(){
                //触发little-brother组件的hit-little事件
                angle.$emit("hit-little")
            }

keep-alive缓存

keep-alive在项目中如何应用?

当在组件之间进行切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。当从a页面跳转到b页面再跳转回a页面时,可以不用再次进行数据请求,直接从缓存里面获取数据,减少性能消耗。

例如我们来展开说一说这个多标签界面:

你会注意到,如果你选择了一篇文章,切换到 Archive 标签,然后再切换回 Posts,是不会继续展示你之前选择的文章的。这是因为你每次切换新标签的时候,Vue 都创建了一个新的 currentTabComponent 实例。

重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive> 元素将其动态组件包裹起来。

keep-alive的生命周期函数

当组件在<keep-alive>内被切换时,它的activeddeactived这两个生命周期钩子函数将会被对应执行,初始化操作放在actived里面,一旦切换组件,因为组件没有被销毁,所以它不会执行销毁阶段的钩子函数,所以移除操作需要放在deactived里面,在里面进行一些善后操作,这个时候created钩子函数只会执行一次,销毁的钩子函数一直没有执行。

keep-alive的属性

Keep-alive提供了两个属性:允许组件有条件的缓存
include:我只需要缓存哪一些组件
exclude:除了这个组件之外其他组件都会被缓存

原理:
在created的时候,将需要缓存的虚拟dom节点放到cache中,在render的时候根据name再进行取出。

keep-alive只能在组件切换的标签中进行缓存

<keep-alive include="indexCom">
      <router-view/>
    </keep-alive>

使用传统写法比较麻烦,我们可以在需要被缓存的页面的路由中添加keepAlive:true字段

{
    path: '/',
    name: 'index',
    meta:{
        keepAlive:true //该字段表明页面需要缓存
    },
    component: resolve=>require(["@/page/index"],resolve)
}

在组件切换的时候,检查下原数据上面有没有keepAlive这个属性,如果有的话就keep-alive缓存一下,没有的话就该怎么显示怎么显示。

<keep-alive>
  <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

指令

说一下vue常用的内置指令

1、v-bind:响应并更新DOM特性;例如:v-bind:href v-bind:class v-bind:title v-bind:bb

2、v-on:用于监听DOM事件; 例如:v-on:click v-on:keyup

3、v-model:数据双向绑定;用于表单输入等;例如:<input v-model="message">

4、v-show:条件渲染指令,为DOM设置css的style属性

5、v-if:条件渲染指令,动态在DOM内添加或删除DOM元素

6、v-else:条件渲染指令,必须跟v-if成对使用

7、v-for:循环指令;例如:<li v-for="(item,index) in todos"></li>

8、v-else-if:判断多层条件,必须跟v-if成对使用;

9、v-text:更新元素的textContent;例如:<span v-text="msg"></span> 等同于 <span>{{msg}}</span>;

10、v-html:更新元素的innerHTML;

11、v-pre:不需要表达式,跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度;例如:<span v-pre>{{ this will not be compiled }}</span>;

12、v-cloak:不需要表达式,这个指令保持在元素上直到关联实例结束编译;

13、v-once:不需要表达式,只渲染元素或组件一次,随后的渲染,组件/元素以及下面的子元素都当成静态页面不在渲染。

如何注册自定义指令

使用Vue.directive(id,definition)注册全局自定义指令,接收两个参数,指令ID以及定义对象。使用组件的directives选项注册局部自定义指令。

自定义指令的钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

unbind:只调用一次,指令与元素解绑时调用。

拓展:自定义指令(VUE官网)

https://cn.vuejs.org/v2/guide/custom-directive.html#ad

v-model是什么?

v-model是用于表单的据双向绑定的指令

如何自己去实现一个v-model数据绑定?

两个步骤:

  1. v-bind 绑定了一个value的属性

  2. v-on 把当前元素绑定到了一个事件上

    <template>
        <div class="logs">
            logs!!!
            <p><input type="text" v-model="msg"></p>
            <p>{{msg}}</p>
            <hr>
            <p><input type="text" :value="msg2" @input="msg2 = $event.target.value"></p>
            <p>{{msg2}}</p>
        </div>
    </template>
    
    <script>
    export default {
        name:"logs",
        data(){
            return {
                msg:"helloworld",
                msg2:""
            }
        }
    }
    </script>
    

v-if和v-show的区别

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。 v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

路由守卫

讲一下都有哪些路由守卫?

全局前置守卫:router.beforeEach 在路由切换开始时候调用

全局后置守卫:roter.afterEach 在路由切换离开时候调用

局部路由守卫:beforeEnter 写在路由对象里

组件内的守卫:

beforeRouteEnter 路由进入到这个组件之前调用,不能获取当前组件对象 this

beforeRouteUpdate 在组件被复用时调用

beforeRouteLeave 路由离开这个组件之后调用,能获取当前组件对象 this

路由守卫中的参数都是什么意思

每个钩子方法接收三个参数:

  • to: Route: 即将要进入的目标,路由对象
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
  • next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。

确保要调用 next 方法,否则钩子就不会被 resolved。

具体说一下每个路由守卫的功能

导航守卫(路由钩子/路由守卫/导航守卫/导航钩子/路由生命周期)

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的,单个路由独享的,,或者组件级的。

在某些情况下,当路由跳转前或跳转后、进入、离开某一个路由前、后,需要做某些操作,就可以使用路由钩子来监听路由的变化。

全局守卫

(1)全局前置守卫beforeEach

当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中,所以页面不会加载任何内容。

image

一定要在路由守卫中调用next()方法来resolve这个钩子

// 全局前置路由
router.beforeEach((to,from,next)=>{
  console.log("beforeEach:全局前置守卫")
  // 一定要调用next()方法来resolve这个钩子
  next();
})
image

(2)全局后置守卫afterEach

也可以注册全局后置钩子,然而和前置守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

// 全局后置钩子
router.afterEach((to,from)=>{
  if (to.path === "/list/audio") {
    alert("已经进入list列表audio页面")
  }
})
image

局部守卫

路由独享的守卫

在路由配置上直接定义beforeEnter守卫:

{ path :"/mine",component:()=>import("../views/Mine"),beforeEnter(to,from,next){
      console.log("进入到mine页面了")
      next();
    }}
image

组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫:

  1. beforeRouteEnter

    在进入该组件之前执行,该路由守卫中获取不到实例this,因为此时组件实例还没被创建。beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

    在Home.vue中添加beforeRouteEnter:

    beforeRouteEnter(to,from,next){
        console.log("beforeRouteEnter:进入组件之前",this)
        next()
      }
    

    打印this,显示undefined

    image

    不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

    beforeRouteEnter (to, from, next) {
      next(vm => {
        // 通过 `vm` 访问组件实例
      })
    }
    

    注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

  1. beforeRouteLeave

    在离开该组件之前执行,该路由守卫中可以访问组件实例"this"。

    在Home.vue中添加beforeRouteLeave:

      beforeRouteLeave(to,from,next){
        console.log("beforeRouteLeave:离开组件之前",this)
        next()
      }
    

    此时可以打印组件实例"this"

    image

    这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

  1. beforeRouteUpdate(2.2 新增)

    当一个组件被重复调用的时候执行该守卫

    在Deatil.vue中添加beforeRouteUpdate:

        beforeRouteUpdate(to,from,next){
            console.log("beforeRouteUpdate")
            next()
        }
    
    image

在vue项目中哪里用到过路由守卫?举一个使用到路由守卫的案例

我们想实现当路由变化的时候头部信息动态更改,我们先把内容写成这种样式:

template中:

<div class = "left">
            <i :class = "['fa','fa-' + icon]"></i>
            <span>{{title}}</span>
        </div>

data中:

data(){
        return{
            icon:"home",
            title:"豆瓣首页",
        }
    },

这时候就需要用到路由守卫,当路由切换的时候可以做一些业务逻辑,首先需要引入全局路由

import router from  "@/router"

在全局前置路由守卫router.beforeEach中,使用switch语句来匹配,根据路由name属性来设置对应的title和icon:

created(){
        router.beforeEach((to,from,next)=>{
            switch(to.name){
                case "home":
                    this.title = "豆瓣首页"
                    this.icon = "home"
                    break;
                case "audio":
                    this.title = "豆瓣影音"
                    this.icon = "audio-description"
                    break;
                case "broadcast":
                    this.title = "豆瓣广播"
                    this.icon = "caret-square-o-left"
                    break;
                case "group":
                    this.title = "豆瓣小组"
                    this.icon = "group"
                    break;
                case "mine":
                    this.title = "豆瓣我的"
                    this.icon = "cog"
                    break;
                default:
                    break;

            }
            next();
        })
    }

路由

什么是SPA单页应用?你怎么理解单页应用?

现在的应用都流行SPA(single page application),传统的项目大多使用多页面结构,需要切换内容的时候我们往往会进行单个html文件的跳转,这个时候受网络、性能影响,浏览器会出现不定时间的空白界面,用户体验不好。

单页面应用就是用户通过某些操作更改地址栏url之后,动态地进行不同模板内容的无刷新切换,用户体验好。

单页应用的优点

  1. 更好的用户体验,让用户在web感受natvie的速度和流畅;
  2. 经典MVC开发模式,前后端各负其责。
  3. 一套Server API,多端使用(web、移动APP等)
  4. 重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交;

Vue切换路由的方法?vue-router的原理?

Vue中会使用官方提供的vue-router插件来使用单页面,原理就是通过检测地址栏变化后将对应的路由组件进行切换(卸载和安装)。

  1. 引入vue-router,如果是在脚手架中,引入VueRouter之后,需要通过Vue.use来注册插件
  2. 创建router路由器
  3. 创建路由表并配置在路由器中
  4. 在根实例里注入router,目的是为了让所有的组件里都能通过this.$router/route来使用路由的相关功能api
  5. 利用router-view来指定路由切换的位置
  6. 使用router-link来创建切换的工具,默认会渲染成a标签,添加to属性来设置要更改的path信息,且会根据当前路由的变化为a标签添加对应的router-link-active/router-link-exact-active(完全匹配成功)类名。

router-link标签的属性

<router-link> 组件支持用户在具有路由功能的应用中(点击)导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 标签,可以通过配置 tag 属性生成别的标签。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。

to

router-link的to属性,默认写的是path(路由的路径),可以通过设置一个对象,来匹配更多。

<router-link tag = "li" :to = "{name:'detail',params:{id:'1'},query:{title:'最近播放'}}">我的歌单</router-link>

name

name是要跳转的路由的名字,也可以写path来指定路径,但是用path的时候就不能使用params传参,params是传路由参数,query传queryString参数。

replace

路由跳转到不同的url默认是push的过程,当用户点击浏览器后退按钮式时,则回到之前url,replace属性可以控制router-link的跳转不被记录。

依次点击首页——列表——音频——视频——我的,点击返回按钮时,依次返回之前的url。

image

在List.vue的<router-link>标签中添加replace属性,则该标签内的跳转不会被记录

<router-link
        v-for = "nav in navs"
        :key = "nav.id"
        :to = "{name:nav.name}"
        active-class = "title"
        replace
      >
        {{nav.title}}
      </router-link>

再依次点击首页——列表——音频——视频——我的,点击返回按钮时,可以看到从音频——视频之间的跳转没有被记录。

image

active-class

<router-link>且会根据当前路由的变化为a标签添加对应的router-link-active/router-link-exact-active(完全匹配成功)类名,我们可以通过它来为标签设置不同的选中样式。

<style lang="scss">
.router-link-active{
    color:blue;
  }
  .router-link-exact-active{
    color:red;
    font-weight:900;
  }
</style>

标签切换时,选中状态的显示效果:

image

还可以通过<router-link>的active-class属性给a标签添加指定一个activeClass名,通过设置这个class的样式,来设置标签选中时的状态,功能类似于router-link-exact-active。

<router-link
        v-for = "nav in navs"
        :key = "nav.id"
        :to = "nav.path"
        active-class = "title"
      >
        {{nav.title}}
      </router-link>

设置.title样式

<style lang = "scss" scoped>
  .title{
    color:aquamarine;
  }
</style>

显示效果:

image

tag

<router-link>默认渲染成带有正确链接的a标签,可以通过配置 tag 属性生成别的标签。

<ul>
      <router-link tag = "li">我的歌单</router-link>
      <router-link tag = "li">最近播放</router-link>
    </ul>
image

路由跳转的方式

  1. <router-link> 组件支持用户在具有路由功能的应用中(点击)导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的a标签,可以通过配置 tag 属性生成别的标签。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。

  2. 编程式导航:this.$router.replace

    This.$router.go

一级路由的实现

  1. 如果不是使用脚手架创建的项目,需要手动安装vue-router路由模块。

     cnpm install vue-router -S
    
  2. 引入vue-router,如果是在脚手架中,引入VueRouter之后,需要通过Vue.use来注册插件。

    import Vue from 'vue'
    import Router from 'vue-router'
    Vue.use(Router)//让vue可以使用vue-router插件
    

    注册vue-router插件之后,this上就有了$route/router属性。

image
  1. 创建router路由器,并导出。

    let router = new Router({
      routes:[
    
      ]
    })
    
    export default router;
    
  2. 在根实例里注入router

    new Vue({
      //挂载router
      router,//为了可以让组件使用this.$route,this.$router的一些api属性或者方法
      store,
      render: h => h(App)
    }).$mount('#app')
    
    

    此时组件可以通过this.$router/router来使用路由相关功能的api。

    image
  3. 创建router路由表

    let router = new Router({
      routes:[
        {
          path:"/home",component:Home
        },
        {
          path: "/list", component: List
        },
        {
          path: "/mine", component: Mine
        }
      ]
    })
    
  4. 在App.vue中利用router-view来指定路由切换的位置

    <template>
      <div id="app">
        Hello app.vue!
        <!-- 显示路由组件的位置 -->
        <router-view></router-view>
      </div>
    </template>
    

    路由切换效果:

    image
  5. 使用router-link来创建切换路由的工具

    <div>
          <router-link to = "/home">首页</router-link>
          <router-link to = "/list">列表</router-link>
          <router-link to = "/mine">我的</router-link>
        </div>
    
    image

    <router-link>会渲染成a标签,添加to属性来设置要更改的path信息

    image

二级路由(路由嵌套)

在创建路由表的时候,可以为每一个路由对象创建children属性,值为数组,在这个里面又可以配置一些路由对象来使用多级路由,注意:一级路由path前加'/',二级路由前不需要加'/'。

{ path :"/list",component:()=>import("../views/List"),children:[
       // 二级路由前不需要加“/”
      { path: "audio", component: () => import("../views/Audio") },
      { path: "video", component: () => import("../views/Video") }
    ]}

二级路由组件的切换位置依然由router-view来指定(指定在父级路由组件的模板中)。

动态路由

在router路由表中配置动态路由,下面的代码就是给detail路由配置接收id的参数,多个参数继续在后面设置。

// 配置动态路由
    { path:"/detail/:id",component:()=>import("../views/Detail.vue")}

在页面中通过<router-link>的to属性传递参数:

<ul>
      <router-link tag = "li" to = "/detail/1">我的歌单</router-link>
      <router-link tag = "li" to = "/detail/2">最近播放</router-link>
    </ul>

在Detail.vue中打印this.$route.params.id

export default {
    created(){
        //获取动态路由传递过来的参数
        console.log(this.$route.params.id)
    }
}

在页面中通过$route.params.id渲染获取到的动态id

<template>
    <div class = "detail">
        我是详情页,我的动态id是:{{$route.params.id}}
    </div>
</template>
image

一个vue组件中能有多个router-view吗?为什么

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

参考:命名识图(VUE官网)

https://router.vuejs.org/zh/guide/essentials/named-views.html#%E5%B5%8C%E5%A5%97%E5%91%BD%E5%90%8D%E8%A7%86%E5%9B%BE

Vue路由传参有几种方式

路由参数

在router路由表中配置动态路由,下面的代码就是给detail路由配置接收id的参数,多个参数继续在后面设置。

// 配置动态路由
    { path:"/detail/:id",component:()=>import("../views/Detail.vue")}

在页面中通过<router-link>的to属性传递参数:

<ul>
      <router-link tag = "li" to = "/detail/1">我的歌单</router-link>
      <router-link tag = "li" to = "/detail/2">最近播放</router-link>
    </ul>

在Detail.vue中打印this.$route.params.id

export default {
    created(){
        //获取动态路由传递过来的参数
        console.log(this.$route.params.id)
    }
}

在页面中通过$route.params.id渲染获取到的动态id

<template>
    <div class = "detail">
        我是详情页,我的动态id是:{{$route.params.id}}
    </div>
</template>
image

queryString参数

queryString参数不需要在路由表设置接收,直接设置?后面的内容:

<ul>
      <router-link tag = "li" to = "/detail/1?title=我的歌单">我的歌单</router-link>
      <router-link tag = "li" to = "/detail/2?title=最近播放">最近播放</router-link>
    </ul>

在路由组件中通过this.$route.query接收

export default {
    created(){
        //获取动态路由传递过来的参数
        console.log(this.$route.params.id,this.$route.query.title)
    }
}

打印结果:

image

页面渲染效果:

image

上面的参数传递也可以写成对象的形式:

<!-- 写成对象的形式 -->
      <router-link tag = "li" :to = "{path:'/detail/2',query:{title:'最近播放'}}">最近播放</router-link>

通过prop将路由与组件解耦

在组件中接收路由参数需要this.$route.params.id,代码冗余,现在可以在路由表里配置props:true。

{ path:"/detail/:id",component:()=>import("../views/Detail.vue"),name:"detail",props:true}

在路由组件中可以通过props接收id参数去使用

props:["id"],

在页面中就可以通过{{id}}的方式来使用路由传递的参数

我的动态id是:{{id}}

路由模式

为了构建SPA(单页面应用),需要引入前端路由系统,这也就是Vue-router存在的意义。前端路由的核心,就在于:改变视图的同时不会向后端发出请求。

hash和history这两个方法应用于浏览器的历史记录站,在当前已有的back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改是,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。

hash:即地址栏URL中的#符号(此hsah 不是密码学里的散列运算)

路由有两种模式:hash、history,默认会使用hash模式

image

比如这个URL:http://www.baidu.com/#/hello, hash 的值为#/hello。它的特点在于:hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。

history:利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法。(需要特定浏览器支持)

如果url里不想出现丑陋hash值(#),在new VueRouter的时候配置mode值为history来改变路由模式,本质使用H5的histroy.pushState方法来更改url,不会引起刷新。

// 默认会使用hash模式
  mode:"history",
image

history模式,会出现404 的情况,需要后台配置。

hash模式和history模式在发生404错误时:

  1. hash模式下,仅hash符号之前的内容会被包含在请求中,当用户访问https://www.baidu.com/#/home时实际请求的是:https://www.baidu.com/。因此对于后端来说,即使没有做到对路由的全覆盖,页面也不会返回404错误;

    image

  2. history模式下,前端的url必须和实际向后端发起请求的url 一致,因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 https://www.baidu.com/home/detail,缺少对/home/detail的路由处理,就会返回 404错误,这就不好看了。

    image

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

hash哈希路由的原理

window.onload = function(){
            //当hash发生变化的时候, 会产生一个事件 onhashchange
            window.onhashchange = function(){
                console.log( '你的hash改变了' );
                //location对象是 javascript内置的(自带的)
                console.log( location );
            }
        }

上例,我们已经通过hash( 就是锚文本 ) 变化, 触发了onhashchange事件, 就可以把hash变化与内容切换对应起来,就实现了单页路由的应用!

监控hash值变化,hash一旦变化,页面内容变化,实现无刷新切换。

参考:js单页hash路由原理与应用实战

https://www.cnblogs.com/ghostwu/p/7357381.html

路由的懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

懒加载也叫延迟加载,即在需要的时候进行加载,随用随载。在单页应用中,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,会出现长时间的白屏,即使做了loading也是不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时。简单的说就是:进入首页时不用一次加载过多资源,造成页面加载用时过长。

懒加载写法:

// 路由的懒加载方式
    { path :"/home",component:()=>import("../views/Home")},// 当我访问/home首页时,页面才去加载Home组件,减少首页加载的时长
    { path :"/list",component:()=>import("../views/List")},
    { path :"/mine",component:()=>import("../views/Mine")}

非按需加载则会把所有的路由组件块的js包打在一起。当业务包很大的时候建议用路由的按需加载(懒加载)。 按需加载会在页面第一次请求的时候,把相关路由组件块的js添加上。

参考:路由的懒加载(VUE官网)

https://router.vuejs.org/zh/guide/advanced/lazy-loading.html

通过正则匹配路由

// The matching uses path-to-regexp, which is the matching engine used
// by express as well, so the same matching rules apply.
// For detailed rules, see https://github.com/pillarjs/path-to-regexp
const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [
    { path: '/' },
    // params are denoted with a colon ":"
    { path: '/params/:foo/:bar' },
    // a param can be made optional by adding "?"
    { path: '/optional-params/:foo?' },
    // a param can be followed by a regex pattern in parens
    // this route will only be matched if :id is all numbers
    { path: '/params-with-regex/:id(\\d+)' },
    // asterisk can match anything
    { path: '/asterisk/*' },
    // make part of th path optional by wrapping with parens and add "?"
    { path: '/optional-group/(foo/)?bar' }
  ]
})

官方例子github地址:https://github.com/vuejs/vue-router/blob/next/examples/route-matching/app.js

Vuex

什么是Vuex?

vuex是一个专门为vue构建的状态集管理工具,vue和react都是基于组件化开发的,项目中包含很多的组件,组件都会有组件嵌套,想让组件中的数据被其他组件也可以访问到就需要使用到Vuex。

Vuex主要解决了什么问题?

Vuex主要是为了解决多组件之间状态共享问题,它强调的是集中式管理(组件与组件之间的关系变成了组件与仓库之间的关系)把数据都放在一个仓库中管理,使用数据的时候直接从仓库中获取,如果仓库中一个数据改变了, 那么所有使用这个数据的组件都会更新。Vuex把组件与组件之间的关系解耦成组件与仓库之间的关系,方便数据维护。

Vuex的流程?Vuex的核心?

image

(1)将需要共享的状态挂载到state上:this.$store.state来调用

创建store,将状态挂载到state上,在根实例里面配置store,之后我们在组件中就可以通过this.$store.state来使用state中管理的数据,但是这样使用时,当state的数据更改的时候,vue组件并不会重新渲染,所以我们要通过计算属性computed来使用,但是当我们使用多个数据的时候这种写法比较麻烦,vuex提供了mapState辅助函数,帮助我们在组件中获取并使用vuex的store中保存的状态。

(2)我们通过getters来创建状态:通过this.$store.getters来调用

可以根据某一个状态派生出一个新状态,vuex也提供了mapGetters辅助函数来帮助我们在组件中使用getters里的状态。

(3)使用mutations来更改state:通过this.$store.commit来调用

我们不能直接在组件中更改state,而是需要使用mutations来更改,mutations也是一个纯对象,里面包含很多更改state的方法,这些方法的形参接收到state,在函数体里更改,这时,组件用到的数据也会更改,实现响应式。vuex提供了mapMutations方法来帮助我们在组件中调用mutations 的方法。

(4)使用actions来处理异步操作:this.$store.dispatch来调用

Actions类似于mutations,不同在于:Actions提交的是mutations,而不是直接变更状态。Actions可以包含任意异步操作。也就是说,如果有这样的需求:在一个异步操作处理之后,更改状态,我们在组件中应该先调用actions,来进行异步动作,然后由actions调用mutations来更改数据。在组件中通过this.$store.dispatch方法调用actions的方法,当然也可以使用mapMutations来辅助使用。

简便版流程:

组件使用数据且通过异步动作更改数据的一系列事情:

1.生成store,设置state
2.在根实例中注入store
3.组件通过计算属性或者mapState来使用状态
4.用户产生操作,调用actions的方法,然后进行异步动作
5.异步动作之后,通过commit调用mutations的方法
6.mutations方法被调用后,更改state
7.state中的数据更新之后,计算属性重新执行来更改在页面中使用的状态
8.组件状态被更改,创建新的虚拟dom
9.组件的模板更新之后重新渲染在dom中

什么情况下用到vuex?

使用Vuex的情况:多组件间频繁通信

目前市场上有两种使用vuex的情况,

第一种:将需要共享、需要管理的状态放入vuex中管理,也就是说在必要时使用

第二种:将所有的数据都交由vuex管理,由vuex来承担更多的责任,组件变得更轻量级,视图层更轻

项目中使用到vuex的一些场景?

(1)购物车数据共享
(2)用户登录
(3)打开窗口,出现一个表单数据,然后关闭窗口,再次打开还想出现,就使用vuex

Vuex的项目结构

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块

来源:项目结构(VUE官网)

https://vuex.vuejs.org/zh/guide/structure.html#%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84

vuex与local storage有什么区别

  1. 区别:vuex存储在内存,localstorage(本地存储)则以文件的方式存储在本地,永久保存;sessionstorage( 会话存储 ) ,临时保存。localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理。

  2. 应用场景:vuex用于组件之间的传值,localstorage,sessionstorage则主要用于不同页面之间的传值。

  3. 永久性:当刷新页面(这里的刷新页面指的是 --> F5刷新,属于清除内存了)时vuex存储的值会丢失,sessionstorage页面关闭后就清除掉了,localstorage不会。

注:很多同学觉得用localstorage可以代替vuex, 对于不变的数据确实可以,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage,sessionstorage无法做到,原因就是区别1。

Slot插槽

vue里提供了一种将父组件的内容和子组件的模板整合的方法:内容分发,通过slot插槽来实现。

在组件标签内部写入的内容默认的会被替换掉,如果想要在组件的模板里使用这些内容,就在对应的位置写上slot标签,这个slot标签就代表着这些内容。

匿名槽口

在父组件中使用子组件的时候,在子组件标签内部写的内容,在子组件的模板中可以通过<slot></slot>来使用

<div id = "app">
        <aaa>
            <h3>slot槽口插入的内容</h3>
        </aaa>
    </div>

    <template id="aaa">
        <div class = "aaa">
            <slot></slot>
            <p>我是aaa组件</p>
        </div>
    </template>
image

具名槽口

父组件在子组件标签内写的多个内容我们可以给其设置slot属性来命名,在子组件的模板通过使用带有name属性的slot标签来放置对应的slot,当slot不存在的时候,slot标签内写的内容就出现。

slot上面通过name属性指定槽口名称,然后使用的时候通过slot="槽口名称"。

<div id = "app">
        <aaa>
            <!-- <h3>slot槽口插入的内容</h3> -->
            <p slot = "s1">在上面的内容</p>
            <p slot = "s2">在下面的内容</p>
        </aaa>
    </div>

    <template id="aaa">
        <div class = "aaa">
            <slot name = "s1"></slot>
            <p>我是aaa组件</p>
            <slot name = "s2"></slot>
        </div>
    </template>
image

slot插槽让我们在原有模版的基础上,定制更加多样化的组件。

作用域插槽

当我们想在父组件中访问子组件内部的一些数据时,就需要在子组件内部的<slot>元素上动态绑定一个自定义属性,将数据传递到自定义属性上,通过slot传递给父组件使用。

<slot :teacher="teacher"></slot>

绑定到<slot>元素上的属性我们称之为slot props。现在,在父组件中我们可以通过slot-scope给包含所有插槽 prop 的对象命名为prop,之后就可以通过prop来使用子组件中的数据了。

<template slot-scope="prop">
          老师姓名:{{prop.teacher.name}} 老师年龄:{{prop.teacher.age}}
        </template>
image

v-slot

v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slotslot-scope 特性的 API 替代方案。在接下来所有的 2.x 版本中slotslot-scope 特性仍会被支持,但已经被官方废弃,且不会出现在 Vue 3 中。

现在我们使用 v-slot 重构上面的代码:

<template v-slot:default="prop">
          老师姓名:{{prop.teacher.name}} 老师年龄:{{prop.teacher.age}}
        </template>

一个不带 name<slot> 出口会带有隐含的名字“default”,使用时可以简化为v-slot="prop“

具名槽口

子组件中通过name属性给槽口设定名称

<slot name="student" :student='student'></slot>

父组件中通过v-slot:名称的方式来使用具名槽口

<template v-slot:student="prop">
          学生姓名:{{prop.student.name}} 学生年龄:{{prop.student.age}}
        </template>

当我们在父组件中多次调用子组件时,可以通过设置不同的样式,来设定子组件中数据的展示形式。

主流js框架对比

Vue和React的区别

(1)react销毁组件的时候,会将组件的dom结构也移除,vue则不然,在调用destory方法销毁组件的时候,组件的dom结构还是存在于页面中的,this.$destory组件结构还是存在的,只是移除了事件监听。

(2)react中没有指令

(3)在vue中,data属性是利用object.defineProperty处理过的,更改data的数据的时候会触发数据的getter和setter,但是react中没有做这样的处理,如果直接更改的话,react是无法得知的。

react更改状态:在setState中,传入一个对象,就会将组件的状态中键值对的部分更改,还可以传入一个函数,这个回调函数必须向上面方式一样的一个对象函数可以接受prevState和props。

(4)react中属性是不允许更改的,状态是允许更改的。react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态,可以通过setState来进行更改。

拓展:对比其他框架(Vue官网给出的详细解释)

https://vue.docschina.org/v2/guide/comparison.html

Vue和JQuery的区别

jQuery是使用选择器($)选取DOM对象,对其进行赋值、取值、事件绑定等操作,其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。

比如需要获取label标签的内容:$("lable").val();,它还是依赖DOM元素的值。

Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,可以说数据和View是分离的,他们通过Vue对象这个vm实现相互的绑定。这就是传说中的MVVM。

  • vue适用的场景:复杂数据操作的后台页面,表单填写页面

  • jquery适用的场景:比如说一些html5的动画页面,一些需要js来操作页面样式的页面

然而二者也是可以结合起来一起使用的,vue侧重数据绑定,jquery侧重样式操作,动画效果等,则会更加高效率的完成业务需求

参考:jquery和vue对比

https://www.cnblogs.com/MR-YY/p/6898464.html

react与vue之间的相同点和不同点?

相同点:

​ 1.都支持ssr服务器端渲染,vue:nuxt;react:next。

​ 2.都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现组件之间的模块化webComponent规范。

​ 3.数据驱动视图,数据改变视图发生更改。

​ 4.都有支持native的方案,React的React native,Vue的weex(终端的原生端端解决方案)

​ 5.都有管理状态集工具,React有redux,Vue有自己的Vuex(自适应vue,量身定做)

不同点:

  1. React严格上只针对MVC的view层(用户界面的javascript库),Vue则是MVVM模式。
  2. virtual DOM不一样:vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制。
  3. 组件写法不一样:React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js';Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,js写在同一个文件;
  4. 数据绑定: vue实现了数据的双向绑定,react没有实现。
  5. state对象在react应用中不可变的,需要使用setState方法更新状态;在vue中,state对象不是必须的,数据由data属性在vue对象中管理。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • 一:什么是闭包?闭包的用处? (1)闭包就是能够读取其他函数内部变量的函数。在本质上,闭包就 是将函数内部和函数外...
    xuguibin阅读 9,523评论 1 52
  • vue是什么? vue是构建数据驱动的web界面的渐进式框架。Vue.js 的目标是通过尽可能简单的 API 实现...
    九四年的风阅读 8,695评论 2 131
  • Vue 3.0 性能提升主要是通过哪几方面体现的? vue2在初始化的时候,对data中的每个属性使用define...
    Smallbore阅读 1,157评论 0 8
  • Vue八个生命周期 beforeCreate【创建前】created【创建后】 beforeMount【载入前】 ...
    艾萨克菊花阅读 1,303评论 0 12
  • 昨天是晴,今天来了雨。心情随着阴也成了阴。我坐在离门口最近的位置,不经意看到行人被来往的车溅一身泥水的皱眉和随意的...
    jeongtony阅读 297评论 0 0