Hidden Features of JavaScript?
你认为每个程序员都应该知道javascript的哪些"隐藏特性"?
在看到以下问题的答案质量非常好之后,我认为是时候向它索要javascript了。
- HTML的隐藏功能
- CSS的隐藏功能
- PHP的隐藏功能
- ASP.NET的隐藏功能
- C的隐藏特征#
- Java的隐藏特性
- Python的隐藏特征
尽管现在JavaScript可以说是最重要的客户端语言(问问Google),但令人惊讶的是,大多数Web开发人员对它到底有多强大的理解微乎其微。
您不需要为函数定义任何参数。您只需使用函数的
1 2 3 4 5 6 7 8 9 | 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:好的部分。
但我只给你拿一个,总是用
1 2 3 | alert('' == '0'); //false alert(0 == ''); // true alert(0 =='0'); // true |
函数是JavaScript中的头等公民:
1 2 3 4 5 6 7 | 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, 2, -1].filter(function(element, index, array) { return element > 0 }); // -> [1,2] |
您还可以声明仅存在于特定函数范围内的"private"函数:
1 2 3 4 | function PrintName() { var privateFunction = function() { return"Steve"; }; return privateFunction(); } |
可以使用In运算符检查对象中是否存在键:
1 2 3 4 5 6 7 | 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 |
如果发现对象文本太难看,可以将其与无参数函数提示结合使用:
1 2 3 4 5 6 7 | 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 |
为变量分配默认值
可以在赋值表达式中使用逻辑或运算符
1 | var a = b || c; |
只有当
这在函数中通常很有用,当您希望在未提供参数的情况下为参数提供默认值时:
1 2 3 | function example(arg1) { arg1 || (arg1 = 'default value'); } |
事件处理程序中的IE回滚示例:
1 2 3 | function onClick(e) { e || (e = window.event); } |
以下语言特性已经存在我们很长时间了,所有的javascript实现都支持它们,但是直到ecmascript第5版之前它们都不是规范的一部分:
在:§;12.15调试器语句中描述
此语句允许您通过以下方式在代码中以编程方式放置断点:
1 2 3 | // ... debugger; // ... |
如果调试器存在或处于活动状态,它将立即导致它在该行中断。
否则,如果调试器不存在或不活动,则此语句没有可观察的效果。
多行字符串文本
描述见:§;7.8.4字符串文字
1 2 3 | var str ="This is a \ really, really \ long line!"; |
您必须小心,因为
javascript没有块作用域(但它有闭包,所以我们甚至称之为块作用域?).
1 2 3 4 5 | var x = 1; { var x = 2; } alert(x); // outputs 2 |
如果你在google上搜索一个关于给定主题的合适的javascript引用,在你的查询中包含"mdc"关键字,你的第一个结果将来自Mozilla开发中心。我不随身携带任何离线参考资料或书籍。我总是使用"mdc"关键字技巧直接得到我想要的。例如:
google:javascript数组排序mdc(在大多数情况下,您可以省略"javascript")。
更新:Mozilla Developer Center已重命名为Mozilla Developer Network。"mdc"关键字技巧仍然有效,但很快我们可能不得不开始使用"mdn"。
可以使用
这允许您查找与变量匹配的属性。
1 2 3 | obj = {a:"test"}; var propname ="a"; var b = obj[propname]; //"test" |
您还可以使用它来获取/设置名称不是合法标识符的对象属性。
1 2 | 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(),这是一个非常糟糕的主意:
1 2 | var propname ="a"; var a = eval("obj." + propname); |
这很难读取,很难在中发现错误(不能使用jslint),执行速度较慢,并可能导致XSS漏洞。
也许对某些人来说有点明显…
安装firebug并使用console.log("hello")。比使用随机警报()好多了,我记得几年前做了很多。
私有方法
对象可以有私有方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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:好的部分"中也提到:
1 2 | parseInt('010') // returns 8! (in FF3) parseInt('010', 10); // returns 10 because we've informed it which base to work with. |
函数是对象,因此可以具有属性。
1 2 3 4 5 6 7 8 9 | fn = function(x) { // ... } fn.foo = 1; fn.next = function(y) { // } |
我要说的是自执行函数。
1 | (function() { alert("hi there");})(); |
因为javascript没有块范围,所以如果要定义局部变量,可以使用自执行函数:
1 2 3 4 | (function() { var myvar = 2; alert(myvar); })(); |
在这里,
知道一个函数期望多少参数
1 2 3 4 | function add_nums(num1, num2, num3 ){ return num1 + num2 + num3; } add_nums.length // 3 is the number of parameters expected. |
知道函数接收了多少参数
1 2 3 4 | function add_many_nums(){ return arguments.length; } add_many_nums(2,1,122,12,21,89); //returns 6 |
以下是一些有趣的事情:
- 把
NaN 和任何东西(甚至NaN 作比较都是错误的,包括== 、< 和> 。 NaN 不是数字,但如果您要求输入类型,它实际上会返回一个数字。Array.sort 可以使用比较器函数,并由类似于快速排序的驱动程序调用(取决于实现)。- 正则表达式"常量"可以保持状态,就像它们匹配的最后一件事一样。
- 有些版本的javascript允许您访问regex上的
$0 、$1 、$2 成员。 null 与其他任何东西都不同。它既不是对象、布尔值、数字、字符串,也不是undefined 。这有点像一个"替代"的undefined 。(注:typeof null =="object" )- 在最外层的上下文中,
this 生成了不可度量的(全局)对象。 - 用
var 声明一个变量,而不是仅仅依赖于该变量的自动声明,这给了运行时优化对该变量访问的真正机会。 with 构造将破坏这种优化- 变量名可以包含Unicode字符。
- javascript正则表达式实际上不是正则的。它们基于Perl的regexs,并且可以用lookaheads构造表达式,而lookaheads的计算时间非常长。
- 块可以标记并用作
break 的目标。循环可以标记并用作continue 的目标。 - 数组不是稀疏的。设置一个空数组的第1000个元素时,应该用
undefined 填充它。(取决于实现) if (new Boolean(false)) {...} 将执行{...} 块。- javascript的正则表达式引擎是特定于实现的:例如,可以编写"不可移植"的正则表达式。
[对好的评论进行了一些更新;请参阅评论]
我知道我参加晚会迟到了,但我真不敢相信,除了"把任何东西都转换成数字"之外,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 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 |
当然,您可以使用
还可以通过重写原型的
1 2 3 4 5 6 | var rnd = { "valueOf": function () { return Math.floor(Math.random()*1000); } }; +rnd; // -> 442; +rnd; // -> 727; +rnd; // -> 718; |
通过原型属性"javascript中的扩展方法"。
1 2 3 4 5 6 | Array.prototype.contains = function(value) { for (var i = 0; i < this.length; i++) { if (this[i] == value) return true; } return false; } |
这将向所有
1 2 | var stringArray = ["foo","bar","foobar"]; stringArray.contains("foobar"); |
要从对象中正确删除属性,应删除该属性,而不是将其设置为未定义:
1 2 3 4 5 6 | var obj = { prop1: 42, prop2: 43 }; obj.prop2 = undefined; for (var key in obj) { ... |
属性prop2仍将是迭代的一部分。如果你想完全摆脱prop2,你应该这样做:
1 | delete obj.prop2; |
当您遍历属性时,属性prop2将不再出现。
它很少使用,坦率地说,很少有用…但是,在有限的情况下,它确实有它的用途。
例如:对象文本对于快速设置新对象的属性非常方便。但是如果您需要更改现有对象上一半的属性呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var user = { fname: 'Rocket', mname: 'Aloysus', lname: 'Squirrel', city: 'Fresno', state: 'California' }; // ... with (user) { mname = 'J'; city = 'Frostbite Falls'; state = 'Minnesota'; } |
AlanStorm指出,这可能有点危险:如果用作上下文的对象没有一个要分配的属性,它将在外部范围内解决,可能会创建或覆盖一个全局变量。如果您习惯于编写代码来处理具有默认值或空值的属性未定义的对象,则这一点尤其危险:
1 2 3 4 5 6 7 8 9 10 11 | var user = { fname:"John", // mname definition skipped - no middle name lname:"Doe" }; with (user) { mname ="Q"; // creates / modifies global variable"mname" } |
因此,最好避免使用
方法(或函数)可以对不是其设计用于的类型的对象进行调用。在自定义对象上调用本机(快速)方法非常好。
1 2 | var listNodes = document.getElementsByTagName('a'); listNodes.sort(function(a, b){ ... }); |
此代码崩溃是因为
1 | Array.prototype.sort.apply(listNodes, [function(a, b){ ... }]); |
此代码之所以有效,是因为
原型继承(由Douglas Crockford推广)彻底改变了您在JavaScript中思考大量事物的方式。
1 2 3 4 5 6 | Object.beget = (function(Function){ return function(Object){ Function.prototype = Object; return new Function; } })(function(){}); |
这是个杀手!可惜几乎没有人使用它。
它允许您"开始"任何对象的新实例,扩展它们,同时保持一个(活动的)原型继承链接到它们的其他属性。例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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' |
有些人会称之为品味问题,但:
1 2 | aWizz = wizz ||"default"; // same as: if (wizz) { aWizz = wizz; } else { aWizz ="default"; } |
三元运算符可以被链接,以像Scheme(Cond…)一样工作:
1 2 3 | (cond (predicate (action ...)) (predicate2 (action2 ...)) (#t default )) |
可以写为…
1 2 3 | predicate ? action( ... ) : predicate2 ? action2( ... ) : default; |
这是非常"功能性"的,因为它分支代码时没有副作用。因此,而不是:
1 2 3 4 5 6 7 | if (predicate) { foo ="one"; } else if (predicate2) { foo ="two"; } else { foo ="default"; } |
你可以写:
1 2 3 | foo = predicate ?"one" : predicate2 ?"two" : "default"; |
也适用于递归:)
数字也是对象。所以你可以做一些很酷的事情,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 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 |
JavaScript中的闭包(类似于C v2.0+中的匿名方法)如何?可以创建创建函数或"表达式"的函数。
闭包示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //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并定义一些属性。我们还重写从对象继承的.toString()方法。
在此之后,我们创建了一个dog类,它扩展了pet并重写了.toString()方法,再次改变了它的行为(多态性)。此外,我们还向子类添加了一些其他属性。
在此之后,我们检查继承链以显示dog仍然是dog类型、pet类型和object类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | // 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 |
这两个问题的答案都是由RayDjajadinata在一篇伟大的msdn文章中修改的代码。
从我的头顶…
功能
arguments.callee引用承载"arguments"变量的函数,因此它可以用于递归匿名函数:
1 2 3 | var recurse = function() { if (condition) arguments.callee(); //calls recurse() again } |
如果你想做这样的事情,这很有用:
1 2 3 4 5 | //do something to all array items within an array recursively myArray.forEach(function(item) { if (item instanceof Array) item.forEach(arguments.callee) else {/*...*/} }) |
物体
关于对象成员有一个有趣的事情:他们可以用任何字符串作为名称:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //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可以将正则表达式作为参数:
1 2 | "hello world with spaces".split(/\s+/g); //returns an array: ["hello","world","with","spaces"] |
string.replace可以将正则表达式作为搜索参数,函数作为替换参数:
1 2 3 | var i = 1; "foo bar baz".replace(/\s+/g, function() {return i++}); //returns"foo1bar2baz3" |
根据异常的类型,可以捕获异常。引自MDC:
1 2 3 4 5 6 7 8 9 10 11 12 | 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规范的一部分,因此除了特定的浏览器之外,不能依赖它。
大多数情况下,您可以使用对象而不是开关。
1 2 3 4 5 6 7 | function getInnerText(o){ return o === null? null : { string: o, array: o.map(getInnerText).join(""), object:getInnerText(o["childNodes"]) }[typeis(o)]; } |
更新:如果你担心预先评估的案例效率低下(为什么你在项目设计的早期就担心效率问题)??)然后您可以这样做:
1 2 3 4 5 6 7 | 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建议的语法扩展,这将成为
1 2 3 4 5 | let getInnerText = o -> ({ string: o -> o, array: o -> o.map(getInnerText).join(""), object: o -> getInnerText(o["childNodes"]) }[ typeis o ] || (->null) )(o); |
在迭代对象的属性时,请确保使用hasownProperty方法:
1 2 3 4 5 | for (p in anObject) { if (anObject.hasOwnProperty(p)) { //Do stuff with p here } } |
这样做是为了只访问对象的直接属性,而不使用原型链下的属性。
带有公共接口的私有变量
它使用了一个简单的小技巧和一个自调用函数定义。对象中返回的所有内容在公共接口中都可用,而其他所有内容都是私有的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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中的时间戳:
1 2 3 4 5 6 7 8 9 | // Usual Way var d = new Date(); timestamp = d.getTime(); // Shorter Way timestamp = (new Date()).getTime(); // Shortest Way timestamp = +new Date(); |
您可以使用左侧的[]来分配局部变量。如果您想从一个函数返回多个值而不创建不必要的数组,则可以使用。
1 2 3 4 5 6 7 8 9 10 | function fn(){ var cat ="meow"; var dog ="woof"; return [cat,dog]; }; var [cat,dog] = fn(); // Handy! alert(cat); alert(dog); |
它是核心JS的一部分,但不知何故,直到今年我才意识到。
JavaScript中的所有对象都实现为哈希表,因此可以通过索引器访问它们的属性,反之亦然。此外,还可以使用for/in运算符枚举所有属性:
1 2 3 4 5 6 7 | 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;" |
如果要从数组中删除元素,可以使用delete运算符,例如:
1 2 3 | var numbers = [1,2,3,4,5]; delete numbers[3]; //numbers is now [1,2,3,undefined,5] |
如您所见,元素被移除,但数组中留下了一个孔,因为元素被一个未定义的值替换。
因此,要解决此问题,而不是使用"删除",请使用拼接数组方法…例如:
1 2 3 | var numbers = [1,2,3,4,5]; numbers.splice(3,1); //numbers is now [1,2,3,5] |
拼接的第一个参数是数组[索引]中的序数,第二个参数是要删除的元素数。
这条线索中有几个答案显示了如何通过原型扩展数组对象。这是一个坏消息想法,因为它打破了
所以如果你不使用
请参阅此处的帮助详细信息。
在函数中,可以返回函数本身:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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');???? |
我不知道什么时候它会有用,不管怎样,它非常奇怪和有趣:
1 2 3 4 5 6 7 8 9 | 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处理日期()的方式让我很兴奋!
1 2 3 | function isLeapYear(year) { return (new Date(year, 1, 29, 0, 0).getMonth() != 2); } |
这是真正的"隐藏特性"。
编辑:删除"?"礼貌纠正意见中建议的条件。是…新日期(年,1,29,0,0).getmonth()!= 2?对:错…请查看评论了解详细信息。
闭门禅
其他人提到了关闭。但令人惊讶的是,有多少人知道闭包,使用闭包编写代码,但仍然错误地认识到闭包实际上是什么。有些人把一级函数和闭包混淆了。但也有人把它看作一种静态变量。
对我来说,闭包是一种"私有"全局变量。也就是说,一种变量,某些函数将其视为全局变量,而其他函数则看不到。现在,我知道这与对底层机制的描述有关,但这就是它的感觉和行为。举例说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 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; } })() |
现在,三个函数
1 2 3 4 | increment(); increment(); decrement(); alert(value()); // will output 1 |
上面的代码并不是真正有用的闭包。事实上,我认为用这种方式使用它是一种反模式。但它对于理解闭包的性质很有用。例如,大多数人在尝试执行如下操作时会被抓到:
1 2 3 4 5 6 | 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 |
这是因为他们不了解闭包的性质。他们认为,当函数实际上共享单个变量
要解决这个问题,您需要分离*闭合:
1 2 3 4 5 6 7 8 9 10 | 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 |
*注意:我不知道这里的正确术语。
我最喜欢的作弊是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 |
这里有几个捷径:
1 2 | var a = []; // equivalent to new Array() var o = {}; // equivalent to new Object() |
您不必使用
也就是说,如果有几个全局变量(无论出于什么原因)命名为
所有的环球都是
javascript使用简单的对象文本:
1 | var x = { intValue: 5, strValue:"foo" }; |
这将构造一个完整的对象。
JavaScript使用基于原型的对象方向,并提供在运行时扩展类型的能力:
1 2 3 4 5 | String.prototype.doubleLength = function() { return this.length * 2; } alert("foo".doubleLength()); |
一个对象将所有不包含其自身的属性访问权委托给另一个对象"原型"。这可以用于实现继承,但实际上更强大(即使更麻烦):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /*"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(); |
使用console.log()处理firebug时,防止在Internet Explorer中测试时出现恼人的错误:
1 2 3 | function log(message) { (console || { log: function(s) { alert(s); }).log(message); } |
我最喜欢的是构造函数类型检查:
1 2 3 4 5 6 7 8 9 10 11 | function getObjectType( obj ) { return obj.constructor.name; } window.onload = function() { alert( getObjectType("Hello World!" ) ); function Cat() { // some code here... } alert( getObjectType( new Cat() ) ); } |
因此,您不必经常使用typeof关键字来获取陈旧的[对象对象],而是可以根据构造函数实际获取真正的对象类型。
另一种方法是使用变量参数来"重载"函数。您所要做的就是使用表达式检测参数的数量并返回重载的输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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框架的源代码中了解到这一点…老路:
1 2 3 4 | var a, b, c, d; b = a; c = b; d = c; |
新(速记)方式:
1 2 | var a, b, c, d; d = c = b = a; |
好好玩:
如果用逗号分隔语句,则几乎可以在括号之间执行任何操作:
1 2 3 4 5 | var z = ( x ="can you do crazy things with parenthesis", ( y = x.split(""), [ y[1], y[0] ].concat( y.slice(2) ) ).join("") ) alert(x +" " + y +" " + z) |
输出:
1 2 3 | can you do crazy things with parenthesis can,you,do,crazy,things,with,parenthesis you can do crazy things with parenthesis |
javascript中最快的循环是(i-)循环。在所有浏览器中。因此,如果循环元素的处理顺序没有那么重要,那么应该使用while(i--)形式:
1 2 3 | var names = new Array(1024), i = names.length; while(i--) names[i] ="John" + i; |
另外,如果必须继续使用for()循环,请记住始终缓存.length属性:
1 2 3 | var birds = new Array(1024); for(var i = 0, j = birds.length; i < j; i++) birds[i].fly(); |
要连接大型字符串,请使用数组(更快):
1 2 3 4 5 6 7 | 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(""); |
它比循环中的
函数语句和函数表达式的处理方式不同。
1 2 | function blarg(a) {return a;} // statement bleep = function(b) {return b;} //expression |
所有的函数语句在代码运行之前都会被解析——javascript文件底部的函数将在第一个语句中可用。另一方面,它将无法利用某些动态上下文,例如周围的
函数表达式直接在遇到它们的地方执行。在那之前它们是不可用的,但是它们可以利用动态上下文。
创建新的"对象"时,括号是可选的。
1 2 3 4 5 6 | function Animal () { } var animal = new Animal(); var animal = new Animal; |
同样的事情。
真实和虚伪价值观的概念。你不需要做这样的事
if(somevar==未定义somevar==空)…
简单地做:
如果(!)萨默瓦尔)
每个值都有相应的布尔表示。
javascript在函数中有静态变量:
1 2 3 4 5 6 7 8 | function someFunction(){ var Static = arguments.callee; Static.someStaticVariable = (Static.someStaticVariable || 0) + 1; alert(Static.someStaticVariable); } someFunction() //Alerts 1 someFunction() //Alerts 2 someFunction() //Alerts 3 |
它还具有对象内部的静态变量:
1 2 3 4 5 6 7 | function Obj(){ this.Static = arguments.callee; } a = new Obj(); a.Static.name ="a"; b = new Obj(); alert(b.Static.name); //Alerts b |
您可以在任何对象上执行对象的方法,不管它是否有该方法。当然,它可能并不总是有效(如果该方法假定对象具有它不具有的内容),但它可能非常有用。例如:
1 2 3 4 | 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 2 3 4 5 6 | >>> 1 == true true >>> 0 == false true >>> 2 == true false |
所有函数实际上都是内置函数类型的实例,该类型具有一个构造函数,该构造函数接受包含函数定义的字符串,因此您可以在运行时通过连接字符串来实际定义函数:
1 2 3 4 5 | //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()将函数定义作为文本字符串返回。
如果您试图沙盒javascript代码,并禁用所有可能的方法将字符串评估为javascript代码,请注意,阻止所有明显的eval/document.write/new function/settimeout/setinterval/innerhtml和其他DOM操作是不够的。
对于任何对象o,
你可以把它改写为
1 2 | var Z="constructor"; Z[Z][Z]("alert('hi')")(); |
有趣的东西。
如果盲目使用json字符串来反序列化它,可能会遇到以下问题:
如果不将JSON字符串括在括号中,则属性名可能会被误认为是标签,从而导致意外行为或语法错误:
1 2 | eval("{ "foo": 42 }"); // syntax error: invalid label eval("({ "foo": 42 })"); // OK |
与var缺少块范围对应的是在javascript 1.7中引入的
- The let statement provides a way to associate values with variables
within the scope of a block, without
affecting the values of like-named
variables outside the block.- The let expression lets you establish variables scoped only to a
single expression.- The let definition defines variables whose scope is constrained
to the block in which they're defined.
This syntax is very much like the
syntax used for var.- You can also use let to establish variables that exist only within the
context of a for loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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。
您可以将"任何具有整型属性的*对象"和"长度"属性转换为数组本身,从而赋予它所有的数组方法,如push、pop、splice、map、filter、reduce等。
1 | Array.prototype.slice.call({"0":"foo","1":"bar", 2:"baz","length":3 }) |
//返回["foo","bar","baz"]
这可以处理来自其他帧的jquery对象、HTML集合和数组对象(作为整个数组类型的一种可能解决方案)。我说,如果它有一个长度属性,你可以把它变成一个数组,这并不重要。除了arguments对象之外,还有很多非数组对象具有length属性。
这是jquery的一个隐藏特性,而不是javascript,但由于永远不会有"jquery的隐藏特性"问题…
您可以在jquery中定义自己的
1 2 3 4 5 | $.extend($.expr[':'], { foo: function(node, index, args, stack) { // decide if selectors matches node, return true or false } }); |
对于使用
node 持有当前节点index 是节点集中节点的索引。args 是一个数组,如果选择器有一个参数或多个名称,则该数组很有用:args[0] 是整个选择器文本(如:foo("bar, baz") )args[1] 是选择器名称(如foo )args[2] 是用来包装参数的引号字符。(例如," 表示:foo("bar, baz") 或空字符串,如果没有引用(:foo(bar, baz) 或未定义,如无争议args[3] 是论点,包括任何引用(如"bar, baz" )如果没有参数,则为未定义
stack 是节点集(包含在该点匹配的所有节点的数组)
如果选择器匹配,函数应返回
例如,以下代码将启用基于全文regexp搜索选择节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $.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()达到类似的效果,但是自定义选择器更灵活,通常更可读。
微软给javascript的礼物:Ajax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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) } } |
模块模式
1 2 3 4 5 6 7 8 9 | <script type="text/javascript"> (function() { function init() { // ... } window.onload = init; })(); |
不使用
要在全局范围内显式定义变量/函数,它们必须以
1 2 | window.GLOBAL_VAR = 12; window.global_function = function() {}; |
函数.toString()(隐式):
1 2 3 4 5 6 7 | function x() { alert("Hello World"); } eval ("x =" + (x +"").replace( 'Hello World', 'STACK OVERFLOW BWAHAHA"); x("')); x(); |
生成器和迭代器(仅适用于Firefox 2+和Safari)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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() +" "); } |
The function containing the
yield
keyword is a generator. When you call
it, its formal parameters are bound to
actual arguments, but its body isn't
actually evaluated. Instead, a
generator-iterator is returned. Each
call to the generator-iterator's
next() method performs another pass
through the iterative algorithm. Each
step's value is the value specified by
theyield keyword. Think ofyield as
the generator-iterator version of
return, indicating the boundary
between each iteration of the
algorithm. Each time you callnext() ,
the generator code resumes from the
statement following theyield .In normal usage, iterator objects are
"invisible"; you won't need to operate
on them explicitly, but will instead
use JavaScript'sfor...in andfor each...in statements to loop naturally
over the keys and/or values of
objects.
1 2 3 4 5 6 7 | var objectWithIterator = getObjectSomehow(); for (var i in objectWithIterator) { document.write(objectWithIterator[i] +" "); } |
1 | if (obj.field === undefined) /* ... */ |
参观:
- http://images.google.com/images?迪斯科舞曲
将此javascript代码粘贴到Web浏览器的地址栏中:
- http://amix.dk/upload/awt/spin.txt下载
- 网址:http://amix.dk/upload/awt/disco.txt
欣赏javascript迪斯科表演-p
Namespaces
在较大的Javascript应用程序或框架中,可以使用它来组织名称代码。Javascript doesn't have a module or namspace concept buildin,but it's easy to emulate using Javascript objects.这将创建一个名为EDOCX1的空间,并将函数与EDOCX1连接起来。
1 2 3 4 5 | if (!window.ns) { window.ns = {}; } window.ns.foo = function() {}; |
通过一个项目使用相同的全球性名称预先定位,并为每个Javascript文件使用子名称是常见的。The name of the sub namspace often matches the file's name.
一个叫
ZZU1
这个是超级隐藏的,只是偶尔有用;-)
您可以使用原型链创建一个对象,该对象委托给另一个对象,而不更改原始对象。
1 2 3 4 5 6 7 8 9 10 11 12 | 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中的新增功能。仔细阅读所有这些例子,找出实际有效且没有错误的例子。
大循环在
即
1 2 3 4 5 | var i, len = 100000; for (var i = 0; i < len; i++) { // do stuff } |
比:
1 2 3 4 | i = len; while (i--) { // do stuff } |
令人惊讶的是,有多少人没有意识到它也是面向对象的。
这些并非总是一个好主意,但你可以用一些不太好的表情来谈论一些事情。重要的一点是,在Javascript中并非每一个价值都是一个客体,因此这些表述将成功地在成员访问像Null和undefined这样的非客体时取得成功。特别是,beware that typeof null==="object",but you can't null.tostring(),or("name"in null).
Convert anything to a number:
1 2 | +anything Number(anything) |
Convert anything to an unsigned four-byte integer:
1 | anything >>> 0 |
Convert anything to a string:
1 2 | '' + anything String(anything) |
Convert anything to a boolean:
1 2 | !!anything Boolean(anything) |
另一方面,使用该类型的名称而不使用"New"来区别条纹、Number、Boolean、Returning a primitive number、String或Boolean value,但使用"New"来回复"boxed"object types,which are nearly useless.
jquery和javascript:
变量名可以包含多个奇数字符。我使用$字符标识包含jquery对象的变量:
1 2 3 | var $links = $("a"); $links.hide(); |
jquery链接对象的模式非常好,但是应用这个模式可能会有点混乱。幸运的是,javascript允许您断行,比如:
1 2 3 4 5 | $("a") .hide() .fadeIn() .fadeOut() .hide(); |
常规javascript:
我发现通过使用自执行函数来模拟作用域很有用:
1 2 3 4 5 6 7 8 9 10 11 | function test() { // scope of test() (function() { // scope inside the scope of test() }()); // scope of test() } |
语法糖:用于循环闭包的内联
1 2 3 4 5 6 | var i; for (i = 0; i < 10; i++) (function () { // do something with i }()); |
几乎打破了道格拉斯·克罗克福德所有的代码约定,但我认为这是一个很好的观察,从来没有少过:)
替代方案:
1 2 3 4 5 6 | var i; for (i = 0; i < 10; i++) (function (j) { // do something with j }(i)); |
存在性检查。我经常看到这样的东西
1 2 3 4 5 6 7 | var a = [0, 1, 2]; // code that might clear the array. if (a.length > 0) { // do something } |
例如,只需执行以下操作:
1 2 3 4 5 6 7 | var a = [0, 1, 2]; // code that might clear the array. if (a.length) { // if length is not equal to 0, this will be true // do something } |
你可以做各种各样的存在检查,但这只是一个简单的例子来说明一点
下面是一个如何使用默认值的示例。
1 2 3 | function (someArgument) { someArgument || (someArgument ="This is the deault value"); } |
那是我的两分钱。还有其他的掘金,但现在就这样。
可以使用"for in"迭代数组
Mark Cidade指出了"for in"循环的有用性:
1 2 3 4 5 6 7 8 9 10 11 | // creating an object (the short way, to use it like a hashmap) var diner = { "fruit":"apple" "veggetable"="bean" } // looping over its properties for (meal_name in diner ) { document.write(meal_name+"<br >"); } |
结果:
1 2 | fruit veggetable |
但还有更多。由于可以使用关联数组这样的对象,因此可以处理键和值,就像foreach循环:
1 2 3 4 5 | // looping over its properties and values for (meal_name in diner ) { document.write(meal_name+" :"+diner[meal_name]+"<br >"); } |
结果:
1 2 | fruit : apple veggetable : bean |
而且,由于数组也是对象,所以可以以完全相同的方式迭代其他数组:
1 2 3 4 5 | var my_array = ['a', 'b', 'c']; for (index in my_array ) { document.write(index+" :"+my_array[index]+"<br >"); } |
结果:
1 2 3 | 0 : a 1 : b 3 : c |
您可以轻松地从数组中删除已知元素
1 2 3 | var arr = ['a', 'b', 'c', 'd']; var pos = arr.indexOf('c'); pos > -1 && arr.splice( pos, 1 ); |
你可以轻松地洗牌
–不是真正的随机分布,请参见注释。
如果您希望基于类的OO感觉有点像CLOS,那么joose是一个很好的对象系统。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Create a class called Point Class("Point", { has: { x: { is: "rw", init: 0 }, y: { is: "rw", init: 0 } }, methods: { clear: function () { this.setX(0); this.setY(0); } } }) // Use the class var point = new Point(); point.setX(10) point.setY(20); point.clear(); |
与数组或nulls一起使用的javascript
这是一个函数,它也将为这些项返回正确的值。阵列识别是从道格拉斯·克罗克福德的书《javascript:好的部分》中复制的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | function typeOf (value) { var type = typeof value; if (type === 'object') { if (value === null) { type = 'null'; } else if (typeof value.length === 'number' && typeof value.splice === 'function' && !value.propertyIsEnumerable('length')) { type = 'array'; } } return type; } |
Javascript被认为在暴露其所有物体时是非常好的,因此,如果它的视窗对象自身。
所以如果我想用JQUERY/YUI DIV POPUP超越浏览器的警报,那么太接受字符串作为参数,就可以简单地使用滑雪机。
1 2 3 4 5 6 7 | <wyn> function divPopup(str) { //code to show the divPopup } window.alert = divPopup; </wyn> |
通过这一改变,所有呼叫警报()将显示基于浏览器特定警报的良好的新数据传输。
要将浮点数转换为整数,您可以使用以下密码黑客之一(请不要这样做):
基本上,对浮动应用任何不会更改最终值(即标识函数)的二进制操作,最终都会将浮动转换为整数。
还有一个几乎未知的javascript语法:
1 2 3 4 5 6 7 8 | var a; a=alert(5),7; alert(a); // alerts undefined a=7,alert(5); alert(a); // alerts 7 a=(3,6); alert(a); // alerts 6 |
关于这个的更多信息。
javascript多功能性-覆盖默认功能
< BR>下面是用jquery ui的对话框小部件覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | jQuery.altAlert = function (options) { var defaults = { title:"Alert", buttons: { "Ok": function() { jQuery(this).dialog("close"); } } }; jQuery.extend(defaults, options); delete defaults.autoOpen; window.alert = function () { jQuery("", { html: arguments[0].replace(/ /,"<br />") }).dialog(defaults); }; }; |
可能是不太知名的一个:
arguments.callee.caller+函数ToString()
1 2 3 4 5 6 7 8 9 10 | function called(){ alert("Go called by: "+arguments.callee.caller.toString()); } function iDoTheCall(){ called(); } iDoTheCall(); |
打印出
嗯,我没读过整篇论文,虽然对我很有兴趣,但让我给你一点捐赠:
1 2 3 4 5 6 | // forget the debug alerts var alertToFirebugConsole = function() { if ( window.console && window.console.log ) { window.alert = console.log; } } |
这似乎只适用于火狐(蜘蛛猴)。函数内部:
arguments[-2] 给出了参数个数(与arguments.length 相同)arguments[-3] 给出了被调用的函数(与arguments.callee 相同)
正如马吕斯已经指出的,函数中可以有公共静态变量。
我通常使用它们来创建只执行一次的函数,或者缓存一些复杂的计算结果。
下面是我以前的"单例"方法的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | var singleton = function(){ if (typeof arguments.callee.__instance__ == 'undefined') { arguments.callee.__instance__ = new function(){ //this creates a random private variable. //this could be a complicated calculation or DOM traversing that takes long //or anything that needs to be"cached" var rnd = Math.random(); //just a"public" function showing the private variable value this.smth = function(){ alert('it is an object with a rand num=' + rnd); }; }; } return arguments.callee.__instance__; }; var a = new singleton; var b = new singleton; a.smth(); b.smth(); |
正如您可能看到的,在这两种情况下,构造函数只运行一次。
例如,我在2004年使用这种方法,当时我不得不创建一个灰色背景的模式对话框覆盖整个页面(类似于lightbox)。互联网资源管理器5.5和6具有最高的堆叠上下文由于元素的"窗口"性质;因此,如果页面包含select元素,覆盖它们的唯一方法是创建一个iframe和把它放在页面的顶部。所以整个剧本是相当复杂,有点慢(它使用过滤器:表达式设置覆盖iframe的不透明度)。这个"shim"脚本只有一个".show()"方法,它创建了填充程序只有一次,并将其缓存在静态变量中:)
功能可以有方法。
I use this pattern of AJAX form submissions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | var fn = (function() { var ready = true; function fnX() { ready = false; // AJAX return function function Success() { ready = true; } Success(); return"this is a test"; } fnX.IsReady = function() { return ready; } return fnX; })(); if (fn.IsReady()) { fn(); } |
闭包:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function f() { var a; function closureGet(){ return a; } function closureSet(val){ a=val;} return [closureGet,closureSet]; } [closureGet,closureSet]=f(); closureSet(5); alert(closureGet()); // gives 5 closureSet(15); alert(closureGet()); // gives 15 |
这里的闭包不是所谓的破坏赋值(
当你写回调的时候,你有很多代码,看起来像这样:
1 2 3 | callback: function(){ stuff(arg1,arg2); } |
您可以使用下面的函数,使其更清晰。
1 | callback: _(stuff, arg1, arg2) |
它使用了一个不太知名的函数,即javascript的函数对象apply。
它还显示了另一个可以用作函数名的字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function _(){ var func; var args = new Array(); for(var i = 0; i < arguments.length; i++){ if( i == 0){ func = arguments[i]; } else { args.push(arguments[i]); } } return function(){ return func.apply(func, args); } } |
简单的自包含函数返回值缓存:
1 2 3 4 5 6 7 | function isRunningLocally(){ var runningLocally = ....; // Might be an expensive check, check whatever needs to be checked. return (isRunningLocally = function(){ return runningLocally; })(); }, |
昂贵的部分只在第一次调用时执行,然后所有的函数都返回这个值。当然,这只对总是返回相同内容的函数有用。
这是一个简单的思考方式。此函数内将引用函数的未来对象实例,通常与新操作员创建。因此,明确地说,这是一个内在函数,将永远不重复一个外在函数。
上面应该保持一个不麻烦的状态。但你能做的事情越来越复杂了"
Example 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function DriveIn() { this.car = 'Honda'; alert(this.food); //'food' is the attribute of a future object //and DriveIn does not define it. } var A = {food:'chili', q:DriveIn}; //create object A whose q attribute //is the function DriveIn; alert(A.car); //displays 'undefined' A.q(); //displays 'chili' but also defines this.car. alert(A.car); //displays 'Honda' |
The rule of this:
当一个函数被称为一个对象的属性时,该函数在该函数中的任何一个出现(但在任何内在函数之外)都被提及到该对象。
即使新操作员被使用,我们也需要澄清"这条规则"的应用。在新场景的后面,这是通过对象的结构或属性与对象相联系的。
Example 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function Insect () { this.bug ="bee"; this.bugFood = function() { alert("nectar"); } } var B = new Insect(); alert(B.constructor); //displays"Insect"; By"The Rule of This" any //ocurrence of 'this' inside Insect now refers //to B. |
为了使这一点更加清晰,我们可以在不使用新操作员的情况下创建一个昆虫实例。
Example 3:
1 2 3 4 5 6 7 | var C = {constructor:Insect}; //Assign the constructor attribute of C, //the value Insect. C.constructor(); //Call Insect through the attribute. //C is now an Insect instance as though it //were created with operator new. [*] alert(C.bug); //Displays"bee." C.bugFood(); //Displays"nectar." |
[*]唯一的实际差别是,以第3个例子来说,建筑物是一个可归纳的属性。当新算子被使用时,制造商成为属性,但不可估量。如果操作中的"For(Var name in object)"返回属性的名称,则属性是可以接受的。
可以将javascript对象绑定为HTML元素属性。
1 2 3 4 5 6 7 8 9 | Klick Me <script type="text/javascript"> var someVariable = 'I was klicked'; var divElement = document.getElementById('jsTest'); // binding function/object or anything as attribute divElement.controller = function() { someVariable += '*'; alert('You can change instance data: ' + someVariable ); }; var onclickFunct = new Function( 'this.controller();' ); // Works in Firefox and Internet Explorer. divElement.onclick = onclickFunct; |
我的第一次提交并不是一个隐藏的特性,而是一个很少使用的特性重新定义特性的应用程序。因为可以重新定义对象的方法,所以可以缓存方法调用的结果,这在计算昂贵且需要延迟计算时很有用。这是记忆化的最简单形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function Circle(r) { this.setR(r); } Circle.prototype = { recalcArea: function() { this.area=function() { area = this.r * this.r * Math.PI; this.area = function() {return area;} return area; } }, setR: function (r) { this.r = r; this.invalidateR(); }, invalidateR: function() { this.recalcArea(); } } |
重构将结果缓存到方法中的代码,可以得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Object.prototype.cacheResult = function(name, _get) { this[name] = function() { var result = _get.apply(this, arguments); this[name] = function() { return result; } return result; }; }; function Circle(r) { this.setR(r); } Circle.prototype = { recalcArea: function() { this.cacheResult('area', function() { return this.r * this.r * Math.PI; }); }, setR: function (r) { this.r = r; this.invalidateR(); }, invalidateR: function() { this.recalcArea(); } } |
如果你想要一个记忆功能,你可以用它来代替。不涉及属性重新定义。
1 2 3 4 5 6 7 8 9 | Object.prototype.memoize = function(name, implementation) { this[name] = function() { var argStr = Array.toString.call(arguments); if (typeof(this[name].memo[argStr]) == 'undefined') { this[name].memo[argStr] = implementation.apply(this, arguments); } return this[name].memo[argStr]; } }; |
请注意,这依赖于标准数组到字符串的转换,通常无法正常工作。把它作为练习留给读者。
我的第二个建议是getters和setters。我很惊讶他们还没有被提到。由于官方标准不同于事实标准(defineproperty与define[gs]etter),Internet Explorer几乎不支持官方标准,因此它们一般不有用。也许这就是他们没有被提及的原因。请注意,您可以很好地将getter和结果缓存结合起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Object.prototype.defineCacher = function(name, _get) { this.__defineGetter__(name, function() { var result = _get.call(this); this.__defineGetter__(name, function() { return result; }); return result; }) }; function Circle(r) { this.r = r; } Circle.prototype = { invalidateR: function() { this.recalcArea(); }, recalcArea: function() { this.defineCacher('area', function() {return this.r * this.r * Math.PI; }); }, get r() { return this._r; } set r(r) { this._r = r; this.invalidateR(); } } var unit = new Circle(1); unit.area; |
有效地将getter、setter和结果缓存结合起来有点麻烦,因为您必须防止无效化,或者在set上不进行自动无效化,这就是下面的示例所做的。如果更改一个属性会使多个其他属性失效(假设在这些示例中有一个"直径"属性),则这主要是一个问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Object.prototype.defineRecalcer = function(name, _get) { var recalcFunc; this[recalcFunc='recalc'+name.toCapitalized()] = function() { this.defineCacher(name, _get); }; this[recalcFunc](); this.__defineSetter__(name, function(value) { _set.call(this, value); this.__defineGetter__(name, function() {return value; }); }); }; function Circle(r) { this.defineRecalcer('area', function() {return this.r * this.r * Math.PI;}, function(area) {this._r = Math.sqrt(area / Math.PI);}, ); this.r = r; } Circle.prototype = { invalidateR: function() { this.recalcArea(); }, get r() { return this._r; } set r(r) { this._r = r; this.invalidateR(); } } |
使用function.apply指定函数将处理的对象:
假设你有课
1 2 3 4 5 | function myClass(){ this.fun = function(){ do something; }; } |
如果以后再这样做:
1 2 3 4 | var a = new myClass(); var b = new myClass(); myClass.fun.apply(b); //this will be like b.fun(); |
甚至可以指定一个调用参数数组作为第二个参数
查看:https://developer.mozilla.org/en/core_javascript_1.5_reference/global_objects/function/apply
除了公共成员之外,还可以使用闭包使具有私有(在"类"定义之外不可访问)静态和非静态成员的"类"。
请注意,下面的代码中有两种类型的公共成员。可以访问私有实例成员的特定实例(在构造函数中定义)和只能访问私有静态成员的共享成员(在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | var MyClass = (function () { // private static var nextId = 1; // constructor var cls = function () { // private var id = nextId++; var name = 'Unknown'; // public (this instance only) this.get_id = function () { return id; }; this.get_name = function () { return name; }; this.set_name = function (value) { if (typeof value != 'string') throw 'Name must be a string'; if (value.length < 2 || value.length > 20) throw 'Name must be 2-20 characters long.'; name = value; }; }; // public static cls.get_nextId = function () { return nextId; }; // public (shared across instances) cls.prototype = { announce: function () { alert('Hi there! My id is ' + this.get_id() + ' and my name is"' + this.get_name() + '"! ' + 'The next fellow\'s id will be ' + MyClass.get_nextId() + '!'); } }; return cls; })(); |
要测试此代码:
1 2 3 4 5 6 7 8 | var mc1 = new MyClass(); mc1.set_name('Bob'); var mc2 = new MyClass(); mc2.set_name('Anne'); mc1.announce(); mc2.announce(); |
如果您有firebug,您会发现除了在定义它们的闭包内设置断点之外,没有其他方法可以访问私有成员。
当定义需要对值进行严格验证并完全控制状态更改的类时,此模式非常有用。
要扩展这个类,您可以将
如果您要替换
合并运算符非常酷,可以生成一些干净、简洁的代码,特别是当您将其链接在一起时:
我经常看到已经放弃并使用全局变量而不是静态变量的代码,因此下面介绍了如何(在我想您可以称之为通用单例工厂的示例中):
1 2 3 4 5 6 7 8 9 10 11 | var getInstance = function(objectName) { if ( !getInstance.instances ) { getInstance.instances = {}; } if ( !getInstance.instances[objectName] ) { getInstance.instances[objectName] = new window[objectName]; } return getInstance.instances[objectName]; }; |
另外,请注意
同样,在处理DOM时,当我第一次初始化要添加的任何功能时,我经常将函数参数和/或标志隐藏到DOM节点中。如果有人尖叫,我会加一个例子。
令人惊讶的是,第一页没有人提到过
1 2 3 4 5 | var x = [1,2,3]; for ( i in x ) { if ( !x.hasOwnProperty(i) ) { continue; } console.log(i, x[i]); } |
阅读此处了解更多信息。
最后,
javascript提示或JSlibs项目。
您可以动态地重新定义运行时环境的大部分,例如修改
一种稍微不那么危险的形式是向现有对象添加助手方法。例如,可以使IE6"本机"支持数组上的indexof。
function l(f,n){n&&l(f,n-1,f(n));}
l( function( loop ){ alert( loop ); },
5 );
警报5、4、3、2、1
好吧,这不是什么特色,但它非常有用。
Shows selectable and formatted alerts:
1 | alert(prompt('',something.innerHTML )); |