vue -- 初级(一)

什么是Vue.js
Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架(React除了开发网站,还可以开发手机App, Vue语法也是可以用于进行手机App开发的,需要借助于Weex)
Vue.js 是前端的主流框架之一,和Angular.js、React.js 一起,并成为前端三大主流框架!
Vue.js 是一套构建用户界面的框架,只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合。(Vue有配套的第三方类库,可以整合起来做大型项目的开发)

前端的主要工作?主要负责MVC中的V这一层;主要工作就是和界面打交道,来制作前端页面效果;

    为什么要学习流行框架
企业为了提高开发效率:在企业中,时间就是效率,效率就是金钱;企业中,使用框架,能够提高开发的效率;
提高开发效率的发展历程:原生JS -> Jquery之类的类库 -> 前端模板引擎 -> Angular.js / Vue.js(能够帮助我们减少不必要的DOM操作;提高渲染效率;双向数据绑定的概念【通过框架提供的指令,我们前端程序员只需要关心数据的业务逻辑,不再关心DOM是如何渲染的了】)
在Vue中,一个核心的概念,就是让用户不再操作DOM元素,解放了用户的双手,让程序员可以更多的时间去关注业务逻辑;
增强自己就业时候的竞争力
人无我有,人有我优

    框架和库的区别
框架:是一套完整的解决方案;对项目的侵入性较大,项目如果需要更换框架,则需要重新架构整个项目。 - node 中的 express;
库(插件):提供某一个小功能,对项目的侵入性较小,如果某个库无法完成某些需求,可以很容易切换到其它库实现需求。
  - 1. 从Jquery 切换到 Zepto
  - 2. 从 EJS 切换到 art-template

    Node(后端)中的 MVC 与 前端中的 MVVM 之间的区别
MVC 是后端的分层开发概念;
MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel
    

Vue之 - `基本的代码结构`和`插值表达式`、`v-cloak`
<script src="../libs/vue.js"></script>
<style>
    [v-cloak]{
        display: none;
    }
</style>
<div id="app">
    <span v-cloak>{{msg}}</span>  //使用v-aloak能够解决 插值表达式闪烁问题
    <span v-text="msg"></span> //默认v-text没有闪烁问题
    <span v-html="msg2"></span>
</div>
</body>
    //创建vue实例
    var vm = new Vue({
        el:"#app",
        data:{
            msg:"欢迎学习vue",
            msg2:"<h1>hello</h1>",
        }
    })

Vue指令之`v-bind`的三种用法
    1. 直接使用指令`v-bind`
    2. 使用简化指令`:`
    3. 在绑定的时候,拼接绑定内容:`:title="btnTitle + ', 这是追加的内容'"`
<!--v-bind:是vue中提供用于绑定属性的指令 ,v-bind中可以写合法的JS表达式 -->
<input type="button" value="按钮" v-bind:title="mytitle" >
data:{
mytitle:"这是一个自定义title"
}

    Vue指令之`v-on`和`跑马灯效果`
<input type="button" value="按钮" v-on:click="show">
methods:{
show:function (e) {
console.log(e)
}
}
    <input type="button" value="开启" @click="go">
    <input type="button" value="停止" @click="stop">
    <h4>{{ msg }}</h4>
    // 注意:在 VM实例中,如果想要获取 data 上的数据,或者 想要调用 methods 中的 方法,必须通过 this.数据属性名  或  this.方法名 来进行访问,这里的this,就表示 我们 new 出来的  VM 实例对象
    var vm = new Vue({
        el: '#app',
        data: {
            msg: '猥琐发育,别浪~~!',
            intervalId: null // 在data上定义 定时器Id
        },
        methods: {
            go() {
                if (this.intervalId != null) return;
                this.intervalId = setInterval(() => {
                    var start = this.msg.substring(0, 1)
                    var end = this.msg.substring(1)    // 获取到 后面的所有字符
                    this.msg = end + start    // 重新拼接得到新的字符串,并赋值给 this.msg
                }, 400)
                // 注意: VM实例,会监听自己身上 data 中所有数据的改变,只要数据一发生变化,就会自动把 最新的数据,从data 上同步到页面中去;【好处:程序员只需要关心数据,不需要考虑如何重新渲染DOM页面】
            },
            stop() { // 停止定时器
                clearInterval(this.intervalId)
                this.intervalId = null;  // 每当清除了定时器之后,需要重新把 intervalId 置为 null
            }
        }
    })
    Vue指令之`v-on的缩写`和`事件修饰符`
事件修饰符:
.stop       阻止冒泡
.prevent    阻止默认事件
.capture    添加事件侦听器时使用事件捕获模式
.self       只当事件在该元素本身(比如不是子元素)触发时触发回调
.once       事件只触发一次

    Vue指令之`v-model`和`双向数据绑定`
<!-- v-bind 只能实现数据的单向绑定,从 M 自动绑定到 V, 无法实现数据的双向绑定  -->
<input type="text" v-bind:value="msg" />
<!-- 使用  v-model 指令,可以实现 表单元素和 Model 中数据的双向数据绑定. 注意: v-model 只能运用在 表单元素中 -->
<!-- input(radio, text, address, email....)   select    checkbox   textarea   -->
<input type="text" v-model="msg" />{{msg}}

    简易计算器案例
<div id="app">
    <input type="text" v-model="n1">
    <select v-model="opt">
        <option value="+">+</option>
        <option value="-">-</option>
        <option value="*">*</option>
        <option value="/">/</option>
    </select>
    <input type="text" v-model="n2">
    <input type="button" value="=" @click="calc">
    <input type="text" v-model="result">
    var vm = new Vue({
        el: '#app',
        data: {
            n1: 0,
            n2: 0,
            result: 0,
            opt: '+'
        },
        methods: {
            calc() { // 计算器算数的方法
                // 逻辑:
                /* switch (this.opt) {
                  case '+':
                    this.result = parseInt(this.n1) + parseInt(this.n2)
                    break;
                  case '-':
                    this.result = parseInt(this.n1) - parseInt(this.n2)
                    break;
                  case '*':
                    this.result = parseInt(this.n1) * parseInt(this.n2)
                    break;
                  case '/':
                    this.result = parseInt(this.n1) / parseInt(this.n2)
                    break;
                } */
                // 注意:这是投机取巧的方式,正式开发中,尽量少用
                var codeStr = 'parseInt(this.n1) ' + this.opt + ' parseInt(this.n2)'
                this.result = eval(codeStr)
            }
        }
    });

    在Vue中使用样式
使用class样式
<!-- 第一种使用方式,直接传递一个数组,注意: 这里的 class 需要使用  v-bind 做数据绑定 -->
<span :class="['thin', 'italic']">样式!!!</span>
<!-- 在数组中使用三元表达式 -->
<span :class="['thin', 'italic', flag?'active':'']">样式!!!</span>
<!-- 在数组中使用 对象来代替三元表达式,提高代码的可读性 -->
<span :class="['thin', 'italic', {'active':flag} ]">样式!!!</span>
<!-- 在为 class 使用 v-bind 绑定 对象的时候,对象的属性是类名, 对象的属性可带引号,也可不带引号,属性的值 是一个标识符 -->
<span :class="classObj">样式!!!</span>
data: {
    flag: true,
    classObj: { red: true, thin: true, italic: false, active: false }
    },
使用内联样式
1. 直接在元素上通过 `:style` 的形式,书写样式对象
<h1 :style="{color: 'red', 'font-size': '40px'}">这是一个H1</h1>
2. 将样式对象,定义到 `data` 中,并直接引用到 `:style` 中   在data上定义样式:
<h1 :style="h1StyleObj">这是一个善良的H1</h1>
data: {
    h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200' }
    }
    + 在元素中,通过属性绑定的形式,将样式对象应用到元素中:
3. 在 `:style` 中通过数组,引用多个 `data` 上的样式对象
<h1 :style="[h1StyleObj, h1StyleObj2]">这是一个善良的H1</h1>
data: {
    h1StyleObj: { color: 'red', 'font-size': '40px', 'font-weight': '200' },
    h1StyleObj2: { fontStyle: 'italic' }
    }

    Vue指令之`v-for`和`key`属性
1. 迭代数组
<li v-for="(item, i) in list">索引:{{i}} --- 姓名:{{item.name}} --- 年龄:{{item.age}}</li>
2. 迭代对象中的属性
<div v-for="(val, key, i) in userInfo">{{val}} --- {{key}} --- {{i}}</div>
3. 迭代数字        i 值从 1 开始
<p v-for="i in 10">这是第 {{i}} 个P标签</p>
> 2.2.0+ 的版本里,当在组件中使用 v-for 时,key 现在是必须的。
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。
注意: v-for 循环的时候,key 属性只能使用 number获取string
注意: key 在使用的时候,必须使用 v-bind 属性绑定的形式,指定 key 的值
在组件中,使用v-for循环的时候,或者在一些特殊情况中,如果 v-for 有问题,必须 在使用 v-for 的同时,指定 唯一的 字符串/数字 类型 :key 值

    Vue指令之`v-if`和`v-show`
v-if 的特点:每次都会重新删除或创建元素
v-show 的特点: 每次不会重新进行DOM的删除和创建操作,只是切换了元素的 display:none 样式
v-if 有较高的切换性能消耗 ,如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if
v-show 有较高的初始渲染消耗 ,如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show

 

<!-- 在Vue中,使用事件绑定机制,为元素指定处理函数的时候,如果加了小括号,就可以给函数传参了 -->
<input type="button" value="添加" class="btn " @click="add()" />

    在2.x版本中[手动实现筛选的方式](https://cn.vuejs.org/v2/guide/list.html#显示过滤-排序结果)
列表案例:
搜素<input type="text" v-model="keywords" />
<ul>
// 在使用 `v-for` 指令循环每一行数据的时候,不再直接 `item in list`,而是 `in` 一个 过滤的methods 方法,同时,把过滤条件`keywords`传递进去
<li v-for="item in search(keywords)" :key="item.id">
{{item.id}} -------- {{item.name}} -----  {{item.time}} -------
<a href="#" @click.prevent="del(item.id)">删除</a>
</li>
</ul>
search(keywords) { // 根据关键字,进行数据的搜索
    /* var newList = []
    this.list.forEach(item => {
      if (item.name.indexOf(keywords) != -1) {
        newList.push(item)
      }
    })
    return newList */
    // 注意:  forEach   some   filter   findIndex   这些都属于数组的新方法, 都会对数组中的每一项,进行遍历,执行相关的操作;
    return this.list.filter(item => {
        // if(item.name.indexOf(keywords) != -1)
        // 注意 : ES6中,为字符串提供了一个新方法,叫做  String.prototype.includes('要包含的字符串')
        //  如果包含,则返回 true ,否则返回 false
        //  contain
        if (item.name.includes(keywords)) {
            return item
        }
    })
}

Vue调试工具`vue-devtools`的安装步骤和使用
[Vue.js devtools - 翻墙安装方式 - 推荐](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=zh-CN)

    过滤器
概念:Vue.js 允许你自定义过滤器,**可被用作一些常见的文本格式化**。过滤器可以用在两个地方:**mustache 插值和 v-bind 表达式**。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符指示;
// 过滤器的定义语法
// Vue.filter('过滤器的名称', function(){})
// 过滤器中的 function ,第一个参数,已经被规定死了,永远都是 过滤器 管道符前面 传递过来的数据
Vue.filter('过滤器的名称', function (data) {
  return data + '123'
})

<p>{{ msg | msgFormat('love', 'you') | test }}</p>
    // 定义一个 Vue 全局的过滤器,名字叫做  msgFormat
Vue.filter('msgFormat', function (msg, arg, arg2) {
    // 字符串的  replace 方法,第一个参数,除了可写一个 字符串之外,还可以定义一个正则
    return msg.replace(/单纯/g, arg + arg2)
})
Vue.filter('test', function (msg) {
    return msg + '========'
})

<td>{{ item.ctime | dateFormat() }}</td> <a href="" @click.prevent="del(item.id)">删除</a>
    // 全局的过滤器, 进行时间的格式化
    // 所谓的全局过滤器,就是所有的VM实例都共享的
Vue.filter('dateFormat', function (dateStr, pattern = "") {
    // 根据给定的时间字符串,得到特定的时间
    var dt = new Date(dateStr)
    //   yyyy-mm-dd
    var y = dt.getFullYear()
    var m = dt.getMonth() + 1
    var d = dt.getDate()
if (pattern.toLowerCase() === 'yyyy-mm-dd') {
    return `${y}-${m}-${d}`
} else {
    var hh = dt.getHours()
    var mm = dt.getMinutes()
    var ss = dt.getSeconds()
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
})

    私有过滤器
1. HTML元素:
<td>{{item.ctime | dataFormat('yyyy-mm-dd')}}</td>
2. 私有 `filters` 定义方式:
filters: { // 私有局部过滤器,只能在 当前 VM 对象所控制的 View 区域进行使用  过滤器有两个 条件  【过滤器名称 和 处理函数】
    // 过滤器调用的时候,采用的是就近原则,如果私有过滤器和全局过滤器名称一致了,这时候 优先调用私有过滤器
    dataFormat(input, pattern = "") { // 在参数列表中 通过 pattern="" 来指定形参默认值,防止报错
      var dt = new Date(input);
      var y = dt.getFullYear();// 获取年月日
      var m = (dt.getMonth() + 1).toString().padStart(2, '0');
      var d = dt.getDate().toString().padStart(2, '0');
      // 如果 传递进来的字符串类型,转为小写之后,等于 yyyy-mm-dd,那么就返回 年-月-日  否则,就返回  年-月-日 时:分:秒
      if (pattern.toLowerCase() === 'yyyy-mm-dd') {
        return `${y}-${m}-${d}`;
      } else {// 获取时分秒
        var hh = dt.getHours().toString().padStart(2, '0');
        var mm = dt.getMinutes().toString().padStart(2, '0');
        var ss = dt.getSeconds().toString().padStart(2, '0');
        return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
      }
    }
  }
> 使用ES6中的字符串新方法 String.prototype.padStart(maxLength, fillString='') 或 String.prototype.padEnd(maxLength, fillString='')来填充字符串;

    全局过滤器
// 定义一个全局过滤器
Vue.filter('dataFormat', function (input, pattern = '') {
  var dt = new Date(input);
  var y = dt.getFullYear();// 获取年月日
  var m = (dt.getMonth() + 1).toString().padStart(2, '0');
  var d = dt.getDate().toString().padStart(2, '0');
  // 如果 传递进来的字符串类型,转为小写之后,等于 yyyy-mm-dd,那么就返回 年-月-日   否则,就返回  年-月-日 时:分:秒
  if (pattern.toLowerCase() === 'yyyy-mm-dd') {
    return `${y}-${m}-${d}`;
  } else {// 获取时分秒
    var hh = dt.getHours().toString().padStart(2, '0');
    var mm = dt.getMinutes().toString().padStart(2, '0');
    var ss = dt.getSeconds().toString().padStart(2, '0');
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
  }
});
> 注意:当有局部和全局两个名称相同的过滤器时候,会以就近原则进行调用,即:局部过滤器优先于全局过滤器被调用!

    键盘修饰符以及自定义键盘修饰符
1.x中自定义键盘修饰符【了解即可】
Vue.directive('on').keyCodes.f2 = 113;
2.x中自定义键盘修饰符](https://cn.vuejs.org/v2/guide/events.html#键值修饰符)
1. 通过`Vue.config.keyCodes.名称 = 按键值`来自定义案件修饰符的别名:
Vue.config.keyCodes.f2 = 113;
2. 使用自定义的按键修饰符:
<input type="text" v-model="name" @keyup.f2="add">

    [自定义指令](https://cn.vuejs.org/v2/guide/custom-directive.html)
1. 自定义全局和局部的 自定义指令:
    // 自定义全局指令 v-focus,为绑定的元素自动获取焦点:
<input type="text" v-model="keywords" v-focus >
    Vue.directive('focus', {
      inserted: function (el) { // inserted 表示被绑定元素插入父节点时调用
        el.focus();
      }
    });

使用  Vue.directive() 定义全局的指令  v-focus
// 其中:参数1 : 指令的名称,注意,在定义的时候,指令的名称前面,不需要加 v- 前缀, 但是: 在调用的时候,必须 在指令名称前 加上 v- 前缀来进行调用
//  参数2: 是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
Vue.directive('focus', {
    bind: function (el) { // 每当指令绑定到元素上的时候,会立即执行这个 bind 函数,只执行一次
    // 注意: 在每个 函数中,第一个参数,永远是 el ,表示 被绑定了指令的那个元素,这个 el 参数,是一个原生的JS对象
    // 在元素 刚绑定了指令的时候,还没有 插入到 DOM中去,这时候,调用 focus 方法没有作用,  因为,一个元素,只有插入DOM之后,才能获取焦点
    // el.focus()
},
    inserted: function (el) {  // inserted 表示元素 插入到DOM中的时候,会执行 inserted 函数【触发1次】
    el.focus()  // 和JS行为有关的操作,最好在 inserted 中去执行,放置 JS行为不生效
},
    updated: function (el) {  // 当VNode更新的时候,会执行 updated, 可能会触发多次
}
})

    // 自定义一个 设置字体颜色的 指令
<input type="text" v-focus v-color="'blue'">
Vue.directive('color', {
    // 样式,只要通过指令绑定给了元素,不管这个元素有没有被插入到页面中去,这个元素肯定有了一个内联的样式,  将来元素肯定会显示到页面中,这时候,浏览器的渲染引擎必然会解析样式,应用给这个元素
    bind: function (el, binding) {    // 和样式相关的操作,一般都可以在 bind 执行
    // el.style.color = 'red'
    // console.log(binding.name)  //color
    // console.log(binding.value)  // blue
    // console.log(binding.expression)  // 'blue'
    el.style.color = binding.value
    }
})

    // 自定义局部指令 v-color 和 v-font-weight,为绑定的元素设置指定的字体颜色 和 字体粗细:
directives: {
    color: { // 为元素设置指定的字体颜色
          bind(el, binding) {
            el.style.color = binding.value;
          }
      },
    'font-weight': function (el, binding2) { // 自定义指令的简写形式,等同于定义了 bind 和 update 两个钩子函数
          el.style.fontWeight = binding2.value;
        }
      }
2. 自定义指令的使用方式:
<input type="text" v-model="searchName" v-focus v-color="'red'" v-font-weight="900">

     Vue 1.x 中 自定义元素指令【已废弃,了解即可】
Vue.elementDirective('red-color', {
  bind: function () {
    this.el.style.color = 'red';
  }
});
使用方式:
<red-color>1232</red-color>

     [vue实例的生命周期](https://cn.vuejs.org/v2/guide/instance.html#实例生命周期)
什么是生命周期:从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期!
[生命周期钩子](https://cn.vuejs.org/v2/api/#选项-生命周期钩子):就是生命周期事件的别名而已;
    生命周期钩子 = 生命周期函数 = 生命周期事件
主要的生命周期函数分类:

    创建期间的生命周期函数:
beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
    运行期间的生命周期函数:
beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
    销毁期间的生命周期函数:
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

     [vue-resource 实现 get, post, jsonp请求](https://github.com/pagekit/vue-resource)
除了 vue-resource 之外,还可以使用 `axios` 的第三方包实现实现数据的请求
1. 之前的学习中,如何发起数据请求?
2. 常见的数据请求类型?  get  post jsonp
3. 测试的URL请求资源地址:
    get请求地址: http://vue.studyit.io/api/getlunbo
    post请求地址:http://vue.studyit.io/api/post
    jsonp请求地址:http://vue.studyit.io/api/jsonp
    4. JSONP的实现原理
由于浏览器的安全性限制,不允许AJAX访问 协议不同、域名不同、端口号不同的 数据接口,浏览器认为这种访问不安全;
可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP(注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求);
具体实现过程:
    先在客户端定义一个回调方法,预定义对数据的操作;
    再把这个回调方法的名称,通过URL传参的形式,提交到服务器的数据接口;
    服务器数据接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端去解析执行;
    客户端拿到服务器返回的字符串之后,当作Script脚本去解析执行,这样就能够拿到JSONP的数据了;
带大家通过 Node.js ,来手动实现一个JSONP的请求例子;
    const http = require('http');
    // 导入解析 URL 地址的核心模块
    const urlModule = require('url');
    const server = http.createServer();
    // 监听 服务器的 request 请求事件,处理每个请求
    server.on('request', (req, res) => {
      const url = req.url;
      // 解析客户端请求的URL地址
      var info = urlModule.parse(url, true);
      // 如果请求的 URL 地址是 /getjsonp ,则表示要获取JSONP类型的数据
      if (info.pathname === '/getjsonp') {
        // 获取客户端指定的回调函数的名称
        var cbName = info.query.callback;
        // 手动拼接要返回给客户端的数据对象
        var data = {
          name: 'zs',
          age: 22,
          gender: '男',
          hobby: ['吃饭', '睡觉', '运动']
        }
        // 拼接出一个方法的调用,在调用这个方法的时候,把要发送给客户端的数据,序列化为字符串,作为参数传递给这个调用的方法:
        var result = `${cbName}(${JSON.stringify(data)})`;
        // 将拼接好的方法的调用,返回给客户端去解析执行
        res.end(result);
      } else {
        res.end('404');
      }
    });
    server.listen(3000, () => {
      console.log('server running at http://127.0.0.1:3000');
    });
案例:
    <script>
        function showInfo123(data) {
        console.log(data)
    }
</script>
<script src="http://127.0.0.1:3000/getscript?callback=showInfo123"></script>
<!--  node-server/app.js  -->
const http = require('http')  // 导入 http 内置模块
const urlModule = require('url')   // 这个核心模块,能够帮我们解析 URL地址,从而拿到  pathname  query
// 创建一个 http 服务器
const server = http.createServer()
// 监听 http 服务器的 request 请求
server.on('request', function (req, res) {
const { pathname: url, query } = urlModule.parse(req.url, true)
if (url === '/getscript') {
    var data = {
        name: 'xjj',
        age: 18,
        gender: '女孩子'
    }
    var scriptStr = `${query.callback}(${JSON.stringify(data)})`
    // res.end 发送给 客户端, 客户端去把 这个 字符串,当作JS代码去解析执行
    res.end(scriptStr)
} else {
    res.end('404')
}
})
// 指定端口号并启动服务器监听
server.listen(3000, function () {
console.log('server listen at http://127.0.0.1:3000')
})

5. vue-resource 的配置步骤:
直接在页面中,通过`script`标签,引入 `vue-resource` 的脚本文件;
注意:引用的先后顺序是:先引用 `Vue` 的脚本文件,再引用 `vue-resource` 的脚本文件;
6. 发送get请求:
getInfo() { // get 方式获取数据
  this.$http.get('http://127.0.0.1:8899/api/getlunbo').then(res => {
    console.log(res.body);
  })
}
7. 发送post请求:
postInfo() {
  var url = 'http://127.0.0.1:8899/api/post';
  // post 方法接收三个参数:
  // 参数1: 要请求的URL地址
  // 参数2: 要发送的数据对象
  // 参数3: 指定post提交的编码类型为 application/x-www-form-urlencoded
  this.$http.post(url, { name: 'zs' }, { emulateJSON: true }).then(res => {
    console.log(res.body);
  });
}
8. 发送JSONP请求获取数据:
jsonpInfo() { // JSONP形式从服务器获取数据
  var url = 'http://127.0.0.1:8899/api/jsonp';
  this.$http.jsonp(url).then(res => {
    console.log(res.body);
  });
}

     配置本地数据库和数据接口API
1. 先解压安装 `PHPStudy`;
2. 解压安装 `Navicat` 这个数据库可视化工具,并激活;
3. 打开 `Navicat` 工具,新建空白数据库,名为 `dtcmsdb4`;
4. 双击新建的数据库,连接上这个空白数据库,在新建的数据库上`右键` -> `运行SQL文件`,选择并执行 `dtcmsdb4.sql` 这个数据库脚本文件;如果执行不报错,则数据库导入完成;
5. 进入文件夹 `vuecms3_nodejsapi` 内部,执行 `npm i` 安装所有的依赖项;
6. 先确保本机安装了 `nodemon`, 没有安装,则运行 `npm i nodemon -g` 进行全局安装,安装完毕后,进入到 `vuecms3_nodejsapi`目录 -> `src`目录 -> 双击运行 `start.bat`
7. 如果API启动失败,请检查 PHPStudy 是否正常开启,同时,检查 `app.js` 中第 `14行` 中数据库连接配置字符串是否正确;PHPStudy 中默认的 用户名是root,默认的密码也是root

// 如果我们通过全局配置了,请求的数据接口 根域名,则 ,在每次单独发起 http 请求的时候,请求的 url 路径,应该以相对路径开头,前面不能带 /  ,否则 不会启用根路径做拼接;
Vue.http.options.root = 'http://vue.studyit.io/';
// 全局启用 emulateJSON 选项
Vue.http.options.emulateJSON = true;
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
    el: '#app',
    data: {
        name: '',
        list: [ // 存放所有品牌列表的数组 ]
    },
created() { // 当 vm 实例 的 data 和 methods 初始化完毕后,vm实例会自动执行created 这个生命周期函数
    this.getAllList()
},
methods: {
getAllList() { // 获取列表
    this.$http.get('api/getprodlist').then(result => {
        var result = result.body
        if (result.status === 0) { this.list = result.message } else { alert('获取数据失败!') }
    })
},
add() {  // 添加列表到后台服务器
    2. this.$http.post() 中接收三个参数:
        2.1 第一个参数: 要请求的URL地址
        2.2 第二个参数: 要提交给服务器的数据 ,要以对象形式提交给服务器 { name: this.name }
        3.3 第三个参数: 是一个配置对象,要以哪种表单数据类型提交过去, { emulateJSON: true }, 以普通表单格式,将数据提交给服务器 application/x-www-form-urlencoded
    3. 在 post 方法中,使用 .then 来设置成功的回调函数,如果想要拿到成功的结果,需要 result.body
this.$http.post('api/addproduct', { name: this.name }).then(result => {
    if (result.body.status === 0) {
        this.getAllList()     // 添加完成后,需要手动,再调用一下 getAllList 刷新品牌列表
        this.name = ''        // 清空 name
    } else { alert('添加失败!') }
})
},
del(id) { // 删除品牌
    this.$http.get('api/delproduct/' + id).then(result => {
        if (result.body.status === 0) { // 删除成功
        this.getAllList()
        } else { alert('删除失败!') }
    })
    }
}
});

     [Vue中的动画](https://cn.vuejs.org/v2/guide/transitions.html)
为什么要有动画:动画能够提高用户的体验,帮助用户更好的理解页面中的功能;
     使用过渡类名
1. HTML结构:
<div id="app">
    <input type="button" value="动起来" @click="myAnimate">
    <!-- 使用 transition 将需要过渡的元素包裹起来 -->
    <transition name="fade">
    <div v-show="isshow">动画哦</div>
    </transition>
</div>
2. VM 实例:
// 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
        isshow: false
    },
    methods: {
    myAnimate() {
    this.isshow = !this.isshow;
    }
    }
    });
3. 定义两组类样式:
    /* 定义进入和离开时候的过渡状态 */
    .fade-enter-active,
    .fade-leave-active {
        transition: all 0.2s ease;
        position: absolute;
    }
/* v-enter 【这是一个时间点】 是进入之前,元素的起始状态,此时还没有开始进入 */
/* v-leave-to 【这是一个时间点】 是动画离开之后,离开的终止状态,此时,元素 动画已经结束了 */
    /* 定义进入过渡的开始状态 和 离开过渡的结束状态 */
    .fade-enter,
    .fade-leave-to {
        opacity: 0;
        transform: translateX(100px);
    }

    [使用第三方 CSS 动画库](https://cn.vuejs.org/v2/guide/transitions.html#自定义过渡类名)
1. 导入动画类库:
    <link rel="stylesheet" type="text/css" href="./lib/animate.css">
2. 定义 transition 及属性:
    <transition
    enter-active-class="fadeInRight"
    leave-active-class="fadeOutRight"
    :duration="{ enter: 500, leave: 800 }">
    <div class="animated" v-show="isshow">动画</div>
    </transition>

     使用动画钩子函数
1. 定义 transition 组件以及三个钩子函数:
<div id="app">
    <input type="button" value="切换动画" @click="isshow = !isshow">
    <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter">
    <div v-if="isshow" class="show">OK</div>
    </transition>
</div>
2. 定义三个 methods 钩子方法:
methods: {// 动画钩子函数的第一个参数:el,表示 要执行动画的那个DOM元素,是个原生的 JS DOM对象
    beforeEnter(el) { // 动画进入之前的回调
        el.style.transform = 'translateX(500px)';
    },
    enter(el, done) { // 动画进入完成时候的回调
        el.offsetWidth;// 这句话,没有实际的作用,但是,如果不写,出不来动画效果; 可以认为 el.offsetWidth 会强制动画刷新
        el.style.transform = 'translateX(0px)';
        el.style.transition = 'all 1s ease'
    //回调函数done是必须的
        done();  // 这里的 done, 起始就是 afterEnter 这个函数,也就是说:done 是 afterEnter 函数的引用
    },
    afterEnter(el) { // 动画进入完成之后的回调
        this.isshow = !this.isshow;
    }
}
3. 定义动画过渡时长和样式:
.show{
    transition: all 0.4s ease;
    }

     [v-for 的列表过渡](https://cn.vuejs.org/v2/guide/transitions.html#列表的进入和离开过渡)
1. 定义过渡样式:
    <style>
    .list-enter,
    .list-leave-to {
    opacity: 0;
    transform: translateY(10px);
    }
    .list-enter-active,
    .list-leave-active {
    transition: all 0.3s ease;
    }
    </style>
2. 定义DOM结构,其中,需要使用 transition-group 组件把v-for循环的列表包裹起来:
    <div id="app">
    <input type="text" v-model="txt" @keyup.enter="add">
<!-- 给 ransition-group 添加 appear 属性,实现页面刚展示出来时候,入场时候的效果 -->
<!-- 通过 为 transition-group 元素,设置 tag 属性,指定 transition-group 渲染为指定的元素,如果不指定 tag 属性,默认,渲染为 span 标签 -->
    <transition-group appear tag="ul" name="list">
    <li v-for="(item, i) in list" :key="i">{{item}}</li>
    </transition-group>
    </div>
3. 定义 VM中的结构:
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: {
    txt: '',
    list: [1, 2, 3, 4]
    },
    methods: {
    add() {
        this.list.push(this.txt);
        this.txt = '';
    }
    },
    del(i) {
        this.list.splice(i, 1)
    }
    });
列表的排序过渡
`<transition-group>` 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 `v-move` 特性,它会在元素的改变定位的过程中应用。
`v-move` 和 `v-leave-active` 结合使用,能够让列表的过渡更加平缓柔和:
    .v-move{
    transition: all 0.8s ease;
    }
    .v-leave-active{
    position: absolute;
    }
相关文章
1. [vue.js 1.x 文档](https://v1-cn.vuejs.org/)
2. [vue.js 2.x 文档](https://cn.vuejs.org/)
3. [String.prototype.padStart(maxLength, fillString)](http://www.css88.com/archives/7715)
4. [js 里面的键盘事件对应的键码](http://www.cnblogs.com/wuhua1/p/6686237.html)
5. [pagekit/vue-resource](https://github.com/pagekit/vue-resource)
6. [navicat如何导入sql文件和导出sql文件](https://jingyan.baidu.com/article/a65957f4976aad24e67f9b9b.html)
7. [贝塞尔在线生成器](http://cubic-bezier.com/#.4,-0.3,1,.33)

     定义Vue组件
什么是组件: 组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;
    组件化和模块化的不同:
模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;
     全局组件定义的三种方式
    // 如果使用 Vue.component 定义全局组件的时候,组件名称使用了 驼峰命名,则在引用组件的时候,需要把 大写的驼峰改为小写的字母,同时,两个单词之前,使用 - 链接; 如果不使用驼峰,则直接拿名称来使用即可;
    // 注意:不论是哪种方式创建出来的组件,组件的 template 属性指向的模板内容,必须有且只能有唯一的一个根元素
1. 使用 Vue.extend 配合 Vue.component 方法:
    var login = Vue.extend({
    template: '<h1>登录</h1>'  // 通过 template 属性,指定了组件要展示的HTML结构
    });
    Vue.component('login', login);
2. 直接使用 Vue.component 方法:
    Vue.component('register', {
    template: '<h1>注册</h1>'
    });
③<!-- 在 被控制的 #app 外面,使用 template 元素,定义组件的HTML模板结构  -->
    <template id="tmpl">
        <div>这是通过 template 元素,在外部定义的组件结构,这个方式,有代码的只能提示和高亮</div>
    </template>
    Vue.component('mycom3', {
            template: '#tmpl'
        })
3. 将模板字符串,定义到script标签种:
    <script id="tmpl" type="x-template">
        <div><a href="#">登录</a> | <a href="#">注册</a></div>
    </script>
    同时,需要使用 Vue.component 来定义组件:
    Vue.component('account', {
    template: '#tmpl'
    });
    > 注意: 组件中的DOM结构,有且只能有唯一的根元素(Root Element)来进行包裹!

     组件中展示数据和响应事件
1. 在组件中,`data`需要被定义为一个方法,例如:
    // 1. 组件可以有自己的 data 数据
    // 2. 组件的 data 和 实例的 data 有点不一样,实例中的 data 可以为一个对象,但是 组件中的 data 必须是一个方法
    // 3. 组件中的 data 除了必须为一个方法之外,这个方法内部,还必须返回一个对象才行;
Vue.component('account', {
    template: '#tmpl',
    data() {
        return {
            msg: '大家好!'
        }
    },
    methods:{
        login(){
            alert('点击了登录按钮');
        }
    }
    });

2. 在子组件中,如果将模板字符串,定义到了script标签中,那么,要访问子组件身上的`data`属性中的值,需要使用`this`来访问;
     【重点】为什么组件中的data属性必须定义为一个方法并返回一个对象
    1. 通过计数器案例演示
    <template id="tmpl">
        <div>
            <input type="button" value="+1" @click="increment">
            <h3>{{count}}</h3>
        </div>
    </template>
        var dataObj = { count: 0 }
        // 这是一个计数器的组件, 身上有个按钮,每当点击按钮,让 data 中的 count 值 +1
        Vue.component('counter', {
            template: '#tmpl',
            data: function () {
                // return dataObj
                return { count: 0 }
            },
            methods: {
                increment() {
                    this.count++
                }
            }
        })

     使用`components`属性定义局部子组件
    1. 组件实例定义方式:
    <script>
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {},
            components: { // 定义子组件
                account: { // account 组件
                    template: '<div><h1>这是Account组件{{name}}</h1><login></login></div>', // 在这里使用定义的子组件
                    components: { // 定义子组件的子组件
                        login: { // login 组件
                            template: "<h3>这是登录组件</h3>"
                        }
                    }
                }
            }
        });
    </script>
    2. 引用组件:
    <div id="app">
        <account></account>
    </div>

     使用`flag`标识符结合`v-if`和`v-else`切换组件
1. 页面结构:
    <div id="app">
        <input type="button" value="toggle" @click="flag=!flag">
        <my-com1 v-if="flag"></my-com1>
        <my-com2 v-else="flag"></my-com2>
    </div>
2. Vue实例定义:
    <script>
        Vue.component('myCom1', {
            template: '<h3>hello world</h3>'
        })
        Vue.component('myCom2', {
            template: '<h3>world hello</h3>'
        })
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {
                flag: true
            },
            methods: {}
        });
    </script>

     使用`:is`属性来切换不同的子组件,并添加切换动画
1. 组件实例定义方式:
    // 登录组件
    const login = Vue.extend({
    template: `<div>
        <h3>登录组件</h3>
    </div>`
    });
    Vue.component('login', login);
    // 注册组件
    const register = Vue.extend({
    template: `<div>
        <h3>注册组件</h3>
    </div>`
    });
    Vue.component('register', register);
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    data: { comName: 'login' },
    methods: {}
    });
2. 使用`component`标签,来引用组件,并通过`:is`属性来指定要加载的组件:
    <div id="app">
        <a href="#" @click.prevent="comName='login'">登录</a>
        <a href="#" @click.prevent="comName='register'">注册</a>
        <hr>
        <transition mode="out-in">      <!-- 通过 mode 属性,设置组件切换时候的 模式 -->
            <component :is="comName"></component>  <!-- component 是一个占位符, :is 属性,可以用来指定要展示的组件的名称 -->
        </transition>
    </div>
3. 添加切换样式:
    <style>
        .v-enter,
        .v-leave-to {
            opacity: 0;
            transform: translateX(30px);
        }
        .v-enter-active,
        .v-leave-active {
            position: absolute;
            transition: all 0.3s ease;
        }
        h3{
            margin: 0;
        }
    </style>

    父组件向子组件传值
1. 组件实例定义方式,注意:一定要使用`props`属性来定义父组件传递过来的数据
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {// 注意: 子组件中的 data 数据是子组件自身私有的,比如: 子组件通过 Ajax ,请求回来的数据,都可以放到 data 身上;data 上的数据,都是可读可写的;
                msg: '这是父组件中的消息'
            },
            components: {
                son: {
                    template: '<h1>这是子组件 --- {{finfo}}</h1>',
                    // 注意: 组件中的 所有 props 中的数据,都是通过 父组件传递给子组件的,  props 中的数据,都是只读的,无法重新赋值
                    props: ['finfo']
                }
            }
        });
    2. 使用`v-bind`或简化指令,将数据传递到子组件中:
    <div id="app">
        <son :finfo="msg"></son>
    </div>

     子组件向父组件传值
1. 原理:父组件将方法的引用,传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据,在调用方法的时候当作参数传递进去;
2. 父组件将方法的引用传递给子组件,其中,`getMsg`是父组件中`methods`中定义的方法名称,`func`是子组件调用传递过来方法时候的方法名称
    <son @func="getMsg"></son>
3. 子组件内部通过`this.$emit('方法名', 要传递的数据)`方式,来调用父组件中的方法,同时把数据传递给父组件使用
    <div id="app">
        <!-- 引用父组件 -->
        <son @func="getMsg"></son>
        <!-- 组件模板定义 -->
        <script type="x-template" id="son">
            <div>
                <input type="button" value="向父组件传值" @click="sendMsg" />
            </div>
        </script>
    </div>
    <script>
        // 子组件的定义方式
        Vue.component('son', {
            template: '#son', // 组件模板Id
            methods: {
                sendMsg() { // 按钮的点击事件
                    this.$emit('func', 'OK'); // 调用父组件传递过来的方法,同时把数据传递出去
                }
            }
        });
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {
                getMsg(val){ // 子组件中,通过 this.$emit() 实际调用的方法,在此进行定义
                    alert(val);
                }
            }
        });
    </script>

     评论列表案例

     使用 `this.$refs` 来获取元素和组件
    <div id="app">
        <div>
            <input type="button" value="获取元素内容" @click="getElement" />
            <!-- 使用 ref 获取元素 -->
            <h1 ref="myh1">这是一个大大的H1</h1>
            <hr>
            <!-- 使用 ref 获取子组件 -->
            <my-com ref="mycom"></my-com>
        </div>
    </div>
    <script>
        Vue.component('my-com', {
            template: '<h5>这是一个子组件</h5>',
            data() {
                return {
                    name: '子组件'
                }
            }
        });
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {
                getElement() {
                    // 通过 this.$refs 来获取元素
                    console.log(this.$refs.myh1.innerText);
                    // 通过 this.$refs 来获取组件
                    console.log(this.$refs.mycom.name);
                }
            }
        });
    </script>

     什么是路由?
1. 后端路由:对于普通的网站,所有的超链接都是URL地址,所有的URL地址都对应服务器上对应的资源;
2. 前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点:HTTP请求中不会包含hash相关的内容;所以,单页面程序中的页面跳转主要用hash实现;
3. 在单页面应用程序中,这种通过hash改变来切换页面的方式,称作前端路由(区别于后端路由);

     在 vue 中使用 vue-router
1. 导入 vue-router 组件类库:
    <!-- 1. 导入 vue-router 组件类库 -->
    <script src="./lib/vue-router-2.7.0.js"></script>
2. 使用 router-link 组件来导航
    <!-- 2. 使用 router-link 组件来导航 -->
    <router-link to="/login" tag="span">登录</router-link>
    <router-link to="/register">注册</router-link>
3. 使用 router-view 组件来显示匹配到的组件
    <!-- 3. 使用 router-view 组件来显示匹配到的组件 -->
    <router-view></router-view>
4. 创建使用`Vue.extend`创建组件
    // 4.1 使用 Vue.extend 来创建登录组件
    var login = Vue.extend({
    template: '<h1>登录组件</h1>'
    });
    // 4.2 使用 Vue.extend 来创建注册组件
    var register = Vue.extend({
    template: '<h1>注册组件</h1>'
    });
5. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则
    // 5. 创建一个路由 router 实例,通过 routers 属性来定义路由匹配规则
    // 2. 创建一个路由对象, 当 导入 vue-router 包之后,在 window 全局对象中,就有了一个 路由的构造函数,叫做 VueRouter
    var router = new VueRouter({
    routes: [
    { path: '/', redirect: '/login' },
    { path: '/login', component: login },// 注意: component 的属性值,必须是一个 组件的模板对象, 不能是 组件的引用名称;
    { path: '/register', component: register }
    ],
    linkActiveClass: 'myactive'   //设置路由高亮
    });
6. 使用 router 属性来使用路由规则
    // 6. 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
    el: '#app',
    router: router // 使用 router 属性来使用路由规则
    });

    <!-- 如果在路由中,使用 查询字符串,给路由传递参数,则 不需要修改 路由规则的 path 属性 -->
    <router-link to="/login?id=10&name=zs">登录</router-link>
    <router-view></router-view>
        var login = {
            template: '<h1>登录 --- {{ $route.query.id }} --- {{ $route.query.name }}</h1>',
            created(){ // 组件的生命周期钩子函数
                // console.log(this.$route)
                // console.log(this.$route.query.id)
            }
        }
        var router = new VueRouter({
            routes: [
                { path: '/login', component: login },
            ]
        })

     在路由规则中定义参数
1. 在规则中定义参数:
    { path: '/register/:id', component: register }
2. 通过 `this.$route.params`来获取路由中的参数:
    var register = Vue.extend({
    template: '<h1>注册组件 --- {{this.$route.params.id}}</h1>'
    });
    <router-link to="/login/12/ls">登录</router-link>
    <router-view></router-view>
        var login = {
            template: '<h1>登录 --- {{ $route.params.id }} --- {{ $route.params.name }}</h1>',
            created(){ // 组件的生命周期钩子函数
                console.log(this.$route.params.id)
            }
        }
        var router = new VueRouter({
            routes: [
                { path: '/login/:id/:name', component: login },
            ]
        })

     使用 `children` 属性实现路由嵌套
    <div id="app">
        <router-link to="/account">Account</router-link>
        <router-view></router-view>
    </div>
    <script>
        // 父路由中的组件
        const account = Vue.extend({
            template: `<div>
        这是account组件
        <router-link to="/account/login">login</router-link> |
        <router-link to="/account/register">register</router-link>
        <router-view></router-view>
      </div>`
        });
        // 子路由中的 login 组件
        const login = Vue.extend({
            template: '<div>登录组件</div>'
        });
        // 子路由中的 register 组件
        const register = Vue.extend({
            template: '<div>注册组件</div>'
        });
        // 路由实例
        var router = new VueRouter({
            routes: [
                { path: '/', redirect: '/account/login' }, // 使用 redirect 实现路由重定向
                {
                    path: '/account',
                    component: account,
                    children: [ // 通过 children 数组属性,来实现路由的嵌套
                        { path: 'login', component: login }, // 注意,子路由的开头位置,不要加 / 路径符
                        { path: 'register', component: register }
                    ]
                }
            ]
        });
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {},
            components: {
                account
            },
            router: router
        });
    </script>

     命名视图实现经典布局
1. 标签代码结构:
    <div id="app">
        <router-view></router-view>
        <div class="content">
            <router-view name="a"></router-view>
            <router-view name="b"></router-view>
        </div>
    </div>
2. JS代码:
    <script>
        var header = Vue.component('header', {
            template: '<div class="header">header</div>'
        });
        var sidebar = Vue.component('sidebar', {
            template: '<div class="sidebar">sidebar</div>'
        });
        var mainbox = Vue.component('mainbox', {
            template: '<div class="mainbox">mainbox</div>'
        });
        // 创建路由对象
        var router = new VueRouter({
            routes: [
                {
                    path: '/', components: {
                        default: header,
                        a: sidebar,
                        b: mainbox
                    }
                }
            ]
        });
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {},
            router
        });
    </script>
    3. CSS 样式:
    <style>
        .header {
            border: 1px solid red;
        }
        .content{
            display: flex;
        }
        .sidebar {
            flex: 2;
            border: 1px solid green;
            height: 500px;
        }
        .mainbox{
            flex: 8;
            border: 1px solid blue;
            height: 500px;
        }
    </style>

     `watch`属性的使用
考虑一个问题:想要实现 `名` 和 `姓` 两个文本框的内容改变,则全名的文本框中的值也跟着改变;(用以前的知识如何实现???)
    1. 监听`data`中属性的改变:
    <div id="app">
        <input type="text" v-model="firstName"> +
        <input type="text" v-model="lastName"> =
        <span>{{fullName}}</span>
    </div>
    <script>
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {
                firstName: 'jack',
                lastName: 'chen',
                fullName: 'jack - chen'
            },
            methods: {},
            watch: {
                'firstName': function (newVal, oldVal) { // 第一个参数是新数据,第二个参数是旧数据
                    this.fullName = newVal + ' - ' + this.lastName;
                },
                'lastName': function (newVal, oldVal) {
                    this.fullName = this.firstName + ' - ' + newVal;
                }
            }
        });
    </script>
2. 监听路由对象的改变:
    <div id="app">
        <router-link to="/login">登录</router-link>
        <router-link to="/register">注册</router-link>
        <router-view></router-view>
    </div>
    <script>
        var login = Vue.extend({
            template: '<h1>登录组件</h1>'
        });
        var register = Vue.extend({
            template: '<h1>注册组件</h1>'
        });
        var router = new VueRouter({
            routes: [
                { path: "/login", component: login },
                { path: "/register", component: register }
            ]
        });
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {},
            methods: {},
            router: router,
            watch: {
                '$route': function (newVal, oldVal) {
                    if (newVal.path === '/login') {
                        console.log('这是登录组件');
                    }
                }
            }
        });
    </script>

     `computed`计算属性的使用
1. 默认只有`getter`的计算属性:
    <div id="app">
        <input type="text" v-model="firstName"> +
        <input type="text" v-model="lastName"> =
        <span>{{fullName}}</span>
    </div>

    <script>
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {
                firstName: 'jack',
                lastName: 'chen'
            },
            methods: {},
            computed: { // 计算属性; 特点:当计算属性中所以来的任何一个 data 属性改变之后,都会重新触发 本计算属性 的重新计算,从而更新 fullName 的值
                fullName() {
                    return this.firstName + ' - ' + this.lastName;
                }
            }
        });
    </script>
    2. 定义有`getter`和`setter`的计算属性:
    <div id="app">
        <input type="text" v-model="firstName">
        <input type="text" v-model="lastName">
        <!-- 点击按钮重新为 计算属性 fullName 赋值 -->
        <input type="button" value="修改fullName" @click="changeName">
        <span>{{fullName}}</span>
    </div>

    <script>
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {
                firstName: 'jack',
                lastName: 'chen'
            },
            methods: {
                changeName() {
                    this.fullName = 'TOM - chen2';
                }
            },
            computed: {
                fullName: {
                    get: function () {
                        return this.firstName + ' - ' + this.lastName;
                    },
                    set: function (newVal) {
                        var parts = newVal.split(' - ');
                        this.firstName = parts[0];
                        this.lastName = parts[1];
                    }
                }
            }
        });
    </script>

     `watch`、`computed`和`methods`之间的对比
1. `computed`属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;
2. `methods`方法表示一个具体的操作,主要书写业务逻辑;
3. `watch`一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是`computed`和`methods`的结合体;

     `nrm`的安装使用
作用:提供了一些最常用的NPM包镜像地址,能够让我们快速的切换安装包时候的服务器地址;
什么是镜像:原来包刚一开始是只存在于国外的NPM服务器,但是由于网络原因,经常访问不到,这时候,我们可以在国内,创建一个和官网完全一样的NPM服务器,只不过,数据都是从人家那里拿过来的,除此之外,使用方式完全一样;
    1. 运行`npm i nrm -g`全局安装`nrm`包;
    2. 使用`nrm ls`查看当前所有可用的镜像源地址以及当前所使用的镜像源地址;
    3. 使用`nrm use npm`或`nrm use taobao`切换不同的镜像源地址;

    Webpack:
    在网页中会引用哪些常见的静态资源?
    + JS
    - .js  .jsx  .coffee  .ts(TypeScript  类 C# 语言)
    + CSS
    - .css  .less   .sass  .scss
    + Images
    - .jpg   .png   .gif   .bmp   .svg
    + 字体文件(Fonts)
    - .svg   .ttf   .eot   .woff   .woff2
    + 模板文件
    - .ejs   .jade  .vue【这是在webpack中定义组件的方式,推荐这么用】

     网页中引入的静态资源多了以后有什么问题???
1. 网页加载速度慢, 因为 我们要发起很多的二次请求;
2. 要处理错综复杂的依赖关系

     如何解决上述两个问题
1. 合并、压缩、精灵图、图片的Base64编码
2. 可以使用之前学过的requireJS、也可以使用webpack可以解决各个包之间的复杂依赖关系;

     什么是webpack?
webpack 是前端的一个项目构建工具,它是基于 Node.js 开发出来的一个前端工具;

     如何完美实现上述的2种解决方案
    1. 使用Gulp, 是基于 task 任务的;
    2. 使用Webpack, 是基于整个项目进行构建的;
    + 借助于webpack这个前端自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆等诸多功能。
    + 根据官网的图片介绍webpack打包的过程
    + [webpack官网](http://webpack.github.io/)

     webpack安装的两种方式
    1. 运行`npm i webpack -g`全局安装webpack,这样就能在全局使用webpack的命令
    2. 在项目根目录中运行`npm i webpack --save-dev`安装到项目依赖中

     初步使用webpack打包构建列表隔行变色案例
1. 运行`npm init`初始化项目,使用npm管理项目中的依赖包
2. 创建项目基本的目录结构
3. 使用`cnpm i jquery --save`安装jquery类库
4. 创建`main.js`并书写各行变色的代码逻辑:
    // 导入jquery类库
    import $ from 'jquery'
    $('#list li:even').css('backgroundColor','lightblue');    // 设置偶数行背景色,索引从0开始,0是偶数
    $('#list li:odd').css('backgroundColor','pink');    // 设置奇数行背景色
5. 直接在页面上引用`main.js`会报错,因为浏览器不认识`import`这种高级的JS语法,需要使用webpack进行处理,webpack默认会把这种高级的语法转换为低级的浏览器能识别的语法;
6. 运行`webpack 入口文件路径 输出文件路径`对`main.js`进行处理:
    webpack src/js/main.js dist/bundle.js

     使用webpack的配置文件简化打包时候的命令
1. 在项目根目录中创建`webpack.config.js`
2. 由于运行webpack命令的时候,webpack需要指定入口文件和输出文件的路径,所以,我们需要在`webpack.config.js`中配置这两个路径:
    // 导入处理路径的模块
    var path = require('path');
    // 导出一个配置对象,将来webpack在启动的时候,会默认来查找webpack.config.js,并读取这个文件中导出的配置对象,来进行打包处理
    module.exports = {
    entry: path.resolve(__dirname, 'src/js/main.js'), // 项目入口文件
    output: { // 配置输出选项
    path: path.resolve(__dirname, 'dist'), // 配置输出的路径
    filename: 'bundle.js' // 配置输出的文件名
    }
    }

     实现webpack的实时打包构建
1. 由于每次重新修改代码之后,都需要手动运行webpack打包的命令,比较麻烦,所以使用`webpack-dev-server`来实现代码实时打包编译,当修改代码之后,会自动进行打包构建。
2. 运行`cnpm i webpack-dev-server --save-dev`安装到开发依赖
3. 安装完成之后,在命令行直接运行`webpack-dev-server`来进行打包,发现报错,此时需要借助于`package.json`文件中的指令,来进行运行`webpack-dev-server`命令,在`scripts`节点下新增`"dev": "webpack-dev-server"`指令,发现可以进行实时打包,但是dist目录下并没有生成`bundle.js`文件,这是因为`webpack-dev-server`将打包好的文件放在了内存中
    + 把`bundle.js`放在内存中的好处是:由于需要实时打包编译,所以放在内存中速度会非常快
    + 这个时候访问webpack-dev-server启动的`http://localhost:8080/`网站,发现是一个文件夹的面板,需要点击到src目录下,才能打开我们的index首页,此时引用不到bundle.js文件,需要修改index.html中script的src属性为:`<script src="../bundle.js"></script>`
    + 为了能在访问`http://localhost:8080/`的时候直接访问到index首页,可以使用`--contentBase src`指令来修改dev指令,指定启动的根目录:
    "dev": "webpack-dev-server --contentBase src"
    同时修改index页面中script的src属性为`<script src="bundle.js"></script>`

     使用`html-webpack-plugin`插件配置启动页面
由于使用`--contentBase`指令的过程比较繁琐,需要指定启动的目录,同时还需要修改index.html中script标签的src属性,所以推荐大家使用`html-webpack-plugin`插件配置启动页面.
1. 运行`cnpm i html-webpack-plugin --save-dev`安装到开发依赖
2. 修改`webpack.config.js`配置文件如下:
    // 导入处理路径的模块
    var path = require('path');
    // 导入自动生成HTMl文件的插件
    var htmlWebpackPlugin = require('html-webpack-plugin');
    module.exports = {
    entry: path.resolve(__dirname, 'src/js/main.js'), // 项目入口文件
    output: { // 配置输出选项
    path: path.resolve(__dirname, 'dist'), // 配置输出的路径
    filename: 'bundle.js' // 配置输出的文件名
    },
    plugins:[ // 添加plugins节点配置插件
    new htmlWebpackPlugin({
    template:path.resolve(__dirname, 'src/index.html'),//模板路径
    filename:'index.html'//自动生成的HTML文件的名称
    })
    ]
    }
3. 修改`package.json`中`script`节点中的dev指令如下:
    "dev": "webpack-dev-server"
4. 将index.html中script标签注释掉,因为`html-webpack-plugin`插件会自动把bundle.js注入到index.html页面中!

     实现自动打开浏览器、热更新和配置浏览器的默认端口号
**注意:热更新在JS中表现的不明显,可以从一会儿要讲到的CSS身上进行介绍说明!**
     方式1:
修改`package.json`的script节点如下,其中`--open`表示自动打开浏览器,`--port 4321`表示打开的端口号为4321,`--hot`表示启用浏览器热更新:
    "dev": "webpack-dev-server --hot --port 4321 --open"
     方式2:
1. 修改`webpack.config.js`文件,新增`devServer`节点如下:
    devServer:{
    hot:true,
    open:true,
    port:4321
    }
2. 在头部引入`webpack`模块:
var webpack = require('webpack');
3. 在`plugins`节点下新增:
new webpack.HotModuleReplacementPlugin()

     使用webpack打包css文件
1. 运行`cnpm i style-loader css-loader --save-dev`
2. 修改`webpack.config.js`这个配置文件:
    module: { // 用来配置第三方loader模块的
    rules: [ // 文件的匹配规则
    { test: /\.css$/, use: ['style-loader', 'css-loader'] }//处理css文件的规则
    ]
    }
3. 注意:`use`表示使用哪些模块来处理`test`所匹配到的文件;`use`中相关loader模块的调用顺序是从后向前调用的;

     使用webpack打包less文件
1. 运行`cnpm i less-loader less -D`
2. 修改`webpack.config.js`这个配置文件:
    { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },

     使用webpack打包sass文件
1. 运行`cnpm i sass-loader node-sass --save-dev`
2. 在`webpack.config.js`中添加处理sass文件的loader模块:
    { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }

     使用webpack处理css中的路径
1. 运行`cnpm i url-loader file-loader --save-dev`
2. 在`webpack.config.js`中添加处理url路径的loader模块:
    { test: /\.(png|jpg|gif)$/, use: 'url-loader' }
3. 可以通过`limit`指定进行base64编码的图片大小;只有小于指定字节(byte)的图片才会进行base64编码:
{ test: /\.(png|jpg|gif)$/, use: 'url-loader?limit=43960' },

     使用babel处理高级JS语法
1. 运行`cnpm i babel-core babel-loader babel-plugin-transform-runtime --save-dev`安装babel的相关loader包
2. 运行`cnpm i babel-preset-es2015 babel-preset-stage-0 --save-dev`安装babel转换的语法
3. 在`webpack.config.js`中添加相关loader模块,其中需要注意的是,一定要把`node_modules`文件夹添加到排除项:
    { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }
4. 在项目根目录中添加`.babelrc`文件,并修改这个配置文件如下:
    {
    "presets":["es2015", "stage-0"],
    "plugins":["transform-runtime"]
    }
5. 注意:语法插件`babel-preset-es2015`可以更新为`babel-preset-env`,它包含了所有的ES相关的语法;**

相关文章
[babel-preset-env:你需要的唯一Babel插件](https://segmentfault.com/p/1210000008466178)
[Runtime transform 运行时编译es6](https://segmentfault.com/a/1190000009065987)

    webpack.config.js:
    // 由于 webpack 是基于Node进行构建的,所有,webpack的配置文件中,任何合法的Node代码都是支持的
    var path = require('path')
    // 在内存中,根据指定的模板页面,生成一份内存中的首页,同时自动把打包好的bundle注入到页面底部
    // 如果要配置插件,需要在导出的对象中,挂载一个 plugins 节点
    var htmlWebpackPlugin = require('html-webpack-plugin')
    // 当以命令行形式运行 webpack 或 webpack-dev-server 的时候,工具会发现,我们并没有提供 要打包 的文件的 入口 和 出口文件,此时,他会检查项目根目录中的配置文件,并读取这个文件,就拿到了导出的这个 配置对象,然后根据这个对象,进行打包构建
    module.exports = {
    entry: path.join(__dirname, './src/main.js'), // 入口文件
    output: { // 指定输出选项
    path: path.join(__dirname, './dist'), // 输出路径
    filename: 'bundle.js' // 指定输出文件的名称
    },
    plugins: [ // 所有webpack  插件的配置节点
    new htmlWebpackPlugin({
    template: path.join(__dirname, './src/index.html'), // 指定模板文件路径
    filename: 'index.html' // 设置生成的内存页面的名称
    })
    ],
    module: { // 配置所有第三方loader 模块的
    rules: [ // 第三方模块的匹配规则
    { test: /\.css$/, use: ['style-loader', 'css-loader'] }, // 处理 CSS 文件的 loader
    { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] }, // 处理 less 文件的 loader
    { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }, // 处理 scss 文件的 loader
    { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: 'url-loader?limit=7631&name=[hash:8]-[name].[ext]' }, // 处理 图片路径的 loader
    // limit 给定的值,是图片的大小,单位是 byte, 如果我们引用的 图片,大于或等于给定的 limit值,则不会被转为base64格式的字符串, 如果 图片小于给定的 limit 值,则会被转为 base64的字符串
    { test: /\.(ttf|eot|svg|woff|woff2)$/, use: 'url-loader' }, // 处理 字体文件的 loader
    { test: /\.js$/, use: 'babel-loader', exclude: /node_modules/ }, // 配置 Babel 来转换高级的ES语法
    ]
    }
    }

     在webpack中配置.vue组件页面的解析
1. 运行`cnpm i vue -S`将vue安装为运行依赖;
2. 运行`cnpm i vue-loader vue-template-compiler -D`将解析转换vue的包安装为开发依赖;
3. 运行`cnpm i style-loader css-loader -D`将解析转换CSS的包安装为开发依赖,因为.vue文件中会写CSS样式;
4. 在`webpack.config.js`中,添加如下`module`规则:
module: {
    rules: [
    { test: /\.css$/, use: ['style-loader', 'css-loader'] },
    { test: /\.vue$/, use: 'vue-loader' }
    ]
    }
5. 创建`App.js`组件页面:
<template>
        <!-- 注意:在 .vue 的组件中,template 中必须有且只有唯一的根元素进行包裹,一般都用 div 当作唯一的根元素 -->
        <div>
            <h1>这是APP组件 - {{msg}}</h1>
            <h3>我是h3</h3>
        </div>
    </template>
<script>
        // 注意:在 .vue 的组件中,通过 script 标签来定义组件的行为,需要使用 ES6 中提供的 export default 方式,导出一个vue实例对象
export default {
            data() {
                return {
                    msg: 'OK'
                }
            }
        }
    </script>
<style scoped>
        h1 {
            color: red;
        }
    </style>
6. 创建`main.js`入口文件:
// 导入 Vue 组件
import Vue from 'vue'
// 导入 App组件
    import App from './components/App.vue'
// 创建一个 Vue 实例,使用 render 函数,渲染指定的组件
    var vm = new Vue({
    el: '#app',
    render: c => c(App)
    });

     在使用webpack构建的Vue项目中使用模板对象?
1. 在`webpack.config.js`中添加`resolve`属性:
resolve: {
    alias: {
    'vue$': 'vue/dist/vue.esm.js'
    }
    }

     ES6中语法使用总结
1. 使用 `export default` 和 `export` 导出模块中的成员; 对应ES5中的 `module.exports` 和 `export`
2. 使用 `import ** from **` 和 `import '路径'` 还有 `import {a, b} from '模块标识'` 导入其他模块
3. 使用箭头函数:`(a, b)=> { return a-b; }`
// 注意: export default 向外暴露的成员,可以使用任意的变量来接收
// 注意: 在一个模块中,export default 只允许向外暴露1次
// 注意: 在一个模块中,可以同时使用 export default 和 export 向外暴露成员
export var content = '哈哈哈'
// 注意: 使用 export 向外暴露的成员,只能使用 { } 的形式来接收,这种形式,叫做 【按需导出】
// 注意: export 可以向外暴露多个成员, 同时,如果某些成员,我们在 import 的时候,不需要,则可以 不在 {}  中定义
// 注意: 使用 export 导出的成员,必须严格按照 导出时候的名称,来使用  {}  按需接收;
// 注意: 使用 export 导出的成员,如果 就想 换个 名称来接收,可以使用 as 来起别名;

    在vue组件页面中,集成vue-router路由模块
    [vue-router官网](https://router.vuejs.org/)
1. 导入路由模块:
    import VueRouter from 'vue-router'
2. 安装路由模块:
    Vue.use(VueRouter);
3. 导入需要展示的组件:
    import login from './components/account/login.vue'
    import register from './components/account/register.vue'
4. 创建路由对象:
    var router = new VueRouter({
    routes: [
    { path: '/', redirect: '/login' },
    { path: '/login', component: login },
    { path: '/register', component: register }
    ]
    });
5. 将路由对象,挂载到 Vue 实例上:
    var vm = new Vue({
    el: '#app',
    // render: c => { return c(App) }
    render(c) {
    return c(App);
    },
    router // 将路由对象,挂载到 Vue 实例上
    });
6. 改造App.vue组件,在 template 中,添加`router-link`和`router-view`:
    <router-link to="/login">登录</router-link>
    <router-link to="/register">注册</router-link>
    <router-view></router-view>

     组件中的css作用域问题
<style lang="scss" scoped>
    /* 普通的 style 标签只支持 普通的 样式,如果想要启用 scss 或 less ,需要为 style 元素,设置 lang 属性 */
    // 只要 咱们的 style 标签, 是在 .vue 组件中定义的,那么,推荐都为 style 开启 scoped 属性
</style>
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值