如何在JavaScript模块模式中从私有函数调用公共函数

8
如何在JavaScript模块模式中从私有函数中调用公共函数?
例如,在以下代码中,
var myModule = (function() {
    var private1 = function(){
        // How to call public1() here?
        // this.public1() won't work
    }

    return {
        public1: function(){ /* do something */}
    }
})();

这个问题已经两次 被问到了, 并且每次都有一个不同的被接受的答案。

  1. 在返回对象之前保存对该对象的引用,然后使用该引用来访问公共方法。请参见答案
  2. 在闭包中保存公共方法的引用,并使用该引用来访问公共方法。请参见答案

虽然这些解决方案可行,但从OOP的角度来看,它们并不令人满意。为了说明我的意思,让我们以一个具体的雪人实现为例,比较它们与一个简单的对象文字。

雪人 1:保存返回对象的引用

var snowman1 = (function(){
  var _sayHello = function(){
    console.log("Hello, my name is " + public.name());
  };

  var public = {
    name: function(){ return "Olaf"},
    greet: function(){
      _sayHello();
    }
  };
  return public;
})()

雪人2:保存对公共函数的引用

var snowman2 = (function(){
  var _sayHello = function(){
    console.log("Hello, my name is " + name());
  };
  var name = function(){ return "Olaf"};

  var public = {
    name: name,
    greet: function(){
      _sayHello();
    }
  };
  return public;
})()

雪人 3:对象字面量

var snowman3 = {
    name: function(){ return "Olaf"},
    greet: function(){
      console.log("Hello, my name is " + this.name());
    }
}

我们可以看到这三个在功能上完全相同,并且具有完全相同的公共方法。

然而,如果我们进行简单覆盖的测试

var snowman = // snowman1, snowman2, or snowman3
snowman.name = function(){ return "Frosty";}
snowman.greet(); // Expecting "Hello, my name is Frosty"
                 // but snowman2 says "Hello, my name is Olaf"

我们发现 #2 失败了。

如果我们运行原型覆盖的测试,

var snowman = {};
snowman.__proto__ = // snowman1, snowman2, or snowman3
snowman.name = function(){ return "Frosty";}
snowman.greet(); // Expecting "Hello, my name is Frosty"
                 // but #1 and #2 both reply "Hello, my name is Olaf"

我们可以看到, #1 和 #2 都失败了。
这是一个非常丑陋的情况。仅仅因为我选择以某种方式重构我的代码,返回对象的用户就必须仔细查看我如何实现所有内容,才能确定他 / 她是否可以覆盖我的对象方法并期望它正常工作!虽然在这里意见不一,但我个人认为正确的覆盖行为是简单对象文字的行为。
所以,这是真正的问题:
有没有办法从私有方法调用公共方法,以便生成的对象对于覆盖行为的行为类似于对象文字?

2
你所讨论的陷阱中有一个重要的注意事项:我对模块模式的理解是,它确实是JS原型/OOP多态性方法的另一种选择。因此,我认为避免同时使用这两种方法是有意义的,我并不认为这本身是一个缺陷。 - EyasSH
@EyasSH - 它可以这样使用,但我认为它更像是一种可以用于某种继承的工具而不是一个故意设计的特性。它似乎主要用于模拟私有成员(或不暴露其他人不应该操作的内容),并且还可以提高性能。 - RobG
1
@RobG,没错,但是你可以在闭包中声明私有函数,同时坚持使用原型模式。在这种情况下,使用this是可以的。我的观点是,如果你正在使用模块模式,那么我认为查看模块<->原型交互的奇怪行为不是不使用该方法的真正原因。如果你认为使用你的代码的人会这样做,那么坚持使用原型可能是一个理由。 - EyasSH
我认为你应该在snowman1snowman2snowman3的末尾添加(),否则它们将成为函数。另外,在原型覆盖中,你可能需要自定义snowman.name,否则我不知道它如何输出“Frosty”。 - Oriol
谢谢,@Oriol,我已经做出了修正。 - I-Lin Kuo
我不明白为什么你首先想要从一个私有方法中调用公共方法。对我来说,这似乎是一个XY问题。 - Evan Davis
2个回答

2
您可以使用this来获取调用您的特权方法greet的对象。
然后,您可以将该值传递给您的私有方法_sayHello,例如使用callapply或作为参数。
var snowman4 = (function() {
    var _sayHello = function() {
        console.log("Hello, my name is " + this.name);
    };
    return {
        name: "Olaf",
        greet: function() {
            _sayHello.call(this);
        }
    };
})();

现在您可以做到:
var snowman = Object.create(snowman4);
snowman.greet(); // "Hello, my name is Olaf"
snowman.name = "Frosty";
snowman.greet(); // "Hello, my name is Frosty"

And also

snowman4.greet(); // "Hello, my name is Olaf"
snowman4.name = "Frosty";
snowman4.greet(); // "Hello, my name is Frosty"

这个答案似乎没有回答如何在模块模式中从私有函数调用公共函数的问题 - 特别是,_sayHello()应该如何调用name()。在IIFE中包装对象文字也不会使greet()成为特权函数。 - I-Lin Kuo
@I-LinKuo 是的,它是特权的。它可以访问在自执行函数内声明的私有数据。 - Oriol
是的,它很特权:它可以访问在自执行函数内声明的私有数据。我使用了一个特权方法而不是私有方法来简化它,但确实没有直接回答问题,所以我进行了修复。 - Oriol

1
使用模块模式,您将对象的所有内在部分隐藏在本地变量/函数中,并通常在公共函数中使用它们。每次使用模块模式创建新对象时,都会创建一组新的公开函数 - 具有其自己作用域状态。
使用原型模式,您可以为某些类型的所有对象提供相同的方法集。这些方法的变化是该对象的“this”-换句话说,那就是它们的状态。但是,“this”从未被隐藏。
不用说,混合这些是很困难的。一种可能的方法是使用Object.create将私有方法提取到模块结果对象的原型中。例如:
var guardian = function() {
    var proto = {
        greet: function () {
            console.log('I am ' + this.name());
        },
        name: function() {
            return 'Groot';
        }
    };
    var public = Object.create(proto);
    public.argue = function() {
        privateGreeting();
    };

    var privateGreeting = public.greet.bind(public);
    return public;
};

var guardian1 = guardian();
guardian1.argue(); // I am Groot
var guardian2 = guardian();
guardian2.name = function() {
  return 'Rocket';
};
guardian2.argue(); // I am Rocket
var guardian3 = guardian();
guardian3.__proto__.name = function() {
  return 'Star-Lord';
};
guardian3.argue(); // I am Star-Lord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接