OO JavaScript - 构造函数中自执行函数的上下文/作用域

4

我有一个关于在构造函数中使用自执行匿名函数时上下文/范围的快速问题。

请观察以下可行的代码:

function Foo() {
    this.height = 10;
    this.width = 10;
    this.init = function() {
        this.create();
    };
    this.create = function() {
        alert('test');
    };
} 

var span1 = new Foo();
span1.init();

警告框按预期显示。然而,我不想在底部调用span1.init。我宁愿在构造函数中将init函数设为自执行。这会给我像下面这样的代码:
function Foo() {
    this.height = 10;
    this.width = 10;
    this.init = (function() {
        this.create();
    })();
    this.create = function() {
        alert('test');
    };
} 

var span1 = new Foo();

经过一些谷歌搜索,似乎使用自执行函数赋予它全局作用域,因此 this.create 在全局范围内不存在。

我认为我必须使用call/apply来做些什么,但我不确定具体在哪里以及如何使用。

如果有任何提示,将不胜感激。

谢谢, Ad。


2
为什么需要自执行函数?只需去掉所有的函数内容并在构造函数作用域中执行即可。 - Bergi
5个回答

2

Adi,你在这个例子中做的有两个问题。

第一个问题是,在立即调用函数内部,this === window

第二个问题是这些都是函数表达式。因此,它们都是在行内定义的。

function makeIt () {
    this.a = function () { this.b(); };
    this.b = function () { console.log("B"); };
}

这将会起作用,因为使用了后期静态绑定。 这意味着在a中,在函数被调用之前,浏览器不知道this代表什么。随后它会在那一时刻找到this所代表的对象。 否则,这就像分配变量一样:
function makeIt () {
    this.a = this.b;
    this.b = "George";
}

你会遇到一个错误。 为什么?因为在你赋值给a的时候,b还没有一个值。

function Foo () {
    this.init = (function (context) { context.change(); }(this));
    this.change = function () { doStuff(); };
}

那么这个语句有什么问题呢?嗯,立即调用函数就是立即调用的函数。这意味着,尽管我们通过将 this 的值作为参数传递到内部作用域中解决了 this 问题...但我们要求它运行尚不存在的内容。
function Foo () {
    this.change = function () { doStuff(); };
    this.init = (function (context) { context.change(); }(this));
}

这应该可以正常工作。 ...然而...

...你为什么要这样做呢? 也就是说,当你想让它自动构建时,为什么要给它一个publicinit属性(它是undefined)?

为什么initundefined?因为你没有返回任何东西——你正在运行一个函数并将init设置为函数的返回值,但它并没有返回任何东西,所以它将init设置为undefined。那为什么还要有init呢?

两个解决方案:

function Foo () {
    this.change = function () { doStuff(); };
    var init = function () { this.change(); };
    // other stuff......
    init();
}

或者:

function Foo () {
    this.change = function () { doStuff(); };
    // other stuff....


    (function (context) {
       context.change();
       /* a bunch of other stuff that would be in init
          if there was no other stuff, why not just call this.change()? */
    }(this));
}

说实话,如果init是私有的,并且自动运行,那么create是否真的需要公开?

在已经创建了对象后,您是否会调用myObj.create();

为什么不这样做:

function Foo () {
    this.public1 = "Bob";
    this.public2 = 32;
    this.publicMethod = function () {};

    var create = function () { /* initialize here */ };
    create();
}

或者,如果你不仅仅是在创建:create

function Foo () {
    this.public1 = "Bob";
    this.public2 = 32;
    this.arrayOfThings = [];
    this.publicMethod = function () {};

    var create = function () {},
        overclock = function () {},
        polish = function () {};

    // Initialize Everything
    (function (context) {
        var thing;
        for (/* ... */) {
            thing = create();
            polish(thing);
            context.arrayOfThings.push(thing);
        }
        overclock(context.arrayOfThings); 
    }(this));
}

现在,你已经将所有的函数、属性和变量都放在了一个作用域中,并将初始化放在了另一个作用域中——所有设置逻辑都与最终对象的逻辑分离开来......而且你可以根据输入参数对对象进行分支处理(例如多态构造函数,它会基于输入修改其输出,同时保持相同的接口——或者是自包含工厂模式,其中所有蓝图都是100%私有和封闭的),而不会让实际赋值看起来像 ifs 和 fors 的混乱。
你不必在完成的对象之外调用设置(这意味着没有其他人能够调用已完成对象上的设置,以重新创建或重置它)。而所有这些只需要使用一个匿名函数,在this.init上使用即可。

+1,尽管您的最后一个示例仍缺少正确的this值。 - Bergi
哈哈。哎呀。是的。除了将上下文传递到最后一个函数中,我认为这些足以完成工作。我现在要修复它 - 感谢你的发现。 - Norguard

0

你可以将上下文传递给新的闭包:

this.init = (function(ctx) {
    ctx.create();
})(this);

请注意,这是在声明init时执行的,因此除非在分配init之前分配create,否则它将无法工作。在您以前的示例中,这不是问题,因为在分配createinit之后手动调用了span1.init()

0

这是它的工作原理(查看演示:http://jsfiddle.net/Jw8jz/1/):

function Foo() {
  this.height = 10;
  this.width = 10;

  this.init();
}

Foo.prototype = {
  init: function() {
    this.create();
  },

  create: function() {
    alert('test');
  }
};

var span = new Foo();

了解原型属性的更多信息!


如果我理解正确,OP希望不调用span.init(); - Kobi
3
实际上,我建议不要将Foo.prototype设置为一个对象字面量,因为这样会覆盖.prototype上可能存在的任何属性。最好单独定义.prototype.init.prototype.create - jackwanders
1
@SperanskyDanil 当你这样做的时候,你会压垮在此点之上的整个原型链。如果有任何与此级别之前的构造函数相关联的原型属性,则它们都将被删除。想想bob = {name: "Bob"}; bob.age = 32;bob = {name: "Bob"}; bob = {age: 32};之间的区别。如果你以某种方式堆叠了原型实例,以尝试一个不是平面的继承模式,那么除了最后一站,你已经压垮了整个链。 - Norguard

0

我可能有所遗漏,但也许你想要的是:

function Foo() {
    this.height = 10;
    this.width = 10;
    this.init = function() {
        this.create();
    };
    this.create = function() {
        alert('test');
    };

    this.init();
} 

当然,类似地,您可以完全删除init函数或将其设置为私有。例如:
function Foo() {
    this.height = 10;
    this.width = 10;
    this.create = function() {
        alert('test');
    };

    this.create();
} 

此外,请注意,this.init = (function() {})();this.init设置为undefined,因为您的函数没有返回任何内容。

0

是的,call 可以使用(apply 只是有一个不同的参数,你根本不使用它):

this.init = (function() {
    this.create();
    // return something?
}).call(this);

“不起作用”是什么意思?你期望它做什么? - Bergi

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