引入:使用jquery和其他框架的区别
原生JS实现一个todo-list
<body>
<div>
<input type="text" name="" id="txt-title"> <br>
<button id="btn-submit">submit</button>
</div>
<div>
<ul id="ul-list"></ul>
</div>
<script>
let $txtTitle = document.getElementById('txt-title');
let $buttonSubmit = document.getElementById('btn-submit');
let $ulList = document.getElementById('ul-list');
$buttonSubmit.addEventListener('click', function () {
let title = $txtTitle.value;
if(!title) return false;
let $li = document.createElement('li');
$li.innerText = title;
$ulList.appendChild($li);
$txtTitle.value = '';
})
</script>
</body>
vue实现todo-list
<body>
<div id="app">
<div>
<input v-model="title"> <br>
<button id="btn-submit" v-on:click="add">submit</button>
</div>
<div>
<ul id="ul-list">
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</div>
<script>
let vm = new window.Vue({
el: '#app',
data: {
title: '',
list: []
},
methods: {
add: function () {
this.list.push(this.title);
this.title = '';
}
}
})
</script>
</body>
两者之间的区别
- 数据和视图分离(开放封闭原则: 扩展开放,修改封闭)
- 数据驱动视图
对mvvm的理解
具体的理解自己再去整理
MVVM框架的三大要素:
响应式、模板引擎、渲染
响应式的实现
修改data属性之后,立马就能监听到。
data属性挂在到vm实例上面。
有下面的一个问题,我们是如何监听属性的获取和属性的赋值的。
let obj = {
name: 'yanle',
age: 25
};
console.log(obj.name);
obj.age = 26;
是通过Object.defineProperty 实现的, 下面的代码就可以实现一个完整的属性修改和获取的监听。
let vm = {};
let data = {
name: 'yanle',
age: 25
};
let key, value;
for (key in data) {
(function (key) {
Object.defineProperty(vm, key, {
get: function () {
console.log('get', data[key]);
return data[key]; // data的属性代理到vm 上
},
set: function (newValue) {
console.log('set', newValue);
data[key] = newValue;
}
})
})(key)
}
vue中的模板
模板
本质就是字符串;
有逻辑: if for 等;
跟html格式很像, 但是区别很大;
最终要转为HTML来现实;
模板需要用JS代码来实现, 因为有逻辑,只能用JS来实现;
render函数-with用法:
let obj = {
name: 'yanle',
age: 20,
getAddress: function () {
alert('重庆')
}
};
// 不用with 的情况
// function fn() {
// alert(obj.name);
// alert(obj.age);
// obj.getAddress();
// }
// fn();
// 使用with的情况
function fn1() {
with (obj) {
alert(name);
alert(age);
getAddress();
}
}
fn1();
这种with 的使用方法就如上所述。但是尽量不要用,因为《JavaScript语言精粹》中,作者说过,这种使用方式会给代码的调试带来非常大的困难。
但是vue源码中的render 就是用的这个;
模板中的所有信息都包含在了render 函数中。
一个特别简单的示例:
let vm = new Vue({
el: '#app',
data: {
price: 200
}
});
// 一下是手写的
function render() {
with (this) { // 就是vm
_c(
'div',
{
attr: {'id': 'app'}
},
[
_c('p', [_v(_s(price))])
]
)
}
}
function render1() {
return vm._c(
'div',
{
attr: {'id': 'app'}
},
[
_c('p', [vm._v(vm._s(vm.price))]) // vm._v 是创建文本, _s 就是toString
]
)
}
如果我们用一个复杂的例子来描述这个东西。在源码中, 搜索code.render, 然后在在此之前打印render 函数,就可以看看这个到底是什么东西了。
var createCompiler = createCompilerCreator(function baseCompile (
template,
options
) {
var ast = parse(template.trim(), options);
if (options.optimize !== false) {
optimize(ast, options);
}
var code = generate(ast, options);
console.log(code.render);
return {
ast: ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
});
然后运行, 就可以看到到底render 函数是什么东西了。 就可以截取源码出来看了。
相对应的模板如下:
<div id="app">
<div>
<input v-model="title"> <br>
<button id="btn-submit" v-on:click="add">submit</button>
</div>
<div>
<ul id="ul-list">
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</div>
截取的render函数如下:
function codeRender() {
with (this) {
return _c('div',
{attrs: {"id": "app"}},
[
_c('div', [
_c('input', {
directives: [{
name: "model",
rawName: "v-model",
value: (title), // 渲染 指定数据
expression: "title"
}],
domProps: {"value": (title)}, // 渲染 指定数据
on: { // 通过input输入事件, 修改title
"input": function ($event) {
if ($event.target.composing) return;
title = $event.target.value
}
}
}),
_v(" "), // 文本节点
_c('br'),
_v(" "),
_c('button', { // dom 节点
attrs: {"id": "btn-submit"},
on: {"click": add} // methods 里面的东西也都挂在this上面去了
},
[_v("submit")])]),
_v(" "),
_c('div', [
_c('ul',
{attrs: {"id": "ul-list"}},
_l((list), function (item) { // 数组节点
return _c('li', [_v(_s(item))])
})
)
])
])
}
}
从vue2.0开始支持预编译, 我们在开发环境下,写模板, 编译打包之后, 模板就变成了JS代码了。vue已经有工具支持这个过程。
vue中的渲染
vue的渲染是直接渲染为虚拟dom ,这一块儿的内容,其实是借鉴的snabbdom, 非常相似,可以去看看snabbdom 就可以一目了然了。
vue中的具体渲染实现:
整体流程的实现
第一步: 解析模板形成render 函数
- with 用法
- 模板中的所有数据都被render 函数包含
- 模板中data的属性,变成了JS变量
- 模板中的v-model、v-for、v-on都变成了JS的逻辑
- render函数返回vnode
第二步: 响应式开始监听数据变化
- Object.defineProperty 的使用
- 讲data中的属性代理到vm 上
第三步: 首次渲染,显示页面,而且绑定数据和依赖
- 初次渲染, 执行updateComponent, 执行vm._render();
- 执行render函数, 会访问到vm.list和vm.title等已经绑定好了的数据;
- 会被详情是的get 方法监听到
为何一定要监听get, 直接监听set 不行吗? data中有很多的属性,有的被用到了,有的没有被用到。被用到的会走get, 不被用到的不会走get。
没有被get监听的属性,set的时候也不会被坚挺。为的就是减少不必要的重复渲染,节省性能。 - 执行updateComponent的时候,会执行vdom的patch方法
- patch 讲vnode渲染为DOM, 初次渲染完成
第四步: data属性变化,出发render
- 修改属性值, 会被响应式的set监听到
- set中会执行updateComponent, 重新执行vm.render()
- 生成vnode和prevVnode, 通过patch进行对比
- 渲染到html中