JavaScript源码:
// 父组件
let Component = {
version: 'v1.0',
// 实现初始化
init() {
// 创建全局属性`props`
// 1 设置相关属性为不可枚举
Object.defineProperties(this, {
init: {
enumerable: false
},
watch: {
enumerable: false
},
extends: {
enumerable: false
},
render: {
enumerable: false
}
});
// 2 获取对象的所有可枚举属性
let keys = Object.keys(this);
this.props = {}; // 初始化一个全局属性
keys.forEach(key => {
// 填充全局属性对象
this.props[key] = this[key];
});
},
// 实现属性监控
watch(prop, obj, cb) {
// 初始化`obj`的私有属性值
let _prop = '_' + prop;
if (!(_prop in obj)) {
// 利用操作符`in`来判断属性是否已经存在
obj[_prop] = obj[prop];
}
// 实现自动渲染
// 通过`[[getter]]`和`[[setter]]`特性来实现对象的属性监控
Object.defineProperty(obj, prop, {
get() {
// 这里的`this`指向`obj`
return this[_prop];
},
set: value => {
// 因箭头函数不绑定`this`,故这里的`this`指向`Component`
obj[_prop] = value;
// 执行渲染回调函数
cb();
}
});
},
// 实现子对象扩展
extends(name, obj) {
// 初始化全局属性`props`
this.init();
// 拷贝全局属性`props`
let props = {...this.props};
// 业务组件的数据对象
let state = obj.state();
// 将子组件的属性`state`返回的数据对象以及全局属性`props`合并至子组件的`methods`属性中
Object.assign(obj.methods, state, {props});
// 冻结全局属性对象`props`,保证全局对象中的属性不可被增加、删除和修改
Object.freeze(obj.methods.props);
// 将子组件作为属性值加入到`Component`中
this[name] = obj;
// 监控`methods`中的属性值变化,实现自动渲染
for (let prop in state) {
if (state.hasOwnProperty(prop)) {
this.watch(prop, obj.methods, () => {
this.render(name);
});
}
}
},
render(name) {
// 获取模板
let template = this[name].template;
// 替换模板中的占位符为真实数据
for (let prop in this[name].methods) {
if (this[name].methods.hasOwnProperty(prop)) {
template = template.replace('{{' + prop + '}}', this[name].methods[prop]);
}
}
// 绘制HTML
document.getElementById('container').innerHTML = template;
}
};
let MyApp = {
// HTML模板
template: '<a href="{{url}}">{{name}}</a>',
// 返回具体的数据对象
state() {
return {
url: 'https://www.google.com',
name: 'Google'
};
},
// 方法对象
methods: {
show() {
console.log(this.url, this.name, this.props.version); // https://www.google.com Google v1.0
// 尝试修改父组件中的全局属性值
this.props.version = 'v2.0'; // 因全局属性对象`props`已被冻结,故此修改无效
console.log(this.props.version); // v1.0
},
update(obj) {
this.url = obj.url;
this.name = obj.name;
}
}
};
Component.extends('myApp', MyApp);
Component.myApp.methods.show();
HTML源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="container"></div>
<button onclick="handleClick()">更改为百度</button>
<script src="core/index.js"></script>
<script>
Component.render('myApp');
function handleClick() {
Component.myApp.methods.update({url: 'https://www.baidu.com', name: '百度'});
}
</script>
</body>
</html>
初始页面展示的是Google的链接:
当点击按钮后,链接将动态更改为百度: