您认为每个程序员都应该知道 JavaScript 的哪些“隐藏特性”?
在看到以下问题的优秀答案后,我认为是时候询问 JavaScript 了。
尽管 JavaScript 可以说是目前最重要的客户端语言(问问 Google),但令人惊讶的是,大多数 Web 开发人员很少意识到它的强大功能。
您认为每个程序员都应该知道 JavaScript 的哪些“隐藏特性”?
在看到以下问题的优秀答案后,我认为是时候询问 JavaScript 了。
尽管 JavaScript 可以说是目前最重要的客户端语言(问问 Google),但令人惊讶的是,大多数 Web 开发人员很少意识到它的强大功能。
您不需要为函数定义任何参数。您可以只使用该函数的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
我可以引用 Douglas Crockford 的大部分优秀著作 JavaScript: The Good Parts。
但我只给你一个,总是用===
and!==
而不是==
and!=
alert('' == '0'); //false
alert(0 == ''); // true
alert(0 =='0'); // true
==
不是传递的。如果您使用===
它,那么所有这些陈述都会像预期的那样给出错误的结果。
函数是 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();
}
您可以使用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
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 漏洞利用。
如果您在谷歌上搜索给定主题的合适的 JavaScript 参考,请在查询中包含“mdc”关键字,您的第一个结果将来自 Mozilla 开发人员中心。我没有随身携带任何离线参考资料或书籍。我总是使用“mdc”关键字技巧来直接找到我正在寻找的东西。例如:
Google:javascript 数组排序 mdc
(在大多数情况下,您可以省略“javascript”)
更新: Mozilla 开发者中心已更名为 Mozilla 开发者网络。“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 = 函数(x){ // ... } fn.foo = 1; fn.next = 函数(y){ // }
我不得不说自动执行功能。
(function() { alert("hi there");})();
因为 Javascript没有块作用域,如果要定义局部变量,可以使用自执行函数:
(function() {
var myvar = 2;
alert(myvar);
})();
在这里,myvar
is 不会干扰或污染全局作用域,并且会在函数终止时消失。
知道一个函数需要多少个参数
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
代表 Not a Number 但如果您询问类型,它实际上会返回一个数字。Array.sort
可以采用比较器功能并由类似快速排序的驱动程序调用(取决于实现)。$0
, $1
,$2
成员。null
不同于其他任何东西。它既不是对象、布尔值、数字、字符串,也不是undefined
. 这有点像“替代品” undefined
。(注typeof null == "object"
:)this
产生其他无法命名的 [Global] 对象。var
,而不是仅仅依赖于变量的自动声明,让运行时真正有机会优化对该变量的访问with
构造将破坏此类优化break
. 循环可以被标记并用作 的目标continue
。undefined
. (取决于实施)if (new Boolean(false)) {...}
将执行{...}
块[根据好评进行了一些更新;请看评论]
我知道我参加聚会迟到了,但我简直不敢相信+
除了“将任何东西转换为数字”之外还没有提到运算符的用处。也许这就是它隐藏的功能有多好?
// 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 中的扩展方法”通过原型属性。
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';
}
Alan Storm 指出这可能有些危险:如果用作上下文的对象没有被分配的属性之一,它将在外部范围内解析,可能会创建或覆盖全局变量。如果您习惯于编写代码来处理具有默认值或空值的属性未定义的对象,这尤其危险:
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
定义了足够多的类似数组的属性(长度、[] 运算符)供sort()
.
原型继承(由 Douglas Crockford 推广)彻底改变了您对 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 += '<p>paragraph #'+i+'</p>';
});
// 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 文章修改的代码。
Off the top of my head...
Functions
arguments.callee refers to the function that hosts the "arguments" variable, so it can be used to recurse anonymous functions:
var recurse = function() {
if (condition) arguments.callee(); //calls recurse() again
}
That's useful if you want to do something like this:
//do something to all array items within an array recursively
myArray.forEach(function(item) {
if (item instanceof Array) item.forEach(arguments.callee)
else {/*...*/}
})
Objects
An interesting thing about object members: they can have any string as their names:
//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);
}
Strings
String.split can take regular expressions as parameters:
"hello world with spaces".split(/\s+/g);
//returns an array: ["hello", "world", "with", "spaces"]
String.replace can take a regular expression as a search parameter and a function as a replacement parameter:
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]
如您所见,该元素已被删除,但由于该元素被替换为未定义的值,因此在数组中留下了一个洞。
因此,要解决这个问题,而不是使用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
JavaScript 与 Date() 一起工作的方式让我很兴奋!
function isLeapYear(year) {
return (new Date(year, 1, 29, 0, 0).getMonth() != 2);
}
这确实是“隐藏功能”。
编辑:删除“?” 政策正确性评论中建议的条件。是: ... new Date(year, 1, 29, 0, 0).getMonth() != 2 吗?true : false ... 详情请看评论。
Here's a couple of shortcuts:
var a = []; // equivalent to new Array()
var o = {}; // equivalent to new Object()
闭包之禅
其他人提到了关闭。但令人惊讶的是,有多少人知道闭包,使用闭包编写代码,但仍然对闭包的真正含义有错误的认识。有些人将一等函数与闭包混淆。还有一些人将其视为一种静态变量。
对我来说,闭包是一种“私有”全局变量。也就是一种变量,一些函数认为是全局变量,而其他函数看不到。现在,我知道这对底层机制的描述是快速而松散的,但这就是它的感觉和行为方式。为了显示:
// 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
*注意:我不知道这里的正确术语。
My favorite trick is using apply
to perform a callback to an object's method and maintain the correct "this" variable.
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
您永远不必使用eval()
来组装全局变量名。
也就是说,如果您有多个名为 的全局变量(无论出于何种原因)spec_grapes, spec_apples
,则不必使用eval("spec_" + var)
.
所有全局变量都是 的成员window[]
,所以你可以这样做window["spec_" + var]
。
为 Firebug 使用 console.log() 时,防止在 Internet Explorer 中测试时出现恼人的错误:
function log(message) {
(console || { log: function(s) { alert(s); }).log(message);
}
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();
我的最爱之一是构造函数类型检查:
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 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();
要加入大字符串,请使用数组(它更快):
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"
它比循环内快得多。
window.name
的值在页面更改中保持不变,如果在同一域中(如果在 iframe 中,则document.getElementById("your frame's ID").contentWindow.name
用于访问它)可以由父窗口读取,并且仅受可用内存的限制。
真值和假值的概念。你不需要做类似的事情
if(someVar === undefined || someVar === null) ...
只需这样做:
如果(!someVar)。
每个值都有对应的布尔表示。
创建新“对象”时,括号是可选的。
function Animal () {
}
var animal = new Animal();
var animal = new Animal;
一样。
函数语句和函数表达式的处理方式不同。
function blarg(a) {return a;} // statement
bleep = function(b) {return b;} //expression
在代码运行之前解析所有函数语句 - JavaScript 文件底部的函数将在第一个语句中可用。另一方面,它将无法利用某些动态上下文,例如周围with
的语句 -with
解析函数时尚未执行。
函数表达式在遇到它们的地方内联执行。在此之前它们不可用,但它们可以利用动态上下文。
let
.
JavaScript 1.7中引入了var 缺乏块作用域的对应物。let
- 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。
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
它在对象内部也有静态变量:
function Obj(){
this.Static = arguments.callee;
}
a = new Obj();
a.Static.name = "a";
b = new Obj();
alert(b.Static.name); //Alerts b
== 运算符有一个非常特殊的属性,它创建了这种令人不安的相等性(是的,我知道在 Perl 等其他动态语言中会出现这种行为,但 JavaScript 通常不会尝试在比较中变得聪明):
>>> 1 == true
true
>>> 0 == false
true
>>> 2 == true
false
如果您尝试对 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')")();
好玩的东西。
如果你盲目地eval()
对一个 JSON 字符串进行反序列化,你可能会遇到问题:
如果您不将 JSON 字符串括在括号中,则属性名称可能会被误认为是标签,从而导致意外行为或语法错误:
eval("{ \"foo\": 42 }"); // syntax error: invalid label
eval("({ \"foo\": 42 })"); // OK
您可以在任何对象上执行对象的方法,无论它是否具有该方法。当然,它可能并不总是有效(如果该方法假定对象具有它没有的东西),但它可能非常有用。例如:
function(){
arguments.push('foo') // This errors, arguments is not a proper array and has no push method
Array.prototype.push.apply(arguments, ['foo']) // Works!
}
所有函数实际上都是内置Function类型的实例,它有一个构造函数,该构造函数接受一个包含函数定义的字符串,因此您实际上可以在运行时通过例如连接字符串来定义函数:
//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()将函数定义作为文字字符串返回。
您可以将具有整数属性和长度属性的“任何*对象”转换为适当的数组,从而赋予它所有数组方法,例如 push、pop、splice、map、filter、reduce 等。
Array.prototype.slice.call({"0":"foo", "1":"bar", 2:"baz", "length":3 })
// 返回 ["foo", "bar", "baz"]
这适用于来自其他框架的 jQuery 对象、html 集合和 Array 对象(作为整个数组类型事物的一种可能解决方案)。我说,如果它有一个长度属性,你可以把它变成一个数组,没关系。除了参数对象之外,还有许多具有长度属性的非数组对象。
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)
}
}
模块模式
<script type="text/javascript">
(function() {
function init() {
// ...
}
window.onload = init;
})();
</script>
没有声明或在函数外部声明的变量和函数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)
没有参数,则为 undefinedargs[3]
是参数,包括任何引号,(例如"bar, baz"
)或 undefined 如果没有参数stack
是节点集(包含在该点匹配的所有节点的数组)true
如果选择器匹配,则该函数应返回,false
否则。
例如,以下代码将启用基于全文正则表达式搜索的节点选择:
$.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) /* ... */
生成器和迭代器(仅适用于 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() + "<br>\n");
}
包含
yield
关键字的函数是生成器。当你调用它时,它的形式参数绑定到实际参数,但它的主体实际上并没有被评估。相反,会返回一个生成器迭代器。对生成器迭代器方法的每次调用都会next()
通过迭代算法执行另一次传递。每个步骤的值是yield
关键字指定的值。可以将yield
其视为生成器-迭代器版本的 return,指示算法每次迭代之间的边界。每次调用next()
时,生成器代码都会从yield
.在正常使用中,迭代器对象是“不可见的”;您不需要显式地对它们进行操作,而是使用 JavaScript
for...in
和for each...in
语句自然地循环遍历对象的键和/或值。
var objectWithIterator = getObjectSomehow();
for (var i in objectWithIterator)
{
document.write(objectWithIterator[i] + "<br>\n");
}
访问:
将此 JavaScript 代码粘贴到 Web 浏览器的地址栏中:
享受 JavaScript 迪斯科表演:-p
All your "hidden" features are right here on the Mozilla wiki: http://developer.mozilla.org/en/JavaScript.
There's the core JavaScript 1.5 reference, what's new in JavaScript 1.6, what's new in JavaScript 1.7, and also what's new in JavaScript 1.8. Look through all of those for examples that actually work and are not wrong.
这个是超级隐藏的,只是偶尔有用;-)
您可以使用原型链创建一个委托给另一个对象的对象,而无需更改原始对象。
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 上的“简单”值。如果你修改了一个数组或另一个对象,那么原型就不再“保护”原始对象。当您在类定义/原型中有 {} 或 [] 时请注意。
在较大的 JavaScript 应用程序或框架中,在命名空间中组织代码会很有用。JavaScript 没有内置模块或命名空间概念,但使用 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() {};
令人惊讶的是,有多少人没有意识到它也是面向对象的。
大循环在 - 条件下和向后更快while
- 也就是说,如果循环的顺序对您无关紧要。在我大约 50% 的代码中,它通常不会。
IE
var i, len = 100000;
for (var i = 0; i < len; i++) {
// do stuff
}
慢于:
i = len;
while (i--) {
// do stuff
}
这些并不总是一个好主意,但您可以使用简洁的表达式转换大多数内容。这里重要的一点是,并非 JavaScript 中的每个值都是对象,因此这些表达式将在对 null 和 undefined 等非对象的成员访问失败的情况下成功。特别要注意 typeof null == "object",但不能 null.toString() 或 ("name" in null)。
将任何内容转换为数字:
+anything
Number(anything)
将任何内容转换为无符号的四字节整数:
anything >>> 0
将任何内容转换为字符串:
'' + anything
String(anything)
将任何内容转换为布尔值:
!!anything
Boolean(anything)
此外,使用不带“new”的类型名称对于 String、Number 和 Boolean 的行为不同,返回原始数字、字符串或布尔值,但使用“new”这些将返回“装箱”对象类型,这几乎是无用的。
jQuery 和 JavaScript:
变量名可以包含许多奇数字符。我使用 $ 字符来识别包含 jQuery 对象的变量:
var $links = $("a");
$links.hide();
jQuery 的对象链接模式非常好,但是应用这种模式会有点混乱。幸运的是,JavaScript 允许您换行,如下所示:
$("a")
.hide()
.fadeIn()
.fadeOut()
.hide();
通用 JavaScript:
我发现使用自执行函数来模拟作用域很有用:
function test()
{
// scope of test()
(function()
{
// scope inside the scope of test()
}());
// scope of test()
}
Mark Cidade 指出了“for in”循环的用处:
// 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 \n>");
}
结果 :
fruit
veggetable
但还有更多。由于您可以使用关联数组之类的对象,因此您可以处理键和值,就像 foreach 循环一样:
// looping over its properties and values
for (meal_name in diner ) {
document.write(meal_name+" : "+diner[meal_name]+"<br \n>");
}
结果 :
fruit : apple
veggetable : bean
而且由于 Array 也是对象,因此您可以以完全相同的方式迭代其他数组:
var my_array = ['a', 'b', 'c'];
for (index in my_array ) {
document.write(index+" : "+my_array[index]+"<br \n>");
}
结果 :
0 : a
1 : b
3 : c
var arr = ['a', 'b', 'c', 'd'];
var pos = arr.indexOf('c');
pos > -1 && arr.splice( pos, 1 );
– 不是真正的随机分布,请参阅评论。arr.sort(function() Math.random() - 0.5);
Joose is a nice object system if you would like Class-based OO that feels somewhat like CLOS.
// 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();
语法糖:内联 for 循环闭包
var i;
for (i = 0; i < 10; i++) (function ()
{
// do something with i
}());
几乎打破了道格拉斯·克罗克福德的所有代码约定,但我认为它看起来非常好,而且永远不会少:)
选择:
var i;
for (i = 0; i < 10; i++) (function (j)
{
// do something with j
}(i));
存在检查。我经常看到这样的东西
var a = [0, 1, 2];
// code that might clear the array.
if (a.length > 0) {
// do something
}
而不是例如只是这样做:
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
}
你可以做各种各样的存在检查,但这只是一个简单的例子来说明一点
这是有关如何使用默认值的示例。
function (someArgument) {
someArgument || (someArgument = "This is the deault value");
}
那是我的两分钱。还有其他的掘金,但目前仅此而已。
typeof
与数组或空值一起使用的JavaScript运算符总是返回object
值,在某些情况下,这可能不是程序员所期望的。
这是一个函数,它也将为这些项目返回正确的值。数组识别是从 Douglas Crockford 的书“JavaScript: The Good Parts”中复制而来的。
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;
}
嗯,虽然对我来说很有趣,但我没有阅读整个主题,但让我捐赠一点:
// forget the debug alerts
var alertToFirebugConsole = function() {
if ( window.console && window.console.log ) {
window.alert = console.log;
}
}
还有一种几乎不为人知的 JavaScript 语法:
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
更多关于这里的信息。
To convert a floating point number to an integer, you can use one of the following cryptic hacks (please don't):
3.14 >> 0
(via 2.9999999999999999 >> .5?)3.14 | 0
(via What is the best method to convert floating point to an integer in JavaScript?)3.14 & -1
3.14 ^ 0
~~3.14
Basically, applying any binary operation on the float that won't change the final value (i.e. identity function) ends up converting the float to an integer.
也许是鲜为人知的其中之一:
arguments.callee.caller + Function#toString()
function called(){
alert("Go called by:\n"+arguments.callee.caller.toString());
}
function iDoTheCall(){
called();
}
iDoTheCall();
打印出iDoTheCall
-- 已弃用的源代码,但有时当警报是您唯一的选择时会很有用....
This seems to only work on Firefox (SpiderMonkey). Inside a function:
arguments[-2]
gives the number of arguments (same as arguments.length
)arguments[-3]
gives the function that was called (same as arguments.callee
)正如 Marius 已经指出的那样,您可以在函数中使用公共静态变量。
我通常使用它们来创建只执行一次的函数,或者缓存一些复杂的计算结果。
这是我的旧“单例”方法的示例:
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)。Internet Explorer 5.5 和 6 的 <select> 或 <iframe> 元素由于其“窗口”性质而具有最高的堆叠上下文;因此,如果页面包含选择元素,覆盖它们的唯一方法是创建一个 iframe 并将其放置在页面的“顶部”。所以整个脚本非常复杂而且有点慢(它使用过滤器:表达式来设置覆盖 iframe 的不透明度)。“shim”脚本只有一个“.show()”方法,它只创建了一次 shim 并将其缓存在静态变量中:)
JavaScript 被认为非常擅长暴露它的所有对象,所以不管它的窗口对象本身。
因此,如果我想使用 JQuery/YUI div 弹出窗口覆盖浏览器警报,该弹出窗口也接受字符串作为参数,只需使用以下代码段即可完成。
function divPopup(str)
{
//code to show the divPopup
}
window.alert = divPopup;
通过此更改,所有对 alert() 的调用都将显示基于 div 的良好弹出窗口,而不是浏览器特定的警报。
window.alert
这是使用 jQuery UI 的Dialog 小部件
覆盖该函数的代码。我是作为一个 jQuery 插件来做的。你可以在我的博客上阅读它;altAlert,一个用于个性化警报消息的 jQuery 插件。
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("<div />", {
html: arguments[0].replace(/\n/, "<br />")
}).dialog(defaults);
};
};
您可以将 JavaScript 对象绑定为 HTML 元素属性。
<div id="jsTest">Klick Me</div>
<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:\n' + someVariable ); };
var onclickFunct = new Function( 'this.controller();' ); // Works in Firefox and Internet Explorer.
divElement.onclick = onclickFunct;
</script>
您可以即时重新定义大部分运行时环境,例如修改Array
构造函数或定义undefined
. 不是你应该这样做,但它可能是一个强大的功能。
一种不太危险的形式是向现有对象添加辅助方法。例如,您可以使 IE6 “本机”支持数组上的 indexOf。
当您编写回调时,您有很多代码,如下所示:
callback: function(){
stuff(arg1,arg2);
}
您可以使用下面的功能,使其更干净一些。
callback: _(stuff, arg1, arg2)
它使用 javascript 的 Function 对象的一个鲜为人知的函数,apply。
它还显示了另一个可以用作函数名的字符:_。
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);
}
}
除了公共成员之外,您还可以使用闭包创建具有私有(在“类”定义之外无法访问)静态和非静态成员的“类”。
请注意,下面的代码中有两种类型的公共成员。可以访问私有实例prototype
成员的特定实例(在构造函数中定义),以及只能访问私有静态成员的共享成员(在对象中定义) 。
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() + '"!\r\n' +
'The next fellow\'s id will be ' + MyClass.get_nextId() + '!');
}
};
return cls;
})();
要测试此代码:
var mc1 = new MyClass();
mc1.set_name('Bob');
var mc2 = new MyClass();
mc2.set_name('Anne');
mc1.announce();
mc2.announce();
如果您有 Firebug,您会发现除了在定义它们的闭包内设置断点外,没有其他方法可以访问私有成员。
在定义需要对值进行严格验证并完全控制状态更改的类时,这种模式非常有用。
要扩展此类,您将MyClass.call(this);
在扩展类的构造函数的顶部放置。您还需要复制MyClass.prototype
对象(不要重复使用它,因为您也会更改其成员MyClass
。
如果要替换该announce
方法,则可以像这样调用MyClass.announce
它:MyClass.prototype.announce.call(this);
使用Function.apply指定函数将作用于的对象:
假设你有课
function myClass(){
this.fun = function(){
do something;
};
}
如果以后你这样做:
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
合并运算符非常酷,并且可以生成一些干净、简洁的代码,尤其是当您将它们链接在一起时:a || b || c || "default";
问题在于,由于它通过评估为 bool 而不是 null 来工作,如果评估为 false 的值是有效的,它们通常会次过来看了看。不用担心,在这些情况下,只需恢复到好的 ol' 三元运算符。
我经常看到已经放弃并使用全局变量而不是静态变量的代码,所以这里是如何(在我认为你可以称为通用单例工厂的示例中):
var getInstance = function(objectName) {
if ( !getInstance.instances ) {
getInstance.instances = {};
}
if ( !getInstance.instances[objectName] ) {
getInstance.instances[objectName] = new window[objectName];
}
return getInstance.instances[objectName];
};
另外,请注意new window[objectName];
which 是按名称一般实例化对象的关键。2个月前我才发现。
本着同样的精神,在使用 DOM 时,当我第一次初始化要添加的任何功能时,我经常将功能参数和/或标志埋入 DOM 节点中。如果有人尖叫,我将添加一个示例。
令人惊讶的是,第一页上没有人提到hasOwnProperty
,这是一种耻辱。使用for 迭代时,在被迭代的容器上使用方法以确保使用的成员名称是您期望的名称in
,这是一种很好的防御性编程。hasOwnProperty
var x = [1,2,3];
for ( i in x ) {
if ( !x.hasOwnProperty(i) ) { continue; }
console.log(i, x[i]);
}
阅读此处了解更多信息。
最后,with
几乎总是一个坏主意。
闭包:
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
这里的闭包不是所谓的解构赋值([c,d] = [1,3]
相当于c=1; d=3;
),而是a
在closureGet和closureSet中出现的仍然指向同一个变量。即使在closureSet 分配a
了一个新值之后!
我的第一次提交与其说是一个隐藏的功能,不如说是一个很少使用的属性重新定义功能的应用程序。因为您可以重新定义对象的方法,所以您可以缓存方法调用的结果,这在计算成本高且您想要惰性求值时很有用。这给出了最简单的记忆形式。
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();
}
}
将缓存结果的代码重构为一个方法,你会得到:
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();
}
}
如果您想要一个记忆功能,您可以使用它。不涉及属性重新定义。
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];
}
};
请注意,这依赖于标准数组 toString 转换,并且通常无法正常工作。修复它留给读者作为练习。
我的第二个提交是 getter 和 setter。我很惊讶他们还没有被提及。由于官方标准与事实上的标准不同(defineProperty 与define[GS]etter)并且 Internet Explorer 几乎不支持官方标准,因此它们通常没有用处。也许这就是他们没有被提及的原因。请注意,您可以很好地结合 getter 和结果缓存:
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 上不自动失效,这就是以下示例所做的。如果更改一个属性会使其他多个属性无效(想象这些示例中有一个“直径”属性),这主要是一个问题。
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(); }
}
这是思考“这个”的简单方法。函数内部的“this”将引用函数的未来对象实例,通常使用 operator new 创建。很明显,内部函数的“this”永远不会引用外部函数的实例。
以上应该可以避免麻烦。但是你可以用“这个”做更复杂的事情。
示例 1:
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'
这个规则:
每当函数作为对象的属性被调用时,函数内部(但在任何内部函数之外)出现的任何“this”都指的是该对象。
我们需要明确“这个规则”即使在使用 operator new 时也适用。在幕后 new 通过对象的构造函数属性将“this”附加到对象上。
示例 2:
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.
为了更清楚地说明这一点,我们可以创建一个 Insect 实例而不使用 operator new。
示例 3:
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 中,“构造函数”是一个可枚举属性。当使用 operator new 时,'constructor' 成为属性但不可枚举。如果 for-in 操作“for(var name in object)”返回属性的名称,则属性是可枚举的。
函数可以有方法。
我使用这种 AJAX 表单提交模式。
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();
}
简单的自包含函数返回值缓存:
function isRunningLocally(){
var runningLocally = ....; // Might be an expensive check, check whatever needs to be checked.
return (isRunningLocally = function(){
return runningLocally;
})();
},
昂贵的部分只在第一次调用时执行,之后函数所做的就是返回这个值。当然,这只对总是返回相同内容的函数有用。
函数 l(f,n){n&&l(f,n-1,f(n));}
l(函数(循环){警报(循环);},5);
警报 5、4、3、2、1
好吧,这不是一个很大的功能,但它非常有用:
显示可选择和格式化的警报:
alert(prompt('',something.innerHTML ));