2018-03

博客分类: 江河计划-月总结

2018-03

算法

平衡二叉树

    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()           // 静态方法调用

双向绑定的基础实现

对象常用的方法

对象的数据属性

对象的访问器属性

实现 Vue 双绑不含虚拟 DOM

Vue 实现双绑大概如下:

  1. 对数据进行递归的 Observe
  2. 实例化 dep,get 方法内放入 dep.addSubs,此时不会放入对应的 wather,在对模板解析的过程中会 new 一个 wather,watcher 中会先将 target 赋值为当前的 vm,然后调用 get,此时有 target 会将这个 watcher add 到 dep 的实例中,以便模板更新的时候调用 update,找到这个 watcher。
  3. set 方法中绑定 dep.notify,notify 调用与当前数据相关的所有 watcher
  4. 对模板进行递归解析,将 textnode 中的对应模板与数据进行隐射,初始化替换 node 节点内容之后,初始化一个 watcher,将改数据的 key 和 相关该 key 的回调传入 watcher
  5. watcher 实例化直接调用 watcher 的 get 方法,此时 get 方法中已经有一个方法,将该 watcher addSub 到依赖收集器中
  6. 初始化完毕
  7. 数据发生改变时,触发 set 方法,set 方法中触发 dep.notify,notify 触发与这个数据所有相关的 watcher.update 方法
  8. 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

  1. template -> AST render (compiler解析template)将其转换成一些指令,directive,并且每个指令会对应一个 Watcher 的实例,如果这个实例和 Vue 数据有双绑关系,会调用用数据的 get 方法将这个指令放入和数据有关联的 Dep 队列中,以便 dep.notify 的时候进行调用。
  2. AST render -> vNode (render方法运行)将指令转换成虚拟 DOM,虚拟 DOM 是做的类似 DOM 节点的属性的一些方法,少了一些静态的方法来提升性能,比如 DOM 的一些方法
  3. 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 延迟器