您认为每个程序员都应该知道JavaScript的哪些“隐藏功能”?
在看到以下问题的答案的出色质量之后,我认为是时候向它询问JavaScript了。
尽管JavaScript可以说是目前最重要的客户端语言(只需问问Google),但令人惊讶的是,大多数Web开发人员几乎没有意识到它的强大功能。
您认为每个程序员都应该知道JavaScript的哪些“隐藏功能”?
在看到以下问题的答案的出色质量之后,我认为是时候向它询问JavaScript了。
尽管JavaScript可以说是目前最重要的客户端语言(只需问问Google),但令人惊讶的是,大多数Web开发人员几乎没有意识到它的强大功能。
Answers:
您无需为函数定义任何参数。您可以只使用函数的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
arguments
对象会使调用函数的速度大大降低-例如。if(false)参数;会伤害性能。
arguments.callee
已弃用。
我可以引用道格拉斯·克罗克福德(Douglas Crockford)的绝妙著作《JavaScript:The Good Parts》中的大部分 内容。
但我会举一个你,总是使用===
与!==
替代==
和!=
alert('' == '0'); //false
alert(0 == ''); // true
alert(0 =='0'); // true
==
不是可传递的。如果使用===
它,则所有这些语句都将为false。
==
是'\n\t\r ' == 0
=> true
...:D
函数是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
特别是,可以将函数作为参数传递,例如Array.filter()接受回调:
[1, 2, -1].filter(function(element, index, array) { return element > 0 });
// -> [1,2]
您还可以声明仅在特定函数范围内存在的“私有”函数:
function PrintName() {
var privateFunction = function() { return "Steve"; };
return privateFunction();
}
new Function()
就像邪恶一样eval
。不使用。
您可以使用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
为变量分配默认值
您可以||
在赋值表达式中使用逻辑或运算符来提供默认值:
var a = b || c;
该a
变量将获得的价值c
只有当b
是falsy(如果是null
,false
,undefined
,0
,empty 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
。
JavaScript没有块作用域(但是它有闭包,所以我们甚至称它为?)。
var x = 1;
{
var x = 2;
}
alert(x); // outputs 2
您可以使用[]
代替访问对象属性.
这使您可以查找与变量匹配的属性。
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漏洞。
foo.bar
无论如何,根据规范,其行为就像foo["bar"]
。还请注意,所有内容都是字符串属性。即使您进行数组访问,也会array[4]
将4转换为字符串(同样,至少根据ECMAScript v3规范)
如果您要搜索给定主题的不错的JavaScript参考,请在查询中包括“ mdc”关键字,您的第一个结果将来自Mozilla开发人员中心。我没有随身携带任何离线参考书或书籍。我总是使用“ mdc”关键字技巧来直接获得所需的内容。例如:
Google:javascript数组排序mdc
(大多数情况下,您可以省略“ javascript”)
更新: Mozilla开发人员中心已重命名为Mozilla开发人员网络。“ mdc”关键字技巧仍然有效,但是很快我们可能不得不开始使用“ mdn”了。
也许对某些人有些明显...
安装Firebug并使用console.log(“ hello”)。与使用random alert();相比,这要好得多,我记得几年前就做了。
私人方法
对象可以具有私有方法。
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());
在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.
Math.floor
或Number
?10 === Math.floor("010"); 10 === Number("010");
浮点数:42 === Math.floor("42.69"); 42.69 === Number("42.69");
parseInt
可以很容易地使这种无害的功能执行不那么无害的操作。
__parseInt = parseInt; parseInt = function (str, base) { if (!base) throw new Error(69, "All your base belong to us"); return __parseInt(str, base); }
函数是对象,因此可以具有属性。
fn = function(x){ // ... } fn.foo = 1; fn.next = function(y){ // }
我不得不说自我执行功能。
(function() { alert("hi there");})();
由于Javascript 没有块作用域,因此,如果要定义局部变量,可以使用自执行函数:
(function() {
var myvar = 2;
alert(myvar);
})();
在此,myvar
is不会干扰或污染全局范围,并且在函数终止时会消失。
.js
文件封装在一个匿名的自执行函数中,并将希望全局访问的所有内容附加到该window
对象。防止全局名称空间污染。
知道一个函数需要多少个参数
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
function.length
。
以下是一些有趣的事情:
NaN
有什么(甚至NaN
)始终是假的,包括==
,<
和>
。NaN
代表非数字,但如果您要求输入类型,它实际上会返回一个数字。Array.sort
可以采用比较器功能,并由类似quicksort的驱动程序调用(取决于实现)。$0
,$1
,$2
对正则表达式的成员。null
与众不同。它既不是对象,布尔值,数字,字符串,也不是undefined
。有点像“替代” undefined
。(注:typeof null == "object"
)this
产生否则无法命名的[Global]对象。var
,而不仅仅是依赖于变量的自动声明,这为运行时提供了优化访问该变量的真正机会with
结构会破坏这种optimzationsbreak
。可以标记循环并将其用作的目标continue
。undefined
。(取决于实施情况)if (new Boolean(false)) {...}
将执行{...}
块[为了回应好评,进行了一些更新;请参阅评论]
typeof null
返回“对象”。
undefined
,但这只是在查找不存在的索引时获得的默认值。如果选中a [2000],也将是undefined
,但这并不意味着您已经为其分配了内存。在IE8中,某些数组是密集的,而有些则是稀疏的,这取决于当时的JScript引擎的感觉。在此处了解更多信息:blogs.msdn.com/jscript/archive/2008/04/08/…–
我知道我迟到了派对,但是我简直不敢相信+
除了“将任何内容转换为数字”之外,还没有提到运营商的有用性。也许这就是隐藏功能的程度了吗?
// 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"
。
0xFF
写1
而不是用相同的方式+true
。我建议您可以使用+("0x"+somevar)
替代parseInt(somevar, 16)
。
“ 扩展方法在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");
要从对象中正确删除属性,您应该删除该属性,而不仅仅是将其设置为undefined:
var obj = { prop1: 42, prop2: 43 };
obj.prop2 = undefined;
for (var key in obj) {
...
属性prop2仍将是迭代的一部分。如果要完全摆脱prop2,应改为:
delete obj.prop2;
遍历属性prop2时,属性prop2将不再显示。
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
语句用于此类分配可能是一个好主意。
方法(或函数)可以在其设计使用的类型之外的对象上调用。在自定义对象上调用本机(快速)方法非常好。
var listNodes = document.getElementsByTagName('a');
listNodes.sort(function(a, b){ ... });
此代码崩溃,因为listNodes
不是Array
Array.prototype.sort.apply(listNodes, [function(a, b){ ... }]);
该代码之所以有效,是因为listNodes
定义了足够多的类似数组的属性(length,[]运算符)供sort()
。
原型继承(由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'
有些人将其称为品味问题,但是:
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";
也可以很好地与递归一起使用:)
数字也是对象。因此,您可以做一些很酷的事情,例如:
// 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
times
效率不高:Math.floor
每次都调用,而不是一次调用。
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);
您还可以扩展(继承)类并使用所涉及的原型链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上的一篇出色文章中修改的代码。
您可能会根据类型捕获异常。从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规范的一部分,因此,除了特定的浏览器外,不能依赖它。
从我头顶上...
功能
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"
arguments.callee
被弃用,并将在ECMAScript的5抛出和异常
您通常可以使用对象而不是开关。
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);
var arr = []; typeof arr; // object
遍历对象的属性时,请确保使用hasOwnProperty方法:
for (p in anObject) {
if (anObject.hasOwnProperty(p)) {
//Do stuff with p here
}
}
这样做是为了使您仅访问anObject的直接属性,而不使用原型链中的属性。
具有公共接口的私有变量
它使用了带有自调用函数定义的巧妙技巧。返回对象内部的所有内容都可以在公共接口中使用,而其他所有内容都是私有的。
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());