1

Vue.directive 自定义指令


demo

图片加载完毕之前是一个随机的颜色块,之后设置器背景为该图片

// initImg

<template>
  <!-- 自定义指令 -->
  <!-- 图片加载之前是随机颜色背景,图片加载完成之后设置背景为图片 -->
  <div class="imgBox">
    <div class="img-item" v-img="item.url" v-for="(item, index) in imgs" :key="index"></div>
  </div>
</template>

<script>
// import Vue from 'vue'
export default {
  data () {
    return {
      imgs: [
        {
          url: 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=412581619,2356057617&fm=27&gp=0.jpg'
        },
        {
          url: 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2773798747,893160708&fm=27&gp=0.jpg'
        },
        {
          url: 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2310380780,3374798962&fm=27&gp=0.jpg'
        },
        {
          url: 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1670658008,1121581517&fm=27&gp=0.jpg'
        }
      ]
    }
  },
  directives: {
    img: {
      bind: function (el, binding, vnode) {
        const r = Math.floor(Math.random() * 256)
        const g = Math.floor(Math.random() * 256)
        const b = Math.floor(Math.random() * 256)
        let color = `rgb(${r},${g},${b})`
        el.style.backgroundColor = color

        var img = new Image()
        img.src = binding.value
        // 图片加载完毕之后
        img.onload = function () {
          el.style.backgroundImage = 'url(' + binding.value + ')'
        }
      }
    }
  },
  methods: {
    // rgb颜色
    _colorRGB () {
      const r = Math.floor(Math.random() * 256)
      const g = Math.floor(Math.random() * 256)
      const b = Math.floor(Math.random() * 256)

      return `rgb(${r},${g},${b})`
    },
    // rgba颜色
    _colorRGBA () {
      const r = Math.floor(Math.random() * 256)
      const g = Math.floor(Math.random() * 256)
      const b = Math.floor(Math.random() * 256)

      const alpha = Math.random()

      return `rgba(${r}, ${g}, ${b}, ${alpha})`
    }
  }
}
</script>

<style scoped>
.imgBox{
  width: 100%;
}
.imgBox .img-item{
  display: block;
  width: 100%;
  height: 200px;
  margin-bottom: 4px;
}
</style>
跟随鼠标移动的指令
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>练习:自定义指令</title>
    <script src="./vue.min.js"></script>
    <style>
        #itany div{
            width: 100px;
            height: 100px;
            position:absolute;
        }
        #itany .hello{
            background-color:red;
            top:0;
            left:0;
        }
        #itany .world{
            background-color:blue;
            top:0;
            right:0;
        }
    </style>
</head>
<body>
    <div id="itany">
        <div class="hello" v-drag>itany</div>
        <div class="world" v-drag>网博</div>
    </div>

    <script>
        Vue.directive('drag',function(el){
            el.onmousedown=function(e){
                //获取鼠标点击处分别与div左边和上边的距离:鼠标位置-div位置
                var disX=e.clientX-el.offsetLeft;
                var disY=e.clientY-el.offsetTop;
                // console.log(disX,disY);
                //包含在onmousedown里面,表示点击后才移动,为防止鼠标移出div,使用document.onmousemove
                document.onmousemove=function(e){
                    //获取移动后div的位置:鼠标位置-disX/disY
                    var l=e.clientX-disX;
                    var t=e.clientY-disY;
                    el.style.left=l+'px';
                    el.style.top=t+'px';
                }
                //停止移动
                document.onmouseup=function(e){
                    document.onmousemove=null;
                    document.onmouseup=null;
                }
            }
        });
        var vm=new Vue({
            el:'#itany',
            data:{
                msg:'welcome to itany',
                username:'alice'
            },
            methods:{
                change(){
                    this.msg='欢迎来到南京网博'
                }
            }
        });
    </script>
    
</body>
</html>

clipboard.png

Vue.extend() 扩展实例构造器


demo
// 实现了一个简单的组件
// 显示一个姓名,点击可以跳转
<!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="./vue.min.js"></script>
</head>
<body>
    <div id="author"></div>
    <!-- <author></author> -->
    <script>
        var author = Vue.extend({
            template: '<p><a :href="authorUrl">{{authorName}}</a></p>',
            data () {
                return {
                    authorName: 'zjj',
                    authorUrl: 'http://www.jspang.com'
                }
            }
        })
        //new author().$mount('author'); // 挂载到这个标签
        new author().$mount('#author'); // 挂载到这个标签
    </script>
</body>
</html>
  • extend构造过程

Vue.extend的作用

Vue.extend常和Vue的组件配合在一起使用。简单点说:Vue.extend是构造一个组件的语法器,你给这个构造器预设一些参数,而这个构造器给你一个组件,然后这个组件你就可以用到Vue.component这个全局注册方法里,也可以在任意Vue模板里使用这个构造器。

// 实现效果同上
<!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="./vue.min.js"></script>
</head>
<body>
    <div id="app">
        <my-com></my-com>
    </div>
    <script>
        // 【1】 vue.extend 构造一个组件
        var author = Vue.extend({
            template: '<p><a :href="authorUrl">{{authorName}}</a></p>',
            data () {
                return {
                    authorName: 'zjj',
                    authorUrl: 'http://www.jspang.com'
                }
            }
        })
        // 【2】通过component全局注册了一下,即可使用了
        Vue.component('my-com', author);
        var Vue = new Vue({
            el: '#app'
        })
    </script>
</body>
</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>
    <script src="./vue.min.js"></script>
</head>

<body>
    <div id="app">
        <my-com></my-com>
    </div>
    <script>
        // 【1】 vue.extend 构造一个组件
        var author = Vue.extend({
            template: '<p><a :href="authorUrl">{{authorName}}</a></p>',
            data() {
                return {
                    authorName: 'zjj',
                    authorUrl: 'http://www.jspang.com'
                }
            }
        })
        // 【2】通过component全局注册了一下,即可使用了
        Vue.component('my-com', {
            template: '<p><a :href="authorUrl">{{authorName}}</a></p>',
            data() {
                return {
                    authorName: 'zjj',
                    authorUrl: 'http://www.jspang.com'
                }
            }
        });
        var Vue = new Vue({
            el: '#app'
        })
    </script>
</body>

</html>
Vue.component()会注册一个全局的组件,其会自动判断第二个传进来的是Vue继续对象(Vue.extend)还是普通对象({...}),如果传进来的是普能对象的话会自动调用Vue.extend,所以你先继承再传,还是直接传普通对象对Vue.component()的最终结果是没差的。
理解Vue.extend()和Vue.component()是很重要的。由于Vue本身是一个构造函数(constructor),Vue.extend()是一个继承于方法的类(class),参数是一个包含组件选项的对象。它的目的是创建一个Vue的子类并且返回相应的构造函数。而Vue.component()实际上是一个类似于Vue.directive()和Vue.filter()的注册方法,它的目的是给指定的一个构造函数与一个字符串ID关联起来。之后Vue可以把它用作模板,实际上当你直接传递选项给Vue.component()的时候,它会在背后调用Vue.extend()。

vue.extend构造的其实是一个vue构造函数的一个子类、它的参数是一个包含组件选项的对象。其中data 必须是一个函数
import Vue from 'vue

// 【1】 一个包含组件选项的对象

var component = {
    props: {
        active: Boolean,
        propOne: String
    },
    template: `<div>
      <input type="text" v-model="text">
      <span v-show="active">see me if active</span>
    </div>`,
    data () {
        return {
            text: 0
        }
    },
    mounted() {
        console.log('comp mounted');
    }
}

// 【2】创建一个子类

var comVue = Vuew.extend(component);

// 【3】实例化一个子类
new comVue({
    el: '#root,
    propsData: {
        propOne: 'xxx'
    },
    data : {
        text: '123'
    },
    mounted () {
        console.log('instance mounted');
    }
})

Vue.set()

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app2">
<!--想了解这里key的作用请访问:(https://cn.vuejs.org/v2/api/#key)-->
    <p v-for="item in items" :key="item.id">
        {{item.message}}
    </p>
    <!--@click等价于v-on:click-->
    <button class="btn" @click="btn1Click()">点我试试</button><br/>
</div>

<script src="../extend/vue.min.js"></script>
<script>
    var vm2=new Vue({
        el:"#app2",
        data:{
            items:[
                {message:"Test one",id:"1"},
                {message:"Test two",id:"2"},
                {message:"Test three",id:"3"}
            ]
        },
        methods:{
            btn1Click:function(){
                this.items.push({message:"动态新增"});//为data中的items动态新增一条数据
            }
        }
    });
</script>
</body>
</html>

上诉demo利用数组的变异方法来实现数组的增减。但是我们却无法做到对某一条数据的修改。这时候就需要Vue的内置方法来帮忙了~

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app2">
    <p v-for="item in items" :key="item.id">
        {{item.message}}
    </p>
    <button class="btn" @click="btn2Click()">动态赋值</button><br/><br/>
    <button class="btn" @click="btn3Click()">为data新增属性</button>
</div>

<script src="../extend/vue.min.js"></script>
<script>
    var vm2=new Vue({
        el:"#app2",
        data:{
            items:[
                {message:"Test one",id:"1"},
                {message:"Test two",id:"2"},
                {message:"Test three",id:"3"}
            ]
        },
        methods:{
            btn2Click:function(){
                // 【1】 改变某一个索引项的值
                Vue.set(this.items, 0, {message: 'Meils', id: '20'});
                // 下面的这样改是无法成功的,因为Vue无法调用到
                // this.items[0]={message:"Change Test",id:'10'}
                // 
            },
            btn3Click:function(){
                // 【2】 往后面添加值 
                var len = this.items.length;
                Vue.set(this.items, len, {message: 'sss', id:'30'})
            }
        }
    });

</script>
</body>
</html>
调用方法:Vue.set( target, key, value )

target:要更改的数据源(可以是 对象 或者 数组 )

key:要更改的具体数据

value :重新赋的值
当写惯了JS之后,有可能我会想改数组中某个下标的中的数据我直接this.items[XX]就改了,如
// 下面的这样改是无法成功的,因为Vue无法检测到数组数据的改变
// this.items[0]={message:"Change Test",id:'10'}

原因:

由于Javascript的限制,Vue不能自动检测以下变动的数组

  • 当你利用索引直接设置一个项时,vue不会为我们自动更新。
  • 当你修改数组的长度时,vue不会为我们自动更新
Tip: Vue.set()在methods中也可以写成this.$set()

生命周期


<!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>vue生命周期学习</title>
  <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
</head>
<body>
  <div id="app">
    <h1>{{message}}</h1>
  </div>
</body>
<script>
  var vm = new Vue({
    el: '#app',
    data: {
      message: 'Vue的生命周期'
    },
    beforeCreate: function() {
      console.group('------beforeCreate创建前状态------');
      console.log("%c%s", "color:red" , "el     : " + this.$el); //undefined
      console.log("%c%s", "color:red","data   : " + this.$data); //undefined 
      console.log("%c%s", "color:red","message: " + this.message) 
    },
    created: function() {
      console.group('------created创建完毕状态------');
      console.log("%c%s", "color:red","el     : " + this.$el); //undefined
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化 
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化
    },
    beforeMount: function() {
      console.group('------beforeMount挂载前状态------');
      console.log("%c%s", "color:red","el     : " + (this.$el)); //已被初始化
      console.log(this.$el);
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化  
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化  
    },
    mounted: function() {
      console.group('------mounted 挂载结束状态------');
      console.log("%c%s", "color:red","el     : " + this.$el); //已被初始化
      console.log(this.$el);    
      console.log("%c%s", "color:red","data   : " + this.$data); //已被初始化
      console.log("%c%s", "color:red","message: " + this.message); //已被初始化 
    },
    beforeUpdate: function () {
      console.group('beforeUpdate 更新前状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);   
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    updated: function () {
      console.group('updated 更新完成状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el); 
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    beforeDestroy: function () {
      console.group('beforeDestroy 销毁前状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);    
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message); 
    },
    destroyed: function () {
      console.group('destroyed 销毁完成状态===============》');
      console.log("%c%s", "color:red","el     : " + this.$el);
      console.log(this.$el);  
      console.log("%c%s", "color:red","data   : " + this.$data); 
      console.log("%c%s", "color:red","message: " + this.message)
    }
  })
</script>
</html>

  • 依次分析

beforeCreate

刚刚实例化了一下new Vue()

created

clipboard.png

数据data绑定完毕,事件初始化完毕
此时还是没有el选项

beforeMounted

初始化el完毕,数据data绑定完毕,但是还是通过{{message}}进行占位的,因为此时还有挂在到页面上,还是JavaScript中的虚拟DOM形式存在的。

挂载 dom 并 模板编译过程:

1. 首先会判断对象是否有el选项。如果有的话就继续向下编译,如果没有el选项,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el)。
2. 然后进行模板编译

mounted

虚拟dom替换为真实的dom数据结构
beforeMouted 和 mounted的区别

clipboard.png

beforeUpdate

updated

beforeDestroy

钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。

destroyed

钩子函数在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

Meils
1.6k 声望157 粉丝

前端开发实践者