您认为每个程序员应该知道哪些JavaScript的"隐藏功能"?
在看到以下问题的答案的优秀质量后,我认为是时候向它询问JavaScript了.
HTML的隐藏功能
CSS的隐藏功能
PHP的隐藏功能
ASP.NET的隐藏功能
C#的隐藏功能
Java的隐藏功能
Python的隐藏功能
虽然JavaScript现在可以说是最重要的客户端语言(只是问谷歌)但令人惊讶的是,大多数网络开发人员都很少理解它的实力.
您无需为函数定义任何参数.您可以只使用函数的类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
我可以引用道格拉斯·克罗克福德的大部分优秀书籍 JavaScript:The Good Parts.
但我会为你拿一个,总是使用===
而!==
不是==
和!=
alert('' == '0'); //false alert(0 == ''); // true alert(0 =='0'); // true
==
不是传递性的.如果您使用===
它将按预期为所有这些语句提供false.
函数是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(); }
您可以使用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第5版之前它们不是规范的一部分:
该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攻击.
如果您正在使用Google搜索关于给定主题的合适JavaScript引用,请在查询中包含"mdc"关键字,并且您的第一个结果将来自Mozilla开发人员中心.我没有随身携带任何离线参考书或书籍.我总是使用"mdc"关键字技巧来直接找到我正在寻找的东西.例如:
谷歌:javascript数组排序mdc
(在大多数情况下你可能会省略"javascript")
更新: Mozilla开发人员中心已重命名为Mozilla Developer Network."mdc"关键字技巧仍然有效,但很快我们可能不得不开始使用"mdn"了.
对某些人来说可能有点明显......
安装Firebug并使用console.log("hello").比使用随机警报()更好,这是我记得几年前做的很多.
私人方法
对象可以具有私有方法.
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: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.
函数是对象,因此可以具有属性.
fn = function(x) { // ... } fn.foo = 1; fn.next = function(y) { // }
我不得不说自动执行功能.
(function() { alert("hi there");})();
因为Javascript 没有块作用域,所以如果要定义局部变量,可以使用自执行函数:
(function() { var myvar = 2; alert(myvar); })();
这里,myvar
不会干扰或污染全局范围,并在函数终止时消失.
知道函数预期有多少参数
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
以下是一些有趣的事情:
比较NaN
有什么(甚至NaN
)始终是假的,包括==
,<
和>
.
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的正则表达式引擎是特定于实现的:例如,可以编写"非可移植"正则表达式.
[稍微更新以回应好评; 请看评论]
我知道我迟到了,但我无法相信+
除了"将任何东西转换为数字"之外,还没有提到运营商的用处.也许这是隐藏一个功能有多好?
// 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;
" JavaScript中的扩展方法 "通过prototype属性.
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将不再出现.
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,[] operator)sort()
.
原型继承(由道格拉斯克罗克福德推广)彻底改变了你在Javascript中思考大量内容的方式.
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 += 'paragraph #'+i+'
'; }); // adds 1000 parapraphs to the document
如何关闭 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文章修改的代码.
脱离我的头顶......
功能
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"
您可以根据类型捕获异常.引自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规范的一部分,因此除特定浏览器外不能依赖它.
您可以在大多数时间使用对象而不是开关.
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);
迭代对象的属性时,请务必使用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());
JavaScript中的时间戳:
// Usual Way var d = new Date(); timestamp = d.getTime(); // Shorter Way timestamp = (new Date()).getTime(); // Shortest Way timestamp = +new Date();
您可以使用左侧的[]指定局部变量.如果您想从函数返回多个值而不创建不必要的数组,则派上用场.
function fn(){ var cat = "meow"; var dog = "woof"; return [cat,dog]; }; var [cat,dog] = fn(); // Handy! alert(cat); alert(dog);
它是核心JS的一部分,但不知怎的,直到今年我才意识到.
Javascript中的所有对象都实现为哈希表,因此可以通过索引器访问它们的属性,反之亦然.此外,您可以使用for/in运算符枚举所有属性:
var x = {a: 0}; x["a"]; //returns 0 x["b"] = 1; x.b; //returns 1 for (p in x) document.write(p+";"); //writes "a;b;"
这个线程中有几个答案显示了如何通过其原型扩展Array对象.这是一个不好的想法,因为它打破了for (i in a)
声明.
如果您不碰巧for (i in a)
在代码中的任何地方使用,那么可以吗?好吧,只有你自己的代码是你运行的唯一代码,这在浏览器中不太可能.我担心如果人们开始像这样扩展他们的Array对象,Stack Overflow会开始溢出一堆神秘的JavaScript错误.
见有帮助的细节在这里.
如果要从数组中删除元素,可以使用delete运算符,如下所示:
var numbers = [1,2,3,4,5]; delete numbers[3]; //numbers is now [1,2,3,undefined,5]
如您所见,元素已被删除,但由于元素被替换为未定义的值,因此在数组中留下了一个洞.
因此,要解决此问题,请使用splice数组方法,而不是使用delete ...如下:
var numbers = [1,2,3,4,5]; numbers.splice(3,1); //numbers is now [1,2,3,5]
splice的第一个参数是数组[index]中的序数,第二个参数是要删除的元素数.
在函数中,您可以返回函数本身:
function showSomething(a){ alert(a); return arguments.callee; } // Alerts: 'a', 'b', 'c' showSomething('a')('b')('c'); // Or what about this: (function (a){ alert(a); return arguments.callee; }?)('a')('b')('c');????
我不知道什么时候它可能有用,无论如何,它非常奇怪和有趣:
var count = function(counter){ alert(counter); if(counter < 10){ return arguments.callee(counter+1); } return arguments.callee; }; count(5)(9); // Will alert 5, 6, 7, 8, 9, 10 and 9, 10
实际上,Node.js 的FAB框架似乎实现了这个功能; 例如,请参阅此主题.
JavaScript使用Date()的方式让我感到兴奋!
function isLeapYear(year) { return (new Date(year, 1, 29, 0, 0).getMonth() != 2); }
这真的是"隐藏的功能".
编辑:删除"?" pollecorrecteness评论中建议的条件.是:...新日期(年,1月29日,0日,0日).getMonth()!= 2?true:false ...请查看评论以获取详细信息.
这里有几个快捷方式:
var a = []; // equivalent to new Array() var o = {}; // equivalent to new Object()
我最喜欢的技巧是使用apply
对对象方法执行回调并维护正确的"this"变量.
function MakeCallback(obj, method) { return function() { method.apply(obj, arguments); }; } var SomeClass = function() { this.a = 1; }; SomeClass.prototype.addXToA = function(x) { this.a = this.a + x; }; var myObj = new SomeClass(); brokenCallback = myObj.addXToA; brokenCallback(1); // Won't work, wrong "this" variable alert(myObj.a); // 1 var myCallback = MakeCallback(myObj, myObj.addXToA); myCallback(1); // Works as expected because of apply alert(myObj.a); // 2
封闭的禅宗
其他人提到了封闭.但令人惊讶的是,有多少人知道闭包,使用闭包编写代码,但仍然对闭包真正有错误的看法.有些人将第一类函数与闭包混淆.还有一些人认为它是一种静态变量.
对我而言,闭包是一种"私人"全局变量.也就是说,一种变量,某些函数看起来像全局但其他函数看不到.现在,我知道这对于底层机制的描述是快速而松散的,但这就是它的感觉和行为.为了显示:
// Say you want three functions to share a single variable: // Use a self-calling function to create scope: (function(){ var counter = 0; // this is the variable we want to share; // Declare global functions using function expressions: increment = function(){ return ++counter; } decrement = function(){ return --counter; } value = function(){ return counter; } })()
现在是三个函数increment
,decrement
并且value
在不是实际全局变量的counter
情况下共享counter
变量.这是闭包的真正本质:
increment(); increment(); decrement(); alert(value()); // will output 1
以上并不是一个真正有用的闭包.事实上,我会说以这种方式使用它是一种反模式.但它有助于理解闭包的本质.例如,大多数人在尝试执行以下操作时会被抓住:
for (var i=1;i<=10;i++) { document.getElementById('span'+i).onclick = function () { alert('this is span number '+i); } } // ALL spans will generate alert: this span is span number 10
那是因为他们不了解封闭的本质.i
当事实上函数共享一个变量时,他们认为他们正在将值传递给函数i
.就像我之前说的,一种特殊的全局变量.
要解决这个问题,你需要分离*关闭:
function makeClickHandler (j) { return function () {alert('this is span number '+j)}; } for (var i=1;i<=10;i++) { document.getElementById('span'+i).onclick = makeClickHandler(i); } // this works because i is passed by reference // (or value in this case, since it is a number) // instead of being captured by a closure
*注意:我不知道这里的正确术语.
您永远不必使用eval()
汇编全局变量名称.
也就是说,如果你有几个全局变量(无论出于何种原因)spec_grapes, spec_apples
,你就不必使用它们来访问它们eval("spec_" + var)
.
所有全局变量都是成员window[]
,所以你可以这样做window["spec_" + var]
.
JavaScript使用简单的对象文字:
var x = { intValue: 5, strValue: "foo" };
这构造了一个完整的对象.
JavaScript使用基于原型的面向对象,并提供在运行时扩展类型的能力:
String.prototype.doubleLength = function() { return this.length * 2; } alert("foo".doubleLength());
对象将对其不包含自身的属性的所有访问权委托给其"原型",即另一个对象.这可以用来实现继承,但实际上更强大(即使更麻烦):
/* "Constructor" */ function foo() { this.intValue = 5; } /* Create the prototype that includes everything * common to all objects created be the foo function. */ foo.prototype = { method: function() { alert(this.intValue); } } var f = new foo(); f.method();
使用用于Firebug的console.log()时,在Internet Explorer中测试时防止出现恼人的错误:
function log(message) { (console || { log: function(s) { alert(s); }).log(message); }
我最喜欢的一个是构造函数类型检查:
function getObjectType( obj ) { return obj.constructor.name; } window.onload = function() { alert( getObjectType( "Hello World!" ) ); function Cat() { // some code here... } alert( getObjectType( new Cat() ) ); }
因此,您经常使用typeof关键字来获取疲惫的旧[Object对象],而实际上可以基于构造函数获取真实的对象类型.
另一个是使用变量参数作为"重载"函数的方法.您所做的只是使用表达式来检测参数的数量并返回重载的输出:
function myFunction( message, iteration ) { if ( arguments.length == 2 ) { for ( i = 0; i < iteration; i++ ) { alert( message ); } } else { alert( message ); } } window.onload = function() { myFunction( "Hello World!", 3 ); }
最后,我会说赋值运算符的简写.我从jQuery框架的源代码中学到了这一点......旧方法:
var a, b, c, d; b = a; c = b; d = c;
新的(速记)方式:
var a, b, c, d; d = c = b = a;
好开心 :)
如果用逗号分隔语句,则几乎可以在括号之间执行任何操作:
var z = ( x = "can you do crazy things with parenthesis", ( y = x.split(" "), [ y[1], y[0] ].concat( y.slice(2) ) ).join(" ") ) alert(x + "\n" + y + "\n" + z)
输出:
can you do crazy things with parenthesis can,you,do,crazy,things,with,parenthesis you can do crazy things with parenthesis
JavaScript中最快的循环是while(i--).在所有浏览器中.因此,如果对循环元素的处理顺序不重要,那么你应该使用while(i--)形式:
var names = new Array(1024), i = names.length; while(i--) names[i] = "John" + i;
此外,如果你必须使用for()循环继续,请记住始终缓存.length属性:
var birds = new Array(1024); for(var i = 0, j = birds.length; i < j; i++) birds[i].fly();
要连接大字符串,请使用Arrays(它更快):
var largeString = new Array(1024), i = largeString.length; while(i--) { // It's faster than for() loop with largeString.push(), obviously :) largeString[i] = i.toString(16); } largeString = largeString.join("");
它比largeString += "something"
循环内部快得多.
truthy和falsy值的概念.你不需要做类似的事情
if(someVar === undefined || someVar === null)...
简单地说:
如果(!someVar).
每个值都有一个相应的布尔表示.
函数语句和函数表达式的处理方式不同.
function blarg(a) {return a;} // statement bleep = function(b) {return b;} //expression
在运行代码之前解析所有函数语句 - 第一个语句中将提供JavaScript文件底部的函数.另一方面,它将无法利用某些动态上下文,例如周围with
语句 - 在with
解析函数时尚未执行.
函数表达式在遇到它们的地方执行内联.它们在此之前不可用,但它们可以利用动态上下文.
window.name
页面更改的值仍然存在,如果在同一个域中(如果在iframe中,用于document.getElementById("your frame's ID").contentWindow.name
访问它),则父窗口可以读取该值,并且仅受可用内存的限制.
创建新"对象"时,括号是可选的.
function Animal () { } var animal = new Animal(); var animal = new Animal;
一样.
Javascript在函数内部有静态变量:
function someFunction(){ var Static = arguments.callee; Static.someStaticVariable = (Static.someStaticVariable || 0) + 1; alert(Static.someStaticVariable); } someFunction() //Alerts 1 someFunction() //Alerts 2 someFunction() //Alerts 3
它在Objects中也有静态变量:
function Obj(){ this.Static = arguments.callee; } a = new Obj(); a.Static.name = "a"; b = new Obj(); alert(b.Static.name); //Alerts b
所有函数实际上都是内置函数类型的实例,它具有一个构造函数,它接受包含函数定义的字符串,因此您可以在运行时实际定义函数,例如,连接字符串:
//e.g., createAddFunction("a","b") returns function(a,b) { return a+b; } function createAddFunction(paramName1, paramName2) { return new Function( paramName1, paramName2 ,"return "+ paramName1 +" + "+ paramName2 +";"); }
此外,对于用户定义的函数,Function.toString()将函数定义作为文字字符串返回.
您可以对任何对象执行对象的方法,无论它是否具有该方法.当然它可能并不总是有效(如果该方法假定对象具有它没有的东西),但它可能非常有用.例如:
function(){ arguments.push('foo') // This errors, arguments is not a proper array and has no push method Array.prototype.push.apply(arguments, ['foo']) // Works! }
==运算符有一个非常特殊的属性,可以创建这种令人不安的平等(是的,我知道在Perl等其他动态语言中会出现这种行为,但JavaScript通常不会尝试在比较中变得聪明):
>>> 1 == true true >>> 0 == false true >>> 2 == true false
let
.
对口VAR缺乏块作用域的是let
,在JavaScript 1.7引入的.
let语句提供了一种将值与块范围内的变量相关联的方法,而不会影响块外部的同名变量的值.
let表达式允许您创建仅限于单个表达式的变量.
let定义定义了变量,其范围被约束到定义它们的块.此语法非常类似于var的语法.
您还可以使用let来建立仅存在于for循环上下文中的变量.
function varTest() { var x = 31; if (true) { var x = 71; // same variable! alert(x); // 71 } alert(x); // 71 } function letTest() { let x = 31; if (true) { let x = 71; // different variable alert(x); // 71 } alert(x); // 31 }
截至2008年,FireFox 2.0+和Safari 3.x支持JavaScript 1.7.
如果您盲目地eval()
使用JSON字符串对其进行反序列化,则可能会遇到问题:
这不安全.该字符串可能包含恶意函数调用!
如果未将JSON字符串括在括号中,则属性名称可能会被误认为是标签,从而导致意外行为或语法错误:
eval("{ \"foo\": 42 }"); // syntax error: invalid label eval("({ \"foo\": 42 })"); // OK
您可以将"任何具有整数属性的对象和长度属性转换为适当的数组,从而赋予它所有数组方法,如push,pop,splice,map,filter,reduce等.
Array.prototype.slice.call({"0":"foo", "1":"bar", 2:"baz", "length":3 })
//返回["foo","bar","baz"]
这适用于jQuery对象,html集合和来自其他帧的Array对象(作为整个数组类型事物的一种可能的解决方案).我说,如果它有一个长度属性,你可以把它变成一个数组,这没关系.除了参数对象之外,还有许多具有length属性的非数组对象.
如果您正在尝试沙箱javascript代码,并禁用每种可能的方法来将字符串评估为javascript代码,请注意阻塞所有明显的eval/document.write/new Function/setTimeout/setInterval/innerHTML和其他DOM操作不是足够.
给定任何对象o,o.constructor.constructor("alert('hi')")()
将显示一个警告对话框,其中包含单词"hi".
你可以把它重写为
var Z="constructor"; Z[Z][Z]("alert('hi')")();
好玩的东西.
Function.toString()(隐式):
function x() { alert("Hello World"); } eval ("x = " + (x + "").replace( 'Hello World', 'STACK OVERFLOW BWAHAHA"); x("')); x();
微软提供给JavaScript的礼物:AJAX
AJAXCall('http://www.abcd.com/') function AJAXCall(url) { var client = new XMLHttpRequest(); client.onreadystatechange = handlerFunc; client.open("GET", url); client.send(); } function handlerFunc() { if(this.readyState == 4 && this.status == 200) { if(this.responseXML != null) document.write(this.responseXML) } }
该模块模式
在没有var
语句或函数外声明的变量和函数将在全局范围内定义.如果已经存在同名的变量/函数,它将被静默覆盖,这可能导致很难找到错误.一个常见的解决方案是将整个代码体包装成一个匿名函数并立即执行它.这样,所有变量/函数都在匿名函数的范围内定义,不会泄漏到全局范围.
要在全局范围内显式定义变量/函数,必须使用前缀window
:
window.GLOBAL_VAR = 12; window.global_function = function() {};
这是jQuery的隐藏功能,而不是Javascript,但是因为永远不会有"jQuery隐藏功能"的问题......
您可以:something
在jQuery中定义自己的选择器:
$.extend($.expr[':'], { foo: function(node, index, args, stack) { // decide if selectors matches node, return true or false } });
对于使用的选择:foo
,例如$('div.block:foo("bar,baz") span')
,foo
将为与选择器的已处理部分匹配的所有节点调用该函数.论点的含义:
node
保存当前节点
index
是节点集中节点的索引
args
是一个数组,如果选择器有一个参数或多个名称是有用的:
args[0]
是整个选择器文本(例如:foo("bar, baz")
)
args[1]
是选择器名称(例如foo
)
args[2]
是用于包装参数的引用字符(例如"
for :foo("bar, baz")
),如果没有引用(:foo(bar, baz)
)则为空字符串,如果没有参数则为undefined
args[3]
是参数,包括任何引号,(例如"bar, baz"
)或如果没有参数则未定义
stack
是节点集(包含在该点匹配的所有节点的数组)
true
如果选择器匹配,false
则该函数应返回,否则返回.
例如,以下代码将允许基于全文regexp搜索选择节点:
$.extend($.expr[':'], { matches: function(node, index, args, stack) { if (!args.re) { // args is a good place for caching var re = args[3]; if (args[2]) { // get rid of quotes re = re.slice(1,-1); } var separator = re[0]; var pos = re.lastIndexOf(separator); var modifiers = re.substr(pos+1); var code = re.substr(1, pos-1); args.re = new RegExp(code, modifiers); } return $(node).text().match(args.re); } }); // find the answers on this page which contain /**/-style comments $('.answer .post-text code:matches(!/\\*[\\s\\S]*\\*/!)');
您可以使用.filter()的回调版本达到类似的效果,但自定义选择器更灵活,通常更具可读性.
undefined
未定义.所以你可以这样做:
if (obj.field === undefined) /* ... */
访问:
http://images.google.com/images?q=disco
将此JavaScript代码粘贴到Web浏览器的地址栏中:
http://amix.dk/upload/awt/spin.txt
http://amix.dk/upload/awt/disco.txt
享受JavaScript迪斯科节目:-p
生成器和迭代器(仅适用于Firefox 2+和Safari).
function fib() { var i = 0, j = 1; while (true) { yield i; var t = i; i = j; j += t; } } var g = fib(); for (var i = 0; i < 10; i++) { document.write(g.next() + "
\n"); }
包含
yield
关键字的函数是生成器.当你调用它时,它的形式参数绑定到实际的参数,但它的实体并没有被实际评估.而是返回一个generator-iterator.每次调用generator-iterator的next()
方法都会执行另一次迭代算法.每个步骤的值是yield
关键字指定的值.可以将其yield
视为返回的generator-iterator版本,指示算法的每次迭代之间的边界.每次调用时next()
,生成器代码都会从后面的语句中恢复yield
.在正常使用中,迭代器对象是"不可见的"; 您不需要明确地对它们进行操作,而是使用JavaScript
for...in
和for each...in
语句自然地循环对象的键和/或值.
var objectWithIterator = getObjectSomehow(); for (var i in objectWithIterator) { document.write(objectWithIterator[i] + "
\n"); }
这个是超级隐藏的,只是偶尔有用;-)
您可以使用原型链创建一个委托给另一个对象的对象,而无需更改原始对象.
var o1 = { foo: 1, bar: 'abc' }; function f() {} f.prototype = o1; o2 = new f(); assert( o2.foo === 1 ); assert( o2.bar === 'abc' ); o2.foo = 2; o2.baz = true; assert( o2.foo === 2 ); // o1 is unchanged by assignment to o2 assert( o1.foo === 1 ); assert( o2.baz );
这仅涵盖o1上的"简单"值.如果修改数组或其他对象,则原型不再"保护"原始对象.当你在类定义/原型中有{}或[]时,请注意.
所有"隐藏"功能都在Mozilla wiki上:http://developer.mozilla.org/en/JavaScript.
这是JavaScript 1.5的核心参考,JavaScript 1.6中的新功能,JavaScript 1.7中的新功能,以及JavaScript 1.8中的新功能.查看所有这些实际工作且没有错误的示例.
命名空间
在较大的JavaScript应用程序或框架中,在命名空间中组织代码会很有用.JavaScript没有模块或命名空间概念buildin,但很容易使用JavaScript对象进行模拟.这将创建一个名为的命名空间ns
,并将该函数附加foo
到它.
if (!window.ns) { window.ns = {}; } window.ns.foo = function() {};
通常在整个项目中使用相同的全局名称空间前缀,并为每个JavaScript文件使用子名称空间.子命名空间的名称通常与文件名相匹配.
调用的文件的标题ns/button.js
可能如下所示:
if (!window.ns) { window.ns = {}; } if (!window.ns.button) { window.ns.button = {}; } // attach methods to the ns.button namespace window.ns.button.create = function() {};