数据视图双向绑定(纯 JS 利用 Object.defineProperty 实现)

数据视图双向绑定(纯 JS 利用 Object.defineProperty 实现)

Chris Yue No Comment
Posts

上一篇文章里介绍了使用 jQuery 的事件来实现数据视图双向绑定,并且提到 Object.defineProperty。这篇文章专门详解如何用 Object.defineProperty 方法同样实现数据视图双向绑定的功能。

Object.defineProperty 是用来控制 Javascript 对象属性读取行为的方法,用法很直白:

Object.defineProperty(obj, 'key', {
    get: () => {
        console.log('obj->key is got and always return 1');

        return 1;
    },
    set: (val) => {
        console.log('obj->key is set and nothing happend');
    }
});

其中第二个参数是对象的属性名,第三个参数是行为的配置。除了可以对属性值获取以及复制行为进行控制以外,还可以配置是否可写,是否可被遍历等,具体的配置参数在 MDN 相关章节解释得很清楚,我这就不多说了,这里我能用上的也只有 get 和 set 而已。

利用 Object.defineProperty 做双向绑定的具体思路:

  • 利用 get,将页面上的某个地方显示的数据,绑定到对象上
    Object.defineProperty(product, 'price', {
        get: () => +document.getElementById('某个 price 的 id').innerText
    });
  • 利用 set,当对象的属性发生改变时,更新到视图上
    Object.defineProperty(product, 'num', {
        set: (num) => {
            document.getElementById('某个商品数量的 id').innerHTML = num;
        }
    });

完整的代码示例:

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Demo</title>
    </head>
    <body>
        <ul id="cart">
            <li class="product">
                单价 $<span class="price">1</span>
                <button class="incre">+</button> 
                <input type="number" class="number" value="1">
                <button class="decre" disabled>-</button>
                单品总价:$<span class="total-price">1</span>
            </li>
            <li class="product">
                单价 $<span class="price">3</span>
                <button class="incre">+</button>
                <input type="number" class="number" value="2">
                <button class="decre">-</button>
                单品总价:$<span class="total-price">6</span>
            </li>
        </ul>
        <p>总数量:<span id="total-num">3</span></p>
        <p>总价:$<span id="total-price">7</span></p>
        <script>
            // 定义购物车数据模型
            var cart = {
                setProducts: (products) => {
                    cart.products = products;

                    cart.check();
                },

                check: () => {
                    var num = 0;
                    var price = 0;

                    cart.products.forEach((product) => {
                        num += product.num;
                        price += product.getTotalPrice();
                    });

                    cart.num = num;
                    cart.price = price;
                },
            };

            class Product {
                getTotalPrice() {
                    return this.num * this.price;
                }

                setNum(num) {
                    this.num = Math.min(Math.max(num, 1), 10);

                    cart.check();
                }

                canIncre() {
                    return this.num < 10; // 假设库存都为 10
                }

                canDecre() {
                    return this.num > 1;
                }

                incre() {
                    this.setNum(this.num + 1);
                }

                decre() {
                    this.setNum(this.num - 1);
                }
            }

            (() => {
                var $products = document.querySelectorAll('.product');
                var products = [];
                for (var i = 0; i < $products.length; ++i) {
                    let $product = $products[i];
                    let product = new Product();

                    Object.defineProperty(product, 'price', {
                        value: +$product.querySelector('.price').innerText
                    });

                    Object.defineProperty(product, 'num', {
                        get: () => +$product.querySelector('.number').value,
                        set: (num) => {
                            $product.querySelector('.number').value = num;
                            $product.querySelector('.total-price').innerHTML = product.getTotalPrice();

                            $product.querySelector('.incre').disabled = !product.canIncre();
                            $product.querySelector('.decre').disabled = !product.canDecre();
                        }
                    });

                    $product.querySelector('.incre').addEventListener('click', () => {
                        product.incre();
                    });

                    $product.querySelector('.decre').addEventListener('click', () => {
                        product.decre();
                    });

                    $product.querySelector('.number').addEventListener('change', (e) => {
                        product.setNum(e.target.value);
                    });

                    products.push(product);
                }

                Object.defineProperty(cart, 'num', {
                    set: (num) => {
                        document.getElementById('total-num').innerHTML = num;
                    }
                });

                Object.defineProperty(cart, 'price', {
                    set: (price) => {
                        document.getElementById('total-price').innerHTML = price;
                    }
                });

                cart.setProducts(products);
            })();
        </script>
    </body>
</html>

数据视图双向绑定(纯 JS 利用 Object.defineProperty 实现) by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人

发表评论

+ 40 = 42