Vue 第1章 简述 Vue2

一、Vue2

1、特点

  • 组件化模式,实现代码复用
  • 声明式编码,无需直接操作DOM
  • Diff 算法和虚拟 DOM

2、底层原理

1)数据劫持

  • Vue 实例对象为 data 中的属性添加 getter 和 setter 的行为称为 数据劫持

2)数据监测

(1)监测对象
  • 通过 setter 实现监测,且要在 new Vue 时就传入要监测的数据。

  • 新增属性,Vue 默认不做响应式处理。但是可以使用 API

    • 该API不能给 Vue实例对象的根数据对象添加属性
    Vue.set(target,propertyName/index,value)
    
(2)监测数组
  • 通过包裹数组的方法实现,本质是做了两件事
    • 调用原生方法对数组进行更新
    • 重新解析模板,更新页面
    • 数组方法为 shift,unshift,pop,push,reverse,sort,splice

3)数据代理

(1)Vue实例
  • Vue 实例和容器是一对一的关系。

  • el 用于指定当前Vue实例为哪个容器服务

    <div id='root'>
    </div>
    
    new Vue({
        el:'#root',
        data:{
            name:'zhan',
            age:18
    	}
    })
    
(2)Object.defineProperty
  • 指定配置项

    Object.defineProperty( person,'age',{
        value:18,
        enumerable:true,	// 控制属性是否可以枚举,默认 false
        writable:true,		// 控制属性是否可以修改,默认 false
        configurable:true	// 控制属性是否可以删除,默认 false
    })
    
  • 配置 getter 和 setter

    Object.defineProperty( person,'age',{
        get(){
            console.log(" someone 读取 age ")
            return number
        }
        set(value){
        	number = value
    }
    })
    
(3)图示
  • Vue 实例对象将构造函数的data存放到自身的 _ data 中。

  • 为方便开发,Vue实例对象使用数据代理在外层添加 _data中的 name属性和address属性,这一步使用了 Object.defineProperty。

  • 此时修改外层的name属性,实际是修改了 _data 中的name属性。
    在这里插入图片描述

3、响应式原理

1)vue2

  • 实现原理

    • 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)。
    • 数组类型:对数组的变更方法进行了包裹
  • 模拟实现

    // 无法实现新增属性、删除属性
    let person = {
        name:'张三'age:18
    }
    
    let p = {}
    
    Object.defineProperty( p,'name',{
        get(){
            return person.name
        },
        set(value){
            person.name = value
        }
    })
    
    Object.defineProperty( p,'age',{
        get(){
            return person.age
        },
        set(value){
            person.age = value
        }
    })
    
  • 存在问题

    • 新增属性、删除属性,界面不会更新;

      • 补充:新增属性

        this.$set(this.person,'sex','女')
        
      • 补充:删除属性

        this.$delete(this.person,'sex')
        
    • 直接通过下标修改数组,界面不会自动更新。

      • 补充:修改属性

        hobby = ['吃饭''学习','学习']
        
        this.$set(this.hobby,0,'学习')
        
        console.log(this.hobby)	// 学习,学习,学习
        

2)vue3

  • 实现原理

    • 通过 Proxy 代理:拦截对象中任意属性的变化,包括:属性值的读写、添加、删除等。
    • 通过 Reflect 反射:对被代理对象的属性进行操作
  • 模拟实现

    let person = {
        name:'张三'age:18
    }
    
    // 建立映射关系
    const p = new Proxy( person ,{
        //读取
        get(target,propName){
            return Reflect.get(target,propName)
        },
        //修改 新增
        set(target,propName,value){
            Reflect.get(target,propName,value)
        },
        //删除
        deleteProperty(target,propName){
            return Reflect.deleteProperty(target,propName)
        }
    })
    

4、生命周期

  • beforeMount 读不到 Dom 元素,返回值是 undefined
  • beforeUpdate 获取的是更新之前的 Dom 元素,updated 获取的是 更新之后的 Dom 元素。

1)Vue2

  1. 此阶段初始化生命周期。
  2. beforeCreate
    1. 此时无法访问 data 中的数据和 method 中的方法。
  3. 此阶段进行数据监测与数据代理。
  4. created
    1. 可以访问到 data 中的数据和 method 中的方法。
  5. 此阶段解析模板,生成虚拟DOM,但是页面还不能显示解析好的内容。
  6. beforeMount
    1. 页面呈现的是未经 Vue 编译的DOM结构。
    2. 所有对DOM的操作,最终都不奏效。
  7. 此阶段将虚拟DOM转为真实DOM插入页面。
  8. mounted
    1. 页面呈现的是经过 Vue 编译的DOM结构。
    2. 所有对DOM的操作,均奏效。
    3. 一般在此进行初始化操作:开启定时器、发送网络请求、订阅消息、绑定自定义事件等。
  9. 此阶段数据发生改变。
  10. beforeUpdate
    1. 数据是新的,页面是旧的。
  11. 此阶段 根据新数据生成新的虚拟DOM,随后与旧的虚拟DOM比较,最终完成页面更新。
  12. updated
    1. 数据是新的,页面也是新的。
  13. beforeDestroy
    1. 此时的 data、method 都处于可用状态,可以修改但页面不再更新。
    2. 一般在此进行收尾工作:关闭定时器、取消订阅消息、解绑自定义事件等。
  14. destroy
    1. 销毁后自定义事件会失效,但是原生DOM事件依旧有效。

2)Vue3

  • Vue3,setup 代替了 beforecreate 和 created。

  • Vue3 使用 unmount 代替 destroy。

  • Vue3 可以在 setup 中使用组合式生命周期钩子,命名在钩子前面添加 “ on ”。

  • 组合式生命周期钩子的执行时机优先于配置项的钩子。

5、methods 事件处理

1)参数

  • 默认参数为 $event,但是存在其他参数则需要显式指定 $event。

    <div @click='show' ></div>
    <div @click='show1(a,$event)' ></div>
    
    methods:{
        show(){
            console.log( $event )
        },
        show1(a,$event){
            console.log( a,$event )
        }
    }
    

2)事件修饰符

修饰符作用
prevent阻止默认行为
stop阻止事件冒泡
once事件只触发一次
capture使用事件的捕获模式

3)v-model 修饰符

修饰符作用
v-model.trim移除前后空格
v-model.number输入字符串转为有效的数字
v-model.lazy失去焦点后才更新数据

6、computed 计算属性

1)原理

  • 底层借助了 Object.defineProperty 方法提供的 getter 和 setter

  • 优势:内部有缓存机制

2)调用时机

  • 初次读取 fullName 时。
  • 所依赖的数据发生变化时。
computed:{
    fullName:{
        get(){
            return this.firstName + '-' + this.lastName
        },
        set(value){
            console.log(value)
        }
    }
}
  • 简写形式。
computed:{
    fullName(){
         return this.firstName + '-' + this.lastName
    }
}

7、watch 监视属性

  • 可以监视 计算属性

    watch:{
        isHot:{
            deep:true,
            immediate:true,
            handler(new,old){
                
            }
        }
    }
    

1)computed 对比

  1. computed 能完成的功能,watch 都可以完成。
  2. watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。

8、指令

1)内置指令

(1)v-once
  • 所在节点在初次渲染后就不再更新,视为静态内容

    <h2 v-once> {{n}} </h2>
    
(2)v-pre
  • 可以跳过没有使用指令语法和插值语法的结点,加快编译。

    <h2 v-pre> {{n}} </h2>
    

2)自定义指令

  • this 全部指向 window
  • 命名方式:多个单词间使用 - ,不遵循驼峰命名法。
  • 推荐使用 对象式 写法,函数式在特殊情况下(如获取焦点和获取父元素)存在Bug。
(1)函数式
  • 参数1是DOM元素,参数2是绑定的元素

  • 执行时机:

    • 指令与元素成功绑定时(初始化);
    • 指令所在的模板被重新解析时。
    <span v-big-number = 'n' ></span>
    
    data:{
    	n:1
    },
    directives:{
    	'big-number'(element,binding){
    		console.log( binding.value )
    	}
    }
    
(2)对象式
  • bind 和 update 实际组成了 函数式的写法。

    <span v-big = 'n' ></span>
    
    data:{
    	n:1
    },
    directives:{
    	big:{
            // 指令与元素成功绑定时 调用
            bind(element,binding){},
            // 指令所在元素被插入页面时 调用
            inserted(element,binding){
                element.focus()
            },
            // 指令所在模板被重新解析时 调用
            update(element,binding),
        }
    }
    
(3)全局指令
  • 可以在所有 Vue 实例对象内 使用。

    Vue.directive('big',{
            // 指令与元素成功绑定时 调用
            bind(element,binding){},
            // 指令所在元素被插入页面时 调用
            inserted(element,binding){
                element.focus()
            },
            // 指令所在模板被重新解析时 调用
            update(element,binding),
        })
    

9、页面渲染

1)绑定 class 样式

  • 对象写法

    <div :class= "{ a1:true, a2:false }"></div>
    

2)ref 属性

  • 应用在 html 标签上获取的是真实的 DOM 结构。

  • 应用在 组件标签上获取的是 组件实例对象。

    <h1 ref='title'></h1>
    
    showDOM(){
        console.log( this.$refs.title )	// <h1 ref='title'></h1>
    }
    

3)nextTick

  • 作用:在下一次 DOM 更新结束后执行其指定的回调。

  • 当数据改变后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行。

    this.$nextTick( function(){
        this.$refs.title.focus()
    })
    

4)条件渲染

(1)v-if
  • 不展示的 DOM 结构直接被移除
  • v-if 可以与 template 标签配套使用,但是 v-show 与该标签配套使用无效。
(2)v-show
  • DOM 结构依然存在,但是 display : none
  • 频繁切换的推荐使用 v-show。
(3)面试题
  • v-if、v-show、visibility:hidden 的区别

    v-if:DOM 结构直接被移除,即打开 F12 没有该结构。

    v-show:DOM 结构存在,但是增加了 display : none 属性,不在页面中占位。

    visibility:hidden:DOM结构存在,且在页面中占位。

5)列表渲染

(1)key 原理
  1. key 是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成新的虚拟DOM。
  2. 随后Vue采用 Diff 算法将新虚拟DOM与旧虚拟DOM的差异比较。
(2)Diff 算法
  • 旧虚拟DOM中找到了与新虚拟DOM相同的key:

    • 若虚拟DOM中内容没变,直接使用之前的真实DOM!
    • 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
  • 旧虚拟DOM中未找到与新虚拟DOM相同的key:

    • 创建新的真实DOM,随后渲染到到页面。
  • index作为key可能会引发的问题:

    • 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新。
      在这里插入图片描述
(3)源码实现
  • 首先是前序一对一循环比较,遇到不一致的跳出循环。

  • 然后是后序一对一循环比较,遇到不一致的跳出循环。

  • 中间进行一对多遍历比较,尽可能复用。
    在这里插入图片描述

6)动画与过渡

  • appear属性可以增加进场动画。

    <transition name='hello' appear>
    	<h1 v-show="isShow"> </h1>
    </transition>
    
    <style>
        .hello-enter-active{
            animation:a1 1s;
        }
    
        .hello-leave-active{
            animation:a1 1s reverse;
        }
    
    	@keyframes a1{
            from{
                transform:translateX( -100% )
            }
            to{
                transform:translateX(0)
            }                         
        }
    </style>
    
  • tranasition 只能用于一个元素

    <transition name='hello' appear>
    	<h1 v-show="isShow"> </h1>
    </transition>
    
    <style>
    /* 进入的起点,离开的终点 */
    .hello-enter, .hello-leave-to{
        transform:translateX( -100% );
    }
    .hello-enter-active, .hello-leave-active{
        transition: 0,5s linear;                     
    }                        
    /* 进入的终点,离开的起点 */
    .hello-enter-to, .hello-leave{
        transform:translateX( 0 );
    }                         
    </style>
    
  • 多个元素需要使用 transition-group,并且每个元素需要有 key

    <transition-group name='hello' appear>
    	<h1 v-show="isShow" key='1'> </h1>
    	<h1 v-show="isShow" key='2'> </h1>
    </transition-group>
    

10、组件

1)概述

  • 组件的本质是一个名为 VueComponent 的构造函数,由 Vue.extend 生成。

    export default {
        data(){
            
        }
    }
    
    // 等同于
    export default Vue.extend({
        data(){
            
        }
    })
    
  • 编写组件标签,Vue 解析模板时,会创建该组件的实例对象,即执行如下代码

    new VueComponent( options )
    
  • new Vue 配置中,this 指向 Vue实例对象。组件配置中, this 指向 VueComponent 组件实例对象。

2)创建

  • el 不用写,所有组件最终都由一个 Vue实例对象管理,由实例对象的 el 决定服务于哪个容器。

  • 组件的 data 配置项为什么要写成 函数式?

    • 一个组件可能在多个位置被引用,采用对象式的写法,当一个位置修改 data 时,其他位置的 data 也会被修改,即存在引用关系,造成数据混乱。采用函数式写法则不会有这个问题。
    <hello></hello>
    
    const hello = Vue.extend({
        data(){
            return {
                a:1,
                b:2
            }
        },
        template:`<div>{{a}}</div>`
    })
    
    new Vue({
        el:'#root',
        components:{
            hello
        }
    })
    

3)Vue实例与组件的内置关系

  • 实现原理:

    • VueComponent 构造函数的原型对象的原型对象,即 VueComponent.prototype.__proto__ 原本指向 原型链的终点 null,Vue 将之指向了 Vue 实例对象的原型对象。
      在这里插入图片描述
  • 作用:让组件实例对象可以访问到 Vue 原型上的属性和方法。

    VueComponent.prototype.__proto__ = Vue.prototype
    

11、组件间通信

  • props 方式与组件自定义事件有相似
    • 都需要在编写组件标签时声明所传递的数据或者函数名。
    • props 方式需要 props 配置项接收,组件自定义事件需要 this.$emit 函数触发。
  • 全局事件总线是组件自定义事件的升级,通过 $bus 统一管理,把各个组件的自定义事件统一绑定到 Vue 实例对象上。
  • 消息订阅是全局事件总线的换壳版本,将 $bus 的功能交给第三方库实现。
  • 插槽可以传递 html 结构。

1)props

(1)父传子
  • ==props 是只读的,可以将 props 的数据赋值给 data。命名冲突时,父组件的优先级更高。

    // 父组件
    // 传递非字符串类型需要 :
    <Student name='lisi' sex='女' :age='18' />
        
    // 子组件 方式一
    name:'school',
    data(){
        return {
            myAge:this.age
        }
    },
    props:{
        name:String,
        sex:String,
        age:Number
    }
    
  • 可以设置默认值和限制必要性

    // 父组件
    <Student name='lisi' sex='女' :age='18' />
        
    // 子组件 方式一
    name:'school',
    props:{
        name:String,
        sex:String,
        age:Number
    }
    
    // 子组件 方式二
    name:'school',
    props:{
        name:{
            type:String,
            required:true	// 必需的
        },
        age:{
            type:Number,
            default:99		// 默认值
        }
    }
    
(2)子传父
  • 子组件向父组件传递数据,需要父组件先定义和传递一个函数

    // 父组件
    <MyHeader :receive='receive' />
        
    methods:{
        receive(todoObj){
            console.log('this is parent',todoObj )
        }
    }
    
    // 子组件
    props:['receive']
    methods:{
        add(){
            this.receive(todoObj)
        }
    }
    

2)组件的自定义事件

  • 核心:给哪个组件绑定自定义事件,就需要触发这个组件。

  • 当前组件实例对象被销毁,该组件的所有自定义事件都失效,但原生事件不受影响。

(1)子传父
  • 通过触发 Student 组件实例身上的 atguigu 事件 传递数据

    // 父组件
    <Student @atguigu='demo' />
        
    methods:{
        demo(name){
            console.log('demo 被调用了',name)
        }
    }
    
    // 子组件
    <button @click='sendStudent'> </button>
    
    methods:{
        sendStudent(){
            this.$emit('atguigu',this.name)
        }
    }
    
  • 可以解绑组件的自定义事件

    // 子组件
    <button @click='unbind'> </button>
    
    methods:{
        unbind(){
            this.$off('atguigu')	// 解绑一个
            this.$off(['atguigu','demo'])	// 解绑多个
        }
    }
    
(2)查看
  • 通过图片所示方式可以查看已经触发的自定义事件。
    在这里插入图片描述

3)全局事件总线

  • 可以用于任意组件间通信。
(1)安装
  • 安装全局事件总线

    new Vue({
        el:'#app',
        render:h => h(App),
        beforeCreate(){
            Vue.prototype.$bus = this
        }
    })
    
(2)使用
  • 一般在 mounted 函数中注册。

    // 组件A
    methods(){
        demo(data){
            ...
        }
    }
        
    mounted(){
        this.$bus.$on('hello',demo)
    }
    
    // 组件B
    methods(){
        show(){
            this.$bus.$emit('hello',666)
        }
    }
    
  • 解绑

    beforeDestroy(){
        this.$bus.off('hello')
    }
    

4)消息订阅

  • 可以用于任意组件间通信。
(1)下载
  • 下载第三方库

    npm i pubsub-js
    
(2)订阅发布
  • 订阅

    • 参数1是消息的名字
    • 参数2是传递的数据
    import pubsub from 'pubsub-js'
    
    methods(){
        demo(msgName,data){
            ...
        }
    }
    
    mounted(){
        this.pubId = pubsub.subscribe('hello', this.demo )
    }
        
    beforeDestroy(){
        pubsub.unsubscribe( this.pubId )
    }
    
  • 发布

    import pubsub from 'pubsub-js'
    
    pubsub.publish('hello', 666)
    

5)插槽

  • 作用:让父组件可以向子组件指定位置插入 html 结构。
  • 支持双向通信。
(1)具名插槽
  • 父组件没有填充数据时,子组件显示默认值。

  • 样式可以写在父组件也可以写在子组件。

    // 父组件
    <Category title='food'>
        <h1 slot='center'>你好啊</h1>
        <h1 slot='footer'>你好啊!!!</h1>
    </Category>
    
    // 子组件
    <div>
        <slot name='center'>默认值</slot>
        <slot name='footer'>默认值</slot>
    </div>
    
(2)作用域插槽
  • 页面结构由父组件决定,数据存放在子组件。

    // 父组件
    <Category title='food'>
       <template scope='{game}'>   
        	<h1 slot='center'> {{game}}</h1>
       </template>
    </Category>
    
    // 子组件
    <div>
        <slot name='center' :game='game'>默认值</slot>
    </div>
    
    data(){
        return {
            game:[1,2,3]
        }
    }
    

12、脚手架

1)render 函数

  1. vue.js 与 vue.runtime.xxx.js 的区别

    1. vue.js 是完整版的 Vue,包含:核心功能 + 模板解析器。
    2. vue.runtime.xxx.js 是运行版的 Vue,只包含 核心功能。
  2. Vue-CLI 默认引入的是 vue.runtime.xxx.js,没有模板解析器,不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数指定具体内容。

    render(createElement){
        return createElement(App)
    }
    

2)mixin 混入

  • 可以将公共配置提取到 js 文件中,通过 mixin 引入。

    // mixin.js
    export const a1 = {
        methods:{
            showName(){
                alert( this.name )
            }
        }
    }
    
    // 组件
    <h2 @click='showName'></h2>
    
    import { a1 } from '../mixin'
    export default {
        name:'Student',
        mixins:[ a1 ]
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值