3

1. 最简单的vue

  1. el: dom节点
  2. data: 数据

    <!DOCTYPE html>
    <html>
    
    <head>
     <meta charset="utf-8">
     <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
     <script src="https://cdn.staticfile.org/vue/2.4.2/vue.min.js"></script>
    </head>
    
    <body>
     <div id="app">
         <p> {{ message }}</p>
         <input type="text" v-model="message">
     </div>
     <script type="text/javascript">
         var app = new Vue({
             el: '#app',
             data: {
                 message: 'Hello Vue!'
             }
         })
     </script>
    </body>
    
    </html>

    2. Vue 数据里的数组对象更新,但是视图不更新

    2.1 问题

    由于js的限制,Vue 不能检测以上数组的变动,以及对象的添加/删除,很多人会因为像上面这样操作,出现视图没有更新的问题。

2.2 解决办法

  1. this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value)

    this.$set(this.arr, 0, "aa"); // 改变数组
    this.$set(this.obj, "c", "cc"); // 改变对象
  2. 数组原生方法触发视图更新:
    Vue可以监测到数组变化的,数组原生方法:

     splice()、 push()、pop()、shift()、unshift()、sort()、reverse()

    2.3实例

    <!DOCTYPE html>
    <html>
    
    <head>
     <meta charset="utf-8">
     <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
     <script src="https://cdn.staticfile.org/vue/2.4.2/vue.min.js"></script>
    </head>
    
    <body>
     <div id="app">
         <p>arr:{{arr}}</p>
         <p>obj:{{obj}}</p>
         <button @click="arrFn1">修改数组</button>
         <button @click="arrFn2">改变数组</button>
         <button @click="objFn1">增加和删除对象</button>
         <button @click="objFn2">修改对象</button>
         <button @click="this$set">this.$set</button>
     </div>
     <script type="text/javascript">
         var vm = new Vue({
             el: '#app',
             data() {
                 return {
                     arr: [1, 2, 3],
                     obj: {
                         a: 1,
                         b: 2
                     }
                 }
             },
             methods: {
                 objFn1() {
                     this.obj.c = 'c' //增加对象属性
                     delete this.obj.a; //删除对象属性
                     console.log(this.obj) //数据变化,视图没有变化
                 },
                 objFn2() {
                     this.obj.a = 'aa'
                     console.log(this.obj) //数据变化,视图变化
                 },
                 arrFn1() {
                     this.arr[0] = '11'; //修改数组
                     this.arr.length = 1; //修改数组
                     console.log(this.arr) //数据变化,视图没有变化
                 },
                 arrFn2() {
                     // splice()、 push()、pop()、shift()、unshift()、sort()、reverse()
                     this.arr.push('3')
                     console.log(this.arr) //数据变化,视图变化
                 },
                 this$set() {
                     this.$set(this.arr, 0, "11"); // 改变数组
                     this.$set(this.obj, "c", "11"); // 改变对象
                 }
             }
         })
     </script>
    </body>
    
    </html>

    3. filter过滤器的作用

    <!DOCTYPE html>
    <html>
    
    <head>
     <meta charset="utf-8">
     <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
     <script src="https://cdn.staticfile.org/vue/2.4.2/vue.min.js"></script>
    </head>
    
    <body>
     <div id="app">
         <div>{{message | filterTest}}</div>
     </div>
     <script type="text/javascript">
         var vm = new Vue({
             el: '#app',
             data() {
                 return {
                     message: 1
                 }
             },
             filters: {
                 filterTest(value) {
                     // value在这里是message的值
                     // return value + '%';
                     return `${value}%`;
                 }
             }
    
         })
     </script>
    </body>
    
    </html>

    4. v-for与v-if优先级

    v-if尽量不要与v-for在同一节点使用,因为v-for 的优先级比 v-if 更高,如果它们处于同一节点的话,那么每一个循环都会运行一遍v-if
    如果你想根据循环中的每一项的数据来判断是否渲染,那么你这样做是对的:

     <li v-for="todo in todos" v-if="todo.type===1">
      {{ todo }}
    </li>

    如果你想要根据某些条件跳过循环,而又跟将要渲染的每一项数据没有关系的话,你可以将v-if放在v-for的父节点:

    // 数组是否有数据 跟每个元素没有关系
    <ul v-if="todos.length">
      <li v-for="todo in todos">
     {{ todo }}
      </li>
    </ul>
    <p v-else>No todos left!</p>
    

    正确使用v-for与v-if优先级的关系,可以为你节省大量的性能。

5.vue生命周期

5.1 实例

<!DOCTYPE html>
<html>

<head>
    <title></title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
</head>

<body>

    <div id="app">
        <p>{{ message }}</p>
    </div>

    <script type="text/javascript">
        var app = new Vue({
            el: '#app',
            data: {
                message: "xuxiao is boy"
            },
            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>
</body>

</html>

5.2 create和mounted

  1. beforecreated:el 和 data 并未初始化
  2. created:完成了 data 数据的初始化,el没有
  3. beforeMount:完成了 el 和 data 初始化
  4. mounted :完成挂载

另外在标绿处,我们能发现el还是 {{message}},这里就是应用的 Virtual DOM(虚拟Dom)技术,先把坑占住了。到后面mounted挂载的时候再把值渲染进去。

clipboard.png

5.3update 相关

clipboard.png

5.4destroy

有关于销毁,暂时还不是很清楚。我们在console里执行下命令对 vue实例进行销毁。销毁完成后,我们再重新改变message的值,vue不再对此动作进行响应了。但是原先生成的dom元素还存在,可以这么理解,执行了destroy操作,后续就不再受vue控制了。

clipboard.png

5.5 总结

  1. beforecreate : 举个栗子:可以在这加个loading事件(没有this)
  2. created :在这结束loading,还做一些初始化,实现函数自执行
  3. mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些事情
  4. beforeDestroy: 你确认删除XX吗? destroyed :当前组件已被删除,清空相关内容(没有this)

    5.6 参考

    https://segmentfault.com/a/1190000008010666
    Vue的生命周期函数和beforeRouteEnter()/beforeRouteLeave()的函数

    6.vue 为什么采用Virtual DOM

  5. 创建真实DOM的代价高:真实的 DOM 节点 node 实现的属性很多,而 vnode 仅仅实现一些必要的属性,相比起来,创建一个 vnode 的成本比较低。
    2.触发多次浏览器重绘及回流:使用 vnode ,相当于加了一个缓冲,让一次数据变动所带来的所有 node 变化,先在 vnode 中进行修改,然后 diff 之后对所有产生差异的节点集中一次对 DOM tree 进行修改,以减少浏览器的重绘及回流
  6. 虚拟dom由于本质是一个js对象,因此天生具备跨平台的能力,可以实现在不同平台的准确显示。
  7. Virtual DOM 在性能上的收益并不是最主要的,更重要的是它使得 Vue 具备了现代框架应有的高级特性。

    例子
{
    tag: 'div',                 /*说明这是一个div标签*/
    children: [                 /*存放该标签的子节点*/
        {
            tag: 'a',           /*说明这是一个a标签*/
            text: 'click me'    /*标签的内容*/
        }
    ]
}

渲染后可以得到

<div>
    <a>click me</a>
</div>

7. 组件data为什么必须是函数

  1. 因为不使用return包裹的数据会在项目的全局可见,会造成变量污染
  2. 使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件

当一个组件被定义, data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。

8. 组件style的scoped

为什么在组件中用js动态创建的dom,添加样式不生效

 <template>
     <div class="test"></div>
</template>
<script>
    let a=document.querySelector('.test');
    let newDom=document.createElement("div"); // 创建dom
    newDom.setAttribute("class","testAdd" ); // 添加样式
    a.appendChild(newDom); // 插入dom
</script>
<style scoped>
.test{
   background:blue;
    height:100px;
    width:100px;
}
.testAdd{
    background:red;
    height:100px;
    width:100px;
}
</style>

结果

// test生效   testAdd 不生效
<div data-v-1b971ada class="test"><div class="testAdd"></div></div>
.test[data-v-1b971ada]{ // 注意data-v-1b971ada
    background:blue;
    height:100px;
    width:100px;
}

原因

当 <style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。
它会为组件中所有的标签和class样式添加一个scoped标识,就像上面结果中的data-v-1b971ada。
所以原因就很清楚了:因为动态添加的dom没有scoped添加的标识,没有跟testAdd的样式匹配起来,导致样式失效。

解决办法:

去掉scoped即可

9. vue-router实现原理

9.1更新视图而不重新请求页面

SPA(single page application):单一页面应用程序,只有一个完整的页面;它在加载页面时,不会加载整个页面,而是只更新某个指定的容器中内容。单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面;vue-router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式;根据mode参数来决定采用哪一种方式。

9.2. Hash模式

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。 hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说hash 出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置;所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。hash 模式的原理是 onhashchange 事件(监测hash值变化),可以在 window 对象上监听这个事件

<!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>
        ul {
            height: 1000px;
            border-bottom: 1px solid #000;
        }
    </style>
</head>

<body>
    <ul>
        <li class="a">
            <a href="#a">a</a>
        </li>
        <li class="b">
            <a href="#b">b</a>
        </li>
    </ul>
    <div>
        <div id="a">a</div>
        <div id="b">b</div>
    </div>
</body>

</html>

9.3 History模式

由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",这种模式充分利用了html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,比较好看!
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

 export const routes = [ 
  {path: "/", name: "homeLink", component:Home}
  {path: "/register", name: "registerLink", component: Register},
  {path: "/login", name: "loginLink", component: Login},
  {path: "*", redirect: "/"}]

此处就设置如果URL输入错误或者是URL 匹配不到任何静态资源,就自动跳到到Home页面

10.vue自定义指令

10.1 全局注册指令

<!DOCTYPE html>
<html>

<head lang="en">
    <meta charset="UTF-8">
    <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
    <title></title>
</head>

<body>
    <div id="app" class="demo">
        <!-- 全局注册 -->
        <input type="text" placeholder="我是全局自定义指令" v-focus>
    </div>
    <script>
        Vue.directive("focus", {
                inserted: function(el) {
                    el.focus();
                }
            })
            //new Vue要放在后面
        new Vue({
            el: "#app"
        })
    </script>


</body>

</html>

10.2 局部注册指令

<!DOCTYPE html>
<html>

<head lang="en">
    <meta charset="UTF-8">
    <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
    <title></title>
</head>

<body>
    <div id="app" class="demo">
        <!-- 局部注册 -->
        <input type="text" placeholder="我是局部自定义指令" v-focus2>
    </div>
    <script>
        new Vue({
            el: "#app",
            directives: {
                focus2: {
                    inserted: function(el) {
                        el.focus();
                    }
                }
            }
        })
    </script>



</body>

</html>

10.2 钩子函数

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

  1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
  2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
  4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  5. unbind:只调用一次,指令与元素解绑时调用。

    <!DOCTYPE html>
    <html>
    
    <head lang="en">
     <meta charset="UTF-8">
     <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
     <title></title>
    </head>
    
    <body>
    
     <div id="container">
    
         <!-- 准备实现需求:
     在h1标签上面,加上一个按钮,当点击按钮时候,对count实现一次
     自增操作,当count等于5的时候,在控制台输出‘it is a test’
     -->
         <button @click="handleClick">clickMe</button>
         <h1 v-if="count < 6" v-change="count">it is a custom directive</h1>
     </div>
    
     <script>
         //directive
         new Vue({
             el: '#container',
             data: {
                 count: 0,
                 color: '#ff0000'
             },
             methods: {
                 handleClick: function() {
                     //按钮单击,count自增
                     this.count++;
                 }
             },
             directives: {
                 change: {
                     bind: function(el, bindings) {
                         console.log('指令已经绑定到元素了');
                         console.log(el);
                         console.log(bindings);
                             //准备将传递来的参数
                             // 显示在调用该指令的元素的innerHTML
                         el.innerHTML = bindings.value;
                     },
                     update: function(el, bindings) {
                         console.log('指令的数据有所变化');
                         console.log(el);
                         console.log(bindings);
                         el.innerHTML = bindings.value;
                         if (bindings.value == 5) {
                             console.log(' it is a test');
                         }
                     },
                     unbind: function() {
                         console.log('解除绑定了');
                     }
                 }
             }
         })
     </script>
    
    </body>
    
    </html>

10.3 参考

https://www.cnblogs.com/wangruifang/p/7765536.html
https://juejin.im/post/5a3933756fb9a045167d52b1

11.v-if 和 v-show 区别

v-if按照条件是否渲染,v-show是display的block或none

12.v-for 中 :key 到底有什么用

key的作用主要是为了高效的更新虚拟DOM。

12.1参考

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

13. Vue.nextTick()

13.1什么是Vue.nextTick()

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

注意:重点是获取更新后的DOM 就是在开发过程中有个需求是需要在created阶段操作数据更新后的节点 这时候就需要用到Vue.nextTick()

$nextTick就是用来知道什么时候DOM更新完成
nexttick是怎么可以获取到更新后的dom的_深入浅出理解vm.$nextTick

13.2原因

在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以在created中一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题

13.3$refs获取dom节点属性

<!DOCTYPE html>
<html>

<head lang="en">
    <meta charset="UTF-8">
    <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
    <title></title>
</head>

<body>

    <div id="app">
        <div ref="msg1">{{msg1}}</div>
        <div>{{msg2}}</div>
        <button @click="changeMsg">点击我</button>
    </div>

    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    msg1: "11",
                    msg2: "22"
                }
            },
            methods: {
                changeMsg() {
                    this.msg1 = "33"
                    this.msg2 = this.$refs.msg1.textContent;
                    console.log('DOM并未渲染完成' + this.$refs.msg1.textContent)  //11
                    this.$nextTick(function() {
                        console.log('DOM已经何渲染完成' + this.$refs.msg1.textContent)  //33
                    })
                }
            }

        })
    </script>

</body>

</html>

13.4 document.getElementById获取节点

<!DOCTYPE html>
<html>

<head lang="en">
    <meta charset="UTF-8">
    <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
    <title></title>
</head>

<body>

    <div id="app">
        <div ref="msg" id="msg" v-if="isShow">{{msg}}</div>
        <button @click="changeMsg">点击我</button>
    </div>

    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    msg: "aa",
                    isShow: false
                }
            },
            methods: {
                changeMsg() {
                    this.isShow = true
                    console.log(document.getElementById("msg")) //null
                    this.$nextTick(function() {
                        console.log(document.getElementById("msg").innerHTML) //aa
                    })
                }
            }
        })
    </script>

</body>

</html>

13.5 参考

https://juejin.im/post/5b6a60285188251af1221121

14.keep-alive

14.1实现页面缓存

14.1.1 方法一

首先在定义路由的时候配置 meta 字段,自定义一个KeepAlive字段作为该页面是否缓存的标记

routes:[{
    path: '/search',
    name: 'search',
    component: search,
    meta: {
        title: '搜索列表页',
        keepAlive: true // 标记列表页需要被缓存
    }
},
{
    path: '/detail',
    name: 'detail',
    component: detail,
    meta: {
        title: '详情页',
        // 详情页不需要做缓存,所以不加keepAlive标记
    }
}]

由于<keep-alive>组件不支持v-if指令,所以我们在App.vue中采用两个<router-view>的写法,通过当前路由的keepAlive字段来判断是否对页面进行缓存:

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

14.1.1 方法二

使用<keep-alive>提供的 exclude 或者 include 选项,此处我们使用 exclude ,在App.vue中:

<div id="app">
    <keep-alive exclude="detail">
        <router-view />
    </keep-alive>
</div>

需要注意的是,一定要给页面组件加上相应的name,例如在detail.vue中:

<script>

<script>
export default {
    name: 'detail', // 这个name要和keep-alive中的exclude选项值一致
    ...
}
</script>

这么写就代表了在项目中除了name为detail的页面组件外,其余页面都将进行缓存。

15 vue组件通信

常见使用场景可以分为三类:

父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
兄弟通信:
Bus;Vuex
跨级通信:
Bus;Vuex;provide / inject API、$attrs/$listeners

event-bus

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 任何一个页面,发送
import { EventBus } from '@/config/event-bus.js'
 EventBus.$emit('keyDownEvent', params)
// 任何一个页面,接收
import { EventBus } from '@/config/event-bus.js'
EventBus.$on('keyDownEvent', params => {})

$attrs和$listeners

修改前

父组件
<template>
  <div>
    <custom-Image
      :src="src"
      style="height: 100px"
      class="custom-images"
      alt="图片报错"
      @load="onload"
    ></custom-Image>
  </div>
</template>

<script>
import customImage from "@/components/custom-Image.vue";
export default {
  name: "TestView",
  components: {
    customImage,
  },
  data() {
    return {
      src: "https://upload.jianshu.io/users/upload_avatars/23533258/c72270da-b23c-4b67-b70d-3bd55305849a.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/96/h/96",
    };
  },
  methods: {
    onload() {
      console.log("parent");
    },
  },
};
</script>

<style scoped>
.custom-images {
  width: 200px;
}
</style>
子组件
<template>
  <div>
    <el-image :src="src" @load="onload"> </el-image>
  </div>
</template>

<script>
export default {
  props: {
    src: {
      type: String,
      default: "",
    },
  },
  mounted() {
    console.log("$attrs", this.$attrs);
    console.log("$listeners", this.$listeners);
  },
  methods: {
    onload() {
      console.log("children");
      this.$emit("load");
    },
  },
};
</script>

修改后

子组件
<template>
  <div>
    <el-image
      v-bind="$attrs"
      v-on="$listeners"
      @load="onload"
      style="width: 100px; height: 100px"
    >
    </el-image>
  </div>
</template>

<script>
export default {
  mounted() {
    console.log("$attrs", this.$attrs);
    console.log("$listeners", this.$listeners);
  },
  methods: {
    onload() {
      console.log("children");
    },
  },
};
</script>

参考

https://juejin.im/post/5bd97e7c6fb9a022852a71cf
https://github.com/ljianshu/Blog/issues/66

16 Vue 的响应式原理中 Object.defineProperty 有什么缺陷

Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

  1. Object.defineProperty无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
  2. Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象。
  3. Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性

16. Vue计算属性和侦听属性

16.1 计算属性

  1. 计算属性由两部分组成:get和set,分别用来获取计算属性和设置计算属性。默认只有get,如果需要set,要自己添加。另外set设置属性,并不是直接修改计算属性,而是修改它的依赖
  2. computed 是可以缓存的,methods 不能缓存

    data:{ //普通属性
      msg:'aa',
    },
    computed:{ //计算属性
      reverseMsg:function(){
      // 该函数必须有返回值,用来获取属性,称为get函数
      //可以包含逻辑处理操作,同时reverseMsg依赖于msg,一旦msg发生变化,reverseMsg也会跟着变化
     return this.msg.split(' ').reverse().join(' ');
     }
    }  
    

    16.2 监听属性

  3. handler 回调
  4. deep 设置为 true 用于监听对象内部值的变化
  5. immediate 设置为 true 将立即以表达式的当前值触发回调

    <template>
     <button @click="obj.a = 2">修改</button>
    </template>
    <script>
    export default {
     data() {
         return {
             obj: {
                 a: 1,
             }
         }
     },
     watch: {
         obj: {
             handler: function(newVal, oldVal) {
                 console.log(newVal); 
             },
             deep: true,
             immediate: true 
         }
     }
    }
    </script>

16.3 两者之间对比

  1. watch:监测的是属性值, 只要属性值发生变化,其都会触发执行回调函数来执行一系列操
  2. computed:监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才会重新计算

16.4 总结

  1. 计算属性适合用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;
  2. 侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑
  3. computed能做的,watch都能做,反之则不行,能用computed的尽量用computed

16.5 参考

详解Vue计算属性和侦听属性

17 vue组件三大核心概念

17.1 概括

18 keep-alive

彻底揭秘keep-alive原理

19 Vue的钩子函数

Vue的钩子函数[路由导航守卫、keep-alive、生命周期钩子

20 全面解析Vue.nextTick实现原理

nextTick。它可以在DOM更新完毕之后执行一个回调

全面解析Vue.nextTick实现原理
nexttick是怎么可以获取到更新后的dom的_深入浅出理解vm.$nextTick

21 vue.mixin

Vue中mixin的理解

22 vue.use

23 vue.extend

虚拟DOM与diff算法

比较规则

新的DOM节点不存在{type: 'REMOVE', index}
文本的变化{type: 'TEXT', text: 1}
当节点类型相同时,去看一下属性是否相同,产生一个属性的补丁包{type: 'ATTR', attr: {class: 'list-group'}}
节点类型不相同,直接采用替换模式{type: 'REPLACE', newNode}

整个DOM-diff的过程

  1. 用JS对象模拟DOM(虚拟DOM)
  2. 把此虚拟DOM转成真实DOM并插入页面中(render)
  3. 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)
  4. 把差异对象应用到真正的DOM树上(patch)

渣渣辉
1.3k 声望147 粉丝