Vue基础

写在前面

知识是学无止境的,比学习知识更为重要的是运用知识。但是运用知识的前提是你知道这一块知识的大体内容,后面才能更好地查找。

Vue的基本介绍

MVVM模式

  • Model:负责数据存储
  • View:负责页面展示
  • View Model:负责业务逻辑处理(比如Ajax请求等),对数据进行加工后交给视图展示

关于框架

为什么要学习流行框架

1、企业为了提高开发效率:在企业中,时间就是效率,效率就是金钱;企业中,使用框架,能够提高开发的效率。学这么多无非就是减少成本,提高效率

提高开发效率的发展历程

原生JS -> Jquery之类的类库 -> 前端模板引擎 -> Angular.js / Vue.js(能够帮助我们减少不必要的DOM操作;提高渲染效率;双向数据绑定的概念)

2、在Vue中,一个核心的概念就是:数据驱动,避免手动操作DOM元素。这样的话,可以让前端程序员可以更多的时间去关注数据的业务逻辑,而不是关心 DOM 是如何渲染的了

框架和库的区别

框架

框架是一套完整的解决方案。

对项目的侵入性较大,项目如果需要更换框架,则需要重新架构整个项目。但是优点也很明显:功能完善、提供了一整套的解决方案。

库(插件)

只是提供某一个小功能。

对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。

举例:

  • 从Jquery 切换到 Zepto
  • 从 EJS 切换到 art-template:模板引擎

前端的各种框架

Vue 和 React 的相同点

  • 利用虚拟DOM实现快速渲染
  • 轻量级
  • 响应式组件
  • 支持服务器端渲染
  • 易于集成路由工具、打包工具以及状态管理工具

PS:Vue 在国内很受欢迎;React 在国内和国外都很受欢迎,适合做大型网站。

什么是虚拟 DOM

传统的web开发,是利用 jQuery操作DOM,这是非常耗资源的。

我们可以在 JS 的内存里构建类似于DOM的对象,去拼装数据,拼装完整后,把数据整体解析,一次性插入到html里去。这就形成了虚拟 DOM。

Vue1.0没有虚拟DOM,Vue2.0改成了基于虚拟DOM。

前端框架回顾

Vue框架中,没有控制器。

Vue 框架

发展历史

  • 2013年底作为尤雨溪个人实验项目开始开发
  • 2014年2月公开发布。
  • 2014年11月发布0.11版本
  • 2016年10月发布2.0版本。

相关网址

介绍

Vue 本身并不是一个框架,Vue结合周边生态构成一个灵活的、渐进式的框架。

Vue 以及大型 Vue 项目所需的周边技术,构成了生态。

渐进式框架图:

Vue框架的特点

  • 模板渲染:基于 html 的模板语法,学习成本低。
  • 响应式的更新机制:数据改变之后,视图会自动刷新。【重要】
  • 渐进式框架
  • 组件化/模块化
  • 轻量:开启 gzip压缩后,可以达到 20kb 大小。(React 达到 35kb,AngularJS 达到60kb)。

Vue 的环境搭建

我们首先要安装好 NVM、Node.js环境,然后再来做下面的操作。

常见的插件

  • Webpack:代码模块化构建打包工具。
  • Gulp:基于流的自动化构建工具。
  • Babel:使用最新的 规范来编写 js。
  • Vue:构建数据驱动的Web界面的渐进式框架
  • Express:基于 Node.js 平台,快速、开放、极简的 Web 开发框架。

以上这些包,都可以通过 NPM 这个包管理工具来安装。

引用 Vue.js 文件

1、方式一:(CDN的方式进行引用)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>


</body>
</html>

2、方式二:(下载 vue.js 文件)

去网站 https://cdn.jsdelivr.net/npm/vue/ 下载 vue.js 文件,直接放到工程文件里,然后引用。

3、方式三:(NPM的方式安装vue)

# 最新稳定版
$ npm install vue

如果网络不稳定,可以采用下面的方式安装:

$ cnpm i vue --save

然后在代码中通过下面这种方式进行引用:

  import Vue from 'vue'

利用 vue-cli 新建一个空的项目

Vue 提供一个官方命令行工具,可用于快速搭建大型单页应用。该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目。

官方代码参考

  npm install -g @vue/cli

  vue create my-app

  cd my-app

  npm run serve

我们根据上方的参考代码,来看看“利用 vue-cli 新建一个空的项目”的步骤。

安装 vue-cli(命令行工具)

安装命令如下:

# 全局安装 vue-cli
$ npm install -g @vue/cli

初始化一个 simple 项目

(1)首先执行:

  vue create my-app

输入上方命令后,会弹出一个选项:

如果是初学者,直接选default就行。之后会自动生成一个空的初始化项目,包含了项目目录、以及项目依赖的脚本。

这个空项目的工程文件如下:(请务必仔细研究这个项目的写法和目录结构)

我们可以看到这个项目的结构:

  • src:项目源码
  • .babelrc:ES6编译插件的配置
  • index.html:单页面的入口

上方截图中,npm install指的是下载各种依赖包,npm run dev指的是打开发包,npm run build指的是打生产包。

(2)本地运行项目:

  cd my-app

  npm run serve

浏览器输入http://localhost:8080/,就可以让这个空的项目在本地跑起来:

备注:我们在 GitHub上下载的任何Vue有关的项目,第一步都是要首先执行 npm install,安装依赖的 mode_modules,然后再运行。我们发给同事的工程文件,建议不要包含 node_modules

构建一个 非 simple 项目

构建一个空的项目,首先执行:

$ vue create vuedemo2

上图中,选择 Manually select features,然后根据提示依次输入:

  • project name:要求小写
  • description:默认即可。
  • vue-router:需要。
  • ESlint:语法检查,初学者可以暂时不需要。
  • 单元测试:暂时也不需要。
  • e2e test:不需要。

选择 eslint 的配置:

然后让这个空的项目就可以在浏览器上跑起来。

vue 项目结构分析

  • buid:打包配置的文件夹
  • config:webpack对应的配置
  • src:开发项目的源码
    • App.vue:入口组件。.vue文件都是组件。
    • main.js:项目入口文件。
  • static:存放静态资源
  • .babelrc:解析ES6的配置文件
  • .editorcofnig:编辑器的配置
  • .postcssrc.js:html添加前缀的配置
  • index.html:单页面的入口。通过 webpack打包后,会把 src 源码进行编译,插入到这个 html 里面来。
  • package.json:项目的基础配置,包含版本号、脚本命令、项目依赖库、开发依赖库、引擎等。

图片的base64编码

默认是10k以下,建议都通过 base64编码。在配置文件webpack.base.conf.js中进行修改:

      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }

Vue系统指令

本文最初发表于博客园,并在GitHub上持续更新前端的系列文章。欢迎在GitHub上关注我,一起入门和进阶前端。

以下是正文。

主要内容如下

  • 插值表达式
  • v-cloak
  • v-text
  • v-html
  • v-bind
  • v-on
  • 举例:文字滚动显示(跑马灯效果)
  • v-on的事件修饰符

Vue初体验

新建一个空的项目,引入vue.js文件。写如下代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--1、导入Vue的包-->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
<body>
<!--这个div区域就是MVVM中的 View-->
<div id="div1">
    {{name}}
</div>
</body>

<script>
    // 2、创建一个Vue的实例
    //new出来的对象就是MVVM中的 View Module(调度者)
    var myVue = new Vue({
        el: '#div1', //当前vue对象将接管上面的div1区域
        data: {//data就是MVVM中的 module
            name: 'smyhvae'
        }
    });
</script>
</html>

显示效果:

如果我们在控制台输入myVue.$data.name = 'haha'(也可以直接myVue.name = 'haha'),页面会自动更新name的值。意思是,当我们直接修改data数据,页面会自动更新,而不用去操作DOM。

下面来讲一下Vue的各种系统指令。

插值表达式

数据绑定最常见的形式就是使用 “Mustache” 语法(双大括号)的文本插值。例如:

<span>Message: {{ msg }}</span>

Mustache 标签将会被替代为对应数据对象上 msg 属性(msg定义在data对象中)的值。
无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会自动更新

``对JavaScript 表达式支持,例如:

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ name == 'smyhvae' ? 'true' : 'false' }}

{{ message.split('').reverse().join('') }}

但是有个限制就是,每个绑定都只能包含单个表达式,如下表达式无效:

<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}

代码举例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <span>content:{{name}}</span>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                name: 'smyhvae'
            }
        })
    </script>

</body>

</html>

运行结果:

v-cloak

v-cloak:保持和元素实例的关联,直到结束编译后自动消失。

v-cloak指令和CSS 规则一起用的时候,能够解决差值表达式闪烁的问题(即:可以隐藏未编译的标签直到实例准备完毕)。

就拿上一段代码来举例,比如说,这个内容,在网速很慢的情况下,一开始会直接显示这个内容,等网络加载完成了,才会显示smyhvae。那这个闪烁的问题该怎么解决呢?

解决办法是:通过v-cloak隐藏这个内容,当加载完毕后,再显示出来。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>

    /*2、在样式表里设置:只要是有 v-cloak 属性的标签,我都让它隐藏。
    直到 Vue实例化完毕以后,v-cloak 会自动消失,那么对应的css样式就会失去作用,最终将span中的内容呈现给用户 */
    [v-cloak] {
      display: none;
    }
  </style>
</head>

<body>
  <div id="app">
    <!-- 1、给 span 标签添加 v-cloak 属性 -->
    <span v-cloak>{{name}}</span>

  </div>
</body>

<script src="vue2.5.16.js"></script>
<script>
  new Vue({
    el: '#app',
    data: {
      name: 'smyhvae'
    }
  });
</script>

</html>

v-text

v-text可以将一个变量的值渲染到指定的元素中。例如:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <!--vue的版本:2.5.15-->
  <script src="vue.js"></script>
</head>

<body>
  <div id="div1">
    <span v-text="name"></span>
  </div>
</body>

<script>
  new Vue({
    el: '#div1',
    data: {
      name: 'hello smyhvae'
    }
  });
</script>

</html>

结果:

差值表达式和 v-text 的区别

  <!-- 差值表达式 -->
  <span>content:{{name}}</span>

  <!-- v-text -->
  <span v-text="name">/span>

区别1: v-text 没有闪烁的问题,因为它是放在属性里的。

区别2 : 插值表达式只会替换自己的这个占位符,并不会把整个元素的内容清空。v-text 会覆盖元素中原本的内容。

为了解释区别2,我们来用代码举例:

  <!-- 差值表达式 -->
  <p>content:++++++{{name}}------</p>

  <!-- v-text -->
  <p v-text="name">------++++++</p>

上方代码的演示结果:

其实,第二行代码中,只要浏览器中还没有解析到v-text="name"的时候,会显示------++++++;当解析到v-text="name"的时候,name的值会直接替换------++++++

v-html

v-text是纯文本,而v-html会被解析成html元素。

注意:使用v-html渲染数据可能会非常危险,因为它很容易导致 XSS(跨站脚本) 攻击,使用的时候请谨慎,能够使用或者v-text实现的不要使用v-html。

代码举例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p>{{msg}}</p>
        <p v-text="msg"></p>
        <p v-html="msg"></p>

    </div>
    <script src="vue2.5.16.js"></script>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                msg: '<h1>我是一个大大的h1标题</h1>'
            }
        })
    </script>

</body>

</html>

运行结果:

v-bind:属性绑定机制

v-bind:用于绑定属性

比如说:

    <img v-bind:src="imageSrc +'smyhvaeString'">

    <div v-bind:style="{ fontSize: size + 'px' }"></div>

上方代码中,给属性加了 v-bind 之后,属性值里的整体内容是表达式,属性值里的imageSrcsize是Vue实例里面的变量

也就是说, v-bind的属性值里,可以写合法的 js 表达式。

上面两行代码也可以简写成:

    <img :src="imageSrc">

    <div :style="{ fontSize: size + 'px' }"></div>

举例:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
  </style>
</head>

<body>
  <div id="div1">
    <!-- value里的值只是简单的字符串 -->
    <input type="text" value="name">
    <!-- 加上 v-bind 之后,value里的值是 Vue 里的变量 -->
    <input type="text" v-bind:value="name">
    <!-- 超链接后面的path是 Vue 里面的变量 -->
    <a v-bind="{href:'http://www.baidu.com/'+path}">超链接</a>

  </div>
</body>

<script src="vue.js"></script>
<script>
  new Vue({
    el: '#div1',
    data: {
      name: 'smyhvae',
      path: `2.html`
    }
  });
</script>

</html>

上面的代码中,我们给value这个属性绑定了值,此时这个值是一个变量。

效果:

v-on:事件绑定机制

v-on:click:点击事件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <!--vue的版本:2.5.15-->
  <script src="vue.js"></script>
</head>

<body>
  <!--这个div区域就是MVVM中的 View-->
  <div id="div1">
    <!-- 给button节点绑定按钮的点击事件 -->
    {{name}}
    <button v-on:click="change">改变name的值</button>
  </div>


</body>

<script>
  //new出来的对象就是MVVM中的 View Module
  var myVue = new Vue({
    el: '#div1', //当前vue对象将接管上面的div区域
    data: { //data就是MVVM中的 module
      name: 'smyhvae'
    },

    //注意,下方这个 `methods` 是Vue中定义方法的关键字,不能改
    //这个 methods 属性中定义了当前Vue实例所有可用的方法
    methods: {
      change: function() { //上面的button按钮的点击事件
        this.name += '1';
      }
    }
  });
</script>

</html>

上方代码中,我们给button按钮绑定了点击事件。注意,这个button标签要写在div区域里(否则点击事件不生效),因为下方的View module接管的是div区域。

v-on的简写形式

例如:

    <button v-on:click="change">改变name的值</button>

可以简写成:

    <button @click="change">改变name的值</button>

v-on的常用事件

v-on 提供了click 事件,也提供了一些其他的事件。

  • v-on:click
  • v-on:keydown
  • v-on:keyup
  • v-on:mousedown
  • v-on:mouseover
  • v-on:submit
  • ….

举例:文字滚动显示(跑马灯效果)

我们利用上面几段所学的内容,做个跑马灯的小例子。要实现的效果是:类似于LED屏幕上,滚动显示的文字。

文字滚动显示的思路

(1)每次点击按钮后,拿到 msg 字符串,然后调用字符串的substring来进行字符串的截取操作,把第一个字符截取出来,放到最后一个位置即可。这就实现了滚动的效果。
(2)为了实现文字自动连续滚动的效果,需要把步骤(1)中点击按钮的操作,放到定时器中去。

我们先来看一下 点击事件里的代码改怎么写。

步骤 1:每次点击按钮,字符串就滚动一次。代码如下:

    startMethod: function () {
        // 获取 msg 的第一个字符
        var start = this.msg.substring(0, 1);
        // 获取 后面的所有字符
        var end = this.msg.substring(1);
        // 重新拼接得到新的字符串,并赋值给 this.msg
        this.msg = end + start;
    }

步骤2:给上面的操作添加定时器。代码如下:

    startMethod: function () {
        var _this = this;
        //添加定时器:点击按钮后,让字符串连续滚动
        setInterval(function () {
            // 获取 msg 的第一个字符
            var start = _this.msg.substring(0, 1);
            // 获取 后面的所有字符
            var end = _this.msg.substring(1);
            // 重新拼接得到新的字符串,并赋值给 this.msg
            // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去
            _this.msg = end + start;
            console.log(_this.msg);
        }, 400);
    }

上面的代码中,我们发现,如果在定时器中直接使用this,这个this指向的是window。为了解决这个问题,我们是通过_this来解决了这个问题。

另外,我们还可以利用箭头函数来解决this指向的问题,因为箭头函数总的this指向,会继承外层函数的this指向。如下。

步骤2的改进版:用箭头函数来改进定时器,解决this指向的问题。代码如下:

    startMethod: function () {
        //添加定时器:点击按钮后,让字符串连续滚动
        setInterval(() => {
            // 获取 msg 的第一个字符
            var start = this.msg.substring(0, 1);
            // 获取 后面的所有字符
            var end = this.msg.substring(1);
            // 重新拼接得到新的字符串,并赋值给 this.msg
            // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去
            this.msg = end + start;
            console.log(_this.msg);
        }, 400);
    }

步骤3:停止定时器。如下:

我们还需要加一个按钮,点击按钮后,停止文字滚动。也就是停止定时器。

提示:我们最好把定时器的id放在全局的位置(放到data里),这样的话,开启定时器的方法和停止定时器的方法,都可以同时访问到这个定时器。

代码如下:

    data: {
        msg: 'Hello World,永不止步~~~',
        intervalId: null
    },
    methods: {
        startMethod: function () {
            //添加定时器:点击按钮后,让字符串连续滚动
            this.intervalId = setInterval(() => {  //【注意】这个定时器的this,一定不要忘记了
                // 获取 msg 的第一个字符
                var start = this.msg.substring(0, 1);
                // 获取 后面的所有字符
                var end = this.msg.substring(1);
                // 重新拼接得到新的字符串,并赋值给 this.msg
                // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去
                this.msg = end + start;
                console.log(_this.msg);
            }, 400);
        },

        stopMethod: function () {
            //停止定时器:点击按钮后,停止字符串的滚动
            clearInterval(this.intervalId);
        }
    }

【重要】步骤4:一开始的时候,还需要判断是否已经存在定时器。如下:

步骤3中的代码,虽然做了停止定时器的操作,但是有个问题:在连续多次点击“启动定时器”按钮的情况下,此时再点击“停止定时器”的按钮,是没有反应的。因此,我们需要改进的地方是:

  • 在开启定时器之前,先做一个判断:如果定时器不为 null,就不继续往下执行了(即不再开启新的定时器),防止开启了多个定时器。
  • 停止定时器的时候,虽然定时器停止了,但定时器并不为 null。因此,最后我们还需要手动将定时器设置为null。这样,才能恢复到最初始的状态。

完整版代码

针对上面的四个步骤,为了实现这个案例,完整版代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <p>{{msg}}</p>
        <input type="button" value="跑马灯走起" @click="startMethod">
        <input type="button" value="跑马灯停止" @click="stopMethod">

    </div>
    <script src="vue2.5.16.js"></script>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                msg: 'Hello World,永不止步~~~',
                intervalId: null
            },
            methods: {
                startMethod: function () {

                    //【重要】在开启定时器之前,先进行判断,避免出现多个定时器
                    //如果出现了多个定时器,后面的“停止定时器”操作是无效的
                    if (this.intervalId != null) return; //【注意】这个定时器的this,一定不要忘记了

                    //添加定时器:点击按钮后,让字符串连续滚动
                    this.intervalId = setInterval(() => {
                        // 获取 msg 的第一个字符
                        var start = this.msg.substring(0, 1);
                        // 获取 后面的所有字符
                        var end = this.msg.substring(1);
                        // 重新拼接得到新的字符串,并赋值给 this.msg
                        // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去
                        this.msg = end + start;
                        console.log(this.msg);
                    }, 400);
                },

                stopMethod: function () {
                    // 停止定时器:点击按钮后,停止字符串的滚动
                    clearInterval(this.intervalId);
                    // 每当清除了定时器之后,需要重新把 intervalId 置为 null
                    this.intervalId = null;
                }
            }
        })
    </script>

</body>

</html>

上方代码的总结

  • 在Vue的实例中,如果想要获取data里的属性、methods里面的方法,都要通过this来访问。这里的this指向的是Vue的实例对象
  • VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把最新的数据,从 data 上同步到页面中去。这样做 的好处是:程序员只需要关心数据,不需要考虑如何重新渲染DOM页面;减少DOM操作
  • 在调用定时器 id 的时候,代码是this.intervalId,这个this一定不要漏掉了。

V-on的事件修饰符

v-on 提供了很多事件修饰符来辅助实现一些功能。事件修饰符有如下:

  • .stop 阻止冒泡。本质是调用 event.stopPropagation()。
  • .prevent 阻止默认事件(默认行为)。本质是调用 event.preventDefault()。
  • .capture 添加事件监听器时,使用捕获的方式(也就是说,事件采用捕获的方式,而不是采用冒泡的方式)。
  • .self 只有当事件在该元素本身(比如不是子元素)触发时,才会触发回调。
  • .once 事件只触发一次。
  • .{keyCode | keyAlias} 只当事件是从侦听器绑定的元素本身触发时,才触发回调。
  • .native 监听组件根元素的原生事件。

PS:一个事件,允许同时使用多个事件修饰符。

写法示范:

          <!-- click事件 -->
        <button v-on:click="doThis"></button>

        <!-- 缩写 -->
        <button @click="doThis"></button>

        <!-- 内联语句 -->
        <button v-on:click="doThat('hello', $event)"></button>

        <!-- 阻止冒泡 -->
        <button @click.stop="doThis"></button>

        <!-- 阻止默认行为 -->
        <button @click.prevent="doThis"></button>

        <!-- 阻止默认行为,没有表达式 -->
        <form @submit.prevent></form>

        <!--  串联修饰符 -->
        <button @click.stop.prevent="doThis"></button>

.stop的举例

我们来看下面这个例子:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="vue2.5.16.js"></script>
    <style>
        .father {
            height: 300px;
            width: 300px;
            background: pink;
        }

        .child {
            width: 200px;
            height: 200px;
            background: green;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="father" @click="fatherClick">
            <div class="child" @click="childClick">
            </div>
        </div>
    </div>
    <script>

        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {
                fatherClick: function () {
                    console.log('father 被点击了');
                },
                childClick: function () {
                    console.log('child 被点击了');
                }
            }
        })

    </script>
</body>

</html>

上方代码中,存在冒泡的现象,父标签中包含了一个子标签。当点击子标签时,父标签也会被触发。打印顺序是:

  child 被点击了
  father 被点击了

那么问题来了,如果我不想让子标签的点击事件冒泡到父亲,该怎么做呢?办法是:给子标签加一个事件修饰符.stop,阻止冒泡。代码如下:

    <div class="child" @click.stop="childClick">

阻止冒泡后,当点击子标签时,打印结果是:

  child 被点击了

PS:我发现一个有意思的现象。上方的这行代码中,如果把.stop改为:stop,造成的现象是,父标签被触发了,而子标签没有被触发。

.capture举例

.capture触发事件时,采用捕获的形式,而不是冒泡的形式

还是采用上面的例子:当按钮点击时,如果想要采取捕获的方式,而不是冒泡的方式,办法是:可以直接在父标签上加事件修饰符.capture。代码如下:

    <div class="father" @click.capture="fatherClick">

当点击子标签时,打印结果是:

  father 被点击了
  child 被点击了

.prevent的举例1

比如说,超链接<a>默认有跳转行为,那我可以通过事件修饰符.prevent阻止这种跳转行为。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="vue2.5.16.js"></script>
</head>

<body>
    <div id="app">
        <!-- 通过 .prevent 阻止超链接的默认跳转行为 -->
        <a href="http://www.baidu.com" @click.prevent="linkClick">百度一下</a>
    </div>
    <script>

        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {
                linkClick: function () {
                    console.log('超链接被点击了');
                }
            }
        })
    </script>
</body>

</html>

上方代码中:

  • 如果去掉.prevent,点击按钮后,既
  • 会打印log,又会跳转到百度页面。
  • 现在加上了.prevent,就只会打印log,不会跳转到百度页面。

.prevent的举例2

现在有一个form表单:

    <form action="http://www.baidu.com">
      <input type="submit" value="表单提交">
    </form>

我们知道,上面这个表单因为type="submit",因此它是一个提交按钮,点击按钮后,这个表单就会被提交到form标签的action属性中指定的那个页面中去。这是表单的默认行为。

现在,我们可以用.prevent来阻止这种默认行为。修改为:点击按钮后,不提交到服务器,而是执行我们自己想要的事件(在submit方法中另行定义)。如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="vue2.5.16.js"></script>
</head>

<body>
  <div id="app">
    <!-- 阻止表单中submit的默认事件 -->
    <form @submit.prevent action="http://www.baidu.com">
      <!-- 执行自定义的click事件 -->
      <input type="submit" @click="mySubmit" value="表单提交">
    </form>

  </div>
</body>

<script>
  new Vue({
    el: '#app',
    data: {
    },
    methods: {
      mySubmit: function() {
        alert('ok');
      }
    }
  });
</script>

</html>

上方代码中,我们通过.prevent阻止了提交按钮的默认事件,点击按钮后,执行的是mySubmit()方法里的内容。这个方法名是可以随便起的,我们甚至可以起名为submit,反正默认的submit已经失效了。

.self举例

  • .self 只有当事件在该元素本身(比如不是子元素)触发时,才会触发回调。

我们知道,在事件触发机制中,当点击子标签时,父标签会通过冒泡的形式被触发(父标签本身并没有被点击)。可如果我给父标签的点击事件设置.self修饰符,达到的效果是:子标签的点击事件不会再冒泡到父标签了,只有点击父标签本身,父标签的事件才会被触发。代码如下:

    <div class="father" @click.self="fatherClick">

疑问:既然.stop.self都可以阻止冒泡,那二者有什么区别呢?区别在于:前者能够阻止整个冒泡行为,而后者只能阻止自己身上的冒泡行为

Vue高级系统指令

主要内容

本文主要内容:

  • v-model
  • v-for
  • v-if
  • v-show

v-model:双向数据绑定(只能用于表单元素

之前的文章里,我们通过v-bind,给<input>标签绑定了data对象里的name属性。当data里的name的值发生改变时,<input>标签里的内容会自动更新。

可我现在要做的是:我在<input>标签里修改内容,要求data里的name的值自动更新。从而实现双向数据绑定。该怎么做呢?这就可以利用v-model这个属性。

区别

  • v-bind:只能实现数据的单向绑定,从 M 自动绑定到 V
  • v-model:只有v-model才能实现双向数据绑定。注意,v-model 后面不需要跟冒号

注意:v-model 只能运用在表单元素中。常见的表单元素包括:input(radio, text, address, email….) 、select、checkbox 、textarea。

代码举例如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="vue.js"></script>
</head>
<body>
<div id="app">

    <form action="#">
        <!-- 将 input 标签中的value值双向绑定到 Vue实例中的data。注意,v-model 后面不需要跟冒号 -->
        <input type="text" id="username" v-model="myAccount.username">
        <input type="password" id="pwd" v-model="myAccount.userpwd">

        <input type="submit" v-on:click="submit1" value="注册">

    </form>
</div>
</body>

<script>
    var vm = new Vue({
        el: '#app',
        //上面的标签中采用v-model进行双向数据绑定,数据会自动更新到data里面来
        data: {
            name: 'smyhvae',
            myAccount: {username: '', userpwd: ''}
        },
        //在methods里绑定各种方法,根据业务需要进行操作
        methods: {
            submit1: function () {
                alert(this.myAccount.username + "  pwd=" + this.myAccount.userpwd);
            }
        }
    });
</script>

</html>

此时,便可实现我们刚刚要求的双向数据绑定的效果。

v-model举例:实现简易计算器

题目:现在两个输入框,用来做加减乘除,将运算的结果放在第三个输入框。

实现代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="vue2.5.16.js"></script>
</head>

<body>
    <div id="app">
        <input type="text" v-model="n1">

        <select v-model="opt">
            <option value="+">+</option>
            <option value="-">-</option>
            <option value="*">*</option>
            <option value="/">/</option>
        </select>

        <input type="text" v-model="n2">

        <input type="button" value="=" @click="calc">

        <input type="text" v-model="result">
    </div>

    <script>
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {
                n1: 0,
                n2: 0,
                result: 0,
                opt: '+'
            },
            methods: {
                calc() { // 计算器算数的方法
                    // 逻辑判断:
                    switch (this.opt) {
                        case '+':
                            this.result = parseInt(this.n1) + parseInt(this.n2)
                            break;
                        case '-':
                            this.result = parseInt(this.n1) - parseInt(this.n2)
                            break;
                        case '*':
                            this.result = parseInt(this.n1) * parseInt(this.n2)
                            break;
                        case '/':
                            this.result = parseInt(this.n1) / parseInt(this.n2)
                            break;
                    }

                    //上面的逻辑判断,可能有点啰嗦,我们还可以采取下面的这种方式进行逻辑判断
                    // 注意:这是投机取巧的方式,正式开发中,尽量少用
                    // var codeStr = 'parseInt(this.n1) ' + this.opt + ' parseInt(this.n2)'
                    // this.result = eval(codeStr)
                }
            }
        });
    </script>
</body>

</html>

注意上方代码中的注释,可以了解下eval()的用法。

Vue中通过属性绑定为元素设置class 类样式

注意,是类样式

引入

我们先来看下面这段代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .my-red {
            color: red;
        }

        .my-thin {
            /* 设置字体的粗细 */
            font-weight: 200;
        }

        .my-italic {
            font-style: italic;
        }

        .my-active {
            /* 设置字符之间的间距 */
            letter-spacing: 0.5em;
        }
    </style>
</head>

<body>
    <h1 class="my-red my-thin">我是Hello World,smyhvae</h1>
</body>

</html>

上面的代码中,我们直接通过正常的方式,给<h1>标签设置了两个 class 类的样式。代码抽取如下:

    <h1 class="my-red my-thin">我是Hello World,smyhvae</h1>

上面的效果,我们还可以用Vue来写。这就引入了本段要讲的方式。

方式一:数组

方式一:直接传递一个数组。注意:这里的 class 需要使用 v-bind 做数据绑定。

代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="vue2.5.16.js"></script>
    <style>
        .my-red {
            color: red;
        }

        .my-thin {
            /* 设置字体的粗细 */
            font-weight: 200;
        }

        .my-italic {
            font-style: italic;
        }

        .my-active {
            /* 设置字符之间的间距 */
            letter-spacing: 0.5em;
        }
    </style>
</head>

<body>
    <div id="app">

        <!-- 普通写法 -->
        <h1 class="my-red my-thin">我是Hello World,smyhvae</h1>

        <!-- vue的写法1:数组的形式 -->
        <h1 :class="['my-red', 'my-thin']">我是smyhvae,Hello World</h1>

    </div>

    <script>

        var vm = new Vue({
            el: '#app'
        });

    </script>
</body>

</html>

代码抽取如下:

        <!-- vue的写法1:数组的形式 -->
        <h1 :class="['my-red', 'my-thin']">我是smyhvae,Hello World</h1>

上方代码中,注意,数组里写的是字符串;如果不加单引号,就不是字符串了,而是变量。

演示效果如下:

写法二:在数组中使用三元表达式

<body>
    <div id="app">
        <!-- vue的写法2:在数组中使用三元表达式。注意格式不要写错-->
        <!-- 通过data中布尔值 flag 来判断:如果 flag 为 true,就给 h1 标签添加`my-active`样式;否则,就不设置样式。 -->
        <h1 :class="[flag?'my-active':'']">我是smyhvae,Hello World</h1>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                flag:true
            }
        });
    </script>
</body>

上方代码的意思是,通过data中布尔值 flag 来判断:如果 flag 为 true,就给 h1 标签添加my-active样式;否则,就不设置样式。

注意,三元表达式的格式不要写错了。

写法三:在数组中使用 对象 来代替 三元表达式(提高代码的可读性)

上面的写法二,可读性较差。于是有了写法三。

写法三:在数组中使用对象来代替三元表达式

代码如下:

<body>
    <div id="app">
        <!-- vue的写法3:在数组中使用对象来代替三元表达式。-->
        <h1 :class="[ {'my-active':flag} ]">我是smyhvae,Hello World</h1>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                flag: true
            }
        });
    </script>
</body>

写法四:直接使用对象

写法四:直接使用对象。代码如下:

        <!-- vue的写法4:直接使用对象-->
        <!-- 在为 class 使用 v-bind 绑定 对象的时候,对象的属性是类名。由于 对象的属性名可带引号,也可不带引号,所以 这里我没写引号;  属性的值 是一个标识符 -->
        <h1 :class="{style1:true, style2:false}">我是smyhvae,Hello World</h1>

上方代码的意思是,给<h1>标签使用样式style1,不使用样式style2。注意:

1、既然class样式名是放在对象中的,这个样式名不能有中划线,比如说,写成:class="{my-red:true, my-active:false},是会报错的。

2、我们也可以对象通过存放在 data 的变量中。也就是说,上方代码可以写成:

<body>
    <div id="app">
        <!-- vue的写法4:直接使用对象-->
        <!-- 在为 class 使用 v-bind 绑定 对象的时候,对象的属性是类名。由于 对象的属性名可带引号,也可不带引号,所以 这里我没写引号;  属性的值 是一个标识符 -->
        <h1 :class="classObj">我是smyhvae,Hello World</h1>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                classObj:{style1:true, style2:false}
            }
        });
    </script>
</body>

Vue中通过属性绑定为元素设置 style 行内样式

注意,是行内样式(即内联样式)。

写法一

写法一:直接在元素上通过 :style 的形式,书写样式对象。

例如:

        <h1 :style="{color: 'red', 'font-size': '20px'}">我是Hello World</h1>

写法二

写法二:将样式对象,定义到 data 中,并直接引用到 :style 中。

也就是说,把写法一的代码改进一下。代码如下:

<body>
    <div id="app">
        <h1 :style="styleObj">我是Hello World</h1>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                styleObj: { color: 'red', 'font-size': '20px' }
            }
        });
    </script>
</body>

写法三

写法二只用到了一组样式。如果想定义多组样式,可以用写法三。

写法三:在 :style 中通过数组,引用多个 data 上的样式对象。

代码如下:

<body>
    <div id="app">
        <h1 :style="[ styleObj1, styleObj2 ]">我是Hello World,smyhvae</h1>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                styleObj1: { color: 'red', 'font-size': '20px' },
                styleObj2: { 'font-style': 'italic' }
            }
        });
    </script>
</body>

v-for:for循环的四种使用方式

作用:根据数组中的元素遍历指定模板内容生成内容。

引入

比如说,如果我想给一个ul中的多个li分别赋值1、2、3…。如果不用循环,就要挨个赋值:

<body>
  <div id="app">
    <ul>
      <li>{{list[0]}}</li>
      <li>{{list[1]}}</li>
      <li>{{list[2]}}</li>
    </ul>
  </div>
</body>

<script>
  var vm = new Vue({
    el: '#app',
    data: {
      list: [1, 2, 3]
    }

  });
</script>

效果:

为了实现上面的效果,如果我用v-for进行赋值,代码就简洁很多了:

<body>
  <div id="app">
    <ul>
      <!-- 使用v-for对多个li进行遍历赋值 -->
      <li v-for="item in list">{{item}}</li>
    </ul>
  </div>
</body>

<script>
  var vm = new Vue({
    el: '#app',
    data: {
      list: [1, 2, 3]
    }

  });
</script>

接下来,我们详细讲一下v-for的用法。需要声明的是,Vue 1.0的写法和Vue 2.0的写法是不一样的。本文全部采用Vue 2.0的写法。

方式一:普通数组的遍历

针对下面这样的数组:

<script>
  new Vue({
    el: '#app',
    data: {
      arr1: [2, 5, 3, 1, 1],
    }
  });
</script>

将数组中的赋给li:

      <li v-for="item in arr1">{{item}}</li>

将数组中的值和index赋给li:

      <!-- 括号里如果写两个参数:第一个参数代表值,第二个参数代表index 索引 -->
      <li v-for="(item,index) in arr1">值:{{item}} --- 索引:{{index}}</li>

效果如下:

方式二:对象数组的遍历

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="vue2.5.16.js"></script>
    <style>
    </style>
</head>

<body>
    <div id="app">
        <ul>
            <!-- 对象数组的遍历。括号里如果写两个参数:第一个参数代表数组的单个item,第二个参数代表 index 索引-->
            <li v-for="(item, index) in dataList">姓名:{{item.name}} --- 年龄:{{item.age}} --- 索引:{{index}}</li>

        </ul>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                //对象数组
                dataList: [
                    { name: 'smyh', age: '26' },
                    { name: 'vae', age: '32' },
                    { name: 'xiaoming', age: '20' }
                ]
            }
        });
    </script>
</body>

</html>

效果如下:

方式三:对象的遍历

针对下面这样的对象:

<script>
  new Vue({
    el: '#app',
    data: {
      obj1: {
        name: 'smyhvae',
        age: '26',
        gender: '男'
      }
    }
  });
</script>

将上面的obj1对象的数据赋值给li,写法如下:

<body>
  <div id="app">
    <ul>
      <!-- 括号里如果写两个参数:则第一个参数代表value,第二个参数代表key -->
      <li v-for="(value,key) in obj1">值:{{value}} --- 键:{{key}} </li>

      <h3>---分隔线---</h3>

      <!-- 括号里如果写三个参数:则第一个参数代表value,第二个参数代表key,第三个参数代表index -->
      <li v-for="(value,key,index) in obj1">值:{{value}} --- 键:{{key}} --- index:{{index}} </li>
    </ul>
  </div>
</body>

效果如下:

方式四:遍历数字

in后面还可以直接放数字。举例如下:

        <ul>
            <!-- 对象数组的遍历 -->
            <!-- 注意:如果使用 v-for 遍历数字的话,前面的 myCount 值从 1 开始算起 -->
            <li v-for="myCount in 10">这是第 {{myCount}}次循环</li>
        </ul>

效果如下:

v-for中key的使用注意事项

注意:在 Vue 2.2.0+ 版本里,当在组件中使用 v-for 时,key 属性是必须要加上的。

这样做是因为:每次 for 循环的时候,通过指定 key 来标示当前循环这一项的唯一身份

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。

key的类型只能是:string/number,而且要通过 v-bind 来指定。

代码举例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="vue2.5.16.js"></script>
</head>

<body>
    <div id="app">

        <div>
            <label>Id:
                <input type="text" v-model="id">
            </label>

            <label>Name:
                <input type="text" v-model="name">
            </label>

            <input type="button" value="添加" @click="add">
        </div>

        <!-- 注意: v-for 循环的时候,key 属性只能使用 number获取string -->
        <!-- 注意: key 在使用的时候,必须使用 v-bind 属性绑定的形式,指定 key 的值 -->
        <!-- 在组件中,使用v-for循环的时候,或者在一些特殊情况中,如果 v-for 有问题,必须 在使用 v-for 的同时,指定 唯一的 字符串/数字 类型 :key 值 -->
        <p v-for="item in list" :key="item.id">
            <input type="checkbox">{{item.id}} --- {{item.name}}
        </p>
    </div>

    <script>
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {
                id: '',
                name: '',
                list: [
                    { id: 1, name: 'smyh' },
                    { id: 2, name: 'vae' },
                    { id: 3, name: 'smyhvae' },
                    { id: 4, name: 'xiaoming' },
                    { id: 5, name: 'xiaohong' }
                ]
            },
            methods: {
                add() { // 添加方法
                    this.list.unshift({ id: this.id, name: this.name })
                }
            }
        });
    </script>
</body>

</html>

v-if:设置元素的显示和隐藏(添加/删除DOM元素)

作用:根据表达式的值的真假条件,来决定是否渲染元素,如果为false则不渲染(达到隐藏元素的目的),如果为true则渲染

在切换时,元素和它的数据绑定会被销毁并重建。

举例如下:(点击按钮时,切换和隐藏盒子)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="vue.js"></script>
</head>

<body>
  <div id="app">
    <button v-on:click="toggle">显示/隐藏</button>
    <div v-if="isShow">我是盒子</div>
  </div>
</body>

<script>
  new Vue({
    el: '#app',
    data: {
      isShow: true
    },
    methods: {
      toggle: function() {
        this.isShow = !this.isShow;
      }
    }
  });
</script>

</html>

效果如下:

v-show:设置元素的显示和隐藏(在元素上添加/移除style="display:none"属性)

作用:根据表达式的真假条件,来切换元素的 display 属性。如果为false,则在元素上添加 display:none属性;否则移除display:none属性。

举例如下:(点击按钮时,切换和隐藏盒子)

我们直接把上一段代码中的v-if改成v-show就可以了:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="vue.js"></script>
</head>

<body>
  <div id="app">
    <button v-on:click="toggle">显示/隐藏</button>
    <div v-show="isShow">我是盒子</div>
  </div>
</body>

<script>
  new Vue({
    el: '#app',
    data: {
      isShow: true
    },
    methods: {
      toggle: function() {
        this.isShow = !this.isShow;
      }
    }
  });
</script>

</html>

效果如下:

v-if和v-show的区别

v-ifv-show都能够实现对一个元素的隐藏和显示操作。

区别:

  • v-if:每次都会重新添加/删除DOM元素
  • v-show:每次不会重新进行DOM的添加/删除操作,只是在这个元素上添加/移除style="display:none"属性,表示节点的显示和隐藏。

优缺点:

  • v-if:有较高的切换性能消耗。这个很好理解,毕竟每次都要进行dom的添加/删除操作。?
  • v-show:有较高的初始渲染消耗。也就是说,即使一开始v-show="false",该节点也会被创建,只是隐藏起来了。而v-if="false"的节点,根本就不会被创建。

总结

  • 如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show
  • 如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if

  转载请注明: Weslyxl Vue基础

 上一篇
Vue路由 Vue路由
一、什么是路由以前在 React 的文章中说过路由这个东西,这里再说一下「再次加深一下记忆」。路由是什么我们可能不太理解,但是我说一个东西我们一定知道,就是 “路由器”, 路由器的功能用一句话概括就是:数据从一个网络到另一个网络就是靠路由
下一篇 
Java Web如何启动到Servlet&Tomcat Java Web如何启动到Servlet&Tomcat
平时我们在写一般的应用程序的时候,无论如何都会有一个main函数入口。而在进行web开发的时候,从头到尾我们都没有写过一个main函数。最后部署时,打了一个war包,传到web容器下面就可以了。到底这后面发生了什么,带着疑问让我开始吧。
  目录