文件目录树:
引入方式:
-
- 开发版本--全局暴露vue的构造函数
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
- 或者npm模块下载2.0版本
npm i vue@2
导入
在src目录下新建index.js 导入vue模块
impot Vue from 'vue/dist/vue'
创建vue实例
到这里我们就可以在当前文件下创建一个vue实例
const vm = new Vue({
// 元素--表示在根目录哪个区域归我所管
el:'#app',
// 模板--挂载后会替换掉同位置原有标签,重新渲染
template:'<h1 v-on:click="foo">{{c}}</h1>',
// 数据源
data(){
return {
a:1,
b:[1,2,3]
}
},
// 方法 -- 包含一些节点事件,业务逻辑等等
methods:{
foo(){
console.log('v-on的打印事件')
}
}
})
与此同时,我们还应该在根目录html文件中新建:
<div id="app">
<h1>{{b}}</h1>
</div>
id为app就是我们在上面实例中el里的挂载点,且实例创建完成后,template模板会替换掉index.html里id为app节点下面的标签,重新渲染页面
Vue的选项配置 el template $mount
三者存在一定的依赖关系,具体大致分为五种情况:
- ① :
如果el + template 都存在- 先编译template => html片段 => (el)替换掉提供的挂载目标节点
- ② :
如果只存在el 不存在template- 将获取到el.outerHTML的字符串 自动作为template属性 继续①的流程
- ③ :
两者都不存在- 不会编译,也不会渲染
- ④ :
补救措施:- 利用vm.$mount('#app')手动地挂载一个未挂载的实例。这个方法返回实例自身,因而可以链式调用其它实例方法。
- ⑤ :
如果在使用$mount()时未添加节点(ElementOrSelector : 元素选择器),但也想把节点渲染到页面中,- 模板将被渲染为文档之外的的元素,并且你必须使用原生 DOM API 把它插入文档中。
document.body.replaceChild(vm.$el,要替换的当前节点);
具体代码实现如下:
//第一种:el + template都存在
const vm1 = new Vue({
el: '#app',
template: '<h1>{{msg}}</h1>',
data() {
return {
msg: 'el + template',
};
},
});
//第二种:不存在el,实现$mount(#app)手动挂载
const vm2 = new Vue({
template: '<h1>{{msg}}</h1>',
data() {
return {
msg: 'template + $mount(el)',
};
},
}).$mount('#app');
//第三种:写了$monunt 但没有标明具体的挂载点,则利用原生Dom节点手动替换
const vm3 = new Vue({
template: '<h1>{{msg}}</h1>',
data() {
return {
msg: 'template + $mount + document.body.appChild(vm.$el)',
};
},
}).$mount();
document.body.replaceChild(vm.$el,要替换的当前节点);
//第四种:只存在el 不存在template 将获取到el.outerHTML的字符串 自动作为template属性 继续①的流程
const vm4 = new Vue({
el: '#app',
data() {
return {
msg: 'el + el.outerHTML as template',
};
},
});
代理 data 和 methods
原理:
1.什么是数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
就比如你定义了一个对象a和一个对象b,你可以用b里面的属性来修改a里面的属性,这个是响应式的,那么这个究竟是怎么做到的呢
2.原理
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor)
复制代码
- 其中obj是你要要定义属性的对象
- prop是你要定义的属性
- desciptor 是要定义或修改的属性描述符
通过这个方法添加的属性默认情况下是不可以被枚举的
如果你想要其中的属性可以被枚举的话,就可以使用以下配置
Object.defineProperty(a,'name',{
value:'badspider',
enumerable: true
})
复制代码
那么vue中的数据代理就是依据这个方法的getter和setter
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更
let a = {
name:"badspider"
}
let b = {}
object.defineProperty(b,'name',{
get(){
console.log('我被调用了')
return a.name
}
set(value){
console.log('我被修改了')
return a.name = value
}
})
复制代码
当你需要读取这个里面的数据的时候,get函数(getter)就会被调用,将其中的数据给你,当你修改数据的时候,set函数(setter)就会被调用来修改data中的值
vue中的具体底层代码实现:
function proxyProperty(option){
// option 代表传进来的实例
if(!option.data) return data;
if(typeof option.data === 'function'){
//如果是有一个函数就将执行后的结果赋值给option.$data
option.$data = option.data()
};
if(typeof option.data === 'object'){
//如果是一个对象,则直接赋值
option.$data = option.data;
};
//此时打印Object.entries(option.$data)得到一个二维键值对的数组
console.log(Object.entries(option.$data));
//代理data
Object.entries(option.$data).forEach([key]=>{
Object.defineProperty(option.$data,key,{
get(){
return option.$data[key]
};
set(value){
option.$data[key] = value
}
})
})
Object.entries(option.methods || {} ).forEach([key]=>{
Object.defineProperty(option.methods,key,{
get(){
// 修改this指向 => 实例对象
return option.methods[key].bind(option)
};
set(value){
// 添加判断是否为函数
if(typeof value !== 'function')return
option.methods[key] = value
}
})
})
//返回实例对象
return option
}