【2022.4.20更新】Vue_day03_Browser-sync使用_Sublime代码片段_计算属性_自定义指令_todoMVC项目

 Vue 四天课程学习笔记_第3天

课程内容概要:

1. 介绍browser-sync安装和配置使用 (强烈推荐!从此告别手动刷新F5)

2. 灵活运用 数组中的 every 和 some 和 filter 方法 (来实现数据的过滤)

3. Sublime的代码片段.sublime-snippet (类似于XCode里的Code Snippet)

4. 不可见元素template的使用

5. v-for 与 v-show 与 v-if 与 v-else-if 与 v-else 指令

6. v-on 与 v-bind 与 v-model 与 v-cloak 与 v-once 与 v-pre 指令

7. 使用v-text 或 v-cloak命令,解决 Mustache的闪烁问题(高频刷新时)

8. 按键捕获 (包括各种修饰符) 与 指令总结

9. 计算属性(getter/setter) 和 v-model指令 联合使用

10. 观察者 watch指令 与 数据持久化到localStorage

11.  通过计算属性,对列表进行按路由条件 (全部/已完成/未完成) 进行过滤

12. 使用 自定义指令(及5个钩子函数) 操作DOM(获取焦点,自动聚焦)

13. 使用 自定义指令(及5个钩子函数) 模拟实现v-show、v-bind指令 的效果

14. 通过上述知识点,实现一个完整的todoMVC项目


插一句:

从win10开始 出现了异常强大的 窗口分栏功能

其快捷键是 win + 上/下/左/右 (真的吗???)

再插一句:

Vue.config是一个对象,包含Vue的全局配置

可以在启动应用之前 设置以下属性:
例如:
// 取消所有日志与警告的输出
Vue.config.silent = true; 


再插一句:

提到了项目管理软件 worktile trello Teambition 


下面开始 安装 浏览器自动同步刷新神器 browser-sync

第1步,下载browser-sync,官网:browsersync.io

npm install browser-sync --save-dev
或者使用简写(注意大写D)
npm i browser-sync -D

注意: –save-dev 参数表示  这个browser-sync的依赖 并不是必须的! (可有可无)

有了这个-dev的参数, 最后这个browser-sync依赖 会被写到package.json文件的 devDependencies节点

(插入一句说明: npm install 命令 会同时安装 dependencies节点和devDependencies节点)

(插入一句说明: 如果不想安装package.json文件中的devDependencies节点,而只想安装dependencies节点的话,可以使用命令npm install –production)



第2步,打开package.json进行配置

// 注意,package.json中不能写注释

"scripts": {
    // dev代表的就是后面的长长的命令, 后面代表的是要监视的文件路径和类型,public目录下的所有html是我手动加的
    "dev": "browser-sync start --server --files \"*.html, css/*.css, public/*.html, js/*.js\"",

    // 使用npm start命令时,就会执行后面的 npm run dev命令
    // 一般都要写npm run start,但只有这个start比较特殊,可以省掉中间的run,从而简写写npm start
    "start": "npm run dev"
  },


如图所示:


配置scripts节点就是为了方便他人运行该项目(而无需输入具体的.js名字)


第3步: 启动

npm start
经过上面的配置后, 其实就等价于 npm run dev

效果如下: 以后每次改动html文件或css文件,只要一保存,页面就会同步更新了,赞喔~

注意:  browser-sync 有自动同步表单的功能 (幽灵输入) ???Excuse Me???


在这儿扩展一下这个scripts节点的相关知识点

举个例子, 以前我们都是在命令行中直接使用 node node_62_index.js 运行这个js文件

这儿是写死了 node_62_index.js这个名字

如果我们配置了scripts,例如

“scripts” : {

“beyond” : “node node_62_index.js”,

“start” : “npm run beyond”

}

那么我们就可以有3种方式运行node_62_index.js了

(即便别人不知道node_62_index.js这个具体的名字,也能够运行这个文件了)

第1种: 最原始的  node node_62_index.js

第2种: npm run beyond

第3种: npm start

或者:   npm run start

而且 这个 start 比较特殊, 它是可以省略中间的 run的


如图所示:

可能会问 凭啥 start 就比较特殊呀,  就可以省略中间的 run呀

下面的图片给出了答案


下面是关于数组every方法 和 some方法的介绍

如图所示:

下面是数组的filter方法介绍 (特别是在用于数据过滤时,非常好用,比如清除所有已完成的item)


关于Sublime, 这儿插一句:

sublime 的快捷键 Ctrl + T 直接搜索文件 (@btnClick,如果前面加@ 还可以直接搜索方法Function)

如图所示:


下面介绍 Sublime里的.sublime-snippet

先看一张效果图:


创建.sublime-snippet片段的步骤如下:

第1步: 点击 菜单「Tools」–>「New Snippet…」


第2步, 弹出一个新的文本,如图所示:

简单说明一下:

1. tabTrigger标签里的内容   就是我们平常内容时的 触发器 (可以理解为代码片段的 快捷方式! 例如上面gif动图里面的 未闻花名)

2. ${1:beyond}   ${2:面码}    代表这些两个是默认的内容,是可以被替换的,并且是能够 使用tab键 上蹿下跳的

3. 注意: 如果模板代码里面有 $ 符号, 则需要 使用 \ 进行转义  如:  \$mount(‘#id_div_container’)


再比如这个:


第3步, 编辑好内容 和 trigger触发器之后, 按 Alt + S 弹出保存对话框

注意:  1. 必须以.sublime-snippet结尾

2. 必须保存在这个Packages目录下的User路径:

/Users/beyond/Library/Application Support/Sublime Text 3/Packages/User

3. 可以通过Preference菜单–>Browser Packages菜单,快速进入到Packages目录(下的User目录)

最终效果如下:


v-if 是真正的 条件渲染
因为它会确保在切换过程中,条件块内的事件监听器 和 子组件 相应地被创建和销毁


v-if 是惰性的:

如果在初始渲染时,条件为false,则什么也不做
一直到 条件第一次为true时, 才会开始渲染 条件块


对比一下:
v-show 元素总会被渲染, 并且只是简单地进行CSS样式(display: none/block;)的切换


总结就是:
v-if 每次根据条件 创建和销毁,会有更大的开销
v-show 则每次只是初始渲染的高开销
如果,需要频繁地 切换, 推荐使用v-show


如果,运行时,条件极少变化, 推荐使用v-if

注意: 当 v-if 与 v-for 一起使用时

v-for 有更高的优先级 (见列表渲染指南)


v-for 预期: Array | Object | number | string
数组用法 :
v-for=”girl in girlArr”
v-for=”(girl,index) in girlArr”


对象用法 :
v-for=”(value,key) in girl”
v-for=(value,key,index) in girl””


注意: v-for 默认行为 不改变整体
如果要 重新排序 , 则要提供一个key的特殊属性???Excuse Me??? 为啥没效果


<div v-for=”girl in girlArr” :key=”girl.girlAge” >
{{ girl.girlAge }}
</div>

如图所示: (为啥会没有效果???)



列表渲染之条件渲染
v-for可以把一个(过滤后的)数组  对应生成 一组元素
有时候,我们想要显示一个数组的排序副本或者过滤之后的数组,
而不实际改变或重置原始的数组
在这种情况下, 我们就可以创建一个 能够返回 过滤或排序后的数组 的计算属性
例如: 过滤出 loli
<li v-for=”loli in loliArr”>
{{ loli }}
</li>

computed: {
loliArr: {
get: function(){
return this.girlArr.filter(loli =>
loli.girlAge > 8 && loli.girlAge < 14
)
}
}
}


另一种情况:
在计算属性 不适用的情况下(例如 嵌套v-for时),

你可以使用一个method方法
<li v-for=”loli in findLoliArrFunction(girlArr)”> {{ loli.girlName }}
</li>


v-show: 显示不显示 (无论如何都会进行渲染)

v-if指令可以决定哪个DOM元素,是否需要进行渲染


假如有许多个DOM元素都需同时进行判断是否需要进行渲染,而且判断的条件还是同一个的话
那么, 如果每一个都写v-if的话,这样就很容易造成代码的重复和冗余
那么, 如果在外面套一层多余的div的话,又会增加不必要的层级


此时,我们可以把这些DOM元素,全部放进一个虚拟的template标签里,
该template标签 会根据v-if的值,决定其内部的所有子元素是否进行渲染,
如果需要渲染,则最终template标签 在完成任务后,会奇迹般地消失


例如:
<template v-if=”isNeed”>
<h1>动漫: 未闻花名</h1>
<p>女主: 面码</p>
<p>年代: 2011年</p>
</template>


data: {
isNeeded: true/false;

}

vue_18.html代码如下:

<!-- 第3步, vue最终渲染结果的容器 -->
<div id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">
     <template v-if="isNeed">
          <h1>动漫: 未闻花名</h1>
          <p>女主: 面码</p>
          <p>年代: 2011年</p>
     </template>
</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue//dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">
    var appVue = new Vue({
        data: {
           isNeed: true
        },
        methods: {
             
        }
    }).$mount('#id_div_container')

    
</script>  


效果如下:

右键,审查元素,我们可以发现, 虚拟元素<template>奇迹般地完成其历史使命后消失了,仿佛从来没有出现过一样(好神奇)


再啰嗦一下:

在虚拟元素<template>上使用v-if进行条件渲染

因为我们知道,v-if指令必须加到一个HTML元素上,才能生效
但是,如果要同时根据同一条件去判断是否渲染多个HTML元素呢?

此时最好的办法就是: 把一个虚拟元素<template>作为他们的容器(根节点)
这时候,只要在虚拟元素<template>上使用v-if指令即可

最终,渲染出来的页面上,不会存在虚拟元素<template>,但其子元素都可以正常展示


下面简单演示一下v-if与v-else-if与v-else指令的使用

vue_19.html代码如下:

<!-- 第3步, vue最终渲染结果的容器 -->
<div id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">
     <div v-if="girlAge < 8">{{ girlAge }} is baby</div>
     <div v-else-if="girlAge < 14">{{ girlAge }} is loli</div>
     <div v-else-if="girlAge >= 14">{{ girlAge }} is girl</div>
</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue//dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">
    var appVue = new Vue({
        data: {
           girlAge: 13
        },
        methods: {
             
        }
    }).$mount('#id_div_container')

    
</script>  


        <p class="sgcenter sgcontentcolor">
            <b>注意: v-if、v-else-if、v-elseの演示
        </p>


效果如下:


v-on   缩写 @
预期: Function | Inline Statement | Object
参数: event
修饰符: 
.stop    调用event.stopPropagation()
.prevent 调用event.preventDefault()
.capture 添加事件侦听器时使用 capture模式
.self    只有事件是从侦听器绑定的元素上 触发时,才回调
.native  监听组件根元素的原生事件
.once    只触发一次回调
.passive 以passive模式 添加侦听器
.{keyCode | keyAlias} 只有事件是从特定的按键触发时,才回调
.left    只有点击鼠标左键时,才回调
.right   只有点击鼠标右键时,才回调
.middle  只有点击鼠标中键时,才回调


用法:
绑定事件监听器, 事件类型由 参数 决定, 修饰符为可选
支持 不带参数 绑定一个事件/监听器 键值对的对象 ???Excuse Me???(该情形不支持修饰符)


v-bind 缩写 :
预期: any(带参数) | Object(不带参数)
参数: attrOrProp (可选)
修饰符:
.prop  被用于 绑定DOM 属性
.camel 将kebab-case 转换成 camelCase
.sync  语法糖,会扩展成一个更新父组件绑定值的 v-on 侦听器???Excuse Me???
用法:
动态地绑定一个或多个属性 或一个组件 prop 到 表达式
在绑定class或sytle属性时, 支持其他类型(如数组或对象)
在绑定prop时,prop必须在子组件中声明(可用修饰符指定不同绑定类型)
没有参数时, 可以绑定到一个 包含 键值对的对象(该情况下class和style不再支持数组和对象) ???Excuse Me???


v-model 指令
只用于表单控件(input select textarea 和 components)
修饰符:
.lazy   只会在change事件时,执行回调
.number 自动将输入的字符串转成数字
.trim   自动过滤输入的首尾空格


v-cloak 指令 (不需要表达式)
用法:
这个指令保持在元素上 直到关联的实例 结束编译时,该指令被移除
常与下面的css样式 一起使用(消除 闪烁, 因为可以隐藏未编译状态下的Mustache指令)
[v-cloak] {
display: none;
}


使用:
<div id=”id_div_container” v-cloak>
{{ girlName }}

</div>


v-once 指令 (不需要表达式)
用法:  只渲染元素一次. 随后的渲染,都将视该元素/组件及其所有子节点 为 静态内容
作用:  优化性能
<div v-once>
<h1> 未闻花名 </h1>
<p> {{ girlName }} </p>
</div>


v-pre (不需要表达式)
用法:
比v-once更生猛,该指令直接跳过所在元素及其子元素的编译过程
因此,可以用来显示 大量 原始的 Mustache标签,
自动跳过大量没有 指令的 元素(如文章详情), 加快编译

<span v-pre>
{{ 这个是文章详情, 大量图文, 没有指令, 无需参与编译 }}

</span>

效果如下:


下面介绍, 使用v-text指令, 解决 Mustache语法的  闪烁小bug

当我们在使用Mustache语法时, 其实是有一个小bug的

那就是在频繁刷新渲染界面时,会在页面上看到渲染数据之前的占位符{{ girlName }}

因为 浏览器在渲染的时候, 不认Mustache语法, 例如: {{ girlName }}

所以,就会直接将 {{ girlName }}显示到界面上,

等Vue将这个Mustache语法中的girlName 替换之后, 会重新渲染绑定 的值

因此, 就会出现界面的闪烁~


为此,我们可以v-text指令避免这个问题

解决办法之一是: 使用v-text指令 修复刷新时的小bug


<span>{{ girlName }}</span>

<span v-text=”girlName”></span>

// 两者最终的渲染是完全一样的, (唯一不同的是: v-text不会闪烁, 而Mustache 可以进行部分渲染)

v-text之所不闪烁,是因为在Vue插手之前, 浏览器 不认识v-text属性, 所以什么也不会显示, 一片空白

等到Vue进行绑定数据时, 它会将 绑定数据后的DOM元素,替换掉原来的DOM


但是, 由于v-text也有先天不足, 无法部分数据绑定, 并且在很多个html元素上同时使用 v-text 会比较 繁琐和冗余

这儿,我们强烈推荐 终极解决方案: v-cloak指令

第1步: 我们在被Vue管理的Root Element 节点上, 使用 v-cloak指令

第2步: 并且定义一个css样式如下: (属性名为v-cloak的元素, 不显示)

[v-cloak] {

    display: none;

}

html元素如下:

<div id=”id_div_container” v-cloak>

<h1> hello  {{ girlName }} </h1>

</div>


这样一来, 一开始 DOM在渲染时,由于 div 拥有 v-cloak属性, 所以是display: none; 是隐藏的 (自然也就看不到{{ girlName }})

当等到Vue完成数组绑定时,重新渲染DOM时, 会奇迹般自动把div上的v-cloak属性移除,

没有了v-cloak属性了, 自然样式display:none;也就不生效了 (赞~~)


下面开始详细介绍 事件处理 之 捕获按键

按键修饰符
v-on:keydown.enter=”xxxFunction”


.once 修饰符, 让点击事件只触发一次

<a v-on:click.once=”anchorClickFunction”>点我试试</a>


在监听键盘事件时,我们经常需要检查用户按下的键值
可以在v-on:keyup事件中添加 修饰符
例如:
<input v-on:keyup.13=”xxxFunction” />
上面代码,只有在用户按下 keyCode为 13的键时,才调用处理函数xxxFunction


另外,Vue提供了常见的按键别名, 例如
<input v-on:keyup.enter=”xxxFunction” />
或者 使用缩写
<input @keyup.enter=”xxxFunction” />


上面代码的意思是,当用户按上并弹起回车键时, 执行处理函数xxxFunction


全部的按键别名如下:
.enter
.tab
.delete (包括 删除 和 退格)
.esc
.space (空格键)


以及四个方向键
.up
.down
.left
.right


可以通过全局 Vue.config.keyCodes 对象, 自己添加 按键别名
// 左边是自定义别名,     右边是 keyCode
Vue.config.keyCodes.f1 = 112


系统修饰键
.ctrl
.alt
.shift
.meta (Mac上为command键(⌘), Win上为 徽标键(⊞), 在Sun上为 实心宝石键(◆))


例如:
// ctrl + c 复制
<input @keyup.ctrl.67=”copyFunction” />

// alt + c 取消
<input @keyup.alt.67=”cancelFunction” />

// ctrl + click
<input @click.ctrl=”xxxFunction” />


vue_20.html代码如下:

<!-- 第3步, vue最终渲染结果的容器 -->
<div id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">
    <input v-on:keyup.enter="enterFunction" @blur="blurFunction" placeholder="enter" /> {{ result1 }}
    <br/><br/>
    <input @keyup.ctrl.67="copyFunction" @blur="blurFunction"placeholder="ctrl + c" /> {{ result2 }}
    <br/><br/>
    <input @keyup.alt.67="cancelFunction" @blur="blurFunction" placeholder="alt + c" /> {{ result3 }}
    <br/><br/>
    <input @click.meta="xxxFunction" @blur="blurFunction" placeholder="command + click" /> {{ result4 }}
    <br/><br/>
    <input @keyup.esc="escFunction" @blur="blurFunction" placeholder="esc" /> {{ result5 }}
    
    
</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">
    var appVue = new Vue({
        data: {
           result1: '',
           result2: '',
           result3: '',
           result4: '',
           result5: ''
        },
        methods: {
             blurFunction(){
                this.result1 = ''
                this.result2 = ''
                this.result3 = ''
                this.result4 = ''
                this.result5 = ''
             },
             enterFunction(){
                this.result1 = "按下了enter"
             },
             copyFunction(){
                this.result2 = "按下了 ctrl + c"
             },
             cancelFunction(){
                this.result3 = "按下了 alt + c"                
             },
             xxxFunction(){
                this.result4 = "command键 + click单击"                
             },
             escFunction(){
                this.result5 = "esc"                
             }

        }
    }).$mount('#id_div_container')

    
</script>  

        <p class="sgcenter sgcontentcolor">
            <b>注意: Vue按键修饰符
        </p>


效果如下:


指令部分总结

下面开始 对 指令部分,作一个 全面细致的总结:

1. v-text
1.1 跟 Mustache {{ }} 一样
1.2 Mustache {{ }} 会造成闪烁问题
1.3 v-text 没有闪烁问题, 但只能完全匹配
1.4 最终解决方案: 使用v-cloak指令 与 CSS 样式结合


2. v-html

3. v-show
3.1 条件 显示和隐藏
3.2 无论条件真假, 都会渲染到DOM
3.3 true时, display: block;
3.4 false时, display: none;
3.5 适用于 运行期间,需频繁地切换显示和隐藏 的场景

4. v-if
4.1 真正的条件渲染
4.2 true时, 渲染此DOM
4.3 false时, 不渲染并移除此DOM
4.4 适用于 只是一次 显示或隐藏

4.5 总结
v-if 因为每次切换都要 添加和移除DOM,所以有更高的开销
v-show 则有更高的初始渲染开销
v-show 适用于 频繁地切换 显示或隐藏
v-if   适用于 运行期间极少改变的情形

5. v-else  v-else-if
6. v-for
7. v-on
8. v-bind
9. v-model
10. v-pre

11. v-cloak
11.1 使用v-clock,即可保留{{ }},又无闪烁
11.2 head中加入css样式  [v-cloak]{display:none;}
11.3 在Vue管理的节点上, 添加v-cloak属性
11.4 原理解析: 一开始 DOM是隐藏的; 当Vue渲染完之后,Vue会将v-cloak属性移除,故DOM开始显示

12. 自定义指令
发生在需要手动操作DOM的情形下


计算属性与观察者
虽然模板内的Mustache非常地便利,
但是仅适用于 无重复 且 运算简单的情况

如果模板内的Mustache运算异常复杂 且 大量重复的时候,
模板会变得繁重 和 冗余 且 难以维护
例如: 下面的就是模板的 逻辑过重
<div id=”id_div_container”>
{{ kayak.split(”).reverse().join(”) }}
</div>
在上面的这个例子当中, 模板不再简单地一目了然
而是要过老半天,才知道这是在将字符串翻转呀
如果你在多处都要用上面的这个模板的话,将显示更加冗余和难以维护

因此, 这儿我们隆重介绍 计算属性

例如: 计算数组中的loli数目

第1种: 计算属性的完整写法 (对象)
computed: {
loliCount: {
get: function(){
return girlArr.filter(girl => {
girl.girlAge > 8 && girl.girlAge < 14
}).length
}
}
}

第2种: 计算属性的简写 (伪方法,像方法但不是方法,只是属性)
computed:{
loliCount(){
return girlArr.filter(girl => {
girl.girlAge > 8 && girl.girlAge < 14
}).length
}
}

使用 计算属性 (再次强调, 计算属性不是方法)

{{ loliCount }}

计算属性的setter

默认的计算属性只有getter, 但是也可以自己手动实现一个setter
使用有一个checkbox, 功能是选择全部/取消全部选择

<!– 全部看过 / 取消全部
方式1: 在这儿监听 change 事件
v-on:change=”chooseAllOrNotFunction”

方式2:  MVVM 高级玩法
使用计算属性chooseAllOrNot作为v-model 实现双向绑定 到checkbox
因为checkbox既有初始值, 又要勾选设置值,所以要双向绑定
所以,当初始化checkbox时, 会调用计算属性的getter方法

而当你勾选checkbox的时候,就会自动调用计算属性的 setter方法
–>
<input type=”checkbox” v-model=”chooseAllOrNot” />

那么可以使用v-model 将checkbox 与 计算属性 进行双向绑定
computed: {
chooseAllOrNot: {
get: function(){
// 当数组中每一个 anime 都 have seen的时候, 自动勾选 checkbox
// 当数组中 只要有一个anime 不是have seen时, 取消勾选 checkbox
// 注意: 计算属性知道自己依赖了 数组, 所以,当 数组 变化时, 计算属性也会重新计算
return animeArr.every(anime =>
anime.animeHaveSeen === true
)
},

/*
只要用户 勾选或取消 绑定了此计算属性的checkbox, 就会来到setter方法 (因为有 设置值)
数组animeArr中的所有animeHaveSeen的状态也就都会跟着联动
*/
set: function() {
// 0. 非常巧妙! 先通过计算属性的getter方法,获取当前的 勾选 状态
// 1. 先通过getter方法,获取当前的 勾选 状态
var isChecked = this.chooseAllOrNot
// 2. 然后选择相反的状态
var newIsChecked = !isChecked
// 3. 同步更新所有数组中的成员
this.animeArr.forEach(anime =>
anime.animeHaveSeen = newIsChecked
)
}
}
}
运行时, 只要勾选或取消了checkbox, 数组animeArr中的所有animeHaveSeen的状态也都会跟着联动

效果如下:

下面开始介绍如何实现数据持久化

主要有两个方面的知识点

第1个: 使用的初始值animeArr来自本地存储 window.localStorage

第2个: 当animeArr任何时候发生改变时, 将改变的值 存储到window.localStorage

这就要用到Vue中的 watch选项

先看第1个, 关于window.localStorage

localStorage 对象
localStorage 对象存储的数据没有时间限制。
第2天、第3周、第4个月或第5年之后,数据依然可用

常用的有如下几个(以localStorage为例):
保存数据:localStorage.setItem(key,value);
读取数据:localStorage.getItem(key);
删除单个数据:localStorage.removeItem(key);
删除所有数据:localStorage.clear();
得到某个索引的key:localStorage.key(index);

提示: 键/值对 必须以字符串存储

如果直接存对象,则它会bia ji一声,将其toString强转成字符串”[object Object]”,

那么这样一来, 原来的对象就再也找不回来了

如图所示:

下面开始 介绍 Vue里面的 观察者 watch选项

观察者watch选项

类型: {[key: string]: string | Function | Object}
一个对象
对象的键是 需要观察的表达式
值是      对应的回调函数
值也可以是方法名 或 包含选项(deep或immediate)的对象

Vue实例 会在实例化时,调用$watch(), 遍历watch对象的每一个属性
例如:
data: {
girlName: ‘面码’,
girlAge: 15,
animeArr: [{animeName: ‘未闻花名’}]
},
watch: {
girlName: function(value,oldValue){…},
girlAge: ‘girlAgeChangedFunction’,
animeArr: {
deep: true,
immediate: true,
handler: function(value,oldValue){……}
}
}

说明:
1. 观察data选项中的animeArr属性的一举一动,并随之执行相应的业务处理逻辑
2. deep: true  ,数组 和 对象 等引用类型,默认只能监视第1层, 为了能够监视其子成员的改变, 必须 深度观察

3. immediate: true  ,监听开始时,立即调用(不管值 改没改变)

注意:
不能使用 箭头函数 来定义 watch选项中的函数

原因是: 箭头函数 绑定的是父级作用域的上下文

效果如下:

阶段性总结:

上面的内部包括:
1. sublime-snippet
2. 使用v-text解决闪烁问题
3. 使用Mustache语法 加 v-cloak 加 css 解决闪烁问题
css样式如下:
[v-cloak]: {display: none;}
在Vue管理的根节点上,添加v-cloak属性
<div id=”id_div_container” v-cloak>
</div>
当Vue解析完模板后,会自动将v-cloak属性移除
4. v-if 与 v-show的区别
v-if   是根据条件 决定 是否渲染 (因开销较大,适合于运行时,极少切换的情况)
v-show 是根据条件 决定 是否显示 (适合于 切换较频繁场景)
5. v-pre
告诉Vue不要解析 本节点及其内部子节点
提高解析效率,避免不必要的解析
7. 计算属性 computed 与 getter/setter
解决模板的逻辑过重、冗余、重复调用、难以维护等问题
计算属性虽然本质上是getter/setter方法,但是必须当成属性使用
计算属性会缓存计算的结果
再次强调 计算属性 不能当做方法去调用
事件处理函数 仍然 只能写在 methods选项里
8. 观察者 watch
可以对data选项里的 属性进行监视,一旦属性值改变,立即自动调用相应的业务处理逻辑
如果监视 对象或数组,则需要配置 deep:true

下面开始介绍, 如何通过data对象中的一个属性(中间变量|过滤条件)的值的不同, 过滤出不同的数组显示到界面

又如何通过 window.location.hash变化(onhashchange事件), 改变 data对象中的一个属性(中间变量|过滤条件)的值,

从而过滤出不同的数组显示到界面

最终效果如下:

总的来说,上面的demo概括起来就是:

1. 根据 路由routing 的切换, 导致window.location.hash变化
2. 而监听到地址栏的hash的变化时 , 又人为导致 data中的一个 中间变量(hashString)的变化
3. 而条件(hashString)的变化 , 又改变 计算属性 返回的结果数组 filtedAnimeArr
4. 从而实现了对 列表的过滤与切换

下面开始介绍  如何使用 自定义指令(及5个钩子函数) 来操作DOM (获取焦点,自动聚焦)

在不使用Vue时, H5提供的 autofocus 属性 进行自动获取焦点 是没有任何问题的

但是, 一旦使用Vue, autofocus属性 就根本不好使了

因为:Vue会去解析处理HTML

这儿涉及一个敏感话题, 那就是Vue里的虚拟DOM,这不展开讨论

因此, 在这种情况下, 我们就只能手动去操作DOM了

通过注册自定义指令, 我们可以手动去操作DOM

例如: 输入框 自动获得焦点的问题 (注意: autofocus在iOS的Safari上不工作)
<input type=”text” autofocus />

下面,使用全局自定义组件, 实现此功能  (任何组件中都可以使用)

Vue.directive(“beyond-focus”,{
// 当被绑元素 插入到dom时
inserted: function(inputNode){
// 使用原生JS 操作 DOM 获取焦点
inputNode.focus()
}
})

// 参数1: 指令名v-beyond-focus 所对应的 参数1 在定义时, 有两种不同的写法
//           第1种: 除了v- 的部分, 即beyond-focus
//           第2种: 驼峰命名法,可以写成 beyondFocus
//           两种写法,效果一样,都是同一个指令 v-beyond-focus
// 参数2: 是对象,其属性键值对 是5个钩子函数
// 如果是想注册局部指令, 组件中也有一个directives选项 (只能在该组件内使用)
directives: {
beyond-focus: {
inserted: function(inputNode){
inputNode.focus()
}
}

}

自定义的指令的使用方式如下:

<input type=”text” v-beyond-focus />

注意: 1. 在模板中使用自定义指令,需要加上 v- 前缀

2. 对于用驼峰定义的自定义指令,使用时 用 – 连接

再次强调: 在不可避免 要操作DOM时,就会用到 自定义指令
补充说明: 一个指令定义对象  可以  选择性地实现  以下5个钩子函数
bind: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点,  el.parentNode为null)

insert: 只调用一次,被绑定元素 插入到父节点时调用 (仅保证有父节点,但不一定在文档DOM树中)

update: 所在组件的根节点即将更新前 调用 (但其子节点可能尚未开始更新, 需要通过比较 更新前后的 新值 与 旧值,来忽略不必要的模板更新???Excuse Me???) ,  (此时DOM内容 尚未更新, el.innerHTML为旧的内容)

componentUpdated: 所在组件的根节点及所有子节点 全部更新后 调用( 此时 DOM内容 已经全部更新 ,el.innerHTML为新内容)

unbind: 只调用一次, 指令与元素解绑时调用 (做一些收尾工作,如 window.clearInterval(el.timer) )

钩子函数的四个参数: (除了el,其他3个参数全是只读的)
参数1: el: 指令所绑定的元素,可用于直接操作DOM
参数2: binding: 一个对象,有以下属性
rawName: 指令名, 如v-bind (包含前缀)
name: 指令名,不包括v-前缀,如:v-bind中的 bind

arg:  指令的参数,如v-bind:href中 冒号后面的 href
expression: 字符串形式的指令表达式,如v-bind:href=”6 + 7″中 等号后面的 “6 + 7”

value: 指令的绑定值, 如v-bind:href=”6 + 7″中 等号后面的 13 , 也就是上面属性expression表达式的计算的结果
oldValue: 指令绑定的旧值, 只在 update和componentUpdated钩子中可用

modifiers: 一个包含修饰符的对象,例如指令v-beyond.girl.loli中, 修饰符为: {girl:true,loli:true}

参数3: vnode: Vue编译生成的虚拟节点

参数4: oldNode: Vue编译生成的上一个虚拟节点,只在update和comonentUpdated钩子中可用

再次强调:

示例代码vue_27.html如下:

<!-- 第3步, vue最终渲染结果的容器 -->
<div v-cloak id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">

    <input type="checkbox" v-model="isVisible" />
    <h2 v-show="isVisible" v-beyond-test> {{ isVisible }}</h2>
    <input type="text" v-model="isVisible" />

</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">


// ------------------全局自定义指令-------
Vue.directive('beyond-test',{
    bind: function (el,binding) {
        console.log('调用了 bind ',el,el.parentNode,binding )
    },
    inserted: function (el,binding) {
        console.log('调用了 inserted ',el,el.parentNode,binding )  
    },
    update: function (el,binding) {
        // console.log('调用了 update ',el,el.parentNode,el.innerHTML,binding )
        console.log('update innerHTML: ',el.innerHTML)
    },
    componentUpdated: function (el,binding) {
        // console.log('调用了 componentUpdated ',el,el.parentNode,el.innerHTML,binding )
        console.log('componentUpdated innerHTML: ',el.innerHTML)
    },
    unbind: function (el,binding) {
        console.log('调用了 unbind ',el,el.parentNode,binding )
    },
})

    var appVue = new Vue({
        data: {
           isVisible: true
        },
        methods: {
             

        }
    }).$mount('#id_div_container')

    
</script>  

        <p class="sgcenter sgcontentcolor">
            <b>注意: update钩子中, el.innerHTML是更新前的内容<br/>
            componentUpdated钩子中, el.innerHTML是更新后的内容
        </p>

效果如下:

自定义指令的简写形式 ( bind和update中 代码相同时)
Vue.directive(‘beyond-show’,function(el,binding){
if(binding.value === true){
el.setAttribute(‘sytle’,’color:black’)
}else{
el.setAttribute(‘sytle’,’color:white’)
}
})

对象字面量
如果,指令需要多个值,那么可以传递一个字面量对象
例如:
<div v-beyond-bind:css.a.b.c=”{color: ‘white’, content: ‘snow’}” >
</div>

Vue.directive(‘beyond-bind’,function(el,binding){
NSLog(binding.value.color)  // white
NSLog(binding.value.content)  // snow

})

指令名name是:   beyond-bind

原始指令名raw name是     v-beyond-bind:css.a.b.c

指令参数arg是   css

指令修饰符modifiers是   a   b  c

效果如下:

关于 指令、参数、表达式、修饰符的说明

<a v-bind:href=”urlExpression”>点我试试</a>
指令是 v-bind
参数是 href
表达式是 urlExpression

<a v-on:click=”xxxFunction”></a>
指令是 v-on
参数是 click
表达式是 xxxFunction

<form v-on:submit.prevent=”xxxFunction”>…</form>
指令是 v-on
参数是 submit

修饰符是 prevent, 即自动调用event.preventDefault()

指令参数
一些指令能够接收一个参数, 在指令名称后 用: 冒号表示
例如
<a v-on:click=”xxxFunction”></a>
指令是 v-on
指令参数是 click

指令修饰符
一些指令还可以添加修饰符, 修饰符Modifiers 是以半角句号 . 指明的特殊后缀
用于指定一个指令该以何种方式进行绑定
例如
<form v-on:submit.prevent=”xxxFunction”>…</form>
指令是 v-on
指令参数是 submit

指令修饰符是 prevent , 意思就是阻止表单提交的默认事件,event.preventDefault()

下面是demo时间,

第2个输入框,初始化时,自动获取焦点,效果如下:

自定义指令, 操作DOM,让第2个inputView 自动获取焦点 的vue_24.html代码如下:

<!DOCTPYE html>  
<html lang="zh">  
<head>  
    <link rel="icon" href="/public/img/beyond2.jpg" type="image/x-icon"/>
    <meta charset="UTF-8">
    <meta name="author" content="beyond">
    <meta http-equiv="refresh" content="520">
    <meta name="description" content="未闻花名-免费零基础教程-beyond">
    <meta name="viewport" content="width=device-width, 
    initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
    <meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
    <title>beyond心中の动漫神作</title>
    <link rel="stylesheet" type="text/css" href="/public/css/beyondbasestylewhite5.css">
    <script type="text/javascript" src="/public/js/nslog.js"></script>

    <!--[if lt IE 9]>
        <script src="//apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
        <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.js">
        </script>
    <![endif]-->

    <style type="text/css">
        body{
            font-size: 100%; 
            /*声明margin和padding是个好习惯*/  
            margin: 0;  
            padding: 0; 
            background-image: url("/public/img/sakura4.png");  
            background-repeat: no-repeat;  
            background-position: center center;  
        }
    </style>
    <!-- 绿色按钮的css效果 -->
    <link rel="stylesheet" type="text/css" href="/public/css/beyondbuttongreen.css">

    <link rel="stylesheet" type="text/css" href="/public/lib/bootstrap/node_52_v337_bootstrap_backup.css">


    <!-- 引入 jquery 2.1.4 -->
    <!--[if gte IE 9]><!--> 
    <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js">
    </script>
    <!--<![endif]-->

<head>
    <style type="text/css">
        #id_div_container {
            text-align: center;
            margin:0 auto;
            /*background: rgba(0,0,0,0.6);*/
        }
        .class_div_cube {
            width: 50px;
            height: 50px;
            margin:0 auto;
            /*background-color: black;*/
            background-image: url("/public/img/beyond.jpg");  
            background-position: center center;  
            background-size: cover;
        }
        
        [v-cloak] {
            display: none;
        }
    </style>
    
</head>  
  
<body>  
        <h1 style="color:white;text-shadow:2px 2px 4px #000;letter-spacing:5px;" class="sgcontentcolor sgcenter">  
            未闻花名
        </h1>
        
<!-- 第3步, vue最终渲染结果的容器 -->
<div v-cloak id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">
    <input type="text" />
    <input type="text" v-beyond-focus="isNeedAutoFocus" />

</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">


// ------------------全局自定义组件--------------------
// 参数1: 指令名v-beyond-focus 所对应的 参数1 在定义时, 有两种不同的写法
    //       第1种: 除了v- 的部分, 即beyond-focus
    //       第2种: 驼峰命名法,可以写成 beyondFocus
    // 两种写法,效果一样,都是同一个指令 v-beyond-focus

    // 参数2: 是对象,其属性键值对 是5个钩子函数
Vue.directive('beyond-focus',{
    // 钩子1: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点), 可做一些初始化设置
    // 再次强调,此时无父节点
    bind: function (el,binding,vnode,oldNode) {
        console.log('bind: el: ', el)
        console.log('bind: elのparent: ', el.parentNode)
        console.log('bind: binding: ', binding)
        console.log('')
    },
    // 钩子2: 当被绑元素inputNode 插入到dom时,调用insert方法
    // 仅保证父节点存在,但不一定已插入到DOM中
    // 如果你要操作父节点,至少要写在这个钩子里
    inserted: function (el,binding,vnode,oldNode) {
        // 指令是可以传值的, 根据传递的值(即binding对象中的value)
        if (binding.value === true) {
            el.focus()  // 注意:聚焦不能写在bind钩子里
        } else{
            // 否则,不自动聚焦
            el.blur()
        }
        console.log('inserted: el: ', el)
        console.log('inserted: binding: ', binding)
        console.log('inserted: elのparent: ', el.parentNode)
        console.log('')
    },
    // 钩子3: 内部HTML内容更新之前调用
    update: function (el,binding,vnode,oldNode) {
        NSLog('update: el: ' + el)
        NSLog('update: binding: ' + binding)
        
    },
    // 钩子4: 内部HTML内容更新之后调用
    componentUpdated: function (el,binding,vnode,oldNode) {
        NSLog('componentUpdated: el: ' + el)
        NSLog('componentUpdated: binding: ' + binding)
        
    },
    // 钩子5: 
    unbind: function (el,binding,vnode,oldNode) {
        NSLog('unbind: el: ' + el)
        NSLog('unbind: binding: ' + binding)
        
    }
})
// -------------------------------------------



    var appVue = new Vue({
        data: {
           isNeedAutoFocus: true
        },
        methods: {
             

        }
    }).$mount('#id_div_container')

    
</script>  

        <p class="sgcenter sgcontentcolor">
            <b>注意: 
        </p>

        <footer id="copyright">
            <p style="font-size:14px;text-align:center;font-style:italic;">  
            Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>  
            </p>        
        </footer>
    
</body>  
</html>  

下面是自定义指令v-beyond-show,实现 显示和隐藏

vue_25.html代码如下:

<!DOCTPYE html>  
<html lang="zh">  
<head>  
    <link rel="icon" href="/public/img/beyond2.jpg" type="image/x-icon"/>
    <meta charset="UTF-8">
    <meta name="author" content="beyond">
    <meta http-equiv="refresh" content="520">
    <meta name="description" content="未闻花名-免费零基础教程-beyond">
    <meta name="viewport" content="width=device-width, 
    initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
    <meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
    <title>beyond心中の动漫神作</title>
    <link rel="stylesheet" type="text/css" href="/public/css/beyondbasestylewhite5.css">
    <script type="text/javascript" src="/public/js/nslog.js"></script>

    <!--[if lt IE 9]>
        <script src="//apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
        <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/1.10.2/jquery.js">
        </script>
    <![endif]-->

    <style type="text/css">
        body{
            font-size: 100%; 
            /*声明margin和padding是个好习惯*/  
            margin: 0;  
            padding: 0; 
            background-image: url("/public/img/sakura4.png");  
            background-repeat: no-repeat;  
            background-position: center center;  
        }
    </style>
    <!-- 绿色按钮的css效果 -->
    <link rel="stylesheet" type="text/css" href="/public/css/beyondbuttongreen.css">

    <link rel="stylesheet" type="text/css" href="/public/lib/bootstrap/node_52_v337_bootstrap_backup.css">


    <!-- 引入 jquery 2.1.4 -->
    <!--[if gte IE 9]><!--> 
    <script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js">
    </script>
    <!--<![endif]-->

<head>
    <style type="text/css">
        #id_div_container {
            text-align: center;
            margin:0 auto;
            /*background: rgba(0,0,0,0.6);*/
        }
        .class_div_cube {
            width: 50px;
            height: 50px;
            margin:0 auto;
            /*background-color: black;*/
            background-image: url("/public/img/beyond.jpg");  
            background-position: center center;  
            background-size: cover;
        }
        
        [v-cloak] {
            display: none;
        }


    </style>
    
</head>  
  
<body>  
        <h1 style="color:white;text-shadow:2px 2px 4px #000;letter-spacing:5px;" class="sgcontentcolor sgcenter">  
            未闻花名
        </h1>
        
<!-- 第3步, vue最终渲染结果的容器 -->
<div v-cloak id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">

    <input type="checkbox" v-model="isVisible"/>

    <div class="class_div_cube" v-beyond-show="isVisible"></div>

</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">


// ------------------全局自定义指令(简写形式)------------

Vue.directive('beyond-show',function (el,binding) {
    // 指令是可以传值的, 根据传递的值(即binding对象中的value)
        var isVisible = binding.value
        NSLog('isVisible: ' + isVisible)

        var parentNode = el.parentNode
        // 由于是简写,bind钩子 和 update钩子 写在一起
        // 而bind钩子执行的时候,还取不到 父节点 
        if (!parentNode) {
            return
        }
        var divNode = parentNode.getElementsByClassName('class_div_cube')[0]
        if (isVisible === true) {
            // 如果为true,则显示
            divNode.setAttribute('style','display:block')
        } else{
            // 否则,不显示
            divNode.setAttribute('style','display:none')
        }
})

// ------------------全局自定义指令(完整形式)------------
// 参数1: 指令名v-beyond-show 所对应的 参数1 在定义时, 有两种不同的写法
    //       第1种: 除了v- 的部分, 即beyond-show
    //       第2种: 驼峰命名法,可以写成 beyondShow
    // 两种写法,效果一样,都是同一个指令 v-beyond-show

    // 参数2: 是对象,其属性键值对 是5个钩子函数
Vue.directive('beyond-show',{
    // 钩子1: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点), 可做一些初始化设置
    // 再次强调,此时无父节点
    bind: function (el,binding,vnode,oldNode) {
        console.log('bind: el: ', el)
        console.log('bind: elのparent: ', el.parentNode)
        console.log('bind: binding: ', binding)
        console.log('')
    },
    // 钩子2: 当被绑元素inputNode 插入到dom时,调用insert方法 (只会执行一次)
    // 仅保证父节点存在,但不一定已插入到DOM中
    // 如果你要操作父节点,至少要写在这个钩子里
    inserted: function (el,binding,vnode,oldNode) {

        console.log('inserted: el: ', el)
        console.log('inserted: binding: ', binding)
        console.log('inserted: elのparent: ', el.parentNode)
        console.log('')
    },
    // 钩子3:  此时DOM内部尚未更新
    // 如果要获取html更新之前的内容, 代码写这儿
    update: function (el,binding,vnode,oldNode) {
        // 指令是可以传值的, 根据传递的值(即binding对象中的value)
        var isVisible = binding.value
        NSLog('isVisible: ' + isVisible)

        var parentNode = el.parentNode
        var divNode = parentNode.getElementsByClassName('class_div_cube')[0]
        if (isVisible === true) {
            // 如果为true,则显示
            divNode.setAttribute('style','display:block')
        } else{
            // 否则,不显示
            divNode.setAttribute('style','display:none')
        }
        console.log('update: el: ' , el)
        console.log('update: binding: ' , binding)
        console.log('')
        
    },
    // 钩子4: 此时 DOM内容 已经全部更新
    // 如果要获取html更新之后的内容, 代码写这儿
    componentUpdated: function (el,binding,vnode,oldNode) {
        console.log('componentUpdated: el: ' , el)
        console.log('componentUpdated: binding: ' , binding)
        console.log('')
        
    },
    // 钩子5: 
    unbind: function (el,binding,vnode,oldNode) {
        NSLog('unbind: el: ' + el)
        NSLog('unbind: binding: ' + binding)
        
    }
})




    var appVue = new Vue({
        data: {
           isVisible: true
        },
        methods: {
             

        }
    }).$mount('#id_div_container')

    
</script>  

        <p class="sgcenter sgcontentcolor">
            <b>注意: 自定义指令v-beyond-show实现显示和隐藏
        </p>

        <footer id="copyright">
            <p style="font-size:14px;text-align:center;font-style:italic;">  
            Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>  
            </p>        
        </footer>
    
</body>  
</html>  

效果如下:

核心代码:

// ------------------全局自定义指令(完整形式)------------
// 参数1: 指令名v-beyond-show 所对应的 参数1 在定义时, 有两种不同的写法
    //       第1种: 除了v- 的部分, 即beyond-show
    //       第2种: 驼峰命名法,可以写成 beyondShow
    // 两种写法,效果一样,都是同一个指令 v-beyond-show

    // 参数2: 是对象,其属性键值对 是5个钩子函数
Vue.directive('beyond-show',{
    // 钩子1: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点), 可做一些初始化设置
    // 再次强调,此时无父节点
    bind: function (el,binding,vnode,oldNode) {
        console.log('bind: el: ', el)
        console.log('bind: elのparent: ', el.parentNode)
        console.log('bind: binding: ', binding)
        console.log('')
    },
    // 钩子2: 当被绑元素inputNode 插入到dom时,调用insert方法 (只会执行一次)
    // 仅保证父节点存在,但不一定已插入到DOM中
    // 如果你要操作父节点,至少要写在这个钩子里
    inserted: function (el,binding,vnode,oldNode) {

        console.log('inserted: el: ', el)
        console.log('inserted: binding: ', binding)
        console.log('inserted: elのparent: ', el.parentNode)
        console.log('')
    },
    // 钩子3:  此时DOM内部尚未更新
    // 如果要获取html更新之前的内容, 代码写这儿
    update: function (el,binding,vnode,oldNode) {
        // 指令是可以传值的, 根据传递的值(即binding对象中的value)
        var isVisible = binding.value
        NSLog('isVisible: ' + isVisible)

        var parentNode = el.parentNode
        var divNode = parentNode.getElementsByClassName('class_div_cube')[0]
        if (isVisible === true) {
            // 如果为true,则显示
            // divNode.setAttribute('style','display:block')
            divNode.style.display = 'block'
        } else{
            // 否则,不显示
            // divNode.setAttribute('style','display:none')
            divNode.style.display = 'none'
        }
        console.log('update: el: ' , el)
        console.log('update: binding: ' , binding)
        console.log('')
        
    },
    // 钩子4: 此时 DOM内容 已经全部更新
    // 如果要获取html更新之后的内容, 代码写这儿
    componentUpdated: function (el,binding,vnode,oldNode) {
        console.log('componentUpdated: el: ' , el)
        console.log('componentUpdated: binding: ' , binding)
        console.log('')
        
    },
    // 钩子5: 
    unbind: function (el,binding,vnode,oldNode) {
        NSLog('unbind: el: ' + el)
        NSLog('unbind: binding: ' + binding)
        
    }
})

核心代码的简写形式:

// ------------------全局自定义指令(简写形式)------------
Vue.directive('beyond-show',function (el,binding) {
    // 指令是可以传值的, 根据传递的值(即binding对象中的value)
        var isVisible = binding.value
        NSLog('isVisible: ' + isVisible)

        var parentNode = el.parentNode
        // 由于是简写,bind钩子 和 update钩子 写在一起
        // 而bind钩子执行的时候,还取不到 父节点 
        if (!parentNode) {
            return
        }
        var divNode = parentNode.getElementsByClassName('class_div_cube')[0]
        if (isVisible === true) {
            // 如果为true,则显示
            // divNode.setAttribute('style','display:block')
            divNode.style.display = 'block'
        } else{
            // 否则,不显示
            // divNode.setAttribute('style','display:none')
            divNode.style.display = 'none'
        }
})

自定义指令v-bind效果 (动态绑定属性值)

vue_28.html代码如下:

<!-- 第3步, vue最终渲染结果的容器 -->
<div v-cloak id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">

    <h2 v-beyond-bind:title="girlName"> {{ girlName }}</h2>
    <input type="text" v-model="girlName" />

</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">


// ------------------全局自定义指令-------
Vue.directive('beyond-bind',function (el,binding) {
    el.setAttribute(binding.arg,binding.value)
})

    var appVue = new Vue({
        data: {
           girlName: '面码'
        },
        methods: {
             

        }
    }).$mount('#id_div_container')
</script>  

        <p class="sgcenter sgcontentcolor">
            <b>注意: 自定义指令v-beyond-bind演示
        </p>

效果如下:

自定义指令v-beyond-bind 实现添加移除class效果

vue_29.html代码如下

<!-- 第3步, vue最终渲染结果的容器 -->
<div v-cloak id="id_div_container" style="color:#666;padding:10px 10px;border:1px solid Cadetblue;margin:4px;border-radius:8px;">

    <div v-beyond-bind:class="{class_div_cube: isVisible,class_div_center: isCenter}">
        
    </div>

</div>

        <!-- 第1步,导入vue.js -->
<script type="text/javascript" src="/node_modules/vue/dist/vue.js"></script>

        <!-- 第2步,生成一个全新的vue对象实例 -->  
<script type="text/javascript">


// ------------------全局自定义指令-------
Vue.directive('beyond-bind',function (el,binding) {
    if (binding.arg === 'class') {
        // 对象
        for(var className in binding.value){
            if(binding.value[className]){
                el.classList.add(className)
            }else{
                el.classList.remove(className)
            }
        }
    } else{
        el.setAttribute(binding.arg,binding.value)
    }
})

    var appVue = new Vue({
        data: {
           isVisible: false,
           isCenter: false
        },
        methods: {
             

        }
    }).$mount('#id_div_container')
</script>  

        <p class="sgcenter sgcontentcolor">
            <b>注意: 自定义指令v-beyond-bind演示
        </p>

核心代码:

        // 对象
        for(var className in binding.value){
            if(binding.value[className]){
                el.classList.add(className)
            }else{
                el.classList.remove(className)
            }
        }

效果如下:

下面我们开始 介绍 todoMVC完整项目

最终我们需要实现的效果如下:

为了完全实现这样的一个demo,  首先打开todoMVC官网: todomvc.com,

可以看到有不同技术实现的todoMVC

这些我们不用理会,只需下载模板即可

第1步,找到我们要下载其template模板(箭头所指的地方就是模板的下载链接):   github.com/tastejs/todomvc-app-template

第2步,通过git clone命令,将模板template clone到本地

如果 带有参数 –depth=1 则代表 只下载最后一次的提交,可加快下载速度

git clone https://github.com/tastejs/todomvc-app-template.git

第3步, 安装第3方依赖

npm install

如图所示:

现在打开index.html即可开始编写我们自己的todoMVC项目了

使用上面安装的browser-sync, 通过命令 npm start 打开index.html, 如果没有内容的时候,静态效果如下

有关todoMVC这个案例的需求, 如下图所示:  已经全部写在了 百度脑图 

naotu.baidu.com/file/b935b732b2dbf1b2ff12a3291d7f24e5?token=f1973a115e68f4e1

展开百度脑图中的「基础案例: todoMVC」节点, 项目需求,如下图所示:

我的脑图:

完整的todoMVC最终效果如下:

todoMVC完整功能的appVue.js代码如下:

;(function (window) {
	'use strict';

	// var animeArr = [
	// 				{
	// 					animeID: 1,
	// 					animeName: "未闻花名",
	// 					animeHaveSeen: false
	// 				},
	// 				{
	// 					animeID: 2,
	// 					animeName: "龙与虎",
	// 					animeHaveSeen: true
	// 				},
	// 				{
	// 					animeID: 3,
	// 					animeName: "轻音少女",
	// 					animeHaveSeen: false
	// 				}
	// 			]
// ------------------全局自定义组件(简写形式)------------
// Vue.directive('beyond-focus',function (el,binding) {
// 	el.focus()
// })


// ------------------全局自定义指令(完整形式)------------
// 参数1: 指令名v-beyond-show 所对应的 参数1 在定义时, 有两种不同的写法
    //       第1种: 除了v- 的部分, 即beyond-show
    //       第2种: 驼峰命名法,可以写成 beyondShow
    // 两种写法,效果一样,都是同一个指令 v-beyond-show

    // 参数2: 是对象,其属性键值对 是5个钩子函数
Vue.directive('beyond-focus',{
    // 钩子1: 只调用一次,第一次绑定指令到元素时调用(此时,无父节点), 可做一些初始化设置
    // 再次强调,此时无父节点
    bind: function (el,binding,vnode,oldNode) {
    	// 注意:聚焦不能写在bind钩子里
        console.log('bind: el: ', el)
        console.log('bind: elのparent: ', el.parentNode)
        console.log('bind: binding: ', binding)
        console.log('')
    },
    // 钩子2: 当被绑元素inputNode 插入到dom时,调用insert方法 (只会执行一次)
    // 仅保证父节点存在,但不一定已插入到DOM中
    // 如果你要操作父节点,至少要写在这个钩子里
    inserted: function (el,binding,vnode,oldNode) {
    	
        console.log('inserted: el: ', el)
        console.log('inserted: binding: ', binding)
        console.log('inserted: elのparent: ', el.parentNode)
        console.log('')

    },
    // 钩子3:  此时DOM内部尚未更新
    // 如果要获取html更新之前的内容, 代码写这儿
    update: function (el,binding,vnode,oldNode) {
        // 指令是可以传值的, 根据传递的值(即binding对象中的value)
        // v-beyond-focus="currentEditAnime === anime"
		if(binding.value === true){
			el.focus()
		}
        
        
    },
    // 钩子4: 此时 DOM内容 已经全部更新
    // 如果要获取html更新之后的内容, 代码写这儿
    componentUpdated: function (el,binding,vnode,oldNode) {
        // console.log('componentUpdated: el: ' , el)
        // console.log('componentUpdated: binding: ' , binding)
        // console.log('')
        
    },
    // 钩子5: 
    unbind: function (el,binding,vnode,oldNode) {
        NSLog('unbind: el: ' + el)
        NSLog('unbind: binding: ' + binding)
        
    }
})
// ------------------全局自定义指令(完整形式)------------






	// var animeArr = []
	// 初始值来自 localStorage (默认初次 没有值, 便以空数组代替)
	var animeArrInitString = window.localStorage.getItem('animeArr')
	var animeArr = JSON.parse(animeArrInitString || '[]')
	// 这儿必须用window 记住, 因为本函数写在闭包里面
	window.appVue = new Vue({
		// data选项
        data: {
           animeArr: animeArr,
           // 记录 被人双击时的那一个 anime对象
           currentEditAnime: null,
           // 根据 window.location.hash 进行同步改变; 而hashString又作为 过滤数组的重要的判断条件;
           // 从而实现了地址栏 与 列表数组 的联动
           hashString: ''
        },

        // 观察者 选项
        // 注意: 不能使用 箭头函数来定义 watch选项中的函数
		// 原因是: 箭头函数 绑定的是父级作用域的上下文
        watch: {
        	// 观察data选项中的animeArr属性的一举一动,并随之执行相应的业务处理逻辑
        	animeArr: {
        		// 数组 和 对象 等引用类型,默认只能监视第1层,为了能够监视其子成员的改变, 必须 深度观察
        		deep: true,
        		// 监听开始时,立即调用(不管改没改变)
        		immediate: true,
        		// 发现改变时,随之 调用的业务逻辑 (持久化)
        		handler: function (newValue,oldValue) {
        			// NSLog('animeArr发生改变,写入本地存储')
        			window.localStorage.setItem('animeArr',JSON.stringify(newValue))
        		}
        	}
        },

        // 计算属性 本质是一个getter/setter方法, 但是必须也只能按属性使用
        computed: {
        	// 第1种写法: 直接写,  用 方法 的形式写
        	// unSeenAnimeCount(){
        	// 	var leftCount =  this.animeArr.filter(anime => 
        	// 				!anime.animeHaveSeen
        	// 			).length
        	// 	NSLog(leftCount)
        	// 	return leftCount
        	// }


        	// 第2种 完整写法  对象形式
        	unSeenAnimeCount: {
        		// 默认 只有一个 getter
        		get: function () {
        			var leftCount =  this.animeArr.filter(anime => 
        					anime.animeHaveSeen === false
        				).length
	        		// NSLog(leftCount)
	        		return leftCount
        		}
        	},
        	// 选择全部 / 取消选择
        	chooseAllOrNot: {
        		get: function () {
        			
        			// 当数组中每一个 anime 都 have seen的时候, 自动勾选 checkbox
			        // 当数组中 只要有一个anime 不是have seen时, 取消勾选 checkbox
			        // 注意: 计算属性知道自己依赖了 数组, 所以,当 数组 变化时, 计算属性也会重新计算
			        var b = this.animeArr.every(anime => 
			            anime.animeHaveSeen === true
			        )
			        NSLog('get: ' + b)
			        return b
        		},
        		// 只要用户 勾选或取消 绑定了此计算属性的checkbox, 就会来到setter方法 (因为有 设置值)
        		// 数组animeArr中的所有animeHaveSeen的状态也就都会跟着联动
        		set: function () {
        			
			        // 1. 非常巧妙! 先通过计算属性的getter方法,获取当前的 勾选 状态
			        var isChecked = this.chooseAllOrNot
			        // 2. 然后选择相反的状态
			        var newIsChecked = !isChecked
			        // NSLog('set: ' + newIsChecked)
			        // 3. 同步更新所有数组中的成员
			        this.animeArr.forEach(anime => 
			            anime.animeHaveSeen = newIsChecked
			        )
        		}
        	},
        	// 根据条件 过滤出来的 供列表显示的 数组
        	filtedAnimeArr: {
        		get: function () {
        			// 如果 根据路由得出的 中间变量 为 completed 或者 active ,则 过滤相应的数组 展示到界面上
        			if(this.hashString === 'active'){
        				return this.animeArr.filter(anime => 
        						anime.animeHaveSeen === false
        					)
        			}else if(this.hashString === 'completed'){
        				return this.animeArr.filter(anime => 
        						anime.animeHaveSeen === true
        					)
        			}else{
        				return this.animeArr
        			}
        		}
        	}
        },


        methods: {
        	addAnimeFunction(event)	{
        		NSLog('按下回车')
        		// 获取input节点
        		var inputNode = event.target
        		var newAnimeName = inputNode.value.trim()
        		NSLog(newAnimeName)
        		// 过滤非空数据
        		if(!newAnimeName.length){
        			return
        		}
        		var animeArr = this.animeArr
        		// 索引健壮性
        		var newAnimeID = -1
        		if(animeArr.length === 0){
        			newAnimeID = 1
        		}else{
        			newAnimeID = animeArr[animeArr.length -1 ].animeID + 1
        		}
        		
        		var newAnime = {
        			animeID: newAnimeID,
        			animeHaveSeen: false,
        			animeName: newAnimeName
        		}
        		animeArr.push(newAnime)
        		// 清空输入框
        		inputNode.value = ''
             	
	        },
	        selectAllOrNotFunction(event){
	        	// 先拿到节点
	        	var checkNode = event.target
	        	// 再拿到其 选中状态 checked
	        	var checkStatus = checkNode.checked
	        	NSLog(checkStatus)
	        	// 如果选中,则将数组内所有的animeHaveSeen状态置为 true
	        	this.animeArr.forEach(item => {
	        		item.animeHaveSeen = checkStatus
	        	})
	        },
	        // 当事件处理函数,没有传递参数时,第一个参数就是event
	        // 当事件处理函数传递了参数时,就没有办法再获取到event对象了
	        // 因此,我们在传递参数时,就要手动传递事件对象 $event
	        deleteAnimeBtnClicked(event,index){
	        	// 只有在 调用方法时,手动传递了$event对象,这时,我们才可以在方法里面 拿到事件对象
	        	this.animeArr.splice(index,1)
	        },
	        // 双击事件发生时调用
	        // 参数1: 双击事件
	        // 参数2: 索引
	        // 参数3: anime对象
	        doubleClickFunction(event,index,anime){
	        	// 非常巧妙的1步,使用中间变量 保存被双击的对象(其实用索引也行)
	        	this.currentEditAnime = anime
	        },
	        // 按回车 或 失去焦点时, 结束编辑
	        editCompleteFunction(event,index,anime){
	        	// 1. 退出editing样式(让中间变量currentEditAnime 置null)
	        	this.currentEditAnime = null
	        	// 2. 获取并验证 新输入的 animeName
	        	var editNode = event.target
	        	var newAnimeName = editNode.value
	        	NSLog(newAnimeName)
	        	// 2.1 如果为空,则删除该anime,
	        	if (newAnimeName.length === 0) {
	        		this.animeArr.splice(index,1)
	        		return
	        	}
	        	// 2.2 如果有内容,则更新
	        	anime.animeName = newAnimeName

	        },
	        // 按esc时, 结束编辑 不保存
	        cancleEditFunction() {
	        	// 1. 退出editing样式(让中间变量currentEditAnime 置null)
	        	this.currentEditAnime = null
	        },
	        // 点击 清除完成 按钮 事件
	        clearCompletedAnimeBtnClicked() {
	        	// 注意: 使用forEach 遍历数组时,千万不能 删除元素 
	        	// 方式一: 用for语句遍历, 并在删除后,将索引 --
	        	// for (var i = 0; i < animeArr.length; i++) {
	        	// 	if(this.animeArr[i].animeHaveSeen){
	        	// 		this.animeArr.splice(i,1)
		        // 		// 注意: 千万记得 i-- 
		        // 		i--
	        	// 	}
	        		
	        	// }

	        	// 方式二: 推荐使用filter 过滤出新数组
	        	this.animeArr = this.animeArr.filter( anime => 
	        			!anime.animeHaveSeen
	        		)
	        },
	        // 使用方法包装, 获取未观看的动漫数
	        unSeenAnimeCountFunction() {
	        	return this.animeArr.filter(anime => 
	        			!anime.animeHaveSeen
	        		).length
	        }
    	}
    }).$mount('#id_section_container')







// -------------------------------------------
	function onWindowLocationHashChanged(){
		// 拿到 window.location.hash
		var tempHashStr = window.location.hash
		// 赋值给 data的 中间变量 hashString(即 过滤数组的条件) 
		// #/active => active  #/completed => completed
		window.appVue.hashString = tempHashStr.substr(2)
	}
	// 只有hansh 发生 改变时, 才会调用本方法
	window.onhashchange = onWindowLocationHashChanged
	// 页面初始化时,手动调用一次,根据当前路由状态进行回显
	onWindowLocationHashChanged()
// -------------------------------------------


})(window);

todoMVC完整功能的index.html代码如下:

<!doctype html>
<html lang="en">
	<head>
		<link rel="icon" href="/public/img/beyond2.jpg" type="image/x-icon"/>
	    <meta charset="UTF-8">
	    <meta name="author" content="beyond">
	    <meta http-equiv="refresh" content="520">
	    <meta name="description" content="未闻花名-免费零基础教程-beyond">
	    <meta name="viewport" content="width=device-width, 
	    initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
	    <meta name="keywords" content="HTML,CSS,JAVASCRIPT,JQUERY,XML,JSON,C,C++,C#,OC,PHP,JAVA,JSP,PYTHON,RUBY,PERL,LUA,SQL,LINUX,SHELL,汇编,日语,英语,泰语,韩语,俄语,粤语,阿语,魔方,乐理,动漫,PR,PS,AI,AE">
	    <title>beyond心中の动漫神作</title>
		<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
		<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
		
		<link rel="stylesheet" href="css/beyondbasestylewhite5.css">
		<style type="text/css">
			/*解决Mustache 闪烁问题
				起初div是隐藏的
				当Vue数据绑定完成后,重新渲染时,会自动将v-cloak属性移除
			*/
			[v-cloak] {
				display: none;
			}
		</style>
	</head>
	<body>
		<section v-cloak id="id_section_container" class="todoapp">
			<header class="header">
				<h1 style="color:white;text-shadow:2px 2px 4px #e5cdcf;letter-spacing:5px;font-family:inherit;font-weight:380;" class="sgcontentcolor sgcenter">  
		            あの花
		        </h1>

		        <!-- 按下enter时,添加到数组 -->
				<input @keydown.enter="addAnimeFunction"
				 class="new-todo" placeholder="请输入动漫名,按回车键录入" autofocus>
			</header>


			<!-- 使用虚拟元素template将这个元素两个包起来 
				 1是为了 避免代码冗余
				 2是为了 避免套上额外的div
			-->
			<template v-if="animeArr.length">
				<!-- 数组没有东西时,不要渲染 -->
				<section class="main">
					<!-- 全选/全不选
						 方式1: 在这儿监听 change 事件 
								 v-on:change="selectAllOrNotFunction"

						 方式2:  MVVM 高级玩法
						 使用计算属性chooseAllOrNot作为v-model 实现双向绑定 到checkbox

						 因为checkbox既有初始值, 又要勾选设置值,所以要双向绑定

						 所以,当初始化checkbox时, 会调用计算属性的getter方法

						 而当你勾选checkbox的时候,就会自动调用计算属性的 setter方法
					 -->
					<input id="toggle-all" class="toggle-all" type="checkbox"
					v-model="chooseAllOrNot"
					>
					<label for="toggle-all">Mark all as complete</label>


					<ul class="todo-list">
						<!--  三种样式
							  `editing` when editing 
						 	  `completed` when marked as completed 

						 	  这儿实行的是单向绑定class
					 	 -->

					 	 <!-- 注意: 下面这儿关于当前编辑的样式editing 有点儿巧妙
						data新增一个属性 currentEditAnime, 保存的是那一个被双击的 anime对象
						如果 两个对象一样时, 自动进入 正在编辑样式
						 -->

						<li v-for="(anime,index) in filtedAnimeArr"

						v-bind:class="{completed: anime.animeHaveSeen,
						editing: currentEditAnime === anime
						}"
						>

							<div class="view">
								<!-- 根据状态选择是否选中
									 这儿实现的双向绑定
								 -->
								<input class="toggle" type="checkbox" 
								 v-model="anime.animeHaveSeen"
								>

								<!-- 上面的双击 要传递3个参数
							 	  第1是:  双击事件
							 	  第2是:  索引
							 	  第3是:  anime对象
							    -->
								<label v-on:dblclick="doubleClickFunction($event,index,anime)">{{ anime.animeName }}</label>

								<!-- 点击按钮删除item 
									 注意: 
									 第1个参数是索引,
									 第2个参数是手动传递的事件 $event 对象
									 (固定写法,死记硬背)
								-->
								<button class="destroy"
								v-on:click="deleteAnimeBtnClicked($event,index)"
								></button>

							</div>

							<!-- 编辑框 必须是单向绑定,因为
							1. 还有esc撤销编辑的功能 
							2. 去除editing样式,即把currentEditAnime置空即可
							3. 敲回车 或 失去焦点自动保存
							   如果没有内容,则自动删除该item


							   使用自定义指令 自动聚焦,条件是 当前编辑的对象是自己
							-->
							<input class="edit" v-bind:value="anime.animeName"
							v-on:keydown.enter="editCompleteFunction($event,index,anime)"
							v-on:blur="editCompleteFunction($event,index,anime)"
							v-on:keydown.esc="cancleEditFunction"
							v-beyond-focus="currentEditAnime === anime"  
							>
						</li>
					</ul>
				</section>

				<!-- 数组没有东西时,不要渲染 -->
				<footer class="footer">
					<!-- 剩余未未完成的数量
						 这儿有3种写法:
						 第1种: 直接在Mustache语法中 进行 数组的 filter
						 第2种: 使用方法包装
						 第3种: 使用计算属性
					 -->
					<span class="todo-count">
						<strong>
							<!-- 第1种 -->
							<!-- {{ animeArr.filter(anime => 
								!anime.animeHaveSeen
							).length }} -->


							<!-- 第2种,使用方法 包装 
								 要主动调用一下方法
								 如果多处要到的话,会有重复计算的性能问题
							-->
							<!-- {{ unSeenAnimeCountFunction() }} -->


							<!-- 第3种, 计算属性
								 好处1: 有缓存 (会缓存计算结果,提高性能)
								 好处2: 模板没有 厚重的逻辑
								 好处3: 易维护

								 注意: 计算属性 必须也只能按属性使用 (不能用于事件处理函数,虽然它本质是getter和setter方法)
							 -->
							 {{ unSeenAnimeCount }}
					</strong>
					 item left</span>

					<!-- 1. 根据 路由routing 的切换, 导致window.location.hash变化
						 2. 而监听到地址栏的hash的变化时 , 又人为导致 data中的一个 中间变量(hashString)的变化 
					 	 3. 而条件(hashString)的变化 , 又改变 计算属性 返回的结果数组 filtedAnimeArr 
					 	 4. 从而实现了对 列表的过滤与切换  -->
					<ul class="filters">
						<li>
							<a v-bind:class="{selected: hashString === ''}" href="#/">All</a>
						</li>
						<li>
							<a v-bind:class="{selected: hashString === 'active'}" href="#/active">Active</a>
						</li>
						<li>
							<a v-bind:class="{selected: hashString === 'completed'}" href="#/completed">Completed</a>
						</li>
					</ul>
					<!-- 如果没有已经完成的item, 那么不渲染这个按钮 
					这儿非常巧妙地使用 array的some函数
					-->
					<button v-if="animeArr.some(anime => anime.animeHaveSeen)" class="clear-completed"
					v-on:click="clearCompletedAnimeBtnClicked"
					>Clear completed</button>
				</footer>

			</template>



		</section>

		<footer id="copyright">
            <p style="font-size:14px;text-align:center;font-style:italic;">  
            Copyright © <a id="author">2018</a> Powered by <a id="author">beyond</a>  
            </p>        
        </footer>

        <script type="text/javascript" src="js/nslog.js"></script>
		<!-- 第1步,导入vue.js -->
		<script type="text/javascript" src="node_modules/vue/dist/vue.js"></script>
		<!-- 第2步,导入自己的代码appVue.js -->
		<script type="text/javascript" src="js/appVue.js"></script>

	</body>
</html>

 

未完待续,下一章节,つづく