数据驱动:将数据和视图相关联,当数据发生变化时,可以自动更新视图。

单页应用 VS 多用应用

1) 多页应用:
优点:首屏时间快,SEO效果好
缺点:页面切换慢

2) 单页应用:
优点:页面切换快
缺点:首屏时间稍慢,SEO差

计算属性

依赖条件不变的前提下,第一次计算的结果会被缓存起来,后面再使用时直接使用第一次计算的结果。
计算属性当其依赖属性的值发生变化时,这个属性的值会自动更新,与之相关的DOM部分也会同步自动更新。
1)计算属性两种形式

  1. 函数形式
  2. 对象形式,具有get()和set()方法

    computed: {
         url: {
             get() {
                 return `${this.protocol}//${this.host}`;
             },
             set(val) {
                 const tmp = val.split('//');
                 if (tmp.length > 1) {
                     this.protocol = tmp[0];
                     this.host = tmp[1];
                 }
             }
         }
    }

2)计算属性利用闭包传参

computed: {
    url() {
         return function(hash) {
               return `${this.protocol}//${this.host}#${hash}`;
         }
    }
}
侦听器

1.函数形式
2.对象形式

  watch: {
        location: {
            immediate: true,  // 在初始化时执行
            deep: true, // 递归(深度)遍历
            handler(newVal, oldVal) {
                this.url = `${newVal.protocol}//${newVal.host}`;
            }
        }
    }
computed和watch的区别

计算属性和侦听器的实质都是Watcher的实例。
computed基于响应式依赖进行缓存,只在相关响应式依赖发生改变时才会重新求值。
在依赖不变的情况下,计算属性使用缓存的上次的计算结果,而不会重复执行
watch当需要在数据变化时执行异步或开销较大的操作时,使用watch。

v-if和v-show

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if动态增删节点(DOM),v-show控制CSS属性(display)进行切换;
v-if是惰性的,初始条件为false时不会参与模板编译;v-show会始终参与渲染;
v-if会缓存编译结果,当v-if条件为true时,vue会编译为虚拟DOM并缓存,待下一次条件渲染直接使用。
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

v-if中的key

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
给元素添加一个具有唯一值的 key attribute,表示元素是完全独立的,不要复用它们。

<template v-if="loginType === 'username'">
    <label>Username</label>
    <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
    <label>Email</label>
    <input placeholder="Enter your email address" key="email-input">
</template>
v-if和v-for

同一元素上,v-for优先级高于v-if,即先执行遍历,再执行判断。
若条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项。

v-for中的key值的作用:作为同级元素的标识,提高更新效率。
key值的作用先要了解vue更新DOM的机制,即Diff算法。该算法有三个假设, 其中一个是同级元素可以通过唯一id值区分,而key值的作用即作为这个id值,使更新(包括插入,删除,新增,交换等等)时,vue能够甄别到底是哪些虚拟DOM发生了变化,而不是笼统的批量更新。

使用 of 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

<div v-for="item of items" :key="item.id"></div>
变异方法和非变异方法

变异方法:Vue将被侦听的数组的变更方法进行了改写,会变更原始数组

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

非变异方法:不会变更原始数组,而总是返回一个新数组

filter()、concat() 和 slice()
当使用非变更方法时,可以用新数组替换旧数组
事件
<button @click="add(step, $event)"></button>

methods: {
    add(step, event) {
        this.count += step;
        console.log(event);  // 访问原生事件对象
    }
}

事件修饰符:

.stop // 阻止事件冒泡
.prevent  // 阻止浏览器默认行为
.self  // 当触发事件的对象(event.target)是自身才执行
.capture  // 监听器采用事件捕获模型
.once // 监听器触发一次后移除
.passive // 告诉浏览器该监听器是“主动的”,不会阻止浏览器默认行为,让滑动更顺畅。尤其能够提升移动端的性能。
数据双向绑定(v-model)

v-model指令用来在input、select、text、checkbox、radio等表单控件元素上创建双向数据绑定。
v-model是语法糖,在用户输入事件中更新数据。
v-model添加.lazy修饰符将监听change事件
.native 修饰符用于在一个组件的根元素上监听原生事件 // 给组件绑定原生事件
vue-validator表单校验插件

组件化开发

目标:为了可重用性高,减少重复性的开发,即一处开发,多处使用。
单向数据流:父组件向子组件传值

1) is属性, 在table、ul、ol、select中使用
有些 HTML 元素,诸如 <ul>、<ol>、<table> 和 <select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。

<table>
    <tbody>
        <tr is="row"></tr>  // row组件
    </tbody>
</table>

2) 子组件中data必须是一个函数,且返回一个对象

data() {
    return {
        list: []
    }
}

vue组件可能会有很多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染。

  • 根实例对象data可以是对象也可以是函数(根实例是单例),不会产生数据污染情况。
  • 组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。采用函数的形式,initData时会将其作为工厂函数都会返回全新data对象。

3) 动态组件
在不同组件之间进行动态切换

<component :is="type"></component> // type为组件名变量
组件间通信

1) 父子组件通信:props和$emit
2) 兄弟组件通信:事件总线(EventBus)
$emit触发事件,$on监听,$off()用来删除事件监听器
非父子组件间传值( Bus | 总线 | 发布订阅模式 | 观察者模式)

// eventBus.js
Vue.prototype.eventBus = new Vue();

// 组件A
this.eventBus.$emit('send', text); // 触发
// 组件B
this.eventBus.$on('send', (text) => { // 监听
    this.text = text;
});

3) Vuex:状态管理工具
集中式状态管理,对全局共享状态数据进行管理和操作。使用场景:
1)多个view依赖同一个状态(state)
2)多个view要改变同一个状态(state)
store是管理器

state:状态
mutations:更改Vuex的store中的状态的唯一方法,同步
actions:异步
getters:类似于计算属性

组件不允许直接修改store里的state,用commit函数提交mutation方法来修改数据。

插槽

<slot> 作为我们想要插入内容的占位符
1) slot

<div>
    <h1>title</h1>
    <slot></slot>
    <p>说点啥</p>
</div>

2) 具名插槽

// page组件
<div class="page">
    <slot name="header"></slot>
    <slot>default content</slot>
    <slot name="footer"></slot>
</div>

// 使用page组件
<page>
    <page-header  slot="header"></page-header>
    <main class="page__main">page content……</main>
    <page-footer slot="footer"></page-footer>
</page>

3) 作用域插槽(带数据的插槽) // 常用于表格
有时让插槽内容能够访问子组件中才有的数据是很有用的。当一个组件被用来渲染一个项目数组时,这是一个常见的情况,我们希望能够自定义每个项目的渲染方式。

<slot :list="list"></slot>
// 使用作用域插槽
<template slot-scope="scope">
    <p v-for="item of scope.list" :key="item.id">{{item.name}}</p>
</template>

可参考iView组件库

自定义指令

完整的指令:v-name:foo.a="expression"
其中name: 指令名称,foo:参数,a:修饰符,expression:表达式。
1) 全局注册

  1. 回调函数形式

    Vue.directive('test', function(el, bindings, vnode) {
     console.log(el, bindings);
    })
  2. 对象形式 -- 钩子函数

    Vue.directive('test', {
     bind(el, bindings, vnode) { // 初始化数据
         console.log('bind')
     },
     inserted(el, bindings, vnode) { // 获取父级的DOM元素
         console.log('inserted')
     },
     update(el, bindings, vnode) { // 监听绑定的组件的变化
         console.log('update');
     },
     componentUpdated(el, bindings, vnode) { // 监听绑定的组件的变化
         console.log('componentUpdated');
     },
     unbind(el, bindings, vnode) { // 组件销毁时触发
         console.log('unbind');
     },
    })

    2) 局部注册

    directives: {
     test: {
         // 同上
     }
    }

    3) 长按指令

    // 长按指令定义
    Vue.directive('long-press', function(el, bindings) {
     let timer = null;
     const value = bindings.value;
     const duration = value.duration || 700; // 长按时间
     const fn = value.callback; // 回调函数
     const _this = this;
     
     el.addEventListener('touchstart', run);
     el.addEventListener('touchend', stop);
     el.addEventListener('touchmove', stop);
     
     function run() {
         if (timer === null) {
             timer = setTimeout(() => {
                 fn && fn.call(_this, el, value);
                 clearTimeout(timer);
             }, duration);
         }
     }
     
     function stop() { // 取消 -- 清除定时器
         clearTimeout(timer);
         timer = null;
     }
    })
    // 长按指令使用
    // 用value作为对象的方式给long-press指令传参
    <ul>
     <li
         v-for="item of list"
         :key="item.id"
         v-long-press="{ 
            callback: handleLongPress,
             item: item,
             duration: 500
         }"
     ></li>
    </ul>
    
    handleLongPress(el, value) {
     console.log(value);
     // do sth
    }
        
生命周期

一个事物从诞生,到发展,持续,直至最后销毁的过程,叫它的生命周期。
实例创建 => 挂载 => 数据变化 => 更新视图 => 销毁
1) $nextTick([callback]) 设置一个回调,在下次DOM更新之后执行数据变化后,视图不会立即更新。
2) $forceUpdate() 使实例重新渲染,更新视图。
3) 生命周期钩子:

beforeCreate:组件实例还未创建
created:实例创建完成,初始化数据($data),完成配置,常用于异步数据获取
beforeMount:render函数渲染,未执行渲染、更新,dom未创建
mounted:挂载完成,dom已创建,可用于获取访问数据和dom元素。应用:eg: 上报页面访问人数
beforeUpdate时,虚拟DOM已经生成
updated:该实例更新完成  // 若希望子组件等更新完成,在$nextTick([callback])中处理
beforeDestroy:销毁之前,可用于一些定时器或订阅的取消
destroyed:销毁完成
渲染

1) el挂载
2) template模板
3) 自定义render

无论哪种渲染写法在执行$mount时都是将生成render函数。
Render函数调用_render => 虚拟DOM调用_update => 真实DOM

模板编译的过程:
parse生成AST语法树,optimize优化,generate生成render函数code。 // AST抽象语法树
parse的作用是把模板内容转化成AST语法树,方便程序分析;
optimize的作用是优化语法树,通过标记静态节点的方式;
generate生成render函数的code,是字符串类型。
模板编译是为了生成render函数,并不会执行render函数,render生成的是虚拟DOM。

DOM更新:
渲染watcher的回调函数updateComponent实现了DOM更新
_render生成虚拟DOM
_update通过比较虚拟DOM更新真实DOM
数据变化时才可能触发渲染watcher执行更新

Vue-Router
  1. 改变URL不向服务器发送请求
  2. 前端监听URL变化
  3. 解析URL并执行相应操作

三种模式:hash、history、abstract
abstract:适用于所有JavaScript环境,例如服务器端使用Node.js。若没有浏览器API,路由器将自动被强制进入此模式。

1) hash
window.location.assign()
window.location.replace()
window.onhashchange // hashchange事件

2) history
window.history.pushState()
window.history.replaceState()
window.onpopstate // pushState和replaceState不会触发popstate事件

history.pushState/history.replaceState不会触发popstate事件,只改变url和历史记录
history模式采用的是真实路由,需要服务器重定向到主页,否则刷新浏览器还是会向服务器发送请求,报404

3) hash与history的区别
hash:丑(#),hash会占用锚点功能,兼容性较好
history:路由与后端无异,IE10及以上,需要后端支持

4)Router API
$router方法:

$router.push()
$router.replace()
$router.go()
$router.forward()
$router.back()

router.push/router.replace在不同模式下调用的底层API不同,一个改变真实url路径,一个只改变hash。

$route参数:

$route.params  -- 动态路由匹配的参数
$route.query -- 查询参数
$route.hash -- url的hash值
$route.fullPath -- url全路径
$route.meta -- 与页面关联的元信息

5)parse location

parseLocation(location) {
    const { path, query, hash } = location;
    return `${path}?${parseQuery(query)}#${hash}`;
}

parseQuery(query) {
    return Object.keys(query).map(key => {
        return `${key}=${query[key]}`;
    }).join('&');
}

地瓜哥
16 声望0 粉丝

keep learning