10

我最近一直在研究诸如 Angular 和 Meteor 之类的 JavaScript 框架,我想知道他们如何知道对象属性何时发生更改,以便他们可以更新 DOM。

我有点惊讶 Angular 使用普通的旧 JS 对象,而不是要求您调用某种 getter/setter 以便它可以挂钩并进行必要的更新。我的理解是他们只是定期轮询对象以进行更改。

但是随着JS 1.8.5中 getter 和 setter 的出现,我们可以做得更好,不是吗?

作为一个小小的概念验证,我整理了这个脚本:

编辑:更新代码以添加依赖属性/方法支持)

function dependentProperty(callback, deps) {
    callback.__dependencies__ = deps;
    return callback;
}

var person = {
    firstName: 'Ryan',
    lastName: 'Gosling',
    fullName: dependentProperty(function() {
        return person.firstName + ' ' + person.lastName;
    }, ['firstName','lastName'])
};

function observable(obj) {
    if (!obj.__properties__) Object.defineProperty(obj, '__properties__', {
        __proto__: null,
        configurable: false,
        enumerable: false,
        value: {},
        writable: false
    });
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            if(!obj.__properties__[prop]) obj.__properties__[prop] = {
                value: null,
                dependents: {},
                listeners: []
            };
            if(obj[prop].__dependencies__) {
                for(var i=0; i<obj[prop].__dependencies__.length; ++i) {
                    obj.__properties__[obj[prop].__dependencies__[i]].dependents[prop] = true;
                }
                delete obj[prop].__dependencies__;
            }
            obj.__properties__[prop].value = obj[prop];
            delete obj[prop];
            (function (prop) {
                Object.defineProperty(obj, prop, {
                    get: function () {
                        return obj.__properties__[prop].value;
                    },
                    set: function (newValue) {
                        var oldValue = obj.__properties__[prop].value;
                        if(oldValue !== newValue) {
                            var oldDepValues = {};
                            for(var dep in obj.__properties__[prop].dependents) {
                                if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
                                    oldDepValues[dep] = obj.__properties__[dep].value();
                                }
                            }
                            obj.__properties__[prop].value = newValue;
                            for(var i=0; i<obj.__properties__[prop].listeners.length; ++i) {
                                obj.__properties__[prop].listeners[i](oldValue, newValue);
                            }
                            for(dep in obj.__properties__[prop].dependents) {
                                if(obj.__properties__[prop].dependents.hasOwnProperty(dep)) {
                                    var newDepValue = obj.__properties__[dep].value();
                                    for(i=0; i<obj.__properties__[dep].listeners.length; ++i) {
                                        obj.__properties__[dep].listeners[i](oldDepValues[dep], newDepValue);
                                    }
                                }
                            }
                        }
                    }
                });
            })(prop);
        }
    }
    return obj;
}

function listen(obj, prop, callback) {
    if(!obj.__properties__) throw 'object is not observable';
    obj.__properties__[prop].listeners.push(callback);
}

observable(person);

listen(person, 'fullName', function(oldValue, newValue) {
    console.log('Name changed from "'+oldValue+'" to "'+newValue+'"');
});

person.lastName = 'Reynolds';

哪些日志:

名称从“瑞恩·高斯林”更改为“瑞恩·雷诺兹”

我看到的唯一问题是定义方法,例如fullName()依赖于其他两个属性的 person 对象。这需要对对象进行一些额外的标记,以允许开发人员指定依赖项。

除此之外,这种方法有什么缺点吗?

提琴手

4

1 回答 1

1

JS 1.8.5 中 getter 和 setter 的出现——这种方法有什么缺点吗?

  • 除了观察到的变化之外,您不会捕获任何属性变化。当然,这对于建模实体对象来说已经足够了,对于我们可以使用代理的任何其他东西来说,这已经足够了。
  • 它仅限于支持 getter/setter 的浏览器,甚至可能是代理。但是,嘿,谁关心过时的浏览器呢?:-) 在受限环境(Node.js)中,这根本不成立。
  • 访问器属性(带有 getter 和 setter)比真正的 get/set 方法慢得多。当然我不希望它们被用在关键部分,它们可以让代码看起来更漂亮。但是,您需要将其牢记在心。此外,花哨的代码可能会导致误解——通常您会期望属性分配/访问是一个简短的 ( O(1)) 操作,而使用 getter/setter 可能会发生更多事情。您需要注意不要忘记这一点,并且使用实际方法可能会有所帮助。

因此,如果我们知道自己在做什么,是的,我们可以做得更好。

尽管如此,我们需要记住一个重要的点:同步/异步(也看看这个优秀的答案)。Angular 的脏检查允许您在下一个事件循环轮次触发事件之前一次更改一堆属性。这有助于避免(传播)语义上无效的状态。

然而,我也将同步 getter/setter 视为一个机会。它们确实允许我们声明属性之间的依赖关系并由此定义有效状态。它将自动确保模型的正确性,而我们一次只需更改一个属性(而不是一直更改firstNamefullNamefirstName足够了)。然而,在依赖解决期间,这可能不成立,所以我们需要关心它。

因此,与依赖管理无关的监听器应该被异步触发。只是setImmediate他们的循环。

于 2013-02-10T20:24:06.757 回答