你认为每个程序员都应该知道哪些JavaScript“隐藏功能”?
在看过以下问题的优秀答案之后,我想是时候问一下JavaScript了。
虽然JavaScript现在可以说是最重要的客户端语言(只需询问谷歌),但令人惊讶的是,大多数Web开发人员很少欣赏它真正的强大之处。
你认为每个程序员都应该知道哪些JavaScript“隐藏功能”?
在看过以下问题的优秀答案之后,我想是时候问一下JavaScript了。
虽然JavaScript现在可以说是最重要的客户端语言(只需询问谷歌),但令人惊讶的是,大多数Web开发人员很少欣赏它真正的强大之处。
原型继承(由道格拉斯·克罗克福德广为流传)完全革新了你在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"; }
(cond (predicate (action ...))
(predicate2 (action2 ...))
(#t default ))
可以写成...
predicate ? action( ... ) :
predicate2 ? action2( ... ) :
default;
这非常“实用”,它可以在不产生副作用的情况下分支您的代码。因此,不要写成:
if (predicate) {
foo = "one";
} else if (predicate2) {
foo = "two";
} else {
foo = "default";
}
您可以编写:
foo = predicate ? "one" :
predicate2 ? "two" :
"default";
递归使用也很好 :)
数字也是对象。因此,您可以执行一些很酷的操作,例如:
// convert to base 2
(5).toString(2) // returns "101"
// provide built in iteration
Number.prototype.times = function(funct){
if(typeof funct === 'function') {
for(var i = 0;i < Math.floor(this);i++) {
funct(i);
}
}
return this;
}
(5).times(function(i){
string += i+" ";
});
// string now equals "0 1 2 3 4 "
var x = 1000;
x.times(function(i){
document.body.innerHTML += '<p>paragraph #'+i+'</p>';
});
// adds 1000 parapraphs to the document
times
的实现不够高效:每次都会调用 Math.floor
而不是只调用一次。 - dolmenJavaScript中的闭包(类似于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);
function testFunc(){ var str='当超时事件发生时,这将超出范围'; setTimeout(function(){ return function(input){ alert(input); }(str); }, 5000); return; }
- acatalept您还可以使用原型链 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文章修改而来的代码。
你可以根据异常的类型捕获它们。引自Mozilla 开发者网络(MDN):
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
}
注意:条件捕获子句是Netscape(因此也是Mozilla/Firefox)的扩展,不是ECMAScript规范的一部分,因此除了特定的浏览器之外,不能依赖它。
光是凭记忆而言...
函数
arguments.callee 指向包含 "arguments" 变量的函数,因此它可以用于递归匿名函数:
var recurse = function() {
if (condition) arguments.callee(); //calls recurse() again
}
如果你想做类似于这样的事情,那么这就非常有用:
//do something to all array items within an array recursively
myArray.forEach(function(item) {
if (item instanceof Array) item.forEach(arguments.callee)
else {/*...*/}
})
对象
关于对象成员的一个有趣的事情是:它们可以使用任何字符串作为名称:
//these are normal object members
var obj = {
a : function() {},
b : function() {}
}
//but we can do this too
var rules = {
".layout .widget" : function(element) {},
"a[href]" : function(element) {}
}
/*
this snippet searches the page for elements that
match the CSS selectors and applies the respective function to them:
*/
for (var item in rules) {
var elements = document.querySelectorAll(rules[item]);
for (var e, i = 0; e = elements[i++];) rules[item](e);
}
字符串
String.split可以接受正则表达式作为参数:
"hello world with spaces".split(/\s+/g);
//returns an array: ["hello", "world", "with", "spaces"]
String.replace可以将正则表达式作为搜索参数并将函数作为替换参数:
var i = 1;
"foo bar baz ".replace(/\s+/g, function() {return i++});
//returns "foo1bar2baz3"
arguments.callee
在 ECMAScript 5 中已被弃用,将抛出异常。 - Hello71大多数情况下,你可以使用对象代替 switch 语句。
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)]();
}
这种写法比使用switch或对象更费力(或阅读),但它保留了使用对象而不是switch的好处,这些好处在下面的注释部分有详细解释。这种写法还使将其发展成一个合适的“类”更加直接。
更新2:随着ES.next提出的语法扩展,这将变得更加简单:
let getInnerText = o -> ({
string: o -> o,
array: o -> o.map(getInnerText).join(""),
object: o -> getInnerText(o["childNodes"])
}[ typeis o ] || (->null) )(o);
var arr = []; typeof arr; // object
- keeganwatkinstypeof
只是我用作临时示例的东西,与主要观点相比较边缘化。我将编辑其他示例以匹配。 - Breton在迭代一个对象的属性时,请务必使用 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());