在包装 jQuery 插件的匿名函数中使用“窗口”、“文档”和“未定义”作为参数

Posted

技术标签:

【中文标题】在包装 jQuery 插件的匿名函数中使用“窗口”、“文档”和“未定义”作为参数【英文标题】:Using 'window', 'document' and 'undefined' as arguments in anonymous function that wraps a jQuery plugin 【发布时间】:2013-03-24 12:55:55 【问题描述】:

老实说,我不知道如何缩短标题。

通过学习SlidesJS插件的源码,我学会了如何编写一个jQuery插件。当我遇到新事物时,我只是问了我的好朋友 Google,而且大多数时候都得到了满意的答案。不过老实说,我从来没有努力过。我所知道的是$(可能)是一个简写的jQuery对象构造函数,而$()jQuery()在包含jQuery的情况下是一样的。

不过,最近,我尝试了解 jQuery 背后的科学以及如何编写 good jQuery 插件。我遇到了一个非常好的article,其中作者列出了几个用于创建 jQuery 插件的模板。由于其余的太复杂了,我无法理解,所以我喜欢第一个:轻量级的开始。现在,这里是上述模板的代码。

/*!
 * jQuery lightweight plugin boilerplate
 * Original author: @ajpiano
 * Further changes, comments: @addyosmani
 * Licensed under the MIT license
 */


// the semi-colon before the function invocation is a safety 
// net against concatenated scripts and/or other plugins 
// that are not closed properly.
;(function ( $, window, document, undefined ) 

    // undefined is used here as the undefined global 
    // variable in ECMAScript 3 and is mutable (i.e. it can 
    // be changed by someone else). undefined isn't really 
    // being passed in so we can ensure that its value is 
    // truly undefined. In ES5, undefined can no longer be 
    // modified.

    // window and document are passed through as local 
    // variables rather than as globals, because this (slightly) 
    // quickens the resolution process and can be more 
    // efficiently minified (especially when both are 
    // regularly referenced in your plugin).

    // Create the defaults once
    var pluginName = 'defaultPluginName',
        defaults = 
            propertyName: "value"
        ;

    // The actual plugin constructor
    function Plugin( element, options ) 
        this.element = element;

        // jQuery has an extend method that merges the 
        // contents of two or more objects, storing the 
        // result in the first object. The first object 
        // is generally empty because we don't want to alter 
        // the default options for future instances of the plugin
        this.options = $.extend( , defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        this.init();
    

    Plugin.prototype.init = function () 
        // Place initialization logic here
        // You already have access to the DOM element and
        // the options via the instance, e.g. this.element 
        // and this.options
    ;

    // A really lightweight plugin wrapper around the constructor, 
    // preventing against multiple instantiations
    $.fn[pluginName] = function ( options ) 
        return this.each(function () 
            if (!$.data(this, 'plugin_' + pluginName)) 
                $.data(this, 'plugin_' + pluginName, 
                new Plugin( this, options ));
            
        );
    

)( jQuery, window, document );

我已将 cmets 包含在内,以便在我的问题中引用它们。

我有一个粗略的想法,为什么 windowdocument 被包含在包装插件的匿名函数的参数中(我不知道还能叫什么) 因为在 cmets 中给出了它有点缩短了执行时间。但这是如何工作的?包装插件的上述匿名函数的任何参数都会传递到哪里?这些在插件中是如何解决的?

通常情况下,我会使用$(window).resize(function()),但这在这种情况下不起作用。如果我在 Plugin 函数中执行 console.log(window),它会显示“未定义”。

这让我想到另一个问题:undefined 是什么?不是分配给未在范围内定义的对象数据类型吗?它如何作为参数传递?论点不是必须是对象吗? cmets 中写了几行关于此的内容,但我一个字都看不懂:所以我们可以确保它的值是真正未定义的> whaaa?

总结一下:

function($) 究竟是什么意思? 我为什么要包含windowdocumentundefined 作为function($) 的参数? 如果这样做,如何访问实际的 windowdocument 对象? undefined 什么,为什么?

请对我放轻松。我从来没有为了编写应用程序的明确目的而将编程语言作为一门学科。我学习了基本的 C 语言,用于为微型核心微控制器编写面向硬件的低级例程,仅此而已。我确实广泛地学习了 C++ 和一点点 Java。只是为了让您知道会发生什么。

【问题讨论】:

二十年来我一直在编写 javascript,有些方面仍然是个谜。 undefined 的这种特殊用法就是一个很好的例子。尽管作者试图解释它,但我还是无法理解。 What advantages does using (function(window, document, undefined) ... )(window, document) confer?的可能重复 【参考方案1】:

当你编写如下函数时:

(function (foo, bar) 
    return foo.getElementById(bar);
)(document, "myElement")

然后立即使用参数document"myElement" 为参数foobar 调用该函数。因此,在函数内部,foo.getElementById(bar)等价于document.getElementById("myElement")

同样,在您的插件示例中,您会立即使用参数jQuery, document, window 调用函数。

function($) 究竟是什么意思?

$ 仅表示对传递给包装函数的jQuery 对象的引用。稍后,当使用(jQuery, window, document) 调用匿名函数时,函数内部的$ 引用引用了jQuery 对象。这样做的原因有很多,其中最重要的是$ 的输入速度更快。它还允许用户将包装器中的插件应用到jQuery特定实例,可能由jQuery.noConflict() 生成。

为什么我应该包含windowdocumentundefined 作为function($) 的参数?

您不需要包含这些。原作者的推理是分配函数局部变量来引用这些将缩短解析这些变量所需的时间。我断言节省的费用微不足道;除非我大量引用window和/或document,否则我个人不会打扰。

至于undefined,原作者包含此内容的目的是确保有人没有更改 EcmaScript 4 中的 undefined 全局变量(编辑:实际上是 ECMAScript 3——版本 4 从未实现过)或更早版本.同样,我无法想象会出现这个问题。如果你真的担心这可能是一个问题,只需在你的函数中包含这样的内容:

if(typeof undefined !== "undefined") 
    undefined = void 0;

如果这样做,我如何访问实际的 windowdocument 对象?

您所要做的就是确保匿名函数末尾的函数调用传入实际的(jQuery、窗口、文档)参数。或者,不要在函数签名中包含 windowdocument 参数。无论哪种方式,您都将引用 实际 对象,而不管间接级别如何。

undefined 什么,为什么?

undefined 是“未定义”类型的全局变量。尚未初始化的字段与未定义完全相等 (===)。它允许程序员区分一个故意为空的值和一个简单的未初始化的值。在 ECMAScript 5 及更高版本中,undefined 是只读的。在此之前,其他代码可能会修改undefined 的值。您可以始终使用表达式 void 0... 获取真正的值 undefined...,如 myUndefinedVar = void 0;

【讨论】:

你的意思是 ECMAScript 3,ES4 实际上被中止了。除此之外,很好的答案! +1,顺便说一句,无法区分未分配任何值的变量和已分配 undefined 值的变量。 @RobG 这不是真的。例如,在浏览器中,您可以检查 window.hasOwnProperty('neverCreatedVariable') 将返回 false,但 window.hasOwnProperty('assignedVariableToUndefined') 将返回 true。在一个函数中你可以这样做。hasOwnProperty @Lorenz03Tx — 全局执行上下文是唯一可以使用 hasOwnProperty 测试变量的上下文,它不能用于测试函数范围的变量(这不是一般的解决方案)。 OP 和 answer 在函数执行上下文中。 @RobG ,如果你在一个函数中,你可以添加“use strict”并使用 try 块来捕获 ref 错误。通常不会做的事情,但重点是分配未定义的变量和未定义的变量之间存在差异,如果您有用例/希望这样做,您可以检查它。【参考方案2】:

在参数列表中包含标识符实际上与在函数体中声明变量相同,例如

function bar(foo) 

等价于

function bar(foo) 
  var foo;

当然,如果您想将值传递给 foo,您只需执行第一个。

这样做的主要原因:

(function($) 
    // use $ here instead of jQuery
(jQuery));

是,当 jQuery 发布时,Prototype.js 已经使用“$”作为其 main 函数的标识符有一段时间了。上面的模式允许 jQuery 和prototype.js 在同一个页面中使用,使用“$”作为不同事物的标识符。

传入 documentwindow 充其量只是一个没有什么好处的微优化。它不能防止它们被赋予与预期不同的值。只是不要打扰并在函数内使用 windowdocument 作为全局标识符。

在参数中包含 undefined 并且不向其传递值并不是确保 undefined 确实是未定义的明智方法,因为如果值意外出现,它仍然会受到影响通过了。更安全的方法是:

(function() 
    var undefined;
    ...
());

现在您确定函数范围内的 undefined 确实是未定义的。或者,如果您想要分配:

(function() 
    var undefined = void 0;
    ...
());

但这只是额外的输入。

【讨论】:

【参考方案3】:

在详细回答之前让我指出,与其他编程语言不同,javascript 有点奇怪,原因有两个:首先,它是仓促创建的,这意味着很多东西没有得到完善或实现。其次,它很快就被互联网采用了,微软非常快速、非常准确地复制了这种语言(包括语言中的错误)。这样做的结果是,试图纠正语言设计中的错误是困难的和/或不可能的,因为他们不想破坏与现有网站的兼容性。

现在进入细节:

function($) 究竟是什么意思

没什么特别的。在 javascript 函数和变量名称中允许使用字母 a-z,包括大写字母、数字 0-9 以及符号 $_。对于如何使用它们没有其他限制。虽然有一些准则和约定,但有些是语言规范本身提到的,有些是随着编程社区的发展而发展起来的。

因此,$ 只是一个变量名。没有区别:

function foo ($) alert($)

function foo (x) alert(x)

这只是为参数选择的名称。但是,该规范强烈建议 $ 不应被人类编写的代码使用。对于计算机生成的代码(例如咖啡脚本编译器)可以,但对于常规脚本则不行。另一方面,强烈建议在使用 jQuery 的脚本中 $ 始终引用 jQuery 对象(碰巧它也是一个函数,这在 javascript 中很好,因为函数是对象)。

由于您正在编写 jQuery,function ($) 的含义是一个匿名函数,它接受一个参数并且它期望的参数是一个 jQuery 对象。

为什么要在函数($) 的参数中包含 window、document 和 undefined?

可以说,javascript 的设计错误之一是缺乏对常量和/或不可变/受保护的变量/对象的支持。因此,windowdocumentundefined 实际上是常规全局变量 - 任何人都可以将它们重新分配给任何东西。

以下是疯狂但有效的 javascript 代码:

window = 42;

没有理智的程序员会这样做,但它仍然是可能的。 jQuery 开发人员对此非常关注,因此 jQuery 尽最大努力将真正的 windowdocumentundefined 传递到插件中,以防万一有人疯狂地做疯狂的事情。

javascript 的一个特点是函数参数会覆盖全局变量。因此,如果上述任何变量已被其他人劫持,它们将在函数中重新分配给它们的专有名称。

如果我这样做,我如何访问实际的窗口和文档对象?

您应该将正确的windowdocumentundefined 作为名为@9​​87654341@、documentundefined 的参数传递给函数。执行其他任何操作都意味着您无法再访问windowdocumentundefined 对象。

您可以采取一些疯狂的解决方法来尝试获取window 对象(也称为全局对象),然后您可以从那里获取document。 jQuery 代码实际上是在undefined 被劫持的情况下取回undefined 的一种解决方法。

未定义什么,为什么?

正如你所说的那样。 undefined 是 javascript 赋予已声明但未分配值的事物的值。但在 javascript 中,undefined 只是一个全局变量。如果你不碰它,它最初的值是undefined(我知道循环逻辑)。但是可以修改:

undefined = 'boom!';

此后所有未定义的变量都将具有"boom!" 的值。 javascript 语言的最新规范实际上不允许重新分配给 undefined 但截至今天只有 Safari 这样做。

同样,没有理智的程序员会这样做。

【讨论】:

说“函数参数覆盖全局变量”意味着它们改变了它们的值。最好说它们“遮蔽”它们或类似的行话(除非您想解释作用域链上的标识符解析)。 请详细解释作用域链上的标识符解析。 :) @RobG:我发现在向团队中的新手解释时,使用“影子”之类的术语会让他们有些困惑。多年来,我只是告诉他们局部变量会覆盖全局变量,到目前为止,所有人都比更严格的正确陈述更清楚地理解了这个简单的陈述。【参考方案4】:

function($) 是什么意思?

$ 作为参数传递给您的函数使您可以使用jQuery 速记对象,正如您清楚地指出的那样,它只是jQuery 对象的别名(另请查看jQuery.noConflict())。

为什么我应该包含windowdocumentundefined 作为function($) 的参数? 我有一个粗略的想法,为什么 window 和 document 已包含在包装插件的匿名函数的参数中(我不知道还能调用什么),因为它在 cmets 中给出了它有点缩短了执行时间.但这是如何工作的?包装插件的上述匿名函数的任何参数都会传递到哪里?

windowdocument 作为局部变量传递有点缩短执行时间,因为访问局部变量比访问全局变量更容易、更快捷,因为local variables are first in the chain。哪种回答你的第二个问题:参数在匿名函数范围中传递,这就是你创建匿名函数的全部意义:创建一个闭包。

这些参数在函数末尾传递,您可以在其中看到最后一个括号。闭包中的变量window 引用了global window,因为您在最后传递了它。

之所以通过undefined 是因为undefined 是一个Javascript 混乱的混乱。基本上,undefined is a property of the global object,也就是它是一个变量,一个singleton它不受保护,这意味着它可以被覆盖。这意味着您可以拥有一个实际定义的undefined

通过传递undefined,您可以确保即使有人在全局范围内弄乱了undefined(永远不要相信全局范围!:)),您仍然会得到正确的undefined

此外,同样的性能原因也适用于undefined

如果我这样做,我如何访问实际的窗口和文档对象?

您已经在访问它们,因为您正在将它们传递给您的函数。因此,您可以操纵它们。

【讨论】:

以上是关于在包装 jQuery 插件的匿名函数中使用“窗口”、“文档”和“未定义”作为参数的主要内容,如果未能解决你的问题,请参考以下文章

jQuery匿名函数和自定义插件

深入理解jQuery插件开发总结

jQuery绑定方法

将整个 Javascript 文件包装在像“(function() ... )()”这样的匿名函数中的目的是啥?

编写JQuery插件-2

(function($)...)(jQuery)是啥意思