Vue笔记

响应式原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
var data = { name:"poetries "}
observe(data)
function update(value) {
document.querySelector('div').innerText = value
}
new Watcher(data,'name',update)

data.name = 'yyy'


function observe(obj) {
if(!obj||typeof obj!=='object'){
return
}
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}

function defineReactive(obj,key,val) {
observe(val)
let dep = new Dep()
Object.defineProperty(obj,key,{
enumerable:true,

configurable:true,

get:function reactiveGetter() {
console.log('get value')
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},

set:function reactiveSetter(newValue) {
console.log('change value')
val = newValue
dep.notify()
}
})
}


class Dep{
constructor(){
this.subs=[]
}
addSub(sub){
this.subs.push(sub)
}
notify(){
this.subs.forEach(sub=>{
sub.update()
})
}
}
Dep.target=null

class Watcher{
constructor(obj,key,cb){

Dep.target =this
this.cb=cb
this.obj=obj
this.key=key
this.value = obj[key]
Dep.target = null

}
update(){
this.value = this.obj[this.key]

this.cb(this.value)
}

}

//Vue有几个问题,Object.defineProperty不能拦截数组操作
//新增对象属性不触发组件渲染的问题
//Vue通过函数重写的方式解决了这个问题。

export function set(target:Array<any>|Object,key:any,val:any):any{
if(Array.isArray(target)&&isValidArrayIndex(key)){
//调用splice函数派发更新
target.length = Math.max(target.length,key)
target.splice(key,1,val)
return val

}
if(key in target &&!(key in Object.prototype)){
target[key] = val
return val
}

const ob =(target:any).__ob__

if(!ob){
target[key] = val
return val
}

defineReactive(ob.value,key,value)

ob.dep.notify()

return val

}
//解决Array的一些方法无法触发响应式的问题。
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]

methodsToPatch.forEach(function (method) {

const original = arrayProto[method]

def(arrayMethods,method,function mutator(...args){

const result = original.apply(this,args)
const ob =this.__ob__
let inserted

switch (method) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
}
if(inserted) ob.observeArray(inserted)
ob.dep.notify()
return result
})
})

router-link用法

  1. router-link默认渲染为

    href
    1
    2
    3
    4
    5
    6
    ```html
    <router-link tag="a" :to="'/city/' + guessCityid" class="guess_city">
    <span>{{guessCity}}</span>
    </router-link>
    渲染为
    <a data-v-3ea254f4="" href="#/city/2" class="guess_city"><span data-v-3ea254f4="">杭州</span></a>

  2. router-link tag属性可设置为li,span等等,渲染为li,span

  3. :to内有path, query参数 path负责切换路由,query为路由参数

1
2
3
4
<router-link :to="{path: '/food', query: {geohash, title: foodItem.title, restaurant_category_id: getCategoryId(foodItem.link)}}" v-for="foodItem in item" :key="foodItem.id" class="link_to_food">
</router-link>
渲染为
<a data-v-070ab150="" href="#/food?geohash=30.26276,120.21403&amp;title=%E5%95%86%E8%B6%85%E4%BE%BF%E5%88%A9&amp;restaurant_category_id=252" class="link_to_food"></a>

Vue项目中props,data()中数据的区别

  • props用于存放模板,其他函数运行时需要的变量;接住父模板传递过来的参数;也可以是模板运行时的函数名称。
  • data(){ }存放响应式的变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    export default {
    data(){
    return {
    offset: 0, // 批次加载店铺列表,每次加载20个 limit = 20
    shopListArr:[], // 店铺列表数据
    preventRepeatReuqest: false, //到达底部加载数据,防止重复加载
    showBackStatus: false, //显示返回顶部按钮
    showLoading: true, //显示加载动画
    touchend: false, //没有更多数据
    imgBaseUrl,
    }
    },
    mounted(){
    this.initData();
    },
    components: {
    loading,
    ratingStar,
    },
    props: ['restaurantCategoryId', 'restaurantCategoryIds', 'sortByType', 'deliveryMode', 'supportIds', 'confirmSelect', 'geohash'],
    mixins: [loadMore, getImgPath],
    computed: {
    ...mapState([
    'latitude','longitude'
    ]),
    },
    updated(){
    // console.log(this.supportIds, this.sortByType)
    },
    methods: {
    async initData(){
    //获取数据
    let res = await shopList(this.latitude, this.longitude, this.offset, this.restaurantCategoryId);
    ....
    },
    //到达底部加载更多数据
    async loaderMore(){
    ...
    },
    //返回顶部
    backTop(){
    ...
    },
    //监听父级传来的数据发生变化时,触发此函数重新根据属性值获取数据
    async listenPropChange(){
    ...
    },

    },
    watch: {
    //监听父级传来的restaurantCategoryIds,当值发生变化的时候重新获取餐馆数据,作用于排序和筛选
    restaurantCategoryIds: function (value){
    this.listenPropChange();
    },
    //监听父级传来的排序方式
    sortByType: function (value){
    this.listenPropChange();
    },
    //监听父级的确认按钮是否被点击,并且返回一个自定义事件通知父级,已经接收到数据,此时父级才可以清除已选状态
    confirmSelect: function (value){
    this.listenPropChange();
    }
    }
    }

Vue项目中模板传递数据,条件判断写法

Head.vue 抽象基础模板

  • 对外暴露的参数写法均为驼峰式 goBack headTitle signinUp
  • slot name = ‘logo/search/edit’ 占位卡槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<header id='head_top'>
<slot name='logo'></slot>
<slot name='search'></slot>
<section class="head_goback" v-if="goBack" @click="$router.go(-1)">
</section>
<router-link :to="userInfo? '/profile':'/login'" v-if='signinUp' class="head_login">
<svg class="user_avatar" v-if="userInfo">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#user"></use>
</svg>
<span class="login_span" v-else>登录|注册</span>
</router-link>
<section class="title_head ellipsis" v-if="headTitle">
<span class="title_text">{{headTitle}}</span>
</section>
<slot name="edit"></slot>
</header>
</template>

Home.vue

* 将head.vue作为基础模板引入

  • 传递参数用 连接字符命名 signin-up对应参数signinUp
  • slot
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    ```html
    <template>
    <div>
    <head-top signin-up='home'>
    <span slot='logo' class="head_logo" @click="reload"><!--ele.me-->首页</span>
    <!--传入公共模板head.vue的占位卡槽-->
    </head-top>
    ...
    </div>
    </template>
    <script>
    import headTop from 'head.vue'
    export default {
    data(){ }
    mounted(){ }
    components:{
    headTop //模板中可以使用head-top载入
    },
    methods:{
    //点击图标刷新页面
    reload(){
    window.location.reload();
    }
    }
    }
    </script>
  • 几种数据展示方式

  1. head-top 导入的组件
  2. :head-title -> v-bind:head-title go-back 子模板接口
  3. cityname placelist nextpage 当前模板变量小写名称,函数名称
  4. slot=”changecity”卡槽名称
  5. 不在””内,对外暴露的模板参数
  6. class=”title_head ellipsis” 模板类名
  7. v-for=”(item, index) in placelist” each数据操作
  8. @click 模板绑定方法
  9. :key=”index” ->v-bind:key=”index” 性能优化方法
  10. 不在””内,当前对象的字面量值
  11. :class=”{choose_type:sortBy == ‘food’}” class判断逻辑

一小段代码就能弄出11条指令的不同用法,Vue其实也不方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

parent1.vue
<head-top :head-title="cityname" go-back='true'>
<router-link to="/home" slot="changecity" class="change_city">切换城市</router-link>
</head-top>

<li v-for="(item, index) in placelist" @click='nextpage(index, item.geohash)' :key="index">
<div class="sort_item" :class="{choose_type:sortBy == 'food'}" >
<h4 class="pois_name ellipsis">{{item.name}}</h4>
<p class="pois_address ellipsis">{{item.address}}</p>
</li>

basic.vue
<section class="title_head ellipsis" v-if="headTitle">
<span class="title_text">{{headTitle}}</span>
</section>

webpack中利用require.ensure()实现按需加载

  1. 空数组作为参数
1
2
3
require.ensure([], function(require){
require('./a.js');
});

以上代码保证了拆分点被创建,而且 a.js 被 webpack 分开打包。

  1. 依赖作为参数
    1
    2
    3
    require.ensure(['./a.js'], function(require) {
    require('./b.js');
    });

  上面代码, a.js 和 b.js 都被打包到一起,而且从主文件束中拆分出来。但只有 b.js 的内容被执行。a.js 的内容仅仅是可被使用,但并没有被输出。

  想去执行 a.js,我们需要异步地引用它,如 require(‘./a.js’),让它的 JavaScritp 被执行。

  1. 单独打包成自己写的名字配置
      需要配置chunkFilename,和publicPath。publicPath是按需加载单独打包出来的chunk是以publicPath会基准来存放的,chunkFilename:[name].js这样才会最终生成正确的路径和名字
    1
    2
    3
    4
    5
    6
    7
    8
    module.exports={
    entry:'./js/entry.js',
    output:{
    path:path.resolve(__dirname,"./dist"),
    filename:'js/a.bundle.js',
    publicPath:"./",
    chunkFilename:'js/[name].js'
    }

所以router/index.js 修改为懒加载组件:

1
2
3
4
const Province = r => require.ensure([], () => r(require('@/components/Province.vue')), 'chunkname1')
const Segment = r => require.ensure([], () => r(require('@/components/Segment.vue')), 'chunkname2')
const Loading = r => require.ensure([], () => r(require('@/components/Loading.vue')), 'chunkname3')
const User = r => require.ensure([], () => r(require('@/components/User.vue')), 'chunkname3')

  根据 chunkame的不同, 上面的四个组件, 将会被分成3个块打包,最终打包之后与组件相关的js文件会分为3个 (除了app.js,manifest.js, vendor.js)


VueRouter代码详解

main.js
import routes from ‘./router/router’
console.log(routes)

/router/router.js
import App from ‘../App’
console.log(App);


Vuex 用Object.assign 修改数据

src
1
2
3
4
//修改用户名
[RETSET_NAME](state,username) {
state.userInfo = Object.assign({}, state.userInfo,{ username })
},

VuePress文档系统搭建

链接


深度观测如何实现?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) { return }
seen.add(depId)
} if (isA) {
i = val.length
while (i—)
_traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i—)
_traverse(val[keys[i]], seen)
}
}
get(){
pushTarget(this)
...
finally{
if (this.deep) {
traverse(value) ////这深度观测有毛用,value也拿不到啊
}
popTarget()
this.cleanupDeps()
}
}

最新版的nextTick不是走宏任务,微任务的逻辑,是根据终端的特性判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
}else if (!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
}else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
}else{
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
```

## Mutation Observer API
[使用MutationObserver跟踪DOM的改变 ] (https://segmentfault.com/a/1190000014738092)

##nextTick如何理解 ??
其实最好理解的方式就是把 nextTick 看做 setTimeout(fn, 0)
Vue 是一个数据驱动的框架,如果能在UI重渲染之前更新所有数据状态,这对性能的提升是一个很大的帮助,所有要优先选用 microtask(微任务) 去更新数据状态而不是 (macro)task(宏任务),这就是为什么不使用 setTimeout 的原因,因为 setTimeout 会将回调放到 (macro)task 队列中而不是 microtask 队列,所以理论上最优的选择是使用 Promise,当浏览器不支持 Promise 时再降级为 setTimeout
UIWebViews 中存在很奇怪的问题,即 microtask 没有被刷新,对于这个问题的解决方案就是让浏览做一些其他的事情比如注册一个 (macro)task 即使这个 (macro)task 什么都不做,这样就能够间接触发 microtask 的刷新。

if (typeof Promise !== ‘undefined’ && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// In problematic UIWebViews, Promise.then doesn’t completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn’t being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// “force” the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

## 什么是异步更新队列?
queueWatcher 函数的作用就是我们前面讲到过的,它将观察者放到一个队列中等待所有突变完成之后统一执行更新。

## !this.depIds.has(id) { dep.addSub(this) } 在依赖收集逻辑里,这条语句为何可以控制重复收集?
* 1、newDepIds 属性用来在一次求值中避免收集重复的观察者
* 2、每次求值并收集观察者完成之后会清空 newDepIds 和 newDeps 这两个属性的值,并且在被清空之前把值分别赋给了 depIds 属性和 deps 属性
* 3、depIds 属性用来避免重复求值时收集重复的观察者
通过以上三点内容我们可以总结出一个结论,即 newDepIds 和 newDeps 这两个属性的值所存储的总是当次求值所收集到的 Dep 实例对象,而 depIds 和 deps 这两个属性的值所存储的总是上一次求值过程中所收集到的 Dep 实例对象。
```js
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}

答:watch 在get函数中 finally 语句块内调用了观察者对象的 cleanupDeps 方法,这个方法的作用在于 每次求值完毕后都会使用 depIds 属性和 deps 属性保存 newDepIds 属性和 newDeps 属性的值,然后再清空 newDepIds 属性和 newDeps 属性的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
....
} finally {
if (this.deep) { traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}

vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)
vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的DOM

问题?1虚拟节点vnode是什么?

2如何渲染虚拟节点
vm.$el是什么?


1
2
3
4
5
const vm = new Vue({
el: '#foo',
template: '<div id="bar"></div>’
})
vm.$el 是 id 为 bar 的 div 的引用 即为'<div id="bar"></div>

理解 Vue中的Render渲染函数 ,Vue.createElement方法

link

Vue.set不能在根节点上添加属性。

1
2
3
4
5
6
7
8
9
10
11
12
const data = {
obj: {
a: 1
__ob__ // ob2
},
__ob__ // ob1
}
new Vue({
data
})
Vue.set(data, 'someProperty', 'someVal')
//不允许在根对象上添加属性

Vue.set是干什么用的?

Vue 是没有能力拦截到为一个对象(或数组)添加属性(或元素)的,
而 Vue.set 和 Vue.delete 就是为了解决这个问题而诞生的。同时为了方便使用

1
2
3
4
5
6
const ins = new Vue({
data: {
arr: [1, 2] }
})
ins.$data.arr[0] = 3 // 不能触发响应
ins.$set(ins.$data.arr, 0, 3) // 能够触发响应

##如何理解Vue里defineReactive这个函数?
defineReactive 这个函数包含 Object.defineProperty这个关键函数。在Vue源码里主要负责取data中的key,value。

在Object.defineProperty 外部new Dep实例。 为每个data的key 闭包绑定一个Dep,负责在get,set外部传入Dep

get 一个是返回正确的属性值,另一个是收集依赖。 Dep.target.depend()

set 函数也要完成两个重要的事情,第一正确地为属性设置新值,第二是能够触发相应的依赖。notify

Vue.set如何触发依赖收集?

$set 或 Vue.set 给数据对象添加新属性时触发,我们知道由于 js 语言的限制,在没有 Proxy 之前 Vue 没办法拦截到给对象添加属性的操作。

所以 Vue 才提供了 $set 和 Vue.set 等方法让我们有能力给对象添加新属性的同时触发依赖,那么触发依赖是怎么做到的呢?

1
2
3
4
Vue.set = function (obj, key, val) {
defineReactive(obj, key, val)
obj.__ob__.dep.notify()
}

notify 调用watch.run,确认data依赖,变更oldVal = newVal, 执行callback

vue2.0中的组件的继承与扩展

link

一、slot
1.默认插槽和匿名插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<div id="itany">
<my-hello>180812</my-hello>
</div>
<template id="hello">
<div>
<h3>welcome to xiamen</h3>
<slot>如果没有原内容,则显示该内容</slot> //默认插槽
</div>
</template>
<script>
var vm =new Vue({
el:'#itany',
components:{
'my-hello':{
template:'#hello'
}
}
})
</script>
</body>

2.具名插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div id="itany">
<my-hello>
<ul slot="s1">
<li>aaa</li>
<li>bbb</li>
<li>ccc</li>
</ul>
<ol slot="s2">
<li>111</li>
<li>222</li>
<li>333</li>
</ol>
</my-hello>
</div>
<template id="hello">
<div>
<slot name="s2"></slot>
<h3>welcome to xiamen</h3>
<slot name="s1"></slot>
</div>
</template>
<script>
var vm = new Vue({
el:'#itany',
components:{
'my-hello':{
template:'#hello'
}
}
})
</script>

二、mixins

1
2
3
一般有两种用途:
1、在你已经写好了构造器后,需要增加方法或者临时的活动时使用的方法,这时用混入会减少源代码的污染。
2、很多地方都会用到的公用方法,用混入的方法可以减少代码量,实现代码重用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<h1>Mixins</h1>
<div id="app">
<p>num:{{ num }}</p>
<p>
<button @click='add'>增加数量</button>
</p>
</div>
<script>
var addLog = {
updated:function(){
console.log("数据发生变化,变化成"+this.num+".");
}
}
Vue.Mixin({
updated:function(){
console.log("我是全局的混入");
//执行顺序.全局混入的执行顺序要前于混入和组件里的方法。
}
})
var app = new Vue({
el:'#app',
data:{
num:1
},
methods:{
add:function(){
this.num++;
}
},
updated(){
console.log("我是原生update");
},
mixins:[addLog]
})
</script>

三丶extends
1 extends用法
extends选项允许声明扩展另一个组件,而无需使用 Vue.extend。它和混入mixins非常的类似。只不过接收的参数是简单的选项对象或构造函数,所以extends只能单次扩展一个组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<h1>Extends</h1>
<hr>
<div id="app">
num:{{ num }}
<p>
<button @click="add">add</button>
</p>
</div>
<script type="text/javascript">
var bbb = {
updated() {
console.log("我是被扩展出来的");
},
methods: {
add: function () { //跟原生的方法冲突,取原生的方法,这点跟混入一样
console.log('我是被扩展出来的add方法!');
this.num++;
}
}
};
var app = new Vue({
el: '#app',
data: {
num: 1
},
methods: {
add: function () {
console.log('我是原生add方法');
this.num++;
}
},
updated() {
console.log("我是扩展出来的");
},
extends: bbb //<font color="red">接收对象和函数</font>
})
</script>

四 extend
Vue.extend只是创建一个构造器,它是为了创建可复用的组件。其主要用来服务于Vue.component,用来生成组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="itany">
<hello></hello>
<my-world></my-world>
</div>
<script>
//1.使用Vue.extend()创建一个组件构造器
var MyComponent = Vue.extend({
template: '<h3>Hello World</h3>'
});
//2.使用Vue.component(标签名,组件构造器),根据组件构造器来创建组件
Vue.component('hello', MyComponent);
Vue.component('my-world', {
template: '<h1>你好,世界</h1>'
});
var vm = new Vue({ //这里的vm也是一个组件,称为根组件Root
el: '#itany',
data: {}
});
</script>

propsdata是干什么的?

propsdata 创建实例时传递 props。主要作用是方便测试。

1
2
3
4
5
6
7
8
9
var Comp = Vue.extend({
props: ['msg'],
template: '<div>{{ msg }}</div>'
})
var vm = new Comp({
propsData: {
msg: 'hello'
}
})

Vue源码里选项合并有两种方式

1 主动调用 外部调用场景new Vue(options) 的方式实例化一个 Vue 对象;另一种是我们上一节分析的组件过程中内部通过 new Vue(options) 实例化子组件。
都会执行实例的 _init(options) 方法,它首先会执行一个 merge options 的逻辑,相关的代码在 src/core/instance/init.js 中:
2 组件场景
Vue.extend父子传递, extends父子传递,mixins, new Vue等等,propsData父子传递,如何对父子选项进行合并处理的,也知道了它的作用
源代码看起来总之很麻烦


怎么看Vue源代码?

发现Vue源码里typescript的语法很适合看代码。有参数函数返回值,非常直观。
配合文档https://cn.vuejs.org/ http://hcysun.me/vue-design/一步一步看还是比较舒服的。


Vue生命周期图详解 https://segmentfault.com/a/1190000008570622
Vue2.0 $set() https://blog.csdn.net/panyang01/article/details/76665448
mounted () {
this.$set(this.student,”age”, 24) $set()方法,既可以新增属性,又可以触发视图更新。
}


如何实现一个简单的Vue?

  • 每一个v-html, v-text等带有的表达式 内部都有一个Watch对象,在compile模板的过程中,递归fragment,绑定对应事件过程进行的。
  • 编译过程为逐行编译,并非统一编译。 比如碰到一个 编译过程中直接填上data ,new一个watcher绑定到当前指令上,watcher内部再取一次
    的值,触发一次this.get( ) Dep.target = this; 拿到name的值,这个过程中触发defineProperty get( )建立 dep和watch的关系。
  • watch里的this.depIds ={ } 把dep 压入进去, dep里的 this.subs=[ ] 把watch放进去
  • 一个watch里面只能有一个dep, 一个dep里面可以有多个watch (dep其实就是可动态更改的对象数目)
    (watch是模板指令数目)对象在模板上可以对应多个指令进行模板修改。
  • 当method通过addEventListener触发是,Observer监测到对象有变化,在set( )方法里调用Dep的notify方法,对保存在Dep里的Watch进行迭代,调用Watch里的update方法,update方法拿到新set的对象的值,对比老对象的值,调用各个指令节点绑定的方法,进行指令替换。


new MVVM 构造函数里。

  1. 解析指令 解析模板里的指令。
    初始化视i图-> 逐行解析指令,把属性值填入到指令里。
    订阅数据变化-> 把属性值填入指令里下面接着就绑定给指令节点绑定更新函数,并new一个Watch对象。绑定到当前指令上,watcher构建的时再取
    指令的值,拿到name的值,触发一次this.get( )
    添加订阅者 -> Dep.target = this; 这个过程中触发defineProperty get( )建立 dep和watch的关系。
    watch里的this.depIds ={ } 把dep 压入进去, dep里的 this.subs=[ ] 把watch放进去
    一个watch里面只能有一个dep, 一个dep里面可以有多个watch (dep其实就是可动态更改的对象数目)
    (watch是模板指令数目)对象在模板上可以对应多个指令进行模板修改。
  2. 监听属性,Observer 给每个对象变量进行劫持。并初始化Dep对象,以便进行闭包调用
  3. 当method通过addEventListener触发是,Observer监测到对象有变化,在set( )方法里调用Dep的notify方法,对保存在Dep里的Watch进行迭代,调用Watch里的update方法,update方法拿到新set的对象的值,对比老对象的值,调用各个指令节点绑定的方法,进行指令替换。

SSR服务端渲染

link

Vue在项目中的使用

(下面全部搞懂了,Vue基本就会用了。)

  • Vue模板中所有的数据字段由数据库接管。

  • Vue数据流:

    1
    2
    3
    4
    5
    {
    单页面组件数据,data(){ this.data=..} 用来写入template。
    props父亲传子组件数据,父子数据传递
    Vuex全局组件共享数据源,记录APP共享的状态机
    }
  • Vue生命周期:

  • Vue路由对象使用 VueRouter
  • VueLoader(载入程序) js, scss sass,css,less typescript转换工具
  • Vue服务端渲染(未知)

VueX 在项目里是怎么用的?

import {mapState, mapActions} from 'vuex'
export default {
    data(){
        return{
        }
    },
    mounted(){
        //获取用户信息
        this.getUserInfo();
    },
    props: ['signinUp', 'headTitle', 'goBack'],
    computed: {
        ...mapState([
            'userInfo'
        ]),
          //映射this.userInfo 为 store.state.userInfo
        //this.userInfo = store.state.userInfo;
        //this.userInfo没有在data里声明,template也可以直接拿来用。
    },
    methods: {
        ...mapActions([
            'getUserInfo'
        ]),
        //this.getUserInfo没有声明,可以直接拿来用。
    },
}

webpackBootstrap

link
link


其他

1
2
<style lang="scss" scoped> 
只对当前页面有效。

2017-03-15

填充的是路由的模板内容
$ref 获取组件内的元素,
$parent:获取当前组件对象的父组件
$children:获取子组件
$root: 获取new Vew的实例
$el:组件对象的DOM元素

1
2
3
4
5
6
7
VueRouter -> 

window.addEventListener(‘hashchange’,function( ){
switch(location.hash)
case ‘#login’:
div.innerHTML=“<h1>登陆页面</h1>"
})

Vue.$nextTick 获取更新后的 DOM,在 DOM 更新循环结束之后执行延迟回调


2017-03-14

Vue对象生命周期

只能和 配套使用。

Vue内置自定义组件。 父组件传递的DOM结构。

computed:凡是函数内部有this.相关属性,改变都会触发当前函数,监视多个
watch: 监视单个


Vue和原生,jQueryAPI替代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//$选择器可以用ref替代
<section class="menu_right" ref="menuFoodList"></section>
const listContainer = this.$refs.menuFoodList;

$(elem).find('.activity_menu') //替代
this.$refs.wrapperMenu.querySelectorAll('.activity_menu')

$.each //替代
listContainer.forEach((item, index) => {
this.shopListTop[index] = item.offsetTop;
});

v-bind -> setAttribute()
v-text -> innerText
v-html -> innerHTML
v-if ->
parent.removeChild("elem")
parent.append("ssss")
v-show ->
obj.style.display='none/block'
v-on: ->
addEventListener (简写成) @
v-model ->
双向数据流绑定,只能给具有value属性的元素进行双向数据绑定(必须使用的具有value属性的元素)
v-model实现自定义的表单组件:
<currency-input v-model="price"></currency-input>

监听原生组件的事件,当获取到原生组件的值后把 值通过调用 $emit(‘input’ ,data) 方法去触发 input事件

v-bind 可以给任何赋值属性,是从value到页面的单向数据流 简写成 :title

1
2
3
4
5
6
7
<li v-for="item in items”>   
{{ item.message }}
</li>

$.each(items,funciton(i,obj){
$(elem).parent().append("<li>...</li>")
})

v-style :style -> $.addClassRule

props用在定义自定义标签组件的属性上,v-bind用在定义原生标签属性上。

1
2
3
4
5
6
7
8
<span v-text="myText"></span> 
<hr />
<span v-html="myHtml"></span>

<button v-if="isExit"></button>
<button v-if="num==1">测试Num1</button>
<button v-else-if="num==2">测试Num2</button>
<button v-show="isShow">v-show<button>

v-bind是 一个vue 指令,用于绑定 html 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="app”> 
<p v-bind:title="t1 + ' ' + t2">
html属性不能使用双大括号形式绑定,只能使用v-bind指令
</p>
可简写成
<p :title="t1+' '+t2"></p>
</div> ......
var vm = new Vue({
el: '#app',
data: {
t1: 'title1',
t2: 'title2'
}
});