只使用 ES5 的语法,不考虑异常情况下,用最简单的方式在15分钟内,使下面代码功能实现(允许改变结构):
<div id="app"></div>
var Vue = (function () {
var Vue = function () {
}
return Vue;
});
new Vue({
el: '#app',
template: `
<h1>{{ count }}</h1>
<button onClick="add">+</button>
<button onClick="minus">-</button>
`,
data: function () {
return {
count: 0
}
},
methods: {
add: function () {
this.count += 1;
},
minus: function () {
this.count -= 1;
}
},
});
根据题目是要实现一个类似 Vue 的 ViewModel 功能部分,但只能在较短的时间内实现。
分析
- 模板编译是跑不掉的,但时间的关系不能使用分析字符串,那就改为分析 DOM,生成 DOM 树
- DOM 中的标签内容为
{{}}
要替换为 data 中对应的值(Data Bindings) - 获取带有 onClick 属性的标签,为标签绑定 methods 对应的 click 事件处理函数(DOM Listeners)
事件改变数据的同时,还有使视图响应。那么要对数据做代理。针对 set 的同时改变相应 DOM 节点的内容
var Vue = (function () { var reg_var = /{{(.+?)}}/; var Vue = function (options) { this.el = document.querySelector(options.el); this.template = options.template; this.data = options.data(); this.methods = options.methods; this.dataPool = {}; this.init(); } Vue.prototype.init = function () { // 编译 DOM 树 var domTree = compile(this, this.template, this.data, this.methods, this.dataPool); dataProxy(this, this.data, this.dataPool); this.el.appendChild(domTree); } function compile(vm, template, data, methods, dataPool) { var domTree = createElement('div', template); var allNodes = domTree.getElementsByTagName('*'), nodeItem = null; for (var i = 0; i < allNodes.length; i++) { nodeItem = allNodes[i]; // 模版变量替换为 data 中的值 if (reg_var.test(nodeItem.textContent)) { compileData(nodeItem, data, dataPool); } // 节点事件处理函数绑定 if (nodeItem.getAttribute('onClick')) { compileEvent(vm, nodeItem, methods); } } return domTree; } function createElement(tagName, template) { var oEl = document.createElement('div'); oEl.innerHTML = template; return oEl; } function compileData(node, data, dataPool) { var key = node.textContent.match(reg_var)[1].trim(); // 使用数据池做一个数据变量与节点的映射 dataPool[key] = [node, node.textContent]; update(key, data[key], dataPool); } function compileEvent(vm, node, methods) { var cb = node.getAttribute('onClick'); node.addEventListener('click', methods[cb].bind(vm), false); node.removeAttribute('onClick'); } function dataProxy(vm, data, dataPool) { for (var k in data) { (function (k) { Object.defineProperty(vm, k, { get: function () { return data[k]; }, set: function (newValue) { data[k] = newValue; update(k, newValue, dataPool); } }) })(k); } } function update(key, value, dataPool) { dataPool[key][0].textContent = dataPool[key][1].replace(reg_var, value) } return Vue; })();
使下面代码功能实现
var Counter = (function () {
var obj = {
a: 1,
b: 2,
};
})();
Counter.change('a', 100); // a => 100
Counter.add(); // => a + b = ?
Counter.minus(); // => a - b = ?
var Counter = (function () {
var obj = {
a: 1,
b: 2,
};
function change(key, value) {
if(!obj.hasOwnProperty(key)) {
throw new ReferenceError("This key is not exist in obj");
}
obj[key] = value;
}
function add() {
console.log("a + b = " + (obj.a + obj.b));
}
function minus() {
console.log("a - b = " + (obj.a - obj.b));
}
return {
change: change,
add: add,
minus: minus
}
})();
Counter.change('a', 100); // a => 100
Counter.add(); // => a + b = ?
Counter.minus(); // => a - b = ?
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
},
};
console.log(eval('foo.bar()'));
console.log((foo.bar = foo.bar)());
console.log((false || foo.bar)());
console.log(
new Function(foo.bar.toString().match(/return(.*?)\;/)[0]).bind(foo).call(window)
);
- 2
eval 下与 foo.bar() 无异 - 1
foo.bar = foo.bar 赋值是有返回,返回的是其被赋值的值,所以这里相当于是 function () { return this.value }
那么在 function () { return this.value } 是在全局执行,所以为 1 - 1
与上面同理,false || foo.bar 是返回 function () { return this.value },在全局执行,所以为 1 - 2
new Function(foo.bar.toString().match(/return(.*?)\;/)[0])
=>new Function("return this.value")
被 bind() 绑定过的 this 的函数 BoundFunction,会忽略 call() / apply() 的 this 指向来执行。所以还是 2
var a = 0;
if (true) {
console.log(a, window.a);
a = 10;
console.log(a, window.a);
function a() {}
console.log(a, window.a);
a = 11;
console.log(a, window.a);
}
console.log(a, window.a);
在 if block 中是不进行预编译,所以在执行到 if block 时,function a() {} 会提升至 block 最顶
- function(){} 0
a = 10 没什么特别
- 10 0
当遇到 function a() { } 会统一 if block 与 全局的 a
- 10 10
a = 11 没什么特别
- 11 10
全局的 a === window.a
- 10 10
var x = 1;
function test() {
x = 2
}
function foo(
x, y = test
) {
x = 1;
y();
console.log(x);
}
foo();
1