1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// 创建一个Mvvm构造函数
// 这里用es6方法将options赋一个初始值,防止没传,等同于options || {}
function Mvvm(options = {}) {
    // vm.$options Vue上是将所有属性挂载到上面
    // 所以我们也同样实现,将所有属性挂载到了$options
    console.log(options)
    this.$options = options;
    // this._data 这里也和Vue一样
    let data = this._data = this.$options.data;
 
    // 数据劫持
    observe(data);
    // this 代理了this._data
    for(let key in data) {
        Object.defineProperty(this, key, {
            configurable: true,
            get() {
                return this._data[key]; // 如this.a = {b: 1}
            },
            set(newVal) {
                this._data[key] = newVal;
            }
        });
    }
    // 初始化computed,将this指向实例
    initComputed.call(this);
    // 编译   
    new Compile(options.el, this);
    // 所有事情处理好后执行mounted钩子函数
    options.mounted.call(this); // 这就实现了mounted钩子函数
}
// 创建一个Observe构造函数
// 写数据劫持的主要逻辑
function Observe(data) {
    let dep = new Dep();
    // 所谓数据劫持就是给对象增加get,set
    // 先遍历一遍对象再说
    for(let key in data) { // 把data属性通过defineProperty的方式定义属性
        let val = data[key];
        observe(val); // 递归继续向下找,实现深度的数据劫持
        Object.defineProperty(data, key, {
            configurable: true,
            get() {
                Dep.target && dep.addSub(Dep.target);
                return val;
            },
            set(newVal) { // 更改值的时候
                if(val === newVal) { // 设置的值和以前值一样就不理它
                    return;
                }
                val = newVal; // 如果以后再获取值(get)的时候,将刚才设置的值再返回去
                observe(newVal); // 当设置为新值后,也需要把新值再去定义成属性
                dep.notify(); // 让所有watcher的update方法执行即可
            }
        });
    }
}
 
// 外面再写一个函数
// 不用每次调用都写个new
// 也方便递归调用
function observe(data) {
    // 如果不是对象的话就直接return掉
    // 防止递归溢出
    if(!data || typeof data !== 'object') return;
    return new Observe(data);
}
 
// 创建Compile构造函数
function Compile(el, vm) {
    // 将el挂载到实例上方便调用
    vm.$el = document.querySelector(el);
    console.log(vm.$el)
    // 在el范围里将内容都拿到,当然不能一个一个的拿
    // 可以选择移到内存中去然后放入文档碎片中,节省开销
    let fragment = document.createDocumentFragment();
    console.log(fragment)
    console.log(vm.$el.firstChild)
    while(child = vm.$el.firstChild) {
        fragment.appendChild(child); // 此时将el中的内容放入内存中
    }
    console.log(fragment.childNodes)
    // 对el里面的内容进行替换
    function replace(frag) {
        Array.from(frag.childNodes).forEach(node => {
            console.log(node, node.textContent)
            let txt = node.textContent;
            let reg = /\{\{(.*?)\}\}/g; // 正则匹配{{}}
 
            if(node.nodeType === 3 && reg.test(txt)) { // 即是文本节点又有大括号的情况{{}}
                //              console.log(RegExp.$1); // 匹配到的第一个分组 如: a.b, c
                //              let arr = RegExp.$1.split('.');
                //              console.log(arr)
                //              let val = vm;
                //              arr.forEach(key => {
                //                  val = val[key]; // 如this.a.b
                //              });
                //              // 用trim方法去除一下首尾空格
                //              node.textContent = txt.replace(reg, val).trim();
                function replaceTxt() {
                    node.textContent = txt.replace(reg, (matched, placeholder) => {
                        console.log(placeholder); // 匹配到的分组 如:song, album.name, singer...
                        new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容
 
                        return placeholder.split('.').reduce((val, key) => {
                            return val[key];
                        }, vm);
                    });
                };
                // 替换
                replaceTxt();
 
            }
            if(node.nodeType === 1) { // 元素节点
                let nodeAttr = node.attributes; // 获取dom上的所有属性,是个类数组
                console.log(node.attributes)
                Array.from(nodeAttr).forEach(attr => {
                    let name = attr.name; // v-model  type
                    let exp = attr.value; // c        text
                    if(name.includes('v-')) {
                        node.value = vm[exp]; // this.c 为 2
                    }
                    console.log(attr.value)
                    //                  debugger
                    // 监听变化
                    new Watcher(vm, exp, function(newVal) {
                        node.value = newVal; // 当watcher触发时会自动将内容放进输入框中
                    });
 
                    node.addEventListener('input', e => {
                        debugger
                        let newVal = e.target.value;
                        // 相当于给this.c赋了一个新值
                        // 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新
                        vm[exp] = newVal;
                        console.log(vm)
                    });
                });
            }
 
            // 如果还有子节点,继续递归replace
            if(node.childNodes && node.childNodes.length) {
                replace(node);
            }
        });
        // 监听变化
        // 给Watcher再添加两个参数,用来取新的值(newVal)给回调函数传参
        new Watcher(vm, RegExp.$1, newVal => {
            node.textContent = txt.replace(reg, newVal).trim();
        });
 
    }
 
    replace(fragment); // 替换内容
 
    vm.$el.appendChild(fragment); // 再将文档碎片放入el中
}
 
// 发布订阅模式  订阅和发布 如[fn1, fn2, fn3]
function Dep() {
    // 一个数组(存放函数的事件池)
    this.subs = [];
}
Dep.prototype = {
    addSub(sub) {
        this.subs.push(sub);
    },
    notify() {
        // 绑定的方法,都有一个update方法
        this.subs.forEach(sub => sub.update());
    }
};
// 监听函数
// 通过Watcher这个类创建的实例,都拥有update方法
function Watcher(vm, exp, fn) {
    this.fn = fn; // 将fn放到实例上
    this.vm = vm;
    this.exp = exp;
    // 添加一个事件
    // 这里我们先定义一个属性
    Dep.target = this;
    console.log(exp)
    let arr = toString(exp).split('.');
    let val = vm;
    arr.forEach(key => { // 取值
        val = val[key]; // 获取到this.a.b,默认就会调用get方法
    });
    Dep.target = null;
}
Watcher.prototype.update = function() {
    // notify的时候值已经更改了
    // 再通过vm, exp来获取新的值
    console.log(this.exp)
    let arr = toString(this.exp).split('.');
    let val = this.vm;
    arr.forEach(key => {
        val = val[key]; // 通过get获取到新的值
    });
    console.log(this)
    this.fn(val);
};
 
let watcher = new Watcher(() => console.log(111)); //
let dep = new Dep();
dep.addSub(watcher); // 将watcher放到数组中,watcher自带update方法, => [watcher]
dep.addSub(watcher);
dep.notify(); //  111, 111
function initComputed() {
    let vm = this;
    let computed = this.$options.computed; // 从options上拿到computed属性   {sum: ƒ, noop: ƒ}
    // 得到的都是对象的key可以通过Object.keys转化为数组
    Object.keys(computed).forEach(key => { // key就是sum,noop
        Object.defineProperty(vm, key, {
            // 这里判断是computed里的key是对象还是函数
            // 如果是函数直接就会调get方法
            // 如果是对象的话,手动调一下get方法即可
            // 如: sum() {return this.a + this.b;},他们获取a和b的值就会调用get方法
            // 所以不需要new Watcher去监听变化了
            get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
            set() {}
        });
    });
}

  

二:

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>双向绑定实现</title>
    <style>
        #app {
            text-align: center;
        }
    </style>
</head>
<body>
    <div id="app">
        <h2>{{title}}</h2> 
        <input v-model="name">
        <h1>{{name}}</h1>
        <button v-on:click="clickMe">点击!</button>
    </div>
</body>
<script src="js/observer.js"></script>
<script src="js/watcher.js"></script>
<script src="js/compile.js"></script>
<script src="js/index.js"></script>
<script>
 
    new Vue({
        el: '#app',
        data: {
            title: 'vue code',
            name: '练习'
        },
        methods: {
            clickMe() {
                this.title = 'vue code click';
            }
        },
        mounted() {
            window.setTimeout(() => {
                this.title = '1秒';
            }, 1000);
        }
    });
 
</script>
</html>

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Vue(options) {
    var self = this;
    this.data = options.data;
    this.methods = options.methods;
 
    Object.keys(this.data).forEach(function(key) {
        console.log(key);   // title  name
        self.proxyKeys(key);
    });
 
    observe(this.data);
    new Compile(options.el, this);
    options.mounted.call(this);     // 所有事情处理好后执行mounted函数
}
 
Vue.prototype = {
    proxyKeys: function(key) {
        var self = this;
        Object.defineProperty(this, key, {
            enumerable: false,
            configurable: true,
            get: function() {
                return self.data[key];
            },
            set: function(newVal) {
                self.data[key] = newVal;
            }
        });
    }
};

  

 

 observer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function Observer(data) {
    this.data = data;
    this.walk(data);
}
 
Observer.prototype = {
    walk: function(data) {
        var self = this;
        Object.keys(data).forEach(function(key) {
            self.defineReactive(data, key, data[key]);
        });
    },
    defineReactive: function(data, key, val) {
        var dep = new Dep();
         
        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get: function getter() {
                if (Dep.target) {
                    console.log(Dep.target)
                    dep.addSub(Dep.target);
                }
                return val;
            },
            set: function setter(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                dep.notify();
            }
        });
    }
};
 
function observe(val, vm) {
    if (!val || typeof val !== 'object') {
        return;
    }
    return new Observer(val);
}
 
function Dep() {
    this.subs = [];
}
Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    notify: function() {
        this.subs.forEach(function(sub) {
            console.log(sub.update)
            sub.update();
        });
    }
};
Dep.target = null;

  watcher.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Watcher(vm, exp, cb) {
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    this.value = this.get();    // 将自己添加到订阅器的操作
}
 
Watcher.prototype = {
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal);
        }
    },
    get: function() {
        Dep.target = this// 缓存自己
        var value = this.vm.data[this.exp];     // 强制执行监听器里的get函数
        Dep.target = null// 释放自己
        return value;
    }
}

  compile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
function Compile(el, vm) {
    this.vm = vm;
    this.el = document.querySelector(el);
    this.fragment = null;
    this.init();
}
 
Compile.prototype = {
    init: function() {
        if (this.el) {
            this.fragment = this.nodeToFragment(this.el);
            this.compileElement(this.fragment);
            this.el.appendChild(this.fragment);
        } else {
            console.log('Dom元素不存在');
        }
    }, 
    nodeToFragment: function(el) {
        var fragment = document.createDocumentFragment();
        var child = el.firstElementChild;
 
        while (child) {
            // 将Dom元素移入fragment中
            fragment.appendChild(child);
            child = el.firstElementChild;
        }
 
        return fragment;
    },
    compileElement: function(el) {
        var self = this;
        var childNodes = el.childNodes;
 
        [].slice.call(childNodes).forEach(function(node) {
            var reg = /\{\{(.*)\}\}/;
            var text = node.textContent;
 
            if (self.isElementNode(node)) {
                self.compile(node);
            } else if (self.isTextNode(node) && reg.test(text)) {
                self.compileText(node, reg.exec(text)[1]);
            }
 
            if (node.childNodes && node.childNodes.length) {
                self.compileElement(node);
            }
        });
    },
    compile: function(node) {
        var self = this;
        var nodeAttrs = node.attributes;
 
        Array.prototype.forEach.call(nodeAttrs, function(attr) {
            var attrName = attr.name;
            if (self.isDirective(attrName)) {
                var exp = attr.value;
                var dir = attrName.substring(2);    // model  on:click
                if (self.isEventDirective(dir)) {   // 事件命令
                    self.compileEvent(node, self.vm, exp, dir);
                } else {    // v-model指令
                    self.compileModel(node, self.vm, exp, dir);
                }
                node.removeAttribute(attrName);
            }
        });
    },
    compileText: function(node, exp) {
        var self = this;
        var initText = this.vm[exp];
        this.updateText(node, initText);
        new Watcher(this.vm, exp, function(value) {
            self.updateText(node, value);
        });
    },
    compileEvent: function(node, vm, exp, dir) {
        var eventType = dir.split(':')[1];
        var cb = vm.methods && vm.methods[exp];
 
        if (eventType && cb) {
            node.addEventListener(eventType, cb.bind(vm), false);
        }
    },
    compileModel: function(node, vm, exp, dir) {
        var self = this;
        var val = this.vm[exp];     // name
        this.modelUpdater(node, val);
 
        new Watcher(this.vm, exp, function(value) {
            self.modelUpdater(node, value);
        });
 
        node.addEventListener('input', function(e) {
            var newVal = e.target.value;
 
            if (val === newVal) {
                return;
            }
 
            self.vm[exp] = newVal;
            val = newVal;
        });
    },
    updateText: function(node, value) {
        node.textContent = typeof value === 'undefined' ? '' : value;
    },
    modelUpdater: function(node, value, oldVal) {
        node.value = typeof value === 'undefined' ? '' : value;
    },
    isDirective: function(attr) {
        return attr.indexOf('v-') === 0;
    },
    isEventDirective: function(dir) {
        return dir.indexOf('on:') === 0;
    },
    isElementNode: function(node) {
        return node.nodeType === 1;
    },
    isTextNode: function(node) {
        return node.nodeType === 3;
    }
};

  

posted on   ygunoil  阅读(62)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示