文章目录
1. 重点提炼
-
组件系统
- 根组件
- new Vue()
- 可复用组件
-
用户界面(UI)
-
组件选项
- template
- el
-
延迟挂载
- $mount()
-
UI
- data 选项
- 模板语法
- 大胡子 {{表达式}} - 插值表达式
2. vue.js
实际Vue
与React
都十分相似,都是用来构建界面的,即构建UI
的。
vue.js
是一套用于构建用户界面(可理解为模板引擎
)的渐进式框架(vue
和react
共同点都是用于构建用户界面,即构建ui
,但react
其实也是渐进式框架)
2.1 渐进式(诱导式)
刚开始很简单好用,用起来很方便,越往深里坑越多!
第一个圆
是声明式渲染,可理解为模板引擎。但用着会感觉光用这个还不够,因为界面(UI
)复用性是非常高的,除了UI
以外,还包括用户的响应。
所以就发现需要另外一个东西解决这个问题,随着而来就接触了组件系统(第二个圆圈
),用组件系统就可以利用声明式UI
创建出来的页面达到更大化的复用,继而更方便去管理。
需求越来越复杂后,页面越来越多,多页面之间如何进行切换呢?即接触到(第三个圆圈
)客户端路由
(系统)了,这样就能实现多页面跳转式的app
了。
当然做完这些还不够,不可能总是在页面上写很多死的东西(数据),通常情况下需要操作各自各样动态变化的数据来更新我们写的ui
,与用户进行交互,随着数据的越来越庞大,以及应用越来越复杂,所以即需要更好得一种方式去管理和维护这些数据,即接触到(第四个圆圈
)可伸缩式的状态管理器
了。
做完以上功能,会发现项目非常庞大了,此时不可能用原生的方式来构建我们写的应用,随之而来的是需要我们创建和使用一系列的各种各样的工具,比如解析、打包、优化、发布,帮助我们做很多与应用逻辑无关的运维方面的事情,即(第五个圆圈
)自动化构建系统
。
其实在开发过程中,会发现还会用到很多很多其他的系统或组件等,在这个应用基础上进行包装,这样进行一层一层的扩展,慢慢地应用就做出来了,因此这就是渐进式(诱导式)框架的概念。
2.2 Vue 核心
声明式
(UI)渲染
(也可理解为模板引擎
)- (可复用)
组件
(系统):会使声明式UI得到更大化地复用,更方便地管理
3. 引入
我们还是先通过 <script> 的方式来引入 vue
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
其实Vue核心本质上就两个内容:
1、声明式渲染 2、组件 => 并且它俩也不可分割。
我们看一下例子声明式渲染:
React
是构建组件化,通过组件来包装可复用的ui
,然后在ui
内部,通过声明式渲染的语法,如在React
当中使用JSX
语法来声明我们在页面当中想看到的页面内容(ui
的结构及样式)。那么vue
也是一样的,也是基于组件,在组件当中通过一种模板语法的东西来实现对当前UI
的构建。
ReactDom
下有一个render
方法,它把封装好的组件挂载到指定的页面DOM
当中,然后进行渲染。组件内部是通过JSX
语法来实现UI
的绘制的,即UI
的结构定制的。
实际在Vue
中也是一样的概念,在Vue
中组件划分为根组件和可复用的功能组件。
4. 组件
vue
的核心功能之一就是组件
4.1 组件基本分类
- 根组件(页面当中最顶层封装的组件)
- 可复用的功能组件(在页面当中可以多次利用的组件)
4.2 根组件的创建
通过 vue
提供的构造函数可以实例化出来一个根组件实例对象,Vue
中接收一个对象,对象中的属性是用来构建组件的一些配置选项。
组件最为重要的就是组件渲染的结构,在React
当中,如何定义这个结构呢?函数组件,即其返回值,而类组件,则是render
方法的返回值。在Vue
中也提供了一种方式存放组件渲染的结构,即UI
=> 通过template
属性。Vue
挂载页面的方式 => vue
对象下的$mount
方法,传入指定元素,实际传一个选择器进去即可。
let app = new Vue(创建组件所需要的一些配置选项);
4.2.1 example01
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
</div>
<script src="./js/vue.js"></script>
<script>
/**
* 组件
* 声明式渲染语法
*
*/
// Vue实例(代表一个组件)
let app = new Vue({
template: `
<div>
CSDN
</div>
`
});
console.log(app);
app.$mount('#app');
</script>
</body>
</html>
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.01
Branch: branch01commit description:a0.01(example01-vue初始化)
tag:a0.01
4.3 可复用的功能组件
通过 Vue
提供的静态方法 component
窗口可复用的功能组件
let component1 = Vue.component(创建组件所需要的一些配置选项)
组件配置选项:https://cn.vuejs.org/v2/api/
5. 组件内容渲染
渲染一个组件的内容可以通过两种方式来进行
- template 选项
- render 选项(函数)
5.1 template
type : string
组件的模板结构(HTML
),模板内容会被 vue
进行渲染,生成最终的 HTML
内容,并替换占位(挂载)元素
5.2 el
type : string | Element
提供一个在页面上已存在的 DOM
元素作为 Vue
实例的挂载目标。可以是 CSS
选择器,也可以是一个 HTMLElement
实例,
- 该选项只对
new
创建的实例有效 - 如果提供
el
,但是没有提供template
,则el
的内容讲作为template
5.2.1 example02
vue
提供了很多简化式写法 =>
有人喜欢react
,喜欢的是它的直接(它没做过多的东西,即封装更多地东西,和学js
没太多区别,除了jsx
以外,其实几乎与原生一致,所以如果js
掌握非常好的话,学习React
很容易上手,没有太多关注的新东西)。
有人喜欢Vue
是喜欢它的语法糖,隐藏了很多的细节(封装了很多便捷性操作,操作非常方便,用起来很舒服,即可能一句话做了很多事情,所以又很多人不喜欢它这种过于委婉,它隐藏了很多细节,封装了很多api
,不去看手册和文档,是不知道怎么用的,完全不是js
了)
5.2.1.1 example02-1
除此之外还可这样挂载:
内部有el
选项,就会立即进行挂载,实际就是在实例化的时候,Vue
发现有el
选项,就去直接调用$mount
方法了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
</div>
<script src="./js/vue.js"></script>
<script>
/**
* 组件
* 声明式渲染语法
*
*
* react
* 直接
* vue
* 方便 - 语法糖,隐藏了很多的细节
*/
let app = new Vue({
el: '#app',
template: `
<div>
11111
</div>
`
});
</script>
</body>
</html>
显示效果一样。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.02
Branch: branch01commit description:a0.02(example02-vue的el属性demo)
tag:a0.02
5.2.1.2 example02-2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
CSDN
</div>
<script src="./js/vue.js"></script>
<script>
/**
* 组件
* 声明式渲染语法
*
*
* react
* 直接
* vue
* 方便 - 语法糖,隐藏了很多的细节
*/
let app = new Vue({
el: '#app'
});
</script>
</body>
</html>
效果还是一样,但本质还是有区别的,如果实例化的时候发现vue
对象里有el
属性,而没有template
属性,它会把el
属性对应的元素的html
作为template
属性值,即:
如果配置中有el
没有template
,它会把el里面这个选项的元素对应的标签整个显示在页面当中!替换如下:
<div id="app">
CSDN
</div>
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.03
Branch: branch01commit description:a0.03(example02-2-vue的el属性demo)
tag:a0.03
5.2.1.3 example02-3
我们看看,是template
的优先级高?还是html
的优先级高?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
CSDN
</div>
<script src="./js/vue.js"></script>
<script>
/**
* 组件
* 声明式渲染语法
*
*
* react
* 直接
* vue
* 方便 - 语法糖,隐藏了很多的细节
*/
let app = new Vue({
el: '#app',
template: `
<div>
11111
</div>
`
});
</script>
</body>
</html>
我们会发现templete
的优先级会更高一些。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.04
Branch: branch01commit description:a0.04(example02-3-vue的template属性和html的优先级)
tag:a0.04
5.2.1.4 example02-4
el
:千万注意不能把 body
和html
元素作为挂载点
let app = new Vue({
el: 'body',
template: `
<div>
11111
</div>
`
});
这种情形下Vue
没帮助我们进行渲染,报错也提示了不能把html
和body
作为应用的挂载点。
原因在于我们的这里模板最终实现的是替换功能。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.05
Branch: branch01commit description:a0.05(example02-4-el:千万注意不能把 body 和 html 元素作为挂载点)
tag:a0.05
5.2.1.5 example02-5
template
模板最终实现的是替换功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
CSDN
</div>
<script src="./js/vue.js"></script>
<script>
/**
* 组件
* 声明式渲染语法
*
*
* react
* 直接
* vue
* 方便 - 语法糖,隐藏了很多的细节
*/
let app = new Vue({
el: '#app',
template: `
<div z="1">
11111
</div>
`
});
</script>
</body>
</html>
其实可以看出来,它会把解析后的模板,完全替换掉当前的元素,而不是元素里面的内容。如果这个时候把body
作为挂载点,必然会出问题。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.06
Branch: branch01commit description:a0.06(example02-5-template模板最终实现的是替换功能)
tag:a0.06
5.2.1.6 example02-6
template
:注意每一个组件的根元素(节点)有且只能有一个
let app = new Vue({
el: '#app',
template: `<div>111111</div><div>vue</div>`
});
当然报错也提示了,每一个组件的根元素(节点)有且只能有一个,后面说明的v-if
和v-else-if
用来生成的单一节点是排除在外的。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.07
Branch: branch01commit description:a0.07(example02-6-template:注意每一个组件的根元素(节点)有且只能有一个)
tag:a0.07
除此之外,vue
也提供了另外一种渲染方式即render
函数,它可以直接渲染虚拟dom
,它有两个作用,我们往下走。
5.3 render
vue
渲染过程:
html
模板 => (解析成)VDOM
(虚拟DOM
对象) => DOM
元素
这里为啥不直接把模板直接转换成DOM
元素呢?
这里的原因和react
是一样的,目的是我们后续进行dom
更新的过程中,可进行(DOM
)优化。
其实vue
还提供另外一种渲染方式,和React
很像,即render
函数。
可直接通过该函数,对虚拟dom
直接进行渲染,它的作用(好处)有两个。它相较于template
的好处是,它提供一个createElement
方法,通过该方法可直接传 VNode
(虚拟dom
对象),直接创建真实dom
节点(其实就是把html
模板这个步骤省略掉了),优先级高于 el
和 template
,因此它有更高的性能。
在html
模板当中,它还限定了一些语法,即不能直接使用js
进行编程,不能在里面写逻辑,它没有提供这个功能。但是它提供了很多指令,必然它也会有一些的局限性。
因此如果使用render
方法,它是一个js
函数,在其内部可以无所顾忌的使用js
当中的各种指令,所有它有更强大的编程能力(相对于html
模板)。
type : (createElement: () => VNode) => VNode
发挥 JavaScript
最大的编程能力,直接创建 VNode
(虚拟dom对象),优先级高于 el
和 template
,好处如下:
- 有更强大的编程能力
- 有更高的性能
https://cn.vuejs.org/v2/guide/render-function.html
5.4 使用 $mount 方法延迟 Vue
实例的挂载
当 Vue
实例没有 el
选项的时候,它会处于一种 未挂载 的状态,我们可以通过组件 Vue
实例对象的 $mount
方法来手动挂载,通过该方式,我们也可以达到延迟 Vue
实例的挂载的目的
6. 组件中的数据(状态)
我们知道光有一个模板,是不够的,大多数模板引擎都能做到,我们前端除了渲染页面,还需要操作大量的数据,有后端发来的数据,也有自己产生的数据,由一堆丰富的数据构建出了各种各样丰富的app
。死的app
是没有任何意义的,能够交互的app
才是十分重要的。
6.1 data
组件内部使用的数据,data
是一个对象,data
中的值可以中模板中直接访问
Vue
实例组件(根组件)的data
是一个对象- 可复用功能组件的
data
必须是一个函数,且该函数必须返回一个对象。因为复用性,避免多个组件实例引用同一个对象。换句话说,组件中使用的数据必须是一个对象,但是可复用组件的这个数据对象必须通过函数返回
6.2 data 的访问
肯定是在应用相关的UI
界面中进行访问,跟React
一样,要么在函数内部或类的其他方法去调用数据,然后写在render
函数的jsx
中,即可显示在界面上了。
data
数据可以直接通过组件实例对象访问,也可以通过实例对象下的 $data
属性进行访问
组件实例对象下有很多的以
$
开头的属性,这些都是实例对象内置的一些属性和方法,vue
为了区分数据与内置属性方法,内置的属性和方法默认都是以$
开始的,所以我们中数据中应该避免使用$
开头的数据
7. 模板语法
vue 使用了基于 html 的模板语法,使用声明式的方式把实例中的数据(data
)与 DOM 进行绑定,data
中的数据在模板中可以直接使用
7.1 Mustache(双大括号,大胡子) 语法
在 vue 中,我们是通过一对双大括号把实例中的数据渲染到模板内容中
7.1.1 插值表达式
在 {{}}
(JSX
是一对大括号,这是两对大括号,可理解jsx
语法是小胡子,这里是大胡子)中,我们可以放置表达式值
{{表达式}}
new Vue({
el: '#app',
data: {
title: 'vue 框架'
},
template: `<div>{{title}}</div>`
});
<!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="app">
{{title}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
title: 'vue 框架'
}
});
</script>
</body>
</html>
7.1.2 example03
7.1.2.1 example03-1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- {{表达式}}自动绑定到vue实例当中 -->
{{title}}
</div>
<script src="./js/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
title: 'GitHub',
}
});
</script>
</body>
</html>
我们看一下vue对象:
console.log(app);
data
在内部(循环遍历)会转化成__data
同时看到以下标记,可以得出它是被代理(拦截)了。(和原生defineProperty
类似)
其次在vue
实例下方添加title
属性
同时还有一个$data
因此根据以上,我们知道了可以通过很多方式访问这个数据 =>
app.title
app.$data.title
...
我们仔细观察这些vue
属性,会发现规律,要不就是$
开头,要不就是下划线__
开头。
我们在data
属性配置的数据会在vue
对象中转成对应属性。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.08
Branch: branch01commit description:a0.08(example03-1-组件中data的访问)
tag:a0.08
7.1.2.2 example03-2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- {{表达式}}自动绑定到vue实例当中 -->
{{_title}}
</div>
<script src="./js/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
_title: 'GitHub'
}
});
/**
* 注意
* 在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称
*/
console.log(app);
</script>
</body>
</html>
注意:在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称,因为它防止我们定义的属性把其默认属性覆盖了。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.09
Branch: branch01commit description:a0.09(example03-2-组件中data的访问的注意事项)
tag:a0.09
7.2 数据到视图的更新
vue 会把 data
中的数据挂载到实例属性下,同时对它们进行主动的监听拦截,当数据发生变化的时候,重新渲染模板。我们可以通过实例对象对数据进行修改
app.title = '1111';
// or
app.$data.title = '1111';
7.2.1 demo
我们运行上例后修改title
,发现页面也重新渲染了,这就是响应式。
因为在vue
当中拦截了数据,对数据进行了defineProperty
处理,所以当数据发生改变,页面会重新渲染整个组件中与数据关联的部分,这就数据到视图的更新
。
7.3 检测变化的注意事项
在 vue3 之前,数据的监听是通过 Object.defineProperty
方法(它有很多坑)来实现的,但是该方法只能监听拦截单个数据,对于对象新增属性无法监听拦截。所以,对于数据对象中新增的属性,我们需要调用 vue 提供的方法来进行处理。
7.4 扩展
通过 Object.defineProperty
监听拦截中存在一些问题
- 属性新增属性
- 数组方法:push、pop、shift、unshift、splice、sort、reverse
- 数组新增值:[]
- 数组 length 属性
以上的操作中并不会触发监听拦截
vue
对数组中的push
、pop
等方法进行重新包装,所以在vue
中调用这些方法,可以对数组的修改进行监听拦截[https://cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E5%BC%82%E6%96%B9%E6%B3%95-mutation-method](
7.4.1 属性新增属性
它是对对象属性
的拦截而不是对对象
的拦截,所以如果是已经被拦截过
的属性,它的变化会使视图更新的,而对于没有拦截
新增的属性它是不能使视图更新的。
7.4.1.1 example04
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- {{表达式}}自动绑定到vue实例当中 -->
{{title}}
<hr>
<p>用户名:{{user.username}}</p>
<p>年龄:{{user.age}}</p>
<hr>
</div>
<script src="./js/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
title: 'GitHub',
user: {
username: '6xiaodi'
}
}
});
/**
* 注意
* 在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称
*/
console.log(app);
</script>
</body>
</html>
我们可以发现,模板里写了{{user.age}}
,刚开始没有该属性,后面我们新增了这个属性,为啥显示不出来呢?这就是Object.defineProperty
带来的一个问题。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.10
Branch: branch01commit description:a0.10(example04-属性新增属性带来隐患)
tag:a0.10
7.4.1.1.1 深入原理
研究一下原因:研究原生代码——扩展-defineProperty
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let data = {
username: '小明',
gender: '男'
};
Object.defineProperty( data, 'username', {
get() {
},
set() {
console.log('被修改了,开始更新视图');
}
} );
</script>
</body>
</html>
我们修改username
属性,很容易理解它进入set
方法,如果是vue
,则进入视图更新逻辑。
如果新增一个没经defineProperty
处理的属性,是没有意义的,不会调用set
方法,因此后续我们通过.
操作新增的属性,是没有经过defineProperty
拦截的,因此vue
是不会进入视图更新逻辑的。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.11
Branch: branch01commit description:a0.11(属性新增属性带来隐患——深入原理-演示属性新增)
tag:a0.11
7.4.1.1.1.1 封装
那怎么办呢?希望后续增加的属性,希望也可有这种效果。因此这就需要封装了。
let data = {
username: '小明',
gender: '男'
};
for (let k in data) {
if (data.hasOwnProperty(k)) {
dp(data, k);
}
}
function dp(obj, key) {
Object.defineProperty( obj, key, {
get() {
},
set() {
console.log('被修改了,开始更新视图');
}
} );
}
这样一上来就可默认将所有属性经过defineProperty
处理了。
但是对于新增的属性还是没有效果。
我们调用dp
方法才可以,之后就可以触发了。
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.12
Branch: branch01commit description:a0.12(属性新增属性带来隐患——深入原理-演示封装属性新增)
tag:a0.12
7.4.1.1.2 vue.set
在vue
中提供了一种静态方法,我们往下看!
Vue.set
和app.$set
,它俩是等同的,没有任何区别。
这个时候同步修改age
,视图也会更新。
7.5 其余扩展
以下的概念和属性新增属性的情况是一样的,因为数组方法内部和新增值方法、数组length
都是属于后续新增的数据,数组的第n
个这个索引其实就是对象的key
,它一上来只会对数组已有的数据进行处理。
因此数组本质上也是改变不了的,那怎么解决呢?
但在vue
里看起来数组貌似没问题,我们来看看:
- 数组方法:push、pop、shift、unshift、splice、sort、reverse
- 数组新增值:[]
- 数组 length 属性
以上的操作中并不会触发监听拦截
7.5.1 example05
7.5.1.1 example05-1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<!-- {{表达式}}自动绑定到vue实例当中 -->
{{title}}
<hr>
<p>用户名:{{user.username}}</p>
<p>年龄:{{user.age}}</p>
<hr>
{{arr}}
</div>
<script src="./js/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
title: 'GitHub',
user: {
username: '6xiaodi'
},
arr: ['a', 'b']
}
});
/**
* 注意
* 在 data 中避免(禁止)创建 以 _ 或 $ 开头的数据名称
*/
console.log(app);
</script>
</body>
</html>
我们发现了很奇怪的问题,修改数组中已存在的数据,页面不刷新;
而我们新增(push
)数据,却刷新了页面,并且把之前修改数组中已存在的数据,也更新进去了。
这是为什么呢?
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.13
Branch: branch01commit description:a0.13(example05-1——属性新增属性-数组)
tag:a0.13
7.5.1.2 example05-2
我们去看看原生js
的代码。
let data = {
username: '小明',
gender: '男'
};
let arr = ['a', 'b'];
for (let k in data) {
if (data.hasOwnProperty(k)) {
dp(data, k);
}
}
for (let k in arr) {
if (arr.hasOwnProperty(k)) {
dp(arr, k);
}
}
function dp(obj, key) {
Object.defineProperty( obj, key, {
get() {
},
set() {
console.log('被修改了,开始更新视图');
}
} );
}
数组和对象属性的原生操作没啥区别!就算我们调用push
(新增数组),也不会触发set
方法的。
那vue
是如何实现的呢?
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.14
Branch: branch01commit description:a0.14(example05-2——属性新增属性-数组-对比原生)
tag:a0.14
7.5.1.3 example05-3
简单原理实现:重写数组push
方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let data = {
username: '小明',
gender: '男'
};
let arr = ['a', 'b'];
Array.prototype.push = function(val) {
let k = this.length;
dp(this, k);
arr[k] = val;
};
for (let k in data) {
if (data.hasOwnProperty(k)) {
dp(data, k);
}
}
for (let k in arr) {
if (arr.hasOwnProperty(k)) {
dp(arr, k);
}
}
function dp(obj, key) {
Object.defineProperty( obj, key, {
get() {
},
set() {
console.log('被修改了,开始更新视图');
}
} );
}
</script>
</body>
</html>
参考:https://github.com/6xiaoDi/blog-vue-Novice/tree/a0.15
Branch: branch01commit description:a0.15(example05-3——属性新增属性-js原理实现)
tag:a0.15
vue
对数组中的push
、pop
等方法进行重新包装,所以在vue
中调用这些方法,可以对数组的修改进行监听拦截https://cn.vuejs.org/v2/guide/list.html#%E5%8F%98%E5%BC%82%E6%96%B9%E6%B3%95-mutation-method
7.5.2 小结
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
举个例子:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue
相同的效果,同时也将在响应式系统内触发状态更新:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set
实例方法,该方法是全局方法 Vue.set
的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决第二类问题,你可以使用 splice
:
vm.items.splice(newLength)
7.6 使用 Vue.set 方法添加新数据
<!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="app">
<p>title: {{title}}</p>
<p>user.username: {{user.username}}</p>
<p>user.gender: {{user.gender}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: '#app',
data: {
title: 'vue 框架',
user: {
username: 'xiaomi'
}
}
});
</script>
</body>
</html>
在模板中,我们使用了一个不存在的数据 user.gender
如果我们通过 app.user.gender = '男'
的方式来新增,是不会被 vue 拦截监听处理的,我们需要使用
// 这两方法是等同的,没有任何区别
Vue.set(app.user, 'gender', '男');
// 实例.$set 是 Vue.set 的别名
app.$set(app.user, 'gender', '男');
这样的方式,set
方法给 app.user
添加 gender
属性的同时,对它进行了 defineProperty
(后续待补充)