JavaScript的隐藏功能?[关闭]


312

您认为每个程序员都应该知道JavaScript的哪些“隐藏功能”?

在看到以下问题的答案的出色质量之后,我认为是时候向它询问JavaScript了。

尽管JavaScript可以说是目前最重要的客户端语言(只需问问Google),但令人惊讶的是,大多数Web开发人员几乎没有意识到它的强大功能。


1
您是否不是说“看到了另一个问题所吸引的代表观点和观点,我想我会问几乎完全相同的问题来提出自己的问题”?;-)
Bobby Jack

1
当然,悲观主义者。:)我曾考虑将其作为社区问题。同样,获得一定数量的积分后,收益都将递减。
Allain Lalonde

1
足够公平-看起来您似乎不需要“代表”!我想我对C#只是一个大问题-在我看来并不完全像该网站所针对的问题类型。
鲍比·杰克

3
是的,也许不是,但是我发现答案中的知识很棒。我认为,如果不是出于SO的考虑,很难将一个普通的C#程序员集中在一处。要想得出相同的来之不易的清单,需要花费很多年的时间。
Allain Lalonde

7
我从事JavaScript已有10年的专业经验,从该线程中学到了一三件事。谢谢,艾伦!
Andrew Hedges

Answers:


373

您无需为函数定义任何参数。您可以只使用函数的arguments类似数组的对象。

function sum() {
    var retval = 0;
    for (var i = 0, len = arguments.length; i < len; ++i) {
        retval += arguments[i];
    }
    return retval;
}

sum(1, 2, 3) // returns 6

117
值得一提的是,尽管参数的作用类似于数组,但它并不是真正的javascript数组-只是一个对象。因此,您无法执行join(),pop(),push(),slice()等。(如果需要,可以将其转换为真实的数组:“ var argArray = Array.prototype.slice.call(arguments);”)
Jacob Mattison,2009年

51
还值得注意的是,访问Arguments对象的费用相对较高-最好的示例是在Safari,Firefox和Chrome夜间环境中,其中仅引用该arguments对象会使调用函数的速度大大降低-例如。if(false)参数;会伤害性能。
olliej

48
同样,参数具有“ callee”属性,该属性是当前函数本身。这允许使用匿名函数进行递归,很酷!
文森特·罗伯特

4
@Nathan“ f(x,y,z)”看起来比“ f([x,y,z])”更好。
Mark Cidade

16
@Vincent Robert:请注意,arguments.callee已弃用。

204

我可以引用道格拉斯·克罗克福德(Douglas Crockford)的绝妙著作《JavaScript:The Good Parts》中的大部分 内容

但我会举一个你,总是使用===!==替代==!=

alert('' == '0'); //false
alert(0 == ''); // true
alert(0 =='0'); // true

==不是可传递的。如果使用===它,则所有这些语句都将为false。


29
许多人认为克罗克福德无所不知,真是可惜。诚然,这家伙对他的大多数批评是正确的,但我没有像许多开发人员一样对他的东西给予全面认可……
Jason Bunting

21
我支持杰森的警告。这本书本身就是很有意思,它确实给了很多好的建议,但DC是太相信他做事的方式是唯一正确的道路,一切是“有缺陷”。如果您想要一些示例,请查看他在JSLint Yahoo Group上的答复。
Zilk

30
如果您对动态类型感到困惑并且只希望它等于“真正”,那么使用===而不是==是一个很好的建议。我们当中那些了解动态类型的人可能会在我们知道要强制转换的情况下继续使用==,例如0 ==''或0 =='0'。
thomasrutter

20
==和===与动态类型无关。==会输入强制,这是另一种野兽。如果您知道要转换为字符串/数字/等,则可以明确地执行此操作。
Rene Saarsoo,2009年

15
我认为最恐怖的部分=='\n\t\r ' == 0=> true...:D
Shrikant Sharat

189

函数是JavaScript中的一等公民:

var passFunAndApply = function (fn,x,y,z) { return fn(x,y,z); };

var sum = function(x,y,z) {
  return x+y+z;
};

alert( passFunAndApply(sum,3,4,5) ); // 12

函数式编程技术可以用来编写精美的javascript

特别是,可以将函数作为参数传递,例如Array.filter()接受回调:

[1, 2, -1].filter(function(element, index, array) { return element > 0 });
// -> [1,2]

您还可以声明仅在特定函数范围内存在的“私有”函数:

function PrintName() {
    var privateFunction = function() { return "Steve"; };
    return privateFunction();
}

3
使用javascript制作函数的方法有以下三种:function sum(x,y,z){return(x + y + z); }和var sum = new Function(“ x”,“ y”,“ z”,“ return(x + y + z);”); 其他方式。
Marius

6
函数即数据的概念无疑在我的书中赢得了要点。
詹森·邦廷

我刚刚更新了示例,以显示如何使用仅在特定功能范围内存在的“私人”功能。
克里斯·彼得施曼

new Function()就像邪恶一样eval。不使用。
尼古拉斯

11
不确定这是隐藏功能...更像是核心功能。
Claudiu 2010年

162

您可以使用in运算符检查对象中是否存在键:

var x = 1;
var y = 3;
var list = {0:0, 1:0, 2:0};
x in list; //true
y in list; //false
1 in list; //true
y in {3:0, 4:0, 5:0}; //true

如果您发现对象文字太丑陋,则可以将其与无参数功能提示结合使用:

function list()
 { var x = {};
   for(var i=0; i < arguments.length; ++i) x[arguments[i]] = 0;
   return x
 }

 5 in list(1,2,3,4,5) //true

22
不太聪明,它检查是否存在键,而不是值。x在列表中;仅因为x [1]!= null,而不是因为那里的值1而起作用。
Armin Ronacher

1
我已经有一段时间没有使用该技术了,所以我忘记了我之前实际使用过对象文字。感谢您的更正。
Mark Cidade

34
另外,要小心:in运算符还会测试原型链!如果有人在Object.prototype上放置了一个名为“ 5”的属性,则即使您在list(1、2、3、4)中调用了“ 5”,第二个示例也将返回true。您最好使用hasOwnProperty方法:list(1、2、3、4)。即使Object.prototype的属性为“ 5”,hasOwnProperty(5)也会返回false。
Martijn

3
对于最通用的解决方案,可以测试一个对象是否具有自己的属性,即使它被命名为“ hasOwnProperty”,也必须一直尝试:Object.prototype.hasOwnProperty.call(object,name) ;
克里斯·科瓦尔

1
@Kris,除非有人改写Object.prototype.hasOwnProperty;否则)
Nick

153

为变量分配默认值

您可以||在赋值表达式中使用逻辑或运算符来提供默认值:

var a = b || c;

a变量将获得的价值c只有当bfalsy(如果是nullfalseundefined0empty string,或NaN),否则a将获得的价值b

如果您想在不提供参数的情况下为参数提供默认值,这通常在函数中很有用:

function example(arg1) {
  arg1 || (arg1 = 'default value');
}

事件处理程序中的IE后备示例:

function onClick(e) {
    e || (e = window.event);
}

以下语言功能已经存在很长时间了,所有JavaScript实现都支持它们,但是直到ECMAScript 5th Edition才成为规范的一部分:

debugger声明

描述:§12.15调试器语句

该语句允许您通过以下方式以编程方式在代码中放置断点

// ...
debugger;
// ...

如果调试器存在或处于活动状态,它将导致该调试器立即中断,就在那条线上。

否则,如果调试器不存在或处于活动状态,则该语句不会产生可观察的效果。

多行字符串文字

描述:§7.8.4字符串文字

var str = "This is a \
really, really \
long line!";

您必须要小心,因为旁边的字符\ 必须是行终止符,\例如,如果在后面有一个空格,则代码看起来将完全相同,但是会产生一个SyntaxError


28
如果为null,则视为false;否则为false。a = 0 || 42; 会给你42。这可以与Python或C#媲美?操作员。如果您想要C#行为,请执行a =(b === null)吗?c:b;
Armin Ronacher

如果您在ASP.NET上开发,它也可以在Visual Studio中使用:)
chakrit 2010年

2
我希望有适当的|| 仅用于未定义。我今天被0咬住了,因为我想创建重载方法的仿真,因此最后一个参数是可选的,而将使用默认值。
egaga 2010年

+1这个技巧被默认的Google Analytics(分析)代码段利用。`var _gaq = _gaq || [];`; 它可以防止过度热情的用户覆盖自己的作品。
Yahel 2010年

2
我不了解多行字符串文字技术。太好了,谢谢。
Charlie Flowers

145

JavaScript没有块作用域(但是它有闭包,所以我们甚至称它为?)。

var x = 1;
{
   var x = 2;
}
alert(x); // outputs 2

3
那是一个好人。与大多数类似C的语言相比,这是一个非常重要的区别。
Martin Clarke

9
您始终可以执行“ var tmp = function(){/ *块作用域* /}();”。语法很丑陋,但是可以用。
Joeri Sebrechts

3
:或者你也可以用“让”,如果是Firefox的唯一stackoverflow.com/questions/61088/...
尤金横田

10
或者只是:(function(){var x = 2;})(); 警报(x的类型); //未定义
Pim Jager,2009年

@Pim:JSLint说:“将调用移到包含该函数的括号中。” 以及“在“功能”和“(”之间恰好预期有一个空格。”
Hello71 2011年

144

您可以使用[]代替访问对象属性.

这使您可以查找与变量匹配的属性。

obj = {a:"test"};
var propname = "a";
var b = obj[propname];  // "test"

您也可以使用它来获取/设置名称不是合法标识符的对象属性。

obj["class"] = "test";  // class is a reserved word; obj.class would be illegal.
obj["two words"] = "test2"; // using dot operator not possible with the space.

有些人不知道这一点,最终使用了这样的eval(),这是一个非常糟糕的主意

var propname = "a";
var a = eval("obj." + propname);

这更难阅读,更难发现错误(无法使用jslint),执行速度较慢,并且可能导致XSS漏洞。


评估是邪恶的,尽管几乎没有必要
Doug Domeny 2009年

我从不使用eval并记得我发现它的时候。这让我非常高兴。

总之,可以通过点和下标符号来访问对象属性
Russ Cam 2010年

9
有趣的是,点引用实际上是括号引用的语法糖。foo.bar无论如何,根据规范,其行为就像foo["bar"]。还请注意,所有内容都是字符串属性。即使您进行数组访问,也会array[4]将4转换为字符串(同样,至少根据ECMAScript v3规范)
Claudiu 2010年

我想每个JS程序员都应该知道这一点。
Cem Kalyoncu

144

如果您要搜索给定主题的不错的JavaScript参考,请在查询中包括“ mdc”关键字,您的第一个结果将来自Mozilla开发人员中心。我没有随身携带任何离线参考书或书籍。我总是使用“ mdc”关键字技巧来直接获得所需的内容。例如:

Google:javascript数组排序mdc
(大多数情况下,您可以省略“ javascript”)

更新: Mozilla开发人员中心已重命名为Mozilla开发人员网络。“ mdc”关键字技巧仍然有效,但是很快我们可能不得不开始使用“ mdn”了


50
哇,很棒的资源。立刻比w脚的w3schools好…
DisgruntledGoat09 2009年

11
如果您使用的是Firefox,甚至不需要使用Google:只需在地址栏中输入“ array mdc”,然后按Enter。
Sasha Chedygov

2
最好的部分是结果第一页上的堆栈溢出问题:)
Jiaaro 2010年

5
一个适当的建议是:promotingjs.com,这是一个基层SEO计划,旨在在Google搜索结果中进一步推动MDC结果。
Yahel 2010年

3
现在是MDN文档中心,因此'mdc'关键字仍然有效:)
Aleadam 2011年

143

也许对某些人有些明显...

安装Firebug并使用console.log(“ hello”)。与使用random alert();相比,这要好得多,我记得几年前就做了。


12
只是不要忘记在将代码发布给可能未安装Firebug的其他人之前删除控制台语句。
克里斯·诺

161
功能log(msg){if(console)console.log(msg)else alert(msg)}
Josh

4
更好的是,在日志语句前加上“ ;;;” 然后minify会替您处理。(至少,我使用的Perl模块具有该功能,并声称它是司空见惯的。)
Kev

10
Josh:由于未定义控制台,因此无法使用。您可以检查typeof console!==“ undefined”或window.console。
伊莱·格雷

23
始终包括:if(typeof('console')=='undefined'){console = {log:function(){}}; },那么您可以继续使用console.log,它什么也没做。
gregmac

120

私人方法

对象可以具有私有方法。

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

    // A private method only visible from within this constructor
    function calcFullName() {
       return firstName + " " + lastName;    
    }

    // A public method available to everyone
    this.sayHello = function () {
        alert(calcFullName());
    }
}

//Usage:
var person1 = new Person("Bob", "Loblaw");
person1.sayHello();

// This fails since the method is not visible from this scope
alert(person1.calcFullName());

16
那实际上不是私有函数,而是局部作用域中的函数变量。
基思

6
没错,但从所有运营定义来看,我都能想到这是一种方法。这是一个代码块,其名称可以访问实例状态,并且只能由该实例看到。您对私有方法的定义是什么?
Allain Lalonde

14
@Zach,完全是!在使用基于类的OO语言多年后,很容易忘记它们仅仅是OO概念的一种实现。当然,尝试将基于类的OO塞入JS的各种库也无济于事……
Shog9

5
只是想知道,person1是否有法律博客?;-)
travis

4
+1作为被捕的发展参考
Domenic 2010年

99

在Crockford的“ Javascript:好的部分”中也提到了这一点:

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 2009年

多年前,我被基数问题困扰,从未忘记过如此反直觉的事情。需要指出的一件好事,因为它会让您感到困惑。
JamesEggers

4
为什么不使用Math.floorNumber10 === Math.floor("010"); 10 === Number("010");浮点数:42 === Math.floor("42.69"); 42.69 === Number("42.69");
某人

1
@Infinity如果尚未发布答案,则应该这样做。我不知道重写内置函数的行为就这么简单。当然,这应该使人们更仔细地了解他们从其他站点借来的任何代码包。parseInt可以很容易地使这种无害的功能执行不那么无害的操作。
bob-the-destroyer

6
@Infinity:重新定义fn以突出显示“编码错误”怎么办? __parseInt = parseInt; parseInt = function (str, base) { if (!base) throw new Error(69, "All your base belong to us"); return __parseInt(str, base); }
JBR威尔金森

97

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

fn = function(x){
   // ...
}

fn.foo = 1;

fn.next = function(y){
  //
}

13
这是一个非常有用的技巧。例如,您可以将默认值设置为该函数的属性。例如:myfunc.delay = 100; 然后,用户可以更改默认值,并且所有函数调用都将使用新的默认值。例如:myfunc.delay = 200; myfunc();
BarelyFitz

有用...危险!
palswim

看起来很草率,为什么要使用它而不是变量?
Instantsetsuna 2010年

1
@instantsetsuna:为什么还有另一个单独的变量?与往常一样,这归结为“在适当/有用时使用” ;-)
VolkerK 2010年

91

我不得不说自我执行功能。

(function() { alert("hi there");})();

由于Javascript 没有块作用域,因此,如果要定义局部变量,可以使用自执行函数:

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

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


2
这有什么用?通过将警报置于功能之外,您可以获得相同的结果。
PotatoEngineer 2009年

7
这与警报无关,而与一次定义和执行一个函数有关。您可以让自执行函数返回一个值,然后将该函数作为参数传递给另一个函数。
ScottKoon

5
@Paul对封装很有用。
迈克·罗宾逊

22
这对于块作用域也很好。
Jim Hunziker,2009年

24
是的,我将所有.js文件封装在一个匿名的自执行函数中,并将希望全局访问的所有内容附加到该window对象。防止全局名称空间污染。
cdmckay,2009年

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
哈维

6
@Xavi这是答案的第一部分
pramodc84

79

以下是一些有趣的事情:

  • 比较NaN有什么(甚至NaN)始终是假的,包括==<>
  • NaN 代表非数字,但如果您要求输入类型,它实际上会返回一个数字。
  • Array.sort 可以采用比较器功能,并由类似quicksort的驱动程序调用(取决于实现)。
  • 正则表达式“常量”可以保持状态,就像它们匹配的最后一件事一样。
  • 一些JavaScript的版本允许你访问$0$1$2对正则表达式的成员。
  • null与众不同。它既不是对象,布尔值,数字,字符串,也不是undefined。有点像“替代” undefined。(注:typeof null == "object"
  • 在最外面的上下文中,this产生否则无法命名的[Global]对象。
  • 使用声明变量var,而不仅仅是依赖于变量的自动声明,这为运行时提供了优化访问该变量的真正机会
  • with结构会破坏这种optimzations
  • 变量名称可以包含Unicode字符。
  • JavaScript正则表达式实际上不是正则表达式。它们基于Perl的正则表达式,并且可以使用需要非常长的时间进行评估的先行构造表达式。
  • 可以标记块并将其用作的目标break。可以标记循环并将其用作的目标continue
  • 数组不稀疏。设置原本为空的数组的第1000个元素应使用填充undefined。(取决于实施情况)
  • if (new Boolean(false)) {...} 将执行{...}
  • Javascript的正则表达式引擎特定于实现:例如,可以编写“非便携式”正则表达式。

[为了回应好评,进行了一些更新;请参阅评论]


5
null实际上是一个(特殊)对象。typeof null返回“对象”。
Ates Goral,

4
您也可以像这样从任何地方获取[Global]对象:var glb = function(){return this; }();
Zilk

2
浏览器中javascript中的全局对象是window对象。在全局范围内时:window.a == a;
Pim Jager

8
“数组不稀疏”取决于实现。如果设置a [1000]的值并查看a [999],则是,它是undefined,但这只是在查找不存在的索引时获得的默认值。如果选中a [2000],也将是undefined,但这并不意味着您已经为其分配了内存。在IE8中,某些数组是密集的,而有些则是稀疏的,这取决于当时的JScript引擎的感觉。在此处了解更多信息:blogs.msdn.com/jscript/archive/2008/04/08/…–
克里斯·尼尔森

2
@Ates和@SF:typeof返回一系列不同类型的“对象”。但是,一旦您知道它是如何工作的以及将哪些类型标识为“对象”,它就至少在实现上是可靠且一致的。
thomasrutter

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"
nyuszika7h 2011年

9
@ Nyuszika7H:您有点遗漏了一点,那就是将其他基元和对象强制转换数字。当然,您可以只是0xFF1而不是用相同的方式+true。我建议您可以使用+("0x"+somevar)替代parseInt(somevar, 16)
安迪E

75

扩展方法在JavaScript ”通过原型属性。

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

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

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

18
通常认为这是一个坏主意,因为其他代码(不是您的代码)可能会对Array对象进行假设。
克里斯·诺

39
对Array对象进行假设通常也被认为是一个坏主意。:(
失明的眼神

嗯.. javascript 1.6数组附加功能?指数?钟声响了吗?
布列塔尼

2
@Breton:不是特定于Array类的东西,仅是示例。我用它来扩展新的Date()。toString(); 方法,允许使用掩码字符串。任何对象都可以扩展,所有实例都可以使用新方法。
EstebanKüber09年

1
@Mathias:这与DOM无关。
支石墓

60

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

var obj = { prop1: 42, prop2: 43 };

obj.prop2 = undefined;

for (var key in obj) {
    ...

属性prop2仍将是迭代的一部分。如果要完全摆脱prop2,应改为:

delete obj.prop2;

遍历属性prop2时,属性prop2将不再显示。


3
请注意,delete语句并非没有特定于浏览器的怪癖。例如,如果您在IE中尝试使用该对象,而该对象不是本机JS对象(即使在删除自己添加的属性时),则将失败并产生一个大错误。它也不打算像删除myvar那样删除变量。但我认为这确实适用于某些浏览器。上面答案中的代码似乎很安全。
thomasrutter

顺便说一下,undefined也可以是变量!试试var undefined =“ something”
约翰·菲利普·斯特拉特豪森

57

with

它很少使用,坦率地说,很少有用...但是,在有限的情况下,它确实有其用途。

例如:对象文字对于在对象上快速设置属性非常方便。但是,如果您需要更改现有对象的一半属性,该怎么办?

var user = 
{
   fname: 'Rocket', 
   mname: 'Aloysus',
   lname: 'Squirrel', 
   city: 'Fresno', 
   state: 'California'
};

// ...

with (user)
{
   mname = 'J';
   city = 'Frostbite Falls';
   state = 'Minnesota';
}

艾伦风暴指出,这可能是有点危险:如果用作上下文对象不具有的属性之一被分配到,它会在外部范围加以解决,有可能产生或覆盖全局变量。如果您习惯于编写代码以使用带有默认值或空值的属性未定义的对象,则这特别危险:

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块的伪作用域之外的变量。那就是错误。有关更多信息,请访问yuiblog.com/blog/2006/04/11/with-statement-considered-harmful
艾伦·斯托姆

1
Shog,反对不是关于拼写错误的变量,而是关于查看代码块,并能够确定地说该块中的任何特定行的作用。由于Javascript对象是如此动态,因此您无法随时确定它具有什么属性/成员。
艾伦·斯托姆

2
阿们-如果我在发现的任何JS中看到“ with”语句,我都会将其消除,并质疑编写该语句的开发人员,以确保他知道为什么使用它不是一件好事……“隐藏功能”?更像是“令人讨厌的功能”。
詹森·邦廷

1
考虑一个更复杂的链abcd“ with(abc){d.foo = bar;}是强大的,并且本质上不易出错。关键是将根目录缩减一级。并且拼写错误的变量名?您正在引入一个错误如果您在任何地方都执行了此操作,则无论“ with”如何操作
annakata

4
道格拉斯·克罗克福德(Douglas Crockford)最近说,“ with”是.NET Rocks中JavaScript最糟糕的部分之一!播客。
核心

51

方法(或函数)可以在其设计使用的类型之外的对象上调用。在自定义对象上调用本机(快速)方法非常好。

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

此代码崩溃,因为listNodes不是Array

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

该代码之所以有效,是因为listNodes定义了足够多的类似数组的属性(length,[]运算符)供sort()


43

原型继承(由Douglas Crockford推广)彻底改变了您对Java语言中的事物负载的思考方式。

Object.beget = (function(Function){
    return function(Object){
        Function.prototype = Object;
        return new Function;
    }
})(function(){});

这是一个杀手!可惜几乎没有人使用它。

它使您可以“获取”任何对象的新实例,对其进行扩展,同时保持到其其他属性的(实时)原型继承链接。例:

var A = {
  foo : 'greetings'
};  
var B = Object.beget(A);

alert(B.foo);     // 'greetings'

// changes and additionns to A are reflected in B
A.foo = 'hello';
alert(B.foo);     // 'hello'

A.bar = 'world';
alert(B.bar);     // 'world'


// ...but not the other way around
B.foo = 'wazzap';
alert(A.foo);     // 'hello'

B.bar = 'universe';
alert(A.bar);     // 'world'

42

有些人将其称为品味问题,但是:

aWizz = wizz || "default";
// same as: if (wizz) { aWizz = wizz; } else { aWizz = "default"; }

三元运算符可以被链接为类似于Scheme的行为(cond ...):

(cond (predicate  (action  ...))
      (predicate2 (action2 ...))
      (#t         default ))

可以写成...

predicate  ? action( ... ) :
predicate2 ? action2( ... ) :
             default;

这非常“实用”,因为它可以分支代码而没有副作用。所以代替:

if (predicate) {
  foo = "one";
} else if (predicate2) {
  foo = "two";
} else {
  foo = "default";
}

你可以写:

foo = predicate  ? "one" :
      predicate2 ? "two" :
                   "default";

也可以很好地与递归一起使用:)


我喜欢您提供的谓词语法。我从来没有想过像这样的链接。整齐。
Allain Lalonde

2
嗯... JavaScript确实有一个switch()语句。:-)
staticsan

我不是switch语句的忠实拥护者-它们是C语言的产物,而不是函数式编程。在我的示例中,switch语句仍将需要三个单独的语句,所有这些语句均以“ foo =“ =-明显的不必要的重复”开头。
安德烈·费多罗夫

14
我首先欢迎三元运算符。
thomasrutter

8
在重新阅读时,我想指出的是,这并不是“使代码看起来像另一种语言”,而是实际上简化了代码的语义:当您尝试说“将foo设置为以下三种之一”时,事物”,则该语句应以“ foo = ...”开头,而不是“ if”。
安德烈·费多罗夫

41

数字也是对象。因此,您可以做一些很酷的事情,例如:

// convert to base 2
(5).toString(2) // returns "101"

// provide built in iteration
Number.prototype.times = function(funct){
  if(typeof funct === 'function') {
    for(var i = 0;i < Math.floor(this);i++) {
      funct(i);
    }
  }
  return this;
}


(5).times(function(i){
  string += i+" ";
});
// string now equals "0 1 2 3 4 "

var x = 1000;

x.times(function(i){
  document.body.innerHTML += '<p>paragraph #'+i+'</p>';
});
// adds 1000 parapraphs to the document

我的天啊!我不知道toString(radix)...
Ates Goral

1
的实现times效率不高:Math.floor每次都调用,而不是一次调用。
支石墓

33

JavaScript中的闭包如何(类似于C#v2.0 +中的匿名方法)。您可以创建一个创建函数或“表达式”的函数。

关闭示例:

//Takes a function that filters numbers and calls the function on 
//it to build up a list of numbers that satisfy the function.
function filter(filterFunction, numbers)
{
  var filteredNumbers = [];

  for (var index = 0; index < numbers.length; index++)
  {
    if (filterFunction(numbers[index]) == true)
    {
      filteredNumbers.push(numbers[index]);
    }
  }
  return filteredNumbers;
}

//Creates a function (closure) that will remember the value "lowerBound" 
//that gets passed in and keep a copy of it.
function buildGreaterThanFunction(lowerBound)
{
  return function (numberToCheck) {
    return (numberToCheck > lowerBound) ? true : false;
  };
}

var numbers = [1, 15, 20, 4, 11, 9, 77, 102, 6];

var greaterThan7 = buildGreaterThanFunction(7);
var greaterThan15 = buildGreaterThanFunction(15);

numbers = filter(greaterThan7, numbers);
alert('Greater Than 7: ' + numbers);

numbers = filter(greaterThan15, numbers);
alert('Greater Than 15: ' + numbers);

1
我不确定,但是可以返回(numberToCheck> lowerBound)吗?真假; 简单地变成return(numberToCheck> lowerBound); 只是想增进我的理解...
davidsleeps

4
我想说C#中的匿名函数等效于闭包,而不是
反过来

11
闭包和匿名函数是单独的,不同的概念。无需命名即可创建具有匿名功能的功能。将“正在创建”范围内的变量与已创建函数链接在一起是闭包。简而言之,闭包更像是一个隐藏的全局变量。
slebetman 2010年

1
确实如此。仅当匿名方法使用创建范围中的变量时,它才类似于闭包。我已经把答案的英文更新了。仍然有一些不足之处,但是我对正确的英语一无所知。
泰勒(Tyler)2010年

2
我不认为这是关于闭包的最好或最容易理解的例子。只是说。闭包的意义在于,即使当一堆变量似乎“超出范围”时,它们仍可用于该范围内最初定义的函数。在上面的示例中,这意味着即使内部函数buildGreaterThanFunction终止,该内部匿名函数仍然可以访问lowerBound变量。
thomasrutter

32

您还可以扩展(继承)类并使用所涉及的原型链spoon16 覆盖属性/方法

在下面的示例中,我们创建一个Pet类并定义一些属性。我们还将重写从Object继承的.toString()方法。

之后,我们创建一个Dog类,该类扩展Pet并重写.toString()方法,从而再次更改其行为(多态)。此外,我们在子类中添加了其他一些属性。

此后,我们检查继承链以显示Dog仍然是Dog类型,Pet类型和Object类型。

// Defines a Pet class constructor 
function Pet(name) 
{
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
}

// Adds the Pet.toString() function for all Pet objects
Pet.prototype.toString = function() 
{
    return 'This pets name is: ' + this.getName();
};
// end of class Pet

// Define Dog class constructor (Dog : Pet) 
function Dog(name, breed) 
{
    // think Dog : base(name) 
    Pet.call(this, name);
    this.getBreed = function() { return breed; };
}

// this makes Dog.prototype inherit from Pet.prototype
Dog.prototype = new Pet();

// Currently Pet.prototype.constructor
// points to Pet. We want our Dog instances'
// constructor to point to Dog.
Dog.prototype.constructor = Dog;

// Now we override Pet.prototype.toString
Dog.prototype.toString = function() 
{
    return 'This dogs name is: ' + this.getName() + 
        ', and its breed is: ' + this.getBreed();
};
// end of class Dog

var parrotty = new Pet('Parrotty the Parrot');
var dog = new Dog('Buddy', 'Great Dane');
// test the new toString()
alert(parrotty);
alert(dog);

// Testing instanceof (similar to the `is` operator)
alert('Is dog instance of Dog? ' + (dog instanceof Dog)); //true
alert('Is dog instance of Pet? ' + (dog instanceof Pet)); //true
alert('Is dog instance of Object? ' + (dog instanceof Object)); //true

这个问题的两个答案都是Ray Djajadinata在MSDN上的一篇出色文章中修改的代码


31

您可能会根据类型捕获异常。从MDC引用:

try {
   myroutine(); // may throw three exceptions
} catch (e if e instanceof TypeError) {
   // statements to handle TypeError exceptions
} catch (e if e instanceof RangeError) {
   // statements to handle RangeError exceptions
} catch (e if e instanceof EvalError) {
   // statements to handle EvalError exceptions
} catch (e) {
   // statements to handle any unspecified exceptions
   logMyErrors(e); // pass exception object to error handler
}

注意:条件catch子句是Netscape(因此是Mozilla / Firefox)扩展,它不是ECMAScript规范的一部分,因此,除了特定的浏览器外,不能依赖它。


29
我无能为力:赶上(me if youCan)
Ates

6
阅读您引用的MDC页面上的注释:条件catch子句是Netscape(因此是Mozilla / Firefox)扩展,它不是ECMAScript规范的一部分,因此只能依靠特定的浏览器使用。
杰森S

31

从我头顶上...

功能

arguments.callee引用承载“ arguments”变量的函数,因此可用于递归匿名函数:

var recurse = function() {
  if (condition) arguments.callee(); //calls recurse() again
}

如果您想执行以下操作,这将非常有用:

//do something to all array items within an array recursively
myArray.forEach(function(item) {
  if (item instanceof Array) item.forEach(arguments.callee)
  else {/*...*/}
})

对象

关于对象成员的一件有趣的事情:它们可以使用任何字符串作为名称:

//these are normal object members
var obj = {
  a : function() {},
  b : function() {}
}
//but we can do this too
var rules = {
  ".layout .widget" : function(element) {},
  "a[href]" : function(element) {}
}
/* 
this snippet searches the page for elements that
match the CSS selectors and applies the respective function to them:
*/
for (var item in rules) {
  var elements = document.querySelectorAll(rules[item]);
  for (var e, i = 0; e = elements[i++];) rules[item](e);
}

弦乐

String.split可以将正则表达式作为参数:

"hello world   with  spaces".split(/\s+/g);
//returns an array: ["hello", "world", "with", "spaces"]

String.replace可以将正则表达式用作搜索参数,并将函数用作替换参数:

var i = 1;
"foo bar baz ".replace(/\s+/g, function() {return i++});
//returns "foo1bar2baz3"

您提到的内容...是否已在所有浏览器中实现?
cllpse

4
不,我很确定Mosaic缺少其中的大多数。
jsight

2
javascript功能是的,它们已在所有主要浏览器(IE6 / 7,FF2 / 3,Opera 9 +,Safari2 / 3和Chrome)中实现。并非所有浏览器都支持document.querySelectorAll(这是JQuery的$()和Prototype的$$()的W3C版本)
Leo

6
arguments.callee被弃用,并将在ECMAScript的5抛出和异常
Hello71

不太正确。对象键不能(或者应该不)使用字符串“ hasOwnProperty”作为名称,因为它将覆盖内置的对象方法。
布列塔尼

29

您通常可以使用对象而不是开关。

function getInnerText(o){
    return o === null? null : {
        string: o,
        array: o.map(getInnerText).join(""),
        object:getInnerText(o["childNodes"])
    }[typeis(o)];
}

更新:如果您担心事前评估效率低下的案例(为什么在程序设计的初期就担心效率?),则可以执行以下操作:

function getInnerText(o){
    return o === null? null : {
        string: function() { return o;},
        array: function() { return o.map(getInnerText).join(""); },
        object: function () { return getInnerText(o["childNodes"]; ) }
    }[typeis(o)]();
}

这比开关或对象的输入(或读取)更为繁琐,但保留了使用对象而不是开关的好处,这将在下面的评论部分中详细介绍。这种样式还可以使其变得足够简单,以便将其扩展为适当的“类”。

update2:使用ES.next的建议语法扩展,这成为

let getInnerText = o -> ({
    string: o -> o,
    array: o -> o.map(getInnerText).join(""),
    object: o -> getInnerText(o["childNodes"])
}[ typeis o ] || (->null) )(o);

3
这就是Python不使用switch语句的情况。
2009年

2
问题在于它总是评估所有情况。
Kornel

@porneL,这是真的,但是它带来了一些好处:从逻辑上讲它更干净:案例是在哈希表上查找的字符串,而不是表达式,每个表达式必须相等才能进行计算,直到返回真。因此,在评估更多“值”的同时,评估更少的“键”。可以动态生成对象,并可以对其进行修改以实现更高的可伸缩性,以反映在打印UI或生成文档中,甚至可以替换为动态“查找”功能,这比复制/粘贴案例要好。对于中断,掉线或默认值没有任何困惑。可以进行JSON序列化...
Breton

@porneL哦,是的,关于可伸缩性,对象甚至可以很容易地旋转到外部配置或数据文件中,这比使用switch语句更直接一些。用。
布列塔尼

我知道这是一个较晚的条目,但是除非您有一些自定义的类型检查逻辑,否则数组何时可以与您的示例一起使用?var arr = []; typeof arr; // object
keeganwatkins,2011年

25

遍历对象的属性时,请确保使用hasOwnProperty方法:

for (p in anObject) {
    if (anObject.hasOwnProperty(p)) {
        //Do stuff with p here
    }
}

这样做是为了使您仅访问anObject直接属性,而不使用原型链中的属性。


23

具有公共接口的私有变量

它使用了带有自调用函数定义的巧妙技巧。返回对象内部的所有内容都可以在公共接口中使用,而其他所有内容都是私有的。

var test = function () {
    //private members
    var x = 1;
    var y = function () {
        return x * 2;
    };
    //public interface
    return {
        setx : function (newx) {
            x = newx;
        },
        gety : function () {
            return y();
        }
    }
}();

assert(undefined == test.x);
assert(undefined == test.y);
assert(2 == test.gety());
test.setx(5);
assert(10 == test.gety());

1
这被称为模块模式,正如yuiblog.com/blog/2007/06/12/module-pattern的Eric Miraglia 所称的那样, 我确实认为该名称具有误导性,应称为Singleton Pattern或类似的名称。我还可以补充一点,公共方法也可以通过使用“ this”对象来调用其他公共方法。我在代码中一直使用这种模式来保持事物的组织和清洁。
mikeycgto
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.