JavaScript:这段代码是做什么的?

7

如果这个问题太宽泛的话,我提前道歉。实际上,这是4个不同的问题,但都与同一个代码片段相关,并且我认为它们都围绕着同一个原则。

今天,经过多年使用JS后,我决定开始学习JS的工作原理,而不是将其视为在浏览器中运行的C语言。因此,我开始深入研究jQuery代码,看看真正的JS开发人员如何使用这种语言。那时,我发现了一块类似于下面代码的代码块。请注意,我从另一个堆叠的帖子In Javascript, can you extend the DOM?中拿到了这段代码。因此,这并不意味着编写此代码的人甚至知道他在说什么。

var myDOM = (function(){ // #1
    var myDOM = function(elems){ // #2
            return new MyDOMConstruct(elems);
        },
        MyDOMConstruct = function(elems) {
            this.collection = elems[1] ? Array.prototype.slice.call(elems) : [elems];
            return this; // #3
        };
    myDOM.fn = MyDOMConstruct.prototype = {
        forEach : function(fn) {
            var elems = this.collection;
            for (var i = 0, l = elems.length; i < l; i++) {
                fn( elems[i], i );
            }
            return this;
        },
        addStyles : function(styles) {
            var elems = this.collection;
            for (var i = 0, l = elems.length; i < l; i++) {
                for (var prop in styles) {
                    elems[i].style[prop] = styles[prop];
                }
            }
            return this;
        }
    };
    return myDOM; // #4
})();

1 为什么要使用 var myDOM = (function() {})(); 声明函数,而不是使用 var myDOM = function() {};

2 为什么要在 myDOM 函数内部声明 另一个 同名函数?为什么不将所有内部 myDOM 的逻辑放在外部 myDOM 函数中?

3 为什么要明确返回 "this"?这不是会自动完成的吗?

4 这里发生了什么?它是否返回内部 myDOM 的构造函数?如果是,为什么?

更新

现在大部分都讲得通了。关于问题 #1,我以为 myDOM 被赋值为等号后面定义的函数,但实际上不是。它被赋值为该函数返回的内容。这恰好是一个函数。

我仍然不清楚问题 #3。是的,我理解像这样使用函数:

console.log(MyDomConstruct('foo'))

本来应该显示“undefined”,但它并没有这样使用。在几行之前是这个:

return new MyDomConstruct(elems);

我可以理解如果语句像这样明确返回“this”的含义。
return MyDomConstruct(elems);

但事实并非如此。

实际上,new MyDomConstruct(elems) 并不返回 this,而是一个新的 MyDomConstruct 实例。也许在不同的上下文中这更容易理解。 - nre
没错。但是,“this”和一个新的MyDomConstruct实例都是MyDomConstruct的实例,对吧?另外,如果函数将返回自身的实例,为什么还要使用“new”语句呢? - mellowsoon
所有的回答都很好。我选择 jAndy 的答案因为他回答得最早,但其他人也加一分。 - mellowsoon
5个回答

9

为什么要使用var myDOM = (function() {})();而不是var myDOM = function() {};

这被称为自执行匿名函数。它在运行时会自动调用自己。您还会看到这种模式:

(function($){
}(jQuery));

在jQuery世界中,这很常见。在运行时,函数会调用自身,并保证$符号在函数体内引用jQuery对象。
在你的代码片段中,函数调用自身并返回myDOM,一个函数引用。
为什么要在myDOM函数内部声明一个名称完全相同的另一个函数?为什么不将所有内部myDOM逻辑放在外部myDOM函数中?
这只是一种约定。它可以被称为任何你想要的名字,也许作者认为这样做很方便。这种模式的原因是隐私和安全性。通过返回内部的myDOM引用,创建了一个闭包。所以在声明类似于以下内容之后:
var mytest = myDOM([]);

你无法访问MyDOMConstruct,但你的内部函数可以访问。这样你就可以保护你的方法和变量。这也被称为方法模式。在这个上下文中,Douglas Crockford: Javascript the good parts是一个很好的阅读材料。 为什么要明确返回"this"?这不是会自动完成吗? 不是的,默认情况下函数将返回undefined值。通过明确返回this,可以像上面的例子一样链式调用方法:
mytest.forEach([]).addStyles([]); ...

每个方法都返回调用对象,这里是myDOM对象。

这是什么意思?它返回内部的myDOM构造函数吗?如果是,为什么?

希望现在这应该清楚了。

编辑

根据您的更新:

new MyDOMConstruct();

生成一个新对象,该对象从中继承inherits

MyDOMConstruct.prototype

如果没有使用new关键字this将不会绑定到新对象。相反,它将绑定到全局对象(window),您将使用this访问全局变量。


感谢 jAndy。我更新了我的帖子,以获取有关#3的更多信息。 - mellowsoon

6

1. 为什么要使用 var myDOM = (function() {})(); 这种方式来声明函数,而不是使用 var myDOM = function() {}; 呢?

这种形式是一个自执行函数。这意味着myDOM被设置为函数返回的内容。第二种形式将myDOM设置为函数,但不会立即执行函数。

var myDOM = (function() {           // <== This is a function that does something
                // Something
            })();                 // The function is executed right HERE with ().


2. 为什么在myDOM函数内部声明与其名称完全相同的另一个函数?为什么不将所有内部myDOM的逻辑放在外部myDOM函数中?

这是因为你在最后返回内部的myDOM函数...所以即使起初有些混淆,这个命名实际上是有意义的。这经常用于在JS中创建私有变量。由于内部函数将访问它所包含的作用域(自执行匿名函数),但用户不会。

var myDOM = (function() {  // <== This is a function that is going to return 
                           // a function / object
                var myDOM = function() { // <== We're going to return this
                    ...                  //     The outer myDom will be set to it
                };                       //     So it's actually helpful to name
                                         //     it the same for clarity.
                return myDOM;
            })();

// Now we can access that inner object by using the outer myDOM.
// We could access the inner object using myDOM even if the inner object
//   was named otherTHING... It's confusing to acces something
//   called otherTHING in the code by
//   writing myDOM().... so better name the inner returned function
//   myDOM as well.

所以内部对象可以命名为任何名称,并且可以通过 myDOM() 执行,但是如果您将内部函数命名为 blah,仍然可以使用 myDOM() 来执行它......这不太清晰...最好将它和外部函数命名相同。

3. 为什么要明确返回“this”?那不是会自动完成吗?

不,如果您什么都不写,JavaScript 会自动返回 undefined 。参考 MDC:

经常使用返回 this 来保留方法的上下文。这用于使方法链($(here).is().a().chain().of().methods())成为可能。因此,链中的一个方法知道它正在操作的上下文。


谢谢答案。关于第二点,我猜这就是为什么显示一个jQuery对象(例如console.log($(document)))时,会显示"jQuery()"而不是jQuery对象实际上的"jQuery.init()"(就我所知)。这是因为jQuery.init被赋值给名为jQuery的变量。很奇怪。 - mellowsoon

3

关于“自执行匿名函数”的一点说明。将一些JavaScript代码放在(function() { })();块中是创建私有命名空间的一种方式。你在内部函数中所做的所有操作都将是函数私有的,除了返回的值(在本例中为myDOM)。

也就是说,你可以安全地进行以下操作:

var counter = (function() {
   var i = 0;
   var counter = function() {
      alert(i++);
      return i;
   }
   return counter;
})();

counter(); // will alert 0
counter(); // will alert 1
// ... and so on

将一些内部状态保密,不让外部函数知道是使用闭包的第一个原因。第二个原因,正如其他帖子中已经说明的那样,它不会通过变量 i 污染全局命名空间。这就是在jQuery插件开发中使用这些类型函数的最佳实践。


虽然我原则上知道自执行匿名函数是什么,但出于某种原因,我从未想过将其用作为函数提供私有变量的一种方式。它们并不完全是私有属性,但它们可以实现相同的功能。 - mellowsoon

1

和这个一样:


(function () {
    function b(a) {
        return new c(a)
    }
    function c(a) {
        this.a = a[1] ? Array.prototype.slice.call(a) : [a];
        return this
    }
    b.b = c.prototype = {};
    return b
})();

希望有所帮助... :)

1
例如: var fn = (function(){ 返回函数(){ alert(11)}; })(); 当代码运行时,现在fn = function(){ alert(11)}; 试一下!!!

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