Vue核心概念及特性 (一)

Vue核心概念及特性 (一)

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
特点: 易用,灵活,高效 渐进式框架

可以随意组合需要用到的模块 vue + components + vue-router + vuex + vue-cli

一.Vue的概念和特性

1.什么是库,什么是框架?

react 用于构建用户界面的JavaScript 库

  • 库是将代码集合成一个产品,库是我们调用库中的方法实现自己的功能。(jquery、react)
  • 框架则是为解决一类问题而开发的产品,框架是我们在指定的位置编写好代码,框架帮我们调用。

框架与库之间最本质区别在于控制权:you call libs, frameworks call you

Vue属于框架

2.MVC模型 && MVVM模型

在传统的mvc中除了model和view以外的逻辑都放在了controller中,导致controller逻辑复杂难以维护,在mvvm中view和model没有直接的关系,全部通过viewModel进行交互。

可以将html页面看出View,数据看成Model,不需要关系两者之间是如何交互的,不需要关心这个核心调度者,我们写数据会自动放到View上,页面上的数据变化了也会自动更新Model。

Vue是MVVM模式

3.声明式和命令式

  • 自己写for循环就是命令式 (命令其按照自己的方式得到结果)(delete obj[key] 、 target[key]=val)
  • 声明式就是利用数组的方法forEach、map (我们想要的是循环,内部帮我们去做,我们并不知道map内部是如何的,但是它可以帮我们实现功能)

区别是:声明式方便应用,命令式 自己可以控制里面的逻辑。Vue是一个声明式框架,要按照它自己的逻辑去写,不能更改它的逻辑。

二.Vue的基本使用

1.安装

1
2
npm init -y
npm install vue

2.使用

2.1. 基础使用

新建index.html

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="container">
{{name}}
</div>

<script src="./node_modules/vue/dist/vue.js"></script>
<script>
// 这个vm就是Vue的实例,即 View model
// 我们只需要将数据挂在这个View model,上面div line10-12 即视图就能够获取到数据了
let vm = new Vue({
el: '#container',
data(){ // 如果是根节点,这个data可以写成对象,现在这里写成函数
// 这里就是model层
return {
name: 'jf',
}
}
})
// vue是响应式变化的,如果数据更新了,默认会刷新视图
vm.name = 'jf2';
// 这里可以看到页面上的数据变化了 jf2
</script>
</body>
</html>

2.2 响应式数据变化

Vue中使用Object.defineProperty重新将对象中的属性定义,如果是数组的话需要重写数组原型上的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>
<div id="container">
{{name}}
{{arr}}
</div>

<script src="./node_modules/vue/dist/vue.js"></script>
<script>

let vm = new Vue({
el: '#container',
data(){
return {name: 'jf', arr:[1,2,3]}
}
})
// 绑定的数据类型,什么样的数据可以响应式变化
vm.arr[0] = 1000;
// 页面显示 jf [ 1, 2, 3 ],没有改变

// vue源码中是如何实现响应式变化的 新建observe.js
</script>
</body>

新建observe.js

观察一个数据 Vue2.0 definePrototype 看mdn上可以知道definePrototype重定义数组的 length 时会有兼容问题所以
这里只针对对象 数组没有使用definePrototype

  • 1) 增加不存在的属性,不能更新视图(vm.$set 可以)
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
// observer.js
function observer(obj){
if(typeof obj !== 'object' || obj == null){
return obj;
}
// 循环对象 重新定义key属性
for(let key in obj){
defineReactive(obj,key,obj[key]);
}

}
function defineReactive(obj,key,value) {
Object.defineProperty(obj,key,{
get(){
return value;
},
set(newValue){
if(value !== newValue){
value = newValue;
console.log("视图更新");
}
}
})
}
let data = {name:'jf'};
observer(data);
// data.name = 123;
// // Run Code 运行之后可以看到 "视图更新"
data.age = 28;
// Run Code 没有更新视图
// 缺点:
// 1. 增加不存在的属性,不能更新视图(vm.$set 可以)
  • 2) vue默认会递归所有数据,增加getter setter
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
// observer.js

function observer(obj){
if(typeof obj !== 'object' || obj == null){
return obj;
}
for(let key in obj){
// 默认只循环第一层
defineReactive(obj,key,obj[key]);
}

}
function defineReactive(obj,key,value) {
observer(value); // 递归创建 响应式数据,性能不好
Object.defineProperty(obj,key,{
get(){
return value;
},
set(newValue){
if(value !== newValue){
value = newValue;
console.log("视图更新");
}
}
})
}
let data = {name:{n:'jf'}};
observer(data);
data.name.n = 123;
// Run Code 递归创建之后现在 运行之后可以看到 "视图更新"
// 2.vue默认会递归所有数据,增加getter setter
特点: 使用对象的时候必须先声明属性,这个属性才是响应式的
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
function observer(obj){
if(typeof obj !== 'object' || obj == null){
return obj;
}
for(let key in obj){
defineReactive(obj,key,obj[key]);
}

}
function defineReactive(obj,key,value) {
observer(value);
Object.defineProperty(obj,key,{
get(){
return value;
},
set(newValue){ // 给某个key设置值的时候也是一个对象
if(value !== newValue){
observer(newValue); // 这里也要递归创建
value = newValue;
console.log("视图更新");
}
}
})
}
let data = {name:{n:'jf'}};
observer(data);
data.name = {n: '123'}; // Run Code 执行 更新视图了
data.name.n = '456'; // Run Code 也执行视图更新了 如果这里将line8注释掉 则这个n属性不是响应式的,更改数值不更新视图
// 特点: 使用对象的时候必须先声明属性,这个属性才是响应式的
数组
  • 3) 数组里套对象 对象是支持响应式变化的,如果是常量则不可以
  • 4) 修改数组索引和长度是不会更新视图的
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
// 数组
function observer(obj){
if(typeof obj !== 'object' || obj == null){
return obj;
}
if(Array.isArray(obj)){ // 如果调用push unshift splice这三个方法,即增加了数组,也应该把这个值判断下是否是对象
// 这里处理数组
// console.log(obj); // [ 1, 2, 3 ]
for(let i = 0; i<obj.length; i++){
let item = obj[i];
observer(item); // 如果是对象会被 defineReactive
}
}else{
// 这里处理对象
for(let key in obj){
defineReactive(obj,key,obj[key]);
}
}
}
function defineReactive(obj,key,value) {
observer(value);
Object.defineProperty(obj,key,{
get(){
return value;
},
set(newValue){
if(value !== newValue){
observer(newValue);
value = newValue;
console.log("视图更新");
}
}
})
}
let data = {d: [1,2,3,{name:'jf'}]};
observer(data);
// data.d[0] = 100; // 没有更新
data.d[3].name = 'hhh'; // 更新

现在希望更新数组也能更新视图 见index.html

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="container">
{{name[0].n}}
{{name[1]}}
</div>

<script src="./node_modules/vue/dist/vue.js"></script>
<script>

let vm = new Vue({
el: '#container',
data(){
return {name:[{n:123},2]}
}
})
// vm.name[0].n = 100; // 更新的
vm.name[1] = 500; // 不更新

// 分别注释line24 25 可以看到
// 如果数组里面放的是对象,那么更改对象中的属性是可以导致视图更新的 回到observe.js
</script>
</body>
</html>

重写 push unshift splice

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
// observer.js
let arrayProto = Array.prototype; // 这是数组原型上的方法
let proto = Object.create(arrayProto);
['push', 'unshift', 'splice'].forEach(method=>{
proto[method] = function(...args){ // args 这个数组参数也应该被监控
let inserted; // 默认没有插入新的数据
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice': // 数组的splice 只有传递3个参数才有追加效果
inserted = args.slice(2);
default:
break;
}
console.log('视图更新');
ArrayObserver(inserted);
// 相当于 Array.prototype.push.call( [1,2,3,{name:'jf'}],4,5,6,7,8...);
arrayProto[method].call(this,...args); // 谁调的push,this就指向谁
}
})
function ArrayObserver(obj){
for(let i = 0; i<obj.length; i++){
let item = obj[i];
// 如果是普通值 就不监控了
observer(item);
}
}
function observer(obj){
if(typeof obj !== 'object' || obj == null){
return obj;
}
if(Array.isArray(obj)){

Object.setPrototypeOf(obj,proto);// 实现对数组的方法进行重写
// obj.__proto__ = proto; // 意思是把obj原型的链指向proto

// for(let i = 0; i<obj.length; i++){
// let item = obj[i];
// observer(item);
// }
ArrayObserver(obj);
}else{
for(let key in obj){
defineReactive(obj,key,obj[key]);
}
}
}
function defineReactive(obj,key,value) {
observer(value);
Object.defineProperty(obj,key,{
get(){
return value;
},
set(newValue){
if(value !== newValue){
observer(newValue);
value = newValue;
console.log("视图更新");
}
}
})
}
let data = {d: [1,2,3,{name:'jf'}]};
observer(data);
data.d.push({name:'haha'});
data.d[4].name = 'haha2'; // push进去的数据也做了监控
// 可以看到有2次更新视图

// 5.如果新增的数据 Vue中也会帮你监控(对象类型)

// 如果整个数组更新
data.d = 1000; // 也会更新,应为这里看的是 data.d,data是对象,d是它的属性
// data.d[0] = 1000; // 这样就不会更新视图

3.Vue实例上的方法

  • vm.$el;
  • vm.$data;
  • vm.$options;
  • vm.$nextTick();
  • vm.$watch();
  • vm.$set();

新建 instance.html

1】

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
<body>
<div id="app">
{{name}}
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data(){
return {name:'jf'}
}
})
// console.log(vm.$el);// 指渲染后的真实的dom元素

// 1) vue有个特点 不会在本轮代码执行的时候 去重新渲染dom
// 2) 下一个事件环中执行 (promie.then mutationobserver setimmediate settimeout)

// 这里会等待数据更新后,再调用回调函数
vm.$watch("name",function(newVal, oldVal){
console.log('line30~',newVal, oldVal); // jf3 jf
})
vm.name = 'jf2';
vm.name = 'jf3';
// 浏览器 显示 jf3 已改变
</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
<body>
<div id="app">
{{name}}
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data(){
return {name:'jf'}
}
})
vm.$watch("name",function(newVal, oldVal){ // 视图是异步更新的
console.log('line30~',newVal, oldVal);
})
vm.name = 'jf2';
vm.name = 'jf3';

console.log(vm.$el.innerHTML);
// 页面显示 jf3
/* 浏览器打印
jf
line30~ jf3 jf
*/
// 可以看到line19 先打印 line14后打印 视图是异步更新的

// 如何拿到最新的 如下:
</script>
</body>

3】

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
<body>
<div id="app">
{{name}}
{{age}}
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data(){
return {name:'jf'}
}
})
vm.$watch("name",function(newVal, oldVal){
console.log('line30~',newVal, oldVal);
})

vm.name = 'jf2';
vm.name = 'jf3';
// 数据更新后会有一个队列,将watch的callback放到队列中,会将nextTick往后叠加,所以会拿到新的数据
vm.$nextTick(()=>{
console.log(vm.$el.innerHTML); // jf3
})
console.log("vm的数据",vm.$data); // 代表当前实例的数据
console.log("vm中的options",vm.$options);

// vm.$set vm.$delete 帮我们更新属性的
// 新增的属性不会导致视图更新,更改数组索引也不会更新
// 现在增加新的属性
vm.age = 18; // 因为上面有个更新数据,所以这里直接写新的属性,也能够更新,因为更新会取最新的数据,如果上面没有更新name属性 如下:
</script>
</body>

4】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div id="app">
{{name}}
{{age.age}}
</div>
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data(){
return {name:'jf',age:{}}
}
})
// vm.age.age = 18; // 这样不行
vm.$set(vm.age,'age',18); // 这样视图跟新了

// vm.$el
// vm.$options
// vm.$watch
// vm.$nextTick
// vm.$set
</script>
</body>

三.Vue中的指令

在vue中 指令 (Directives) 是带有 v- 前缀的特殊特性,主要的功能就是操作DOM

v-for

1
2
3
4
<template v-for="(fruit,index) in fruits" >
<li :key="`item_${index}`">{{fruit}}</li>
<li :key="`fruit_${index}`">{{fruit}}</li>
</template>

多个元素循环时外层需要增加template标签,需要给真实元素增加key,而且key不能重复,尽量不要采用索引作为key的值

举个key值的例子:

新建 drective.html

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
<body>
<div id="app">
<!-- {{}} 小胡子语法 取值 能运算(算出来得有返回结果) 做三元表达式 -->
{{name}}
<hr/>

{{1+1}}
<hr/>

{{[1,2,3]}}
<hr/>

{{true?'是':null}}
<hr/>

<!-- 取对象 必须空格隔开 -->
{{ {name:'123'} }}
<hr/>

<!--这里name不会改变,只会取第一次的值-->
<span v-once>一次 {{name}}</span>
<hr/>

<!-- 不要将用户输入的内容显示到页面上 xss攻击 -->
<!-- 后端返回的数据 可以通过v-html来格式化 -->
<span v-html="tmp"></span>
<hr/>

<!-- 动态绑定 这里的name是变量属性 -->
<div v-bind:title="name">你好</div>
<!-- 绑定的是一个变量属性 -->
<div :title="name">你好</div>
<hr/>

<!-- v-for 循环数据 数组 对象 数字 (字符串) -->
<!-- 要循环谁 就将 v-for 写到谁的身上 -->

<!-- vue的中的key 有什么作用 区分元素,如果我有个按钮做反序 -->
<!-- 只是静态展示可以使用这个索引 使用唯一的key 来区分元素-->
<!-- 每次循环时候可以自己拼接一些内容保证唯一性 -->
<!-- template是Vue中自定义的模板标签 没有意义 不能在上面加key -->
渲染
<div v-if="true">
<template v-for="(fruit,index) in arr" >
<li :key="`name_${index}`">
{{fruit.name}}
</li>
<li :key="`color_${index}`">
{{fruit.color}}
</li>
</template>
</div>

不渲染
<div v-if="false">
<template v-for="(fruit,index) in arr" >
<li :key="`name_${index}`">
{{fruit.name}}
</li>
<li :key="`color_${index}`">
{{fruit.color}}
</li>
</template>
</div>
<hr/>
<!-- v-if (操作dom是否显示)) v-show(显示隐藏 style) -->
<!-- 指令的功能是封装dom 操作的 -->
<!-- v-for 和 v-if 不要一起用 -->
<!-- v-show 不能和template一起用 -->

<!-- vue默认会采用复用的策略 会复用代码 所以这里input中加了key区分 -->
<template v-if="isShow">
<span>你好</span>
<input type="text" key="1">
</template>
<template v-else>
<span>不好</span>
<input type="text" key="2">
</template>
<hr/>
<!-- 双向绑定 只要能改的 组件也可以双向绑定 -->

<!-- 如何绑定方法 v-on | @ , v-on是@的全拼-->
<!-- $event指代的是事件源 -->
<input type="text" :value="value" @input="fn($event)">
<!-- 很长 -->
<input type="text" :value="value" @input="e=>value=e.target.value">

<!-- v-model 他就是上面的简写 语法糖 -->
<input type="text" v-model="value">
{{value}}
</div>
<!--
v-once
v-html
v-bind
v-for
v-if / else show
v-model 双向绑定
-->
<script src="node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
methods:{// 就是把所有的方法的this 都变成vm,bind了 多次bind 不会生效
fn(e){
this.value = e.target.value
}
},
data() {
return {
value:'你好',
isShow:true,
tmp:'<h1>我很帅</h1>',
name: 'jf',
arr:[{name:'橘子',color:'绿色'},{name:'香蕉',color:' 黄色'}]
}
}
});
</script>
</body>

结果如图:

四.面试题环节

  • 请说下对于MVVM的理解
  • Vue实现数据双向绑定的原理
  • Vue常用的指令有哪些?
  • v-model的原理
  • v-if 和 v-show 区别
  • Vue中 key 值的作用