算法
平衡二叉树
interface Node {
key: number,
left: any,
right: any
}
class AVLTree{
private root:any = null;
private getNode(key:number):Node{
return {
key,
left: null,
right: null
};
}
private getNodeHeight(node:Node){
if(node === null){
return 0;
}
var leftHeight = this.getNodeHeight(node.left);
var rightHeight = this.getNodeHeight(node.right);
return 1 + Math.max(leftHeight, rightHeight);
}
private isBalance(node:Node){
var leftHeight = this.getNodeHeight(node.left);
var rightHeight = this.getNodeHeight(node.right);
if(leftHeight - rightHeight >= 2){
return node;
}else if(rightHeight - leftHeight >= 2){
return node;
}else{
var leftIsBalance = node.left ? this.isBalance(node.left) : null;
if(leftIsBalance){
return leftIsBalance
}
var rightIsBalance = node.right ? this.isBalance(node.right) : null;
if(rightIsBalance){
return rightIsBalance
}
return null;
}
}
private getDirection(node:Node):string{
var left = this.getNodeHeight(node.left);
var right = this.getNodeHeight(node.right);
return left > right ? 'left' : 'right'
}
private findParentNode(parentNode:Node, targetNode:Node){
if(parentNode.left && parentNode.left.key === targetNode.key || parentNode.right && parentNode.right.key === targetNode.key ){
return parentNode;
}
if(parentNode.key > targetNode.key && parentNode.left){
return this.findParentNode(parentNode.left, targetNode);
}
if(parentNode.key < targetNode.key && parentNode.right){
return this.findParentNode(parentNode.right, targetNode);
}
}
private rotate (balanceNode:Node){
var direction = this.getDirection(balanceNode);
var nextNode = balanceNode[direction];
var nextNodeLeft = nextNode.left;
var nextNodeRight = nextNode.right;
if(direction === 'left'){
if((!nextNodeLeft && nextNodeRight) || (nextNodeLeft && nextNodeRight && this.getNodeHeight(nextNodeLeft) < this.getNodeHeight(nextNodeRight))){
nextNodeRight.left = nextNode;
nextNode.right = null;
nextNode = nextNodeRight;
}
if(!nextNode.right){
balanceNode.left = null;
nextNode.right = balanceNode;
}else{
balanceNode.left = nextNode.right;
nextNode.right = balanceNode;
}
}else{
if((nextNodeLeft && !nextNodeRight) && (nextNodeRight && nextNodeLeft && this.getNodeHeight(nextNodeLeft) > this.getNodeHeight(nextNodeRight))){
nextNodeLeft.right = nextNode;
nextNode.left = null;
nextNode = nextNodeLeft;
}
if(!nextNode.left){
balanceNode.right = null;
nextNode.left = balanceNode;
}else{
balanceNode.right = nextNode.left;
nextNode.left = balanceNode;
}
}
if(this.root === balanceNode){
this.root = nextNode;
}else{
this.findParentNode(this.root, balanceNode)[direction] = nextNode;
}
this.balanceTree();
}
private balanceTree(){
var isBalance = this.isBalance(this.root);
if(isBalance){
this.rotate(isBalance);
}
}
private insertNode(node:Node, parentNode:Node){
if(node.key < parentNode.key){
if(parentNode.left === null){
parentNode.left = node;
this.balanceTree();
}else{
this.insertNode(node, parentNode.left);
}
}else{
if(parentNode.right === null){
parentNode.right = node;
this.balanceTree();
}else{
this.insertNode(node, parentNode.right);
}
}
}
public insert(key:number){
var node = this.getNode(key);
if(this.root === null){
this.root = node;
}else{
this.insertNode(node, this.root);
}
}
public search(key:number, _node?:Node){
var node = _node === undefined ? this.root : _node;
if(!node){
return false;
}
if(node.key === key){
return true;
}
if(node.key > key){
return this.search(key, node.left);
}
if(node.key < key){
return this.search(key, node.right);
}
}
public min(_node?:Node){
var node = _node === undefined ? this.root : _node;
if(node.left){
return this.min(node.left);
}
return node.key
}
public max(_node?:Node){
var node = _node === undefined ? this.root : _node;
if(node.right){
return this.max(node.right);
}
return node.key
}
public inOrderTraverse(_node?:Node){
var node = _node === undefined ? this.root : _node;
node.left && this.inOrderTraverse(node.left);
console.log(node.key);
node.right && this.inOrderTraverse(node.right);
}
public preOrderTraverse(_node?:Node){
var node = _node === undefined ? this.root : _node;
console.log(node.key);
node.left && this.preOrderTraverse(node.left);
node.right && this.preOrderTraverse(node.right);
}
public postOrderTraverse(_node?:Node){
var node = _node === undefined ? this.root : _node;
node.left && this.postOrderTraverse(node.left);
node.right && this.postOrderTraverse(node.right);
console.log(node.key);
}
public remove(key:number, _node?:Node){
var node = _node === undefined ? this.root : _node;
if(node.key > key && node.left){
node.left = this.remove(key, node.left);
}else if(node.key < key && node.right){
node.right = this.remove(key, node.right);
}else if(node.key === key){
if(node.left === null && node.right === null){
node = null;
}else if(node.left === null && node.right){
node = node.right;
}else if(node.right === null && node.left){
node = node.left;
}else{
node.key = this.max(node.left);
node.left = this.remove(node.key, node.left);
}
}
if(_node === undefined){
this.balanceTree();
}
return node;
}
}
var tree = new AVLTree();
tree.insert(3);
tree.insert(5);
tree.insert(6);
tree.insert(7);
tree.insert(8);
tree.insert(9);
tree.insert(10);
tree.insert(11);
tree.insert(12);
tree.insert(13);
tree.insert(14);
tree.insert(15);
tree.insert(18);
tree.insert(20);
tree.insert(25);
console.log(tree)
console.log(tree.search(6))
console.log(tree.min())
console.log(tree.max())
console.log(tree.inOrderTraverse())
console.log(tree.preOrderTraverse())
console.log(tree.postOrderTraverse())
console.log(tree.remove(8))
console.log(tree)
图
interface BfsPathNode {
key: string,
str: string
}
class Graph {
private vertices: string[] = [];
private adjList: object = {};
private isShowMap: object = {};
private isTraverseMap: object = {};
private bfsPath: object = {};
private dfsPath: object = {};
public addVertex(v: string) {
this.vertices.push(v);
this.adjList[v] = {};
}
public addEdge(v: string, w: string) {
// 无向图
if (!this.adjList[v][w]) {
this.adjList[v][w] = 0;
}
// 有向图注释下面
if (!this.adjList[w][v]) {
this.adjList[w][v] = 0;
}
this.adjList[v][w]++;
this.adjList[w][v]++;
}
public tostring() {
var s = '';
this.vertices.map(item => {
s += item + ' -> ';
for (var i in this.adjList[item]) {
s += i + ' ';
}
s += '\n';
});
return s;
}
private printSort(key: string) {
if (!this.isShowMap[key]) {
console.log(`Visited Vertex:${key}`)
this.isShowMap[key] = true;
}
}
private printPathTo(start: string, stop: string, path: object) {
var minPathNum, minPath,
str = `${start} 到 ${stop} 的所有路径:`;
for (var i in this.bfsPath) {
if (!minPathNum || minPathNum > this.bfsPath[i]) {
minPathNum = this.bfsPath[i];
minPath = i;
}
str += i + '、';
}
console.log(str.slice(0, -1))
console.log(`最短路径是:${minPath};长度为:${minPathNum}`);
}
private bfsSort(arr: string[]) {
if (arr.length === 0) {
return;
}
var queue = [];
arr.map(item => {
this.isTraverseMap[item] = true;
this.printSort(item);
for (var i in this.adjList[item]) {
if (!this.isTraverseMap[i]) {
queue.push(i);
}
this.printSort(i);
}
});
this.bfsSort(queue);
}
public bfs(start: string) {
this.isShowMap = {};
this.isTraverseMap = {};
this.bfsSort([start]);
}
private bfsPathToSort(bfsPathNodeArr: BfsPathNode[], stop: string) {
if (bfsPathNodeArr.length === 0) {
return;
}
var queue = [];
bfsPathNodeArr.map(item => {
this.isTraverseMap[item.key] = true;
item.str += item.key;
if (stop === item.key) {
this.bfsPath[item.str] = item.str.length;
}
for (var i in this.adjList[item.key]) {
if (!this.isTraverseMap[i]) {
queue.push({
key: i,
str: item.str
});
}
}
});
this.bfsPathToSort(queue, stop);
}
public bfsPathTo(start: string, stop: string) {
this.isTraverseMap = {};
this.bfsPath = {};
this.bfsPathToSort([{
key: start,
str: ''
}], stop);
this.printPathTo(start, stop, this.bfsPath);
}
private dfsSort(arr: string[]) {
if (arr.length === 0) {
return;
}
var queue = [];
arr.map(item => {
this.printSort(item);
this.isTraverseMap[item] = true;
Object.keys(this.adjList[item]).map(_item => {
if (!this.isTraverseMap[_item]) {
queue.push(_item);
}
})
this.dfsSort(queue);
});
}
public dfs(start: string) {
this.isShowMap = {};
this.isTraverseMap = {};
this.dfsSort([start]);
}
private dfsPathToSort(bfsPathNodeArr: BfsPathNode[], stop: string) {
if (bfsPathNodeArr.length === 0) {
return;
}
var queue = [];
bfsPathNodeArr.map(item => {
this.isTraverseMap[item.key] = true;
item.str += item.key;
if (stop === item.key) {
this.bfsPath[item.str] = item.str.length;
}
Object.keys(this.adjList[item.key]).map(_item => {
if (!this.isTraverseMap[_item]) {
queue.push({
key: _item,
str: item.str
});
}
});
this.bfsPathToSort(queue, stop);
});
}
public dfsPathTo(start: string, stop: string) {
this.isTraverseMap = {};
this.dfsPath = {};
this.dfsPathToSort([{
key: start,
str: ''
}], stop);
this.printPathTo(start, stop, this.dfsPath)
}
}
var graph = new Graph();
var myVertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
myVertices.map(item => {
graph.addVertex(item);
});
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('C', 'D');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('E', 'I');
console.log(graph.tostring())
graph.bfs('A');
graph.bfsPathTo('A', 'G')
graph.dfs('A');
graph.dfsPathTo('A', 'G')
二分法搜索
Array.prototype.binarySearch = function (item) {
this.sort((a, b) => {
return a - b;
});
var low = 0,
high = this.length - 1,
mid = Math.ceil((low + high) / 2);
while (low <= high) {
if (this[mid] < item) {
low = mid + 1;
} else if (this[mid] > item) {
high = mid - 1;
} else {
return mid;
}
mid = Math.ceil((low + high) / 2);
}
return -1;
}
var arr = [3, 4, 51, 2, 4, 2, 1, 23, 4, 5, 62, 123, 1, 231, 4, 123];
console.log(arr.binarySearch(1));
斐波那契
const fibonacci = (num: number) => {
if (num === 1 || num === 2) {
return 1
}
return fibonacci(num - 1) + fibonacci(num - 2);
}
console.log(fibonacci(6))
找零问题
动态规划
将问题拆分成小问题,寻求答案
const coinsType = [1,5,10,20,50,100]
const MinConinChange = (coins:number, index?:number) => {
if(index === undefined){
index = coinsType.length - 1;
}
if(index < 0 || coins < 0){
return;
}
var nowCoin = coinsType[index];
var num = Math.floor(coins / nowCoin);
if(num){
console.log(num + '个' + nowCoin + '块');
}
var remain = coins - num * nowCoin;
remain && MinConinChange(remain, --index);
}
MinConinChange(11384)
贪心算法
期盼通过每个阶段的局部最优选择(当前最好的解),从而达到全局的最优(全局最优)
const coinsType = [1,5,10,20,50,100]
const MinConinChange = (coins:number) => {
var total = 0;
for(var i = coinsType.length; i--;){
var coin = coinsType[i];
var num = 0;
while(total + coin < coins){
++num;
total += coin;
}
num && console.log(num + '个' + coin + '块');
}
}
MinConinChange(11384)
背包问题
计算当前内容的大小
const knapsack = (weights:number[], values:number[], total:number) => {
var n = weights.length;
var f = new Array(n);
f[-1] = new Array(total+1).fill(0);
for(var i = 0 ; i < n ; i++){ //注意边界,没有等号
f[i] = new Array(total).fill(0);
for(var j=0; j<=total; j++){//注意边界,有等号
if( j < weights[i] ){ //注意边界, 没有等号
f[i][j] = f[i-1][j];
}else{
f[i][j] = Math.max(f[i-1][j], f[i-1][j-weights[i]]+values[i]);//case 3
}
}
}
return f[n-1][total]
}
var a = knapsack([2,2,6,5,4],[6,3,5,4,6],10)
知识总结
继承
js 的集成基于原型链,弄清楚几个概念:object、Object、prototype、constructor、proto
常见的几种继承
借用构造器
function Super(arg){
// constructor do something
}
Super.prototype = {}
function Sub(){
Super.call(this, arguments);
}
var sub = new Sub();
原型继承
function Super(arg){
// constructor do something
}
Super.prototype = {}
function Sub(){}
Sub.prototype = new Super();
var sub = new Sub();
组合继承
function Super(arg){
// constructor do something
}
Super.prototype = {}
function Sub(){
Super.call(this, arguments);
}
Sub.prototype = new Super();
var sub = new Sub();
寄生继承
function createAnother(original){
var clone = Object.create(original);
clone.xxx = function(){};
return clone;
}
function Super(arg){
// constructor do something
}
function Sub(){
Super.call(this, arguments);
}
Sub.prototype = createAnother(Super);
var sub = new Sub();
寄生组合式继承
function inheritPrototype(sub, super){
var clone = Object.create(Super.prototype);
clone.constructor = sub;
sub.prototype = clone;
}
function Super(){}
Super.prototype = {};
function Sub(){
Subper.call(this, arguments);
}
inheritPrototype(Sub, Super);
Sub.prototype.xxx = {}
class 继承
class Super{}
class Sub extends Super{
constructor(){
super();
}
// Getter
get aaa(){
return 'xxx'
}
// Setter
set bbb(){
return 'xxx'
}
// 静态方法
static ccc(){
}
}
var sub = new Sub();
sub.aaa // xxx
sub.bbb = 'yyy' // xxx
Sub.ccc() // 静态方法调用
双向绑定的基础实现
对象常用的方法
- in:查找原型链和当前对象本身上有没有属性
- hasOwnPeoperty:判断当前属性是否在本身上,不会找原型链
- propertyIsEnumerable:判断是否可枚举
- Object.key:返回所有可枚举属性的数组
- Object.getOwnProperyNames:只会查询对象直接包含的属性,包括不可枚举的
对象的数据属性
- [[Configurable]]:能否把属性修改为访问器属性。默认值为true。
- [[Enumerable]]:能否通过for-in遍历到。默认值为true。
- [[Writable]]:能否修改属性值。默认值为true。
- [[Value]]:值。默认值为undefined。
// 定义数据属性 Object.defineProperty(personB, "name", { configurable : false, enumerable : false, writable : false, value : "Karyn" });
对象的访问器属性
- [[Configurable]]:能否把属性修改为访问器属性。默认值为true。
- [[Enumerable]]:能否通过for-in遍历到。默认值为true。
- [[Get]]:在读取该属性值时调用的函数。默认值为undefined。
- [[Set]]:在写入属性时调用的函数。默认值为undefined。
// 通过 defineProperty 来申明属性 var person = { _name : "karyn", // 通常我们用下划线开头来表示私有变量 _age: 12, edition : 1 } Object.defineProperty(person, "name", { get: function(){ return this._name; }, set: function(newValue){ this._name = newValue; this.edition++; } }); // 申明完成之后 __defineGetter__ 扩展 person.__defineGetter__("age", function(){ return this._age; }); person.__defineSetter__("age", function(newValue){ this._age = newValue; }); // 通过 get 和 set 方法设置 var person = { $name : "karyn", edition : 1, get name(){ console.log("get",this.$name); }, set name(value){ this.$name = value; this.edition++; console.log("set",this.$name) } }
实现 Vue 双绑不含虚拟 DOM
Vue 实现双绑大概如下:
- 对数据进行递归的 Observe
- 实例化 dep,get 方法内放入 dep.addSubs,此时不会放入对应的 wather,在对模板解析的过程中会 new 一个 wather,watcher 中会先将 target 赋值为当前的 vm,然后调用 get,此时有 target 会将这个 watcher add 到 dep 的实例中,以便模板更新的时候调用 update,找到这个 watcher。
- set 方法中绑定 dep.notify,notify 调用与当前数据相关的所有 watcher
- 对模板进行递归解析,将 textnode 中的对应模板与数据进行隐射,初始化替换 node 节点内容之后,初始化一个 watcher,将改数据的 key 和 相关该 key 的回调传入 watcher
- watcher 实例化直接调用 watcher 的 get 方法,此时 get 方法中已经有一个方法,将该 watcher addSub 到依赖收集器中
- 初始化完毕
- 数据发生改变时,触发 set 方法,set 方法中触发 dep.notify,notify 触发与这个数据所有相关的 watcher.update 方法
- watcher.update 触发 run 方法,run 触发实例化 watcher 时与当前数据相关的模板回调。执行回调,发生页面改变
// Vue 简单的对象
const info = {
el: document.querySelector('#karyn'),
template: `<p></p><p></p><p></p>`,
data() {
return {
demo1: 'demo1',
demo2: 'demo2',
data: {
demo: 'demo'
}
}
}
}
// 依赖收集器
class Dep {
private subs: any[] = [];
static target = null;
addSub(sub) {
// 将 watcher 添加进来
this.subs.push(sub);
}
notify() {
this.subs.map(item => {
// 执行 watcher 的 update 方法
item.update();
})
}
}
// 指令监听器
class Watcher {
private cb; // 指令回调
private info; // 主函数信息
private exp; // 匹配的数据项
private value;
constructor(info, exp, cb: Function) {
this.cb = cb;
this.info = info;
this.exp = exp;
// 将 watcher 的 get 方法进行执行
this.value = this.get();
}
update() {
// 这里可以做个延迟,最后一次性 append dom
this.run();
}
run() {
// 深层递归
var exps = this.exp.split('.');
var value = this.info.data;
while (exps.length) {
value = value[exps.shift()];
}
var oldVal = this.value;
// 替换新旧的值执行 watcher 的回执
if (value !== oldVal) {
this.value = value;
this.cb.call(this.info, value, oldVal);
}
}
get() {
Dep.target = this;
// 深层递归
var exps = this.exp.split('.');
var value = this.info.data;
while (exps.length) {
value = value[exps.shift()];
}
Dep.target = null;
return value
}
}
// 编译模板
class Compile {
private info = {};
constructor(info) {
this.info = info;
// 编译模板
this.compileElement(info.el);
}
compileElement(node) {
var childNodes = node.childNodes;
// 将每一个子节点拿出来查询
[].slice.call(childNodes).map(item => {
var reg = /\{\{(.*)\}\}/;
var text = item.textContent;
// 如果是文本节点且有参数
if (this.isTextNode(item) && reg.test(text)) {
// 将模板中的数据进行替换
this.compileText(item, reg.exec(text)[1]);
}
// 如果还有子节点进行递归
if (item.childNodes && item.childNodes.length) {
this.compileElement(item);
}
})
}
compileText(node, exp) {
exp = exp.replace(/^\s*|\s*$/g, '');
var exps = exp.split('.');
var initText = this.info.data;
// 将对应的值拿出来,如果是多层级可能会有问题
while (exps.length) {
initText = initText[exps.shift()];
}
// 更新模板
this.updateText(node, initText);
// 实例化一个 watcher, 当 watcher 触发时执行回调
new Watcher(this.info, exp, (value) => {
this.updateText(node, value);
});
}
updateText(node, text) {
node.nodeValue = text;
}
isTextNode(node) {
return node.nodeType === 3;
}
}
// 核心组件
class Karyn {
private $data: object = {};
private data: object = {};
private template: string = '';
private el: any;
constructor(info) {
// Vue 中,这里是一个拷贝数据
this.$data = info.data.call(this);
// 将双板的数据放入 this.data 中
this.data = info.data.call(this);
// 缓存模板
this.template = info.template;
// 缓存跟节点
this.el = info.el;
// 先将节点拼接进去
this.el.innerHTML = this.template;
// 初始化双绑数据
this.observe(this.data);
// 对模板进行编译
new Compile(this);
}
observe(data) {
// 递归绑定
if (!data || typeof data !== 'object') {
return;
}
// 由于 object 是引用类型,所以递归绑定也是同一个值
Object.keys(data).map(item => {
// 真正绑定的方法
this.defineReactive(data, item, data[item]);
});
}
defineReactive(data, key, val) {
// 递归调用
this.observe(val);
// 初始化一个以来收集器
var dep = new Dep();
// 对一个数据进行绑定
Object.defineProperty(data, key, {
get() {
// 第一次会默认调起
// 如果需要添加订阅者
if (Dep.target) {
// 添加订阅者
dep.addSub(Dep.target);
}
return val
},
set(newVal) {
if (val === newVal) {
return;
}
val = newVal;
// 赋值之后分发事件
dep.notify();
}
})
}
}
var karyn = new Karyn(info)
setTimeout(() => {
karyn.data.data.demo = '000';
karyn.data.demo1 = '111';
karyn.data.demo2 = '222';
}, 2000)
模板替换
var tmp = `aaa
<color:xxx>
<bold:zzz>
aaaa
</bold>
</color>
ssss
<color:yyy>
bbb
</color>
mmm
<bold:zzz>
ccc
</bold>`;
const filterStr = (str: string) => {
var special = ['color', 'bold'];
var format = ['color', 'font-weight']
special.map((item, index) => {
var reg = new RegExp(`<${item}:(.*?)>([\\w\\W]*?)</${item}>`, 'g');
str = str.replace(reg, ($0, $1, $2) => {
return `<div style="${format[index]}:${$1}">${$2}</div>`;
});
});
return str;
}
console.log(filterStr(tmp))
Vue 的流程梳理
template
- template -> AST render (compiler解析template)将其转换成一些指令,directive,并且每个指令会对应一个 Watcher 的实例,如果这个实例和 Vue 数据有双绑关系,会调用用数据的 get 方法将这个指令放入和数据有关联的 Dep 队列中,以便 dep.notify 的时候进行调用。
- AST render -> vNode (render方法运行)将指令转换成虚拟 DOM,虚拟 DOM 是做的类似 DOM 节点的属性的一些方法,少了一些静态的方法来提升性能,比如 DOM 的一些方法
- vNode -> DOM (vdom.patch) 将 VDOM 通过 insertBefore 插入到 DOM 中。
diff vNode 时会有一个重要的方法是 patch,这个方法会进行新老 vNode 对比,diff 的更新 dom,整个是只做同级比较,patchVnode 时无非就是逐个 vNode 及子节点(递归调用 patchVnode)进行对比有没有变化,并且逐一进行更新,但是为了最大复用子节点除了删除和新增操作,还会有移位操作,会存在头尾索引,对比头尾进行移位达到移位复用的效果。
当 DOM 被操作时,directive 会调用 watcher 中的 dep,找到对应变化的数据,对数据进行修改和重新绑定双绑事件
observer
在初始化 data/props 时,会调用 observe 方法对数据进行双绑,在双绑时会生成一个 watcher 实例就是 dep,这个 dep 用于存储和数据相关的所有 VDOM 的指令依赖。
对数据进行双绑时,只会在 get 中存放依赖收集的方法,但这个时候并没有指令放进来,指令放进来的时机是在 template 实例化 watcher 的时候,上述有详解。
当发生 set 调用时,先会重新绑定新的数据,然后会调用 dep.notify(),进行 watcher 的 update 方法,update 方法实质上是会调用当前这个数据绑定的 watcher 对应的回调中对应的指令,生成对应性的 Vnode 的节点,再次进行 diff,更新 DOM 节点。
整体流程
数据初始化 数据代理到 _data observe data
walk 数据,这里是一个递归,对数据进行 defineReactive
实例化 dep get:dep.depend -> 收集视图的 watcher
set:dep.notify -> 遍历数据
进行修改 $mount 视图 template -> AST render AST render -> vNode vNode -> vDOM vDOM -> DOM 通过 watcher 进行 insertBefore
实现new watcher(vm, updateComponent) 首次实例化 watcher 将上面的 updateComponent 赋值给 this.getter,以便视图更新时调用
执行 this.get 收集当前 watcher 的依赖,将数据和 DOM 依赖进行绑定。
并且调用 this.getter this.getter 被调用 -> updateComponent 被调用 -> _update(生成 VDOM) -> insertBefore DOM 至此完成首次渲染
数据变化更新
重新对数据进行 observe dep.notify 遍历当前 dep 中的相关依赖,调用对应的 subs.update
queueWatcher 将 watcher push 到异步队列中
最终调用 watcher.run -> 调用 this.get -> this.getter 被调用 -> updateComponent 被调用 -> _update -> patch diff -> patchNode(进行 DOM 的增删改操作) -> 递归 patchChildNode
非对象的双绑
vue 数组数据双绑,纯数组操作数组的下标 DOM 不会发生改变,DOM 双绑只对对象有效,但是用 push 等方法是可以的,是因为重写了数组的 push 等方法
nextTick
nextTick 实现: 如果支持 promise,实现是 promise.then 实现是延迟到当前函数调用栈的最末端
MutationObserver,监听 DOM 节点的变动,变动之后调用 nextTick
setTimeout 延迟器