简略模拟现市面上流行JS框架的属性传递与自动渲染特性

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的链接:

点击按钮前为Google的链接

当点击按钮后,链接将动态更改为百度:

点击按钮后更新为百度的链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值