【问题标题】:JavaScript: Function chaining or property accesses?JavaScript:函数链还是属性访问?
【发布时间】:2014-10-01 20:24:45
【问题描述】:

如果可以选择以一种或另一种方式设计事物,通常是更好的(通过性能、可维护性、可读性、功能等...)进行函数链接或使用对象属性访问器进行分层访问,为什么?

它是完全取决于用例,还是一般来说更好?

例子:

var foo = bar.get('parent').get('child').get('grandChild')...

// vs

var foo = bar['parent']['child']['grandChild']...
// or
var foo = bar.parent.child.grandChild...

或者有更好的方法吗?

请注意,点符号 . 可能并不总是可行的,因为源缩小、使用的属性值不是有效的 JavaScript 标识符等。

编辑:

既然太笼统了,那来个更具体的例子呢……

考虑以下...

var animals = {
    dogs: {
        'Chloe': {
            name: 'Chloe',
            fetch: function() {},
            rollover: function() {},
            pups: {
                'Charlie': {
                    name: 'Charlie',
                    fetch: function() {},
                    rollover: function() {}
                },
                'Lily': {
                    //...
                }
            }
        },
        'Toby': {
            //...
        }
    },
    cats: {
        'Fido': {
            name: 'Fido',
            meow: function() {},
            kittens: {
                'Mia': {
                    name: 'Mia',
                    meow: function() {}
                },
                'Shadow': {
                    //...
                }
            }
        }
    }
}

请注意,这是一种显示层次结构的尝试,并没有显示任何原型继承,尽管它可能存在(并且可能会出现在示例中)。

在如图所示的层次结构中,可以使用属性访问来获取特定的 pup,如下所示

var myPup = animals.dogs['Chloe'].pups['Charlie'];
// or
var myPup = animals.dogs.Chloe.pups.Charlie;
// or
var myPup = animals['dogs']['Chloe']['pups']['Charlie'];

这是理想的吗?或者使用函数访问小狗是否有显着的好处

var myPup = animals.dogs('Chloe').pups('Charlie');

【问题讨论】:

  • 我想说第二个更干净,你没有真正增加任何价值的“get”。但我认为这在很大程度上是一个偏好问题。
  • 调用函数(可能)比不调用函数更昂贵。使用像这样的显式 getter 函数也不是 JavaScript 惯用的。
  • 后两个是already answered。第一个没有可比性。
  • 是的。它确实完全取决于用例。你到底想做什么?因为您的第一个示例不是该模式的引人注目的用例。
  • @NanoWizard 您的论点确实与问题无关。如果你的函数返回对象本身,你也没有理由不能做animals['Chloe'].fetch().pups['Charlie'].rollover();

标签: javascript function object


【解决方案1】:

我认为您在错误的工作中使用了错误的工具。像您那样深度嵌套对象是数据建模的一个非常糟糕的选择。您的数据模型如下所示:

+ animals
|
+--+ dogs
|  |
|  +--+ Chloe
|  |  |
|  |  +--+ Charlie
|  |  |
|  |  +--+ Lily
|  |
|  +--+ Toby
|
+--+ cats
   |
   +--+ Fido
      |
      +--+ Mia
      |
      +--+ Shadow

这种数据模型称为hierarchical model,因为数据以层次结构的形式排列。

分层数据模型不是一个好的数据模型,因为它不提供access path independence。这仅仅意味着(由于数据建模的方式)在不知道其访问路径的情况下不可能找到给定对象。

例如Lily的访问路径是animals.dogs.Chloe.LilyToby的访问路径是animals.dogs.Toby。访问路径独立性很重要,因为这意味着您无需担心对象在数据模型中的位置。这使得编程更简单,也确保每个对象都将在相同的时间内被访问。

在上面的示例中,访问Lily 比访问Toby 需要更多时间,因为它更深一层。为了说明访问路径依赖性如何使编程变得更加困难,请考虑以下代码:

function findParentOf(animal) {
    var dogs = animals.dogs;

    for (var dog in dogs)
         if (dogs[dog].pups.hasOwnProperty(animal))
             return dog;

    var cats = animals.cats;

    for (var cat in cats)
         if (cats[cat].kittens.hasOwnProperty(animal))
             return cat;

    return null;
}

现在想象一下,你有很多不同类型的动物,比如狗、猫、兔子、仓鼠等。你能看到你会有多少冗余代码吗?

事实上,你的数据模型也有很多冗余。例如,每个对象都有一个name,即使它的键值相同。此外,每只狗和小狗都有一个fetchrollover 函数,每只猫和小猫都有一个meow 函数。

使用正确的抽象可以轻松概括所有这些。例如,我会这样做:

function animal(kind, parent, name) {
    return {
        kind: kind,
        parent: parent,
        name: name
    };
}

var dog = animal.bind(null, "dog", null);
var pup = animal.bind(null, "dog");

var cat = animal.bind(null, "cat", null);
var kitten = animal.bind(null, "cat");

var animals = [
    dog("Chloe"),
    pup("Chloe", "Charlie"),
    pup("Chloe", "Lily"),
    dog("Toby"),
    cat("Fido"),
    kitten("Fido", "Mia"),
    kitten("Fido", "Shadow")
];

这种数据建模方式称为relational model。关系模型最好的事情之一是它具有访问路径独立性。因此,您可以轻松找到对象并执行其他任务。例如:

function findAnimal(name) {
    return animals.find(function (animal) {
        return animal.name === name;
    });
}

function findParentOf(name) {
    var animal = findAnimal(name);

    if (animal) {
        var parent = animal.parent;
        return parent && findAnimal(parent);
    }
}

function findChildrenOf(name) {
    return animals.filter(function (animal) {
        return animal.parent === name;
    });
}

现在我们有了访问路径独立性,我们可以很容易地找到对象。例如:

var lily = findAnimal("Lily");
var toby = findAnimal("Toby");
var fido = findParentOf("Shadow");
var miaAndShadow = findChildrenOf("Fido");

最后,当涉及到fetchrollovermeow 函数时,您可以将它们定义如下:

function fetch(animal) {
    if (animal.kind === "dog") {
        // do something
    }
}

function rollover(animal) {
    if (animal.kind === "dog") {
        // do something
    }
}

function meow(animal) {
    if (animal.kind === "cat") {
        // do something
    }
}

或者更好的是,将它们放入命名空间中。感谢Richard Simpson 给了我这个想法:

dog.fetch = function (animal) {
    if (animal.kind === "dog") {
        // do something
    }
};

dog.rollover = function (animal) {
    if (animal.kind === "dog") {
        // do something
    }
};

cat.meow = function (animal) {
    if (animal.kind === "cat") {
        // do something
    }
};

然后你可以这样做:

dog.rollover(findAnimal("Charlie"));

或者function composition:

var rolloverDog = compose(dog.rollover, findAnimal);

function compose(f, g) {
    return function (x) {
        return f(g(x));
    };
}

rolloverDog("Charlie");

只有我的两分钱。希望这会有所帮助。

【讨论】:

  • 出色的答案。也许建议有一个名为behaviours 的属性,它可以为动物可以做出的动作提供函数原型。
  • @RichardSimpson 您是否建议将它们放入命名空间,以便您可以拥有dog.rollovercat.rollover 函数?是的,那会很好。
  • 批评:括号符号解决了“冗余参数”。关系模型并不全是乐趣和游戏。近年来,人们开始争论its problems,并认为它实际上并没有提供更多的简单性——特别是因为有时你真的只有狗。我还认为最终结果并不简单——您必须实现自己的查询函数,而不是使用简单的点表示法——更不用说聚合查询了。 Impedence mismatch 是一个真正的问题。
  • 这里当然也有负面影响。这一切都取决于实际使用情况。例如,如果永远不需要在所有动物中搜索给定动物并且只在其自身类型中搜索,那么您现在已经增加了寻找给定狗的时间。这是取决于实际情况的问题类型,不可能有一个通用的答案。
  • @BenjaminGruenbaum 我总是喜欢阅读您的批评。我总是学到新东西。这个阻抗不匹配问题:如果我没记错的话,这只是将关系转换为对象时的问题。使用functional relational programming时是否也会出现同样的问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-09
  • 2017-05-25
  • 2016-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多