JavaScript的隐藏特性?

312

1
你是不是想说:“看到了那个问题吸引的声望和观点,我想问几乎完全相同的问题来提高自己的声望”?;-) - Bobby Jack
1
当然,悲观主义者。 :) 我考虑过将这个问题变成社区问题。此外,当你获得一定数量的积分后,所有的回报都会递减。 - Allain Lalonde
1
好的,看起来你并不“需要”声望!我猜我只是对C#这个问题有些困惑——它似乎并不是这个网站旨在解决的问题类型。 - Bobby Jack
3
是的,也许不是这样,但我发现答案中的知识非常棒。如果没有 Stack Overflow(SO),要让一般的 C# 程序员在一个地方接触到所有这些知识将会很难。玩耍多年才能获得同样的宝贵经验清单。 - Allain Lalonde
我喜欢这一系列的问题;我认为答案的“digg”式系统比论坛上的“+1”更好。更容易看出社区认为最重要的是什么。我相信这对于谷歌来说也是良好的链接诱饵! - Martin Clarke
7
我已经专业编写JavaScript 10年了,从这个帖子中我学到了一些东西。谢谢Alan! - Andrew Hedges
99个回答

99

在Crockford的《Javascript: The Good Parts》中也提到:

parseInt()是危险的。如果你传递一个没有告诉它正确基数的字符串,它可能会返回意外的数字。例如,parseInt('010')返回8而不是10。将基数传递给parseInt可以使其正常工作:

parseInt('010') // returns 8! (in FF3)
parseInt('010', 10); // returns 10 because we've informed it which base to work with.

13
在进行代码审查时,一定要注意这一点。在大多数测试中,省略", 10"是一个常见的错误,容易被忽略。 - Doug Domeny
多年前我曾因进制问题而遭受打击,从此便再也没有忘记这样的反直觉的事情。这是一个很好的指出点,因为它会让你想一阵子。 - JamesEggers
4
为什么不使用Math.floorNumber10 === Math.floor("010"); 10 === Number("010"); 浮点数: 42 === Math.floor("42.69"); 42.69 === Number("42.69"); - just somebody
1
@Infinity 如果还没有发布答案,你应该发一个。我不知道覆盖内置函数行为只是这么简单。当然,这应该让人们更仔细地查看他们从其他网站借用的任何代码包。那个无害的 parseInt 函数很容易被制造成一些不太无害的东西。 - bob-the-destroyer
6
@Infinity: 重新定义这个函数来突出显示“编码错误”怎么样? __parseInt = parseInt; parseInt = function (str, base) { if (!base) throw new Error(69, "All your base belong to us"); return __parseInt(str, base); } @Infinity建议将这个函数重新定义为突出显示“编码错误”,你可以使用以下翻译: "将该函数重新定义以强调“编码错误”,请参考下方代码: __parseInt = parseInt; parseInt = function (str, base) { if (!base) throw new Error(69, "All your base belong to us"); return __parseInt(str, base); }" - JBRWilkinson
显示剩余5条评论

97

函数是对象,因此可以拥有属性。

fn = function(x){
   //...
}
fn.foo = 1;
fn.next = function(y){ // }

13
这是一个非常有用的技巧。例如,您可以将默认值设置为函数的属性。例如:myfunc.delay = 100; 然后用户可以更改默认值,所有函数调用将使用新的默认值。例如:myfunc.delay = 200; myfunc(); - BarelyFitz
有用...也危险! - palswim
看起来很懒散,为什么不使用变量呢? - instantsetsuna
1
@instantsetsuna:为什么要再有一个_单独的_变量?通常这归结为“在适当/有用的时候使用”;-) - VolkerK

91

我必须说自执行函数。

(function() { alert("hi there");})();
因为 Javascript 没有块级作用域,所以如果你想定义局部变量,可以使用自执行函数:

因为 JavaScript 没有块级作用域,所以如果你想要定义局部变量,可以使用一个立即执行函数:

(function() {
  var myvar = 2;
  alert(myvar);
})();

这里,myvar不会干扰或污染全局范围,并且在函数终止时消失。


7
重点不在于警报,而在于定义和执行一个函数。你可以让这个自我执行的函数返回一个值,并将该函数作为参数传递给另一个函数。 - ScottKoon
5
@Paul 这有助于封装。 - Mike Robinson
22
它也有助于块级作用域。 - Jim Hunziker
24
是的,我将所有的.js文件都封装在一个匿名自执行函数中,并将任何我想要全局访问的内容附加到window对象上。这样可以防止全局命名空间污染。 - cdmckay
2
这也可以用于重命名变量: (function($){...})(jQuery) 将把全局的 jQuery 对象访问为 $。 - Tgr
显示剩余8条评论

83

知道函数期望的参数数量

function add_nums(num1, num2, num3 ){
    return num1 + num2 + num3;
}
add_nums.length // 3 is the number of parameters expected.

知道函数接收了多少个参数

function add_many_nums(){
    return arguments.length;
}    
add_many_nums(2,1,122,12,21,89); //returns 6

23
从来不知道第一部分,太棒了! - mcjabberz
1
同样地,您可以使用function.length来查找函数期望的参数数量。 - Xavi
6
好的,我会尽力实现您的要求。以下是需要翻译的内容:@Xavi 这是答案的第一部分 - pramodc84

79

以下是一些有趣的内容:

  • 无论将 NaN 与任何东西(包括自己)进行比较,结果总是 false,这包括 ==<>
  • NaN 代表着不是一个数字,但如果你询问它的类型,它实际上返回的是一个数字。
  • Array.sort 可以采用比较函数,并由类似快速排序的驱动程序调用(取决于具体实现)。
  • 正则表达式“常量”可以保持状态,例如最后一个匹配内容。
  • 某些版本的 JavaScript 允许您在正则表达式上访问 $0$1$2 成员。
  • null 不像其他任何东西。它既不是对象、布尔值、数字、字符串也不是 undefined。它有点像“备用”的 undefined。(注意:typeof null == "object"
  • 在最外层的上下文中,this 返回无法命名的 [Global] 对象。
  • 使用 var 声明变量,而不是依靠变量的自动声明,可以让运行时真正优化对该变量的访问。
  • with 结构会破坏这种优化。
  • 变量名可以包含 Unicode 字符。
  • JavaScript 正则表达式实际上不是规则的。它们基于 Perl 的正则表达式,并且可能构造具有前瞻功能的表达式需要很长时间才能计算。
  • 代码块可以标记并用作 break 的目标。循环可以标记并用作 continue 的目标。
  • 数组不是稀疏的。将空数组的第 1000 个元素设置为某个值应该会导致其填充为 undefined。(取决于具体实现)
  • if (new Boolean(false)) {...} 将执行 {...}
  • Javascript的正则表达式引擎是实现特定的:例如,可以编写“非便携式”的正则表达式。

  • 5
    null 实际上是一个特殊的对象。typeof null 返回 "object"。 - Ates Goral
    4
    你可以通过以下方式从任何地方获取[全局]对象: var glb = function () { return this; }(); (提示:此代码片段使用JavaScript编写,用于获取当前运行环境的全局对象。) - Zilk
    2
    在浏览器中,JavaScript 中的全局对象是 window 对象。当在全局作用域中执行以下操作时:window.a == a; - Pim Jager
    8
    “Arrays are not sparse” 这个说法取决于具体的实现方式。如果你给 a[1000] 赋值并查看 a[999],那么是的,它是“未定义”的,但这只是在寻找不存在的索引时得到的默认值。如果你检查 a[2000],那也会是“未定义”,但这并不意味着你已经为其分配了内存空间。在 IE8 中,有些数组是密集的,有些则是稀疏的,这取决于 JScript 引擎在运行时的感觉。阅读更多信息:http://blogs.msdn.com/jscript/archive/2008/04/08/performance-optimization-of-arrays-part-ii.aspx - Chris Nielsen
    2
    @Ates 和 @SF:typeof 对于许多不同的类型返回 "object"。但是一旦你知道它的工作原理和哪些类型被识别为 "object",它至少在实现上是可靠且一致的。 - thomasrutter
    显示剩余10条评论

    77

    我知道我有点晚了,但我真的不敢相信+操作符的有用性仅仅被提到“将任何东西转换为数字”。也许这就是这个功能有多么隐蔽的原因?

    // Quick hex to dec conversion:
    +"0xFF";              // -> 255
    
    // Get a timestamp for now, the equivalent of `new Date().getTime()`:
    +new Date();
    
    // Safer parsing than parseFloat()/parseInt()
    parseInt("1,000");    // -> 1, not 1000
    +"1,000";             // -> NaN, much better for testing user input
    parseInt("010");      // -> 8, because of the octal literal prefix
    +"010";               // -> 10, `Number()` doesn't parse octal literals 
    
    // A use case for this would be rare, but still useful in cases
    // for shortening something like if (someVar === null) someVar = 0;
    +null;                // -> 0;
    
    // Boolean to integer
    +true;                // -> 1;
    +false;               // -> 0;
    
    // Other useful tidbits:
    +"1e10";              // -> 10000000000
    +"1e-4";              // -> 0.0001
    +"-12";               // -> -12
    

    当然,你可以使用Number()来完成所有这些操作,但是+运算符更加优美!
    你还可以通过重写原型的valueOf()方法来为对象定义数值返回值。对该对象执行的任何数字转换都不会导致NaN,而是valueOf()方法的返回值:
    var rnd = {
        "valueOf": function () { return Math.floor(Math.random()*1000); }
    };
    +rnd;               // -> 442;
    +rnd;               // -> 727;
    +rnd;               // -> 718;
    

    你可以简单地使用0xFF等,不需要使用+"0xFF" - alexia
    9
    你有点忽略了重点,那就是强制将其他原始类型和对象转换为数字。当然,你可以直接写0xFF,就像你可以写1代替+true一样。我建议如果需要的话,可以使用+("0x"+somevar)来替代parseInt(somevar, 16) - Andy E

    74

    通过原型属性在JavaScript中实现扩展方法

    Array.prototype.contains = function(value) {  
        for (var i = 0; i < this.length; i++) {  
            if (this[i] == value) return true;  
        }  
        return false;  
    }
    

    这将为所有Array对象添加一个contains方法。您可以使用以下语法调用此方法

    var stringArray = ["foo", "bar", "foobar"];
    stringArray.contains("foobar");
    

    18
    一般认为这样做是不好的,因为其他代码(不是你的代码)可能会对数组对象做出一些假设。 - Chris Noe
    39
    通常认为,对于数组对象做出假设也被视为一个不好的主意。 :( - eyelidlessness
    嗯……JavaScript 1.6 数组扩展?indexOf?有印象吗? - Breton
    2
    @Breton:这不是Array类特定的东西,只是一个例子。我用它来扩展new Date().toString()方法,允许使用掩码字符串。任何对象都可以被扩展,并且所有实例都会得到新的方法。 - Esteban Küber
    不好的想法:http://perfectionkills.com/whats-wrong-with-extending-the-dom/ - Mathias Bynens
    1
    @Mathias:这不是关于DOM的问题。 - dolmen

    60

    要正确地从对象中移除属性,你应该删除该属性,而不仅仅将其设置为undefined

    var obj = { prop1: 42, prop2: 43 };
    
    obj.prop2 = undefined;
    
    for (var key in obj) {
        ...
    

    属性 prop2 仍将成为迭代的一部分。如果要完全摆脱 prop2,则应执行以下操作:

    delete obj.prop2;
    

    当你遍历属性时,属性prop2将不再出现。


    3
    请注意,删除语句存在浏览器特定的问题。例如,在IE中尝试删除非原生JS对象(即使是删除您自己添加的属性)时,这将失败并显示一个大错误。它也不适用于删除变量,比如 delete myvar; 但我认为在某些浏览器中可以使用。上面答案中的代码似乎相当安全。 - thomasrutter
    顺便说一下,未定义也可以是一个变量!尝试使用 var undefined = "something"。 - Johann Philipp Strathausen

    57

    with.

    它很少被使用,而且说实话,也很少有用... 但是,在有限的情况下,它确实有其用途。

    例如:对象字面量非常方便快速地设置一个 新的 对象的属性。但是如果你需要更改现有对象的 一半 属性呢?

    var user = 
    {
       fname: 'Rocket', 
       mname: 'Aloysus',
       lname: 'Squirrel', 
       city: 'Fresno', 
       state: 'California'
    };
    
    // ...
    
    with (user)
    {
       mname = 'J';
       city = 'Frostbite Falls';
       state = 'Minnesota';
    }
    

    Alan Storm指出这可能有一定危险性:如果作为上下文使用的对象没有其中一个被赋值的属性,它将在外部范围中解决,可能会创建或覆盖全局变量。 如果你习惯于编写用于处理对象的代码,其中留空默认值的属性未定义,则这尤其危险:

    var user = 
    {
       fname: "John",
    // mname definition skipped - no middle name
       lname: "Doe"
    };
    
    with (user)
    {
       mname = "Q"; // creates / modifies global variable "mname"
    }
    

    因此,对于这种赋值情况,避免使用with语句可能是一个好主意。

    另请参阅:JavaScript的“with”语句是否有合法用途?


    29
    传统智慧认为应该避免使用with语句。如果用户对象没有你提到的属性之一,那么在with块之外的变量会被修改。这样会导致错误。更多信息请参见http://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/。 - Alana Storm
    1
    Shog,反对意见并不是关于拼写错误的变量,而是关于查看代码块并能够确定该块中任何特定行所执行的操作。因为Javascript对象非常动态,所以无法确定它在任何时刻具有哪些属性/成员。 - Alana Storm
    2
    "Amen - 如果我在任何发现的 JavaScript 代码中看到 "with" 语句,我会将其删除并询问编写它的开发者是否知道为什么不应该使用它...... "隐藏特性"?更像是"可憎的特性"。" - Jason Bunting
    4
    最近,Douglas Crockford 在一期 .NET Rocks! 的播客中表示,“with” 是JavaScript中最糟糕的部分之一。 - core
    1
    @Shog9:这不是一项实现细节:with的定义方式使其无法进行优化。这个演示提供了关于为什么with很慢的信息。在旧版本的IE中,所有语句都很慢,所以with并不比其他语句更慢。 - dolmen
    显示剩余11条评论

    51

    方法(或函数)可以在不是它们设计用于处理的类型的对象上调用。这对于在自定义对象上调用本地(快速)方法非常有用。

    var listNodes = document.getElementsByTagName('a');
    listNodes.sort(function(a, b){ ... });
    

    代码崩溃的原因是listNodes不是一个Array

    Array.prototype.sort.apply(listNodes, [function(a, b){ ... }]);
    

    这段代码之所以可行,是因为listNodes定义了足够类似数组的属性(如length、[]运算符),让sort()可以使用。


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