赞
踩
一、mustache模板引擎(为vue所借鉴)
Vue.js内部使用了Mustache模板引擎来实现数据绑定和渲染
- 背景:什么是模板引擎?是将数据要变为视图最优雅的解决方案。
<li v-for="item in arr"></li>这实际上就是一种模板引擎
- 曾经出现的数据变为视图的方法:
• 纯DOM法:即DOM操作添加数据到DOM节点,节点再挂载到容器上.
• 数组join法: 为省略繁琐的DOM操作,而字符串又不能换行,于是使用数组字符串元素连接.
• ES6的反引号法 :省略数组字符串元素连接, 但当循环内再有循环, 仍热会复杂.
• 模板引擎
- mustache的基本使用
* mustache是“胡子”的意思,因为它的嵌入标记{{ }}非常像胡子.
* mustache是最早的模板引擎库,比Vue诞生的早多了,它的底层实现机理在当时是非常有创造性的、轰动性的.
1.必须要引入mustache库,可以在bootcdn.com上找到.
2.mustache的模板语法: Mustache.render(templateStr, data), 调用后生成可以渲染的页面DOM结构,再挂载到容器即可.
(类似vue,准备好模板、容器、数据)
- mustache的底层核心机理
过程:模板字符串-(编译)-> tokens数组-(结合数据+解析)->dom字符串
tokens特点:
1.tokens是一个JS的嵌套数组,就是模板字符串的JS表示;它是“抽象语法树”、“虚拟节点”等等的开山鼻祖
2.形态:
a.text存其余html和文本,name存变量。
[
["text", "<h1>我买了一个"], (一个token)
["name", "thing"]
]
b.当模板字符串中有循环存在时,它将被编译为嵌套更深的tokens
<div>
<ul>
{{#arr}}
<li>{{.}}</li>
{{/arr}}
</ul>
</div>
[
["text", "<div><ul>"],
["#", "arr", [
["text", "<li>"],
["name", "."],
["text", "</li>"]
]],
["text", "</ul></div>"]
]
-手写实现mustache库
1.mustache官方库使用rollup进行模块化打包,而我们使用webpack(webpack-dev-server)进行模块化打包,
这是因为它能让我们更方便地在浏览器中(而不是nodejs环境中)实时调试程序,相比nodejs控制台,
浏览器控制台更好用,比如能够点击展开数组的每项。
2.生成库是UMD的,这意味着它可以同时在nodejs环境中使用,也可以在浏览器环境中使用。实现UMD不难,只需要一个“通用头”即可(不懂)
手写过程:
-js模块化,利用webpack模块化打包工具识别
-npm i -D webpack@4 webpack-dev-server@3 webpack-cli@3(4版本兼容性好)
-配置热更新,使用没有生成文件的方式,打包后的文件在内存里
webpack.config.js文件:
const path = require('path');
module.exports = {
mode: 'development',
entry: './index.js',
output: {
filename: 'bundle.js'
},
devServer: {
contentBase: path.join( dirname, "www"),
compress: false,
port: 8080,
// 虚拟打包的路径,bundle.js文件没有真正的生成
publicPath: "/xuni/"
}
};
-怎么运行?启动服务,打开端口,修改文件后保存就有热更新。
-代码思路:
1.全局暴露一个对象,拥有render方法(传入模板字符串和数据),方法先从模板字符串获取token数组,再将tokens变成真正的DOM字符串。
2.模板字符串获取token数组(重点):
设置扫描器(类),功能是从0索引开始扫描字符串、决定返回/跳过字符,其拥有:(实例化时传入要转化的模板字符串)
一个游标指针变量,从0开始
一个尾巴,存剩余长度的字符串(初始值等于传入的模板字符串)
scan方法,跳过{{ (或者}}),一次调用跳一次{{ (或者}}),此时游标和尾巴对应变化要跳的tag长度。
scanUntil方法,收集token的值,返回到text/name中。
扫描器执行思路:
1.先找{{ (特殊情况:找不到便全部返回/指标到头 不执行)
2.找到则停止收集,本次收集完成
3.跳过{{ (特殊情况:如果指标到头,不执行)
4.再找}}
5.找到则停止收集,本次收集完成
6.跳过}}
反复循环(使用扫描器跳出循环的条件:指针刚好溢出。 扫描器定义时也能处理循环条件出问题时不溢出)(类似安全使用规范,使用者、设计者的分开)
3.将扫描器通过{{、}}收集到的tokens数组,进一步处理含#和/的折叠情况,返回最终tokens。
4.再将tokens处理成最终返回DOM字符串,即变量替换成传入数据,并解析有数组循环情况。
二、数据响应式原理(vue2更新原理)
侵入式:通过明显的api设置,修改数据和视图
非侵入式:改变属性,视图自动变化
1.Object.defineProperty()方法:给指定的对象定义属性
优点:配置对象可以有额外隐藏属性及get/set方法
Object.defineProperty(obj, 'a', {
value: 3,
// 是否可写
writable: false
});
Object.defineProperty(obj, 'b', {
value: 5,
// 是否可以被枚举,在for..in遍历对象的情况下
enumerable: false
})
Object.defineProperty(obj, 'a', {
// getter
get() {
console.log('你试图访问obj的a属性');
},
// setter
set() {
console.log('你试图改变obj的a属性');
}
});
注意:value和get属性不能同时设置
2.defineReactive函数
背景:
-getter/setter需要中转变量才能工作
var obj={};
var temp;
Object.defineProperty(obj, 'a', {
get() {
console.log('你试图访问obj的a属性');
return temp;
},
set(newValue) {
console.log('你试图改变obj的a属性', newValue);
temp = newValue;
}
})
-使用defineReactive函数不需要设置临时变量了,而是用闭包
(包括之前vue学习的,是一个对象代理到另一个空对象,也是有中间变量。这里解决了中间变量问题,为铺垫了对象往自己属性追加get/set的需求)
export default function defineReactive(data, key, val) {
if (arguments.length == 2) { //未赋值时
val = data[key];
}
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可以被配置,比如可以被delete
configurable: true,
// getter
get() {
console.log('你试图访问' + key + '属性');
return val;
},
// setter
set(newValue) {
console.log('你试图改变' + key + '属性', newValue);
if (val === newValue) {
return;
}
val = newValue;
}
});
};
3.处理不设置值的情况、嵌套属性无法检测get/set的情况。
(首先明白,defineReactive函数是相当于为对象添加 有get/set的属性、或者给已有属性添加get/set)
var obj = {
a: {
m: {
n: 5
}
}
}
defineReactive(obj, 'a'); //不赋值,给obj的a属性增加get/set。
console.log(obj.a.m.n); //只能输出 读取了obj的a属性,没有具体读取了哪些
export default function defineReactive(data, key, val) {
if (arguments.length == 2) { //追加旧属性时,使用该对象的属性原来的默认值,没有则为undefined。(顺便解决了新属性也不赋值的问题)
val = data[key];
}
Object.defineProperty(data, key, {
...
});
};
接下来,引入需求:监测层级深的属性,给对象的所有属性直接添加监听。
observe类:任何一个对象,使得其所有层级属性都能添加检测到。
-深层次监测:
上面定义的defineProperty()函数,不能监听到对象嵌套的形式。
实现过程:(调用一个方法,给对象全部属性添加深层监听)
observe方法:传入的可能是对象或者其它值,如果是对象,且没有__ob__属性(说明没有添加过监听),则new Observer继续监测其下的属性。
Observer类:传入对象进行实例化后,1.给传入对象添加__ob__属性(值是当前Observer类的实例,不可枚举)。
2.遍历对象属性的每个key,分别执行defineReactive(对象,key), 逐个添加监测。
__ob__属性:区分该对象有没有添加监听过。
defineReactive方法:传入对象、属性、[属性值],为该对象的属性添加监测。1.如果有传入属性值,执行observe方法,因为可能值是一个对象 (这个情况只有在单独使用defineReactive且传入了新值才会有效。)
2.set函数的newValue,也要执行observe方法,因为可能赋值一个对象。
(上面两点其实就是,无论是新增/修改属性,其值是对象都要进行监测
注意:1.即便传入空对象也会添加__ob__属性,只是没有属性可以检测
2.解释了vue中直接对象.属性添加,不会有监听效果。 )
---------------------<--------------------------
| |
| |
observe(obj) --> Observer.js --> defineReactive.js
|
|
def.js (添加__ob__属性作为标记)
-考虑数组类型检测:
背景:
Vue是通过改写数组的七个方法(可以改变数组自身内容的方法)来实现对数组的响应式处理
这些方法分别是:push、pop、shift、unshift、splice、sort、reverse
这七个方法都是定义在Array.prototype上,要保留方法的功能,同时增加数据劫持的代码
前置知识:
Object.setPrototypeOf(goalObj, newPrototype) (其实就是改变对象的原型,该方法还会返回新的对象)
参数1:要设置其原型的对象
参数2:新原型对象
Object.create(proto, [propertiesObject]) 创建一个新对象,带着指定的__proto__属性(即原型对象)和属性。(es6新增)
参数proto: 即将新创建对象的原型对象。
参数propertiesObject: 可选。传入一个对象,将其属性作为新对象的属性
思路:
思路就是 以Array.prototype为原型,创建一个新对象arrayMthods,
然后在新对象arrayMthods上添加操作数组的方法(属性),需仍保留原生Array的原型方法去操作数组。
(展开:1.新对象arrayMthods的方法返回值还是原生Array的原型去处理
2.如果是插入新元素,需对新元素添加监听,新元素为一个数组,无论里面有多少项;
3.数组一定不是最高层,已经监听过外层了,有__ob__的属性存放实例,该实例有observeArray方法检测新元素。
)
最后,让数组的原型指向arrayMthods,之后调用7个数组方法便会触发自定义的原型上的方法(在原数组和数组原型之间,多一层自定义的原型)
-收集依赖
前面之所以要劫持数据,目的是当数据的属性发生变化时,可以通知那些曾经用到的该数据的地方(页面DOM/组件)。
而将这些用到数据的地方先收集起来,等属性改变,再触发一遍就好了
*收集方式:在getter中收集依赖
*触发方式 (一行代码)
对象是在setter中触发依赖
数组是在拦截器(重写后的Array原型对象)中触发依赖
Dep类(depend):负责管理依赖,可以收集依赖、删除依赖、向依赖发送通知 (每个Observer的实例(__ob__),成员中都有一个Dep的实例,即检测过的每一层属性都有dep依赖关系)
两次new Dep,一次是Observer实例中,一次是defineReactive闭包中,为什么?
Watcher类:依赖,即需要用到数据的地方(中介的角色,由它交互DOM、组件)
实现过程:代码不是很懂
1.哪个Watcher实例读数据触发了getter,就把哪个Watcher实例 收集到Dep 中.
2.当数据发生变化时触发setter/数组执行方法,Dep会循环依赖列表,把所有的Watcher 都通知一遍。
Component
页面 ---<--- Render ---<--- Render Function <-------
| |
| |(通知组件更新)
| |
-------------> |
(读数据) | |
Data -->-- Observer -->-- Data: 1.getter -------<-------- Watcher
-----> |
2.setter | |
| | (收集依赖) |
| | |(通知watcher)
(通知依赖) | | |
--------> Dep--------->
观察者模式: 也可以简略理解为订阅-发布模式,一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。