JavaScriptのどの「隠された機能」をプログラマは知っておくべきだと思いますか?
次の質問への回答の質の高さを見て、JavaScriptに質問するときがきたと思いました。
JavaScriptはおそらく現在最も重要なクライアント側言語です(Googleに聞いてください)が、ほとんどのWeb開発者がJavaScriptが実際にどれほど強力であるかを理解していないのは驚くべきことです。
JavaScriptのどの「隠された機能」をプログラマは知っておくべきだと思いますか?
次の質問への回答の質の高さを見て、JavaScriptに質問するときがきたと思いました。
JavaScriptはおそらく現在最も重要なクライアント側言語です(Googleに聞いてください)が、ほとんどのWeb開発者がJavaScriptが実際にどれほど強力であるかを理解していないのは驚くべきことです。
回答:
関数のパラメーターを定義する必要はありません。関数のarguments
配列のようなオブジェクトを使用できます。
function sum() {
var retval = 0;
for (var i = 0, len = arguments.length; i < len; ++i) {
retval += arguments[i];
}
return retval;
}
sum(1, 2, 3) // returns 6
arguments
オブジェクトを参照するだけで関数の呼び出しが非常に遅くなるSafari、Firefox、およびChromeナイトリーです。if(false)引数; パフォーマンスが低下します。
arguments.callee
廃止されることに注意してください。
Douglas Crockfordの優れた著書であるJavaScript:The Good Partsのほとんどを引用できます 。
しかし、私は、あなたのためだけのものを取る常に使用します===
と、!==
代わりに==
と!=
alert('' == '0'); //false
alert(0 == ''); // true
alert(0 =='0'); // true
==
推移的ではありません。使用===
すると、期待どおりにこれらのステートメントすべてにfalseが与えられます。
==
は'\n\t\r ' == 0
=> true
...:D
関数はJavaScriptの第一級市民です。
var passFunAndApply = function (fn,x,y,z) { return fn(x,y,z); };
var sum = function(x,y,z) {
return x+y+z;
};
alert( passFunAndApply(sum,3,4,5) ); // 12
関数型プログラミング手法を使用して、エレガントなJavaScriptを記述できます。
特に、関数はパラメーターとして渡すことができます。たとえば、Array.filter()はコールバックを受け入れます。
[1, 2, -1].filter(function(element, index, array) { return element > 0 });
// -> [1,2]
特定の関数のスコープ内にのみ存在する「プライベート」関数を宣言することもできます。
function PrintName() {
var privateFunction = function() { return "Steve"; };
return privateFunction();
}
new Function()
と同じくらい悪eval
です。使ってはいけません。
in演算子を使用して、オブジェクトにキーが存在するかどうかを確認できます。
var x = 1;
var y = 3;
var list = {0:0, 1:0, 2:0};
x in list; //true
y in list; //false
1 in list; //true
y in {3:0, 4:0, 5:0}; //true
オブジェクトリテラルが見苦しい場合は、パラメーターなしの関数ヒントと組み合わせることができます。
function list()
{ var x = {};
for(var i=0; i < arguments.length; ++i) x[arguments[i]] = 0;
return x
}
5 in list(1,2,3,4,5) //true
変数へのデフォルト値の割り当て
||
代入式で論理演算子または演算子を使用して、デフォルト値を提供できます。
var a = b || c;
a
変数は、値の取得するc
場合にのみb
ですfalsyを(ある場合はnull
、false
、undefined
、0
、empty string
、またはNaN
)、それ以外a
の値を取得しますb
。
これは、関数が提供されていない場合に引数にデフォルト値を与えたいときに便利です。
function example(arg1) {
arg1 || (arg1 = 'default value');
}
イベントハンドラーでのIEフォールバックの例:
function onClick(e) {
e || (e = window.event);
}
次の言語機能は長い間使用されてきましたが、すべてのJavaScript実装でサポートされていますが、ECMAScript 5th Editionまでは仕様の一部ではありませんでした。
debugger
声明
説明:§12.15デバッガーステートメント
このステートメントを使用すると、プログラムでブレークポイントをプログラムに配置することができます。
// ...
debugger;
// ...
デバッガが存在するかアクティブな場合、その行でデバッガがすぐに中断します。
それ以外の場合、デバッガーが存在しないかアクティブでない場合、このステートメントは目に見える影響を及ぼしません。
複数行の文字列リテラル
記述:§7.8.4文字列リテラル
var str = "This is a \
really, really \
long line!";
の隣の文字は行末記号で\
なければならないため、注意が必要\
です。たとえば、の後にスペースがある場合、コードはまったく同じに見えますが、が発生しますSyntaxError
。
JavaScriptにはブロックスコープがありません(ただし、クロージャーがあるので、それを呼び出しましょうか?)。
var x = 1;
{
var x = 2;
}
alert(x); // outputs 2
オブジェクトのプロパティにアクセスする[]
代わりに.
これにより、変数に一致するプロパティを検索できます。
obj = {a:"test"};
var propname = "a";
var b = obj[propname]; // "test"
これを使用して、名前が有効な識別子ではないオブジェクトプロパティを取得/設定することもできます。
obj["class"] = "test"; // class is a reserved word; obj.class would be illegal.
obj["two words"] = "test2"; // using dot operator not possible with the space.
一部の人々はこれを知らず、このようにeval()を使用することになります。
var propname = "a";
var a = eval("obj." + propname);
これは読みにくく、エラーを見つけるのが難しく(jslintを使用できません)、実行が遅く、XSSの悪用につながる可能性があります。
foo.bar
とにかく、仕様によると、のように動作しfoo["bar"]
ます。また、すべてが文字列プロパティであることにも注意してください。配列アクセスを行う場合でもarray[4]
、4は文字列に変換されます(これも、少なくともECMAScript v3仕様に従って)
特定のトピックについて適切なJavaScript参照を探している場合は、クエリに「mdc」キーワードを含めると、最初の結果はMozilla Developer Centerから取得されます。私はオフラインでの参照や本を持ち歩きません。私は常に「mdc」キーワードトリックを使用して、探しているものに直接アクセスします。例えば:
Google:javascript array sort mdc
(ほとんどの場合、「javascript」を省略できます)
更新: Mozilla Developer CenterはMozilla Developer Networkに名前が変更されました。「mdc」キーワードトリックは引き続き機能しますが、すぐに代わりに「mdn」の使用を開始しなければならない場合があります。
たぶん少し明白な人もいます...
Firebugをインストールし、console.log( "hello")を使用します。ランダムなalert();を使用するよりもはるかに優れています。これは、数年前によく行ったことです。
プライベートメソッド
オブジェクトはプライベートメソッドを持つことができます。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
// A private method only visible from within this constructor
function calcFullName() {
return firstName + " " + lastName;
}
// A public method available to everyone
this.sayHello = function () {
alert(calcFullName());
}
}
//Usage:
var person1 = new Person("Bob", "Loblaw");
person1.sayHello();
// This fails since the method is not visible from this scope
alert(person1.calcFullName());
Crockfordの「Javascript:The Good Parts」でも言及されています。
parseInt()
危険です。適切なベースを通知せずに文字列を渡すと、予期しない数値が返される場合があります。たとえばparseInt('010')
、10ではなく8を返します。baseをparseIntに渡すと、正しく動作します。
parseInt('010') // returns 8! (in FF3)
parseInt('010', 10); // returns 10 because we've informed it which base to work with.
Math.floor
ないのNumber
ですか?10 === Math.floor("010"); 10 === Number("010");
floats:42 === Math.floor("42.69"); 42.69 === Number("42.69");
parseInt
機能は、それほど無害ではない何かを簡単に実行させることができます。
__parseInt = parseInt; parseInt = function (str, base) { if (!base) throw new Error(69, "All your base belong to us"); return __parseInt(str, base); }
関数はオブジェクトなので、プロパティを持つことができます。
fn = function(x){ // ... } fn.foo = 1; fn.next = function(y){ // }
自己実行関数といえばいいでしょう。
(function() { alert("hi there");})();
JavaScriptにはブロックスコープがないため、ローカル変数を定義する場合は、自己実行関数を使用できます。
(function() {
var myvar = 2;
alert(myvar);
})();
ここでmyvar
は、isはグローバルスコープを妨害または汚染せず、関数が終了すると消えます。
.js
ファイルを匿名の自己実行関数で囲み、その中でグローバルにアクセスできるようにしたいものをwindow
オブジェクトに添付します。グローバルな名前空間の汚染を防ぎます。
関数が予期するパラメーターの数を知る
function add_nums(num1, num2, num3 ){
return num1 + num2 + num3;
}
add_nums.length // 3 is the number of parameters expected.
関数が受け取るパラメーターの数を知る
function add_many_nums(){
return arguments.length;
}
add_many_nums(2,1,122,12,21,89); //returns 6
function.length
。
ここにいくつか興味深いものがあります:
NaN
何でも(もNaN
)と比較すると==
、<
とを含めて常にfalse になり>
ます。NaN
Not a Numberの略ですが、型を要求すると、実際には数値が返されます。Array.sort
コンパレーター関数を使用でき、クイックソートのようなドライバーによって呼び出されます(実装によって異なります)。$0
、$1
、$2
正規表現のメンバー。null
他のものとは異なります。オブジェクトでも、ブール値でも、数値でも、文字列でも、でもありませんundefined
。これは「代替」のようなものundefined
です。(注:typeof null == "object"
)this
は、それ以外の場合は名前を付けることができない[Global]オブジェクトを生成します。var
自動宣言だけに頼るのではなく、を使用して変数を宣言すると、ランタイムはその変数へのアクセスを最適化する本当のチャンスを得ることができますwith
コンストラクトは、このようなoptimzationsを破壊しますbreak
。ループにラベルを付け、のターゲットとして使用できますcontinue
。undefined
です。(実装による)if (new Boolean(false)) {...}
{...}
ブロック を実行します【良いコメントに応えて少し更新しました】コメントをご覧ください]
typeof null
「オブジェクト」を返します。
undefined
。しかし、それは存在しないインデックスを探すときに取得するデフォルト値にすぎません。a [2000]をチェックした場合、それもになりますがundefined
、メモリがまだ割り当てられているわけではありません。IE8では、JScriptエンジンがそのときどのように感じたかに応じて、一部の配列は密であり、一部は疎です。もっとここで読む:blogs.msdn.com/jscript/archive/2008/04/08/...
私はパーティーに遅れていることは知っていますが、+
オペレーターの有用性が「何かを数値に変換する」以上に言及されていないことを信じられません。多分それはそれがどれほどうまく機能を隠しているのですか?
// Quick hex to dec conversion:
+"0xFF"; // -> 255
// Get a timestamp for now, the equivalent of `new Date().getTime()`:
+new Date();
// Safer parsing than parseFloat()/parseInt()
parseInt("1,000"); // -> 1, not 1000
+"1,000"; // -> NaN, much better for testing user input
parseInt("010"); // -> 8, because of the octal literal prefix
+"010"; // -> 10, `Number()` doesn't parse octal literals
// A use case for this would be rare, but still useful in cases
// for shortening something like if (someVar === null) someVar = 0;
+null; // -> 0;
// Boolean to integer
+true; // -> 1;
+false; // -> 0;
// Other useful tidbits:
+"1e10"; // -> 10000000000
+"1e-4"; // -> 0.0001
+"-12"; // -> -12
もちろん、Number()
代わりにこれを使用してこれらすべてを行うことができますが、+
演算子はとてもきれいです!
プロトタイプのvalueOf()
メソッドをオーバーライドして、オブジェクトの数値の戻り値を定義することもできます。そのオブジェクトで実行される数値変換はNaN
、ではなく、valueOf()
メソッドの戻り値になります。
var rnd = {
"valueOf": function () { return Math.floor(Math.random()*1000); }
};
+rnd; // -> 442;
+rnd; // -> 727;
+rnd; // -> 718;
0xFF
など必要ありません+"0xFF"
。
0xFF
、1
代わりに書くことができるのと同じように、単に書くことができます+true
。必要に応じて+("0x"+somevar)
、の代替として使用できることをお勧めしますparseInt(somevar, 16)
。
" JavaScriptの拡張メソッド "は、prototypeプロパティを介して。
Array.prototype.contains = function(value) {
for (var i = 0; i < this.length; i++) {
if (this[i] == value) return true;
}
return false;
}
これにより、contains
すべてのArray
オブジェクトにメソッドが追加されます。この構文を使用してこのメソッドを呼び出すことができます
var stringArray = ["foo", "bar", "foobar"];
stringArray.contains("foobar");
オブジェクトからプロパティを適切に削除するには、単にundefinedに設定するのではなく、プロパティを削除する必要があります。
var obj = { prop1: 42, prop2: 43 };
obj.prop2 = undefined;
for (var key in obj) {
...
プロパティprop2は引き続き反復の一部になります。prop2を完全に削除したい場合は、代わりに次のようにする必要があります。
delete obj.prop2;
プロパティprop2は、プロパティを反復処理しているときに表示されなくなります。
with
。
それはめったに使用されず、率直に言って、ほとんど使用されません...しかし、限られた状況では、それはその用途があります。
たとえば、オブジェクトリテラルは、新しいオブジェクトのプロパティをすばやく設定するのに非常に便利です。しかし、既存のオブジェクトのプロパティの半分を変更する必要がある場合はどうでしょうか。
var user =
{
fname: 'Rocket',
mname: 'Aloysus',
lname: 'Squirrel',
city: 'Fresno',
state: 'California'
};
// ...
with (user)
{
mname = 'J';
city = 'Frostbite Falls';
state = 'Minnesota';
}
コンテキストとして使用されるオブジェクトがない場合:アラン・ストームは、これは多少危険とすることができることを指摘有しに割り当てられた特性のいずれかを、それがおそらくグローバル変数を作成または上書きし、外側のスコープで解決されるであろう。これは、デフォルト値または空の値を持つプロパティが未定義のままになっているオブジェクトを操作するコードの記述に慣れている場合は特に危険です。
var user =
{
fname: "John",
// mname definition skipped - no middle name
lname: "Doe"
};
with (user)
{
mname = "Q"; // creates / modifies global variable "mname"
}
したがって、そのwith
ような割り当てにはステートメントを使用しないことをお勧めします。
メソッド(または関数)は、操作対象として設計されたタイプではないオブジェクトで呼び出すことができます。これは、カスタムオブジェクトでネイティブ(高速)メソッドを呼び出すのに最適です。
var listNodes = document.getElementsByTagName('a');
listNodes.sort(function(a, b){ ... });
このコードlistNodes
は、Array
Array.prototype.sort.apply(listNodes, [function(a, b){ ... }]);
が使用listNodes
するのに十分な配列のようなプロパティ(長さ、[]演算子)を定義しているため、このコードは機能し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
times
は効率的ではありませんMath.floor
。一度だけではなく、毎回呼び出されます。
JavaScriptのクロージャーはどうですか(C#v2.0 +の無名メソッドと同様)。関数または「式」を作成する関数を作成できます。
閉鎖の例:
//Takes a function that filters numbers and calls the function on
//it to build up a list of numbers that satisfy the function.
function filter(filterFunction, numbers)
{
var filteredNumbers = [];
for (var index = 0; index < numbers.length; index++)
{
if (filterFunction(numbers[index]) == true)
{
filteredNumbers.push(numbers[index]);
}
}
return filteredNumbers;
}
//Creates a function (closure) that will remember the value "lowerBound"
//that gets passed in and keep a copy of it.
function buildGreaterThanFunction(lowerBound)
{
return function (numberToCheck) {
return (numberToCheck > lowerBound) ? true : false;
};
}
var numbers = [1, 15, 20, 4, 11, 9, 77, 102, 6];
var greaterThan7 = buildGreaterThanFunction(7);
var greaterThan15 = buildGreaterThanFunction(15);
numbers = filter(greaterThan7, numbers);
alert('Greater Than 7: ' + numbers);
numbers = filter(greaterThan15, numbers);
alert('Greater Than 15: ' + numbers);
また、言及したプロトタイプチェーンspoon16を使用して、クラスを拡張(継承)し、プロパティ/メソッドをオーバーライドすることもできます。
次の例では、Petクラスを作成し、いくつかのプロパティを定義します。Objectから継承した.toString()メソッドもオーバーライドします。
この後、Petを拡張して.toString()メソッドをオーバーライドするDogクラスを作成し、再度その動作(多態性)を変更します。さらに、他のいくつかのプロパティを子クラスに追加します。
この後、継承チェーンをチェックして、DogがまだDog型、Pet型、Object型であることを示します。
// Defines a Pet class constructor
function Pet(name)
{
this.getName = function() { return name; };
this.setName = function(newName) { name = newName; };
}
// Adds the Pet.toString() function for all Pet objects
Pet.prototype.toString = function()
{
return 'This pets name is: ' + this.getName();
};
// end of class Pet
// Define Dog class constructor (Dog : Pet)
function Dog(name, breed)
{
// think Dog : base(name)
Pet.call(this, name);
this.getBreed = function() { return breed; };
}
// this makes Dog.prototype inherit from Pet.prototype
Dog.prototype = new Pet();
// Currently Pet.prototype.constructor
// points to Pet. We want our Dog instances'
// constructor to point to Dog.
Dog.prototype.constructor = Dog;
// Now we override Pet.prototype.toString
Dog.prototype.toString = function()
{
return 'This dogs name is: ' + this.getName() +
', and its breed is: ' + this.getBreed();
};
// end of class Dog
var parrotty = new Pet('Parrotty the Parrot');
var dog = new Dog('Buddy', 'Great Dane');
// test the new toString()
alert(parrotty);
alert(dog);
// Testing instanceof (similar to the `is` operator)
alert('Is dog instance of Dog? ' + (dog instanceof Dog)); //true
alert('Is dog instance of Pet? ' + (dog instanceof Pet)); //true
alert('Is dog instance of Object? ' + (dog instanceof Object)); //true
この質問に対する両方の回答は、Ray DjajadinataによるすばらしいMSDN記事から修正されたコードでした。
タイプによっては、例外をキャッチする場合があります。MDCから引用:
try {
myroutine(); // may throw three exceptions
} catch (e if e instanceof TypeError) {
// statements to handle TypeError exceptions
} catch (e if e instanceof RangeError) {
// statements to handle RangeError exceptions
} catch (e if e instanceof EvalError) {
// statements to handle EvalError exceptions
} catch (e) {
// statements to handle any unspecified exceptions
logMyErrors(e); // pass exception object to error handler
}
注:条件付きcatch句は、Netscape(およびMozilla / Firefox)の拡張であり、ECMAScript仕様の一部ではないため、特定のブラウザ以外では信頼できません。
頭の上から...
関数
arguments.calleeは「arguments」変数をホストする関数を参照するため、無名関数を再帰するために使用できます。
var recurse = function() {
if (condition) arguments.callee(); //calls recurse() again
}
これは、次のようなことをしたい場合に便利です。
//do something to all array items within an array recursively
myArray.forEach(function(item) {
if (item instanceof Array) item.forEach(arguments.callee)
else {/*...*/}
})
オブジェクト
オブジェクトメンバーの興味深い点:名前として任意の文字列を使用できます。
//these are normal object members
var obj = {
a : function() {},
b : function() {}
}
//but we can do this too
var rules = {
".layout .widget" : function(element) {},
"a[href]" : function(element) {}
}
/*
this snippet searches the page for elements that
match the CSS selectors and applies the respective function to them:
*/
for (var item in rules) {
var elements = document.querySelectorAll(rules[item]);
for (var e, i = 0; e = elements[i++];) rules[item](e);
}
文字列
String.splitは、パラメータとして正規表現を使用できます。
"hello world with spaces".split(/\s+/g);
//returns an array: ["hello", "world", "with", "spaces"]
String.replaceは、検索パラメーターとして正規表現を、置換パラメーターとして関数を使用できます。
var i = 1;
"foo bar baz ".replace(/\s+/g, function() {return i++});
//returns "foo1bar2baz3"
arguments.callee
廃止されており、ECMAScriptの5に投げると例外う
ほとんどの場合、スイッチの代わりにオブジェクトを使用できます。
function getInnerText(o){
return o === null? null : {
string: o,
array: o.map(getInnerText).join(""),
object:getInnerText(o["childNodes"])
}[typeis(o)];
}
更新:事前に評価するケースが非効率的であることが心配な場合(なぜ、プログラムの設計の早い段階で効率を心配しているのですか?)、次のようなことができます。
function getInnerText(o){
return o === null? null : {
string: function() { return o;},
array: function() { return o.map(getInnerText).join(""); },
object: function () { return getInnerText(o["childNodes"]; ) }
}[typeis(o)]();
}
これは、スイッチやオブジェクトよりも入力(または読み取り)の負担が大きくなりますが、スイッチの代わりにオブジェクトを使用する利点が保持されます。詳細については、以下のコメントセクションを参照してください。このスタイルはまた、十分に大きくなれば、これを適切な「クラス」にスピンアウトすることをより簡単にします。
update2:ES.nextに提案された構文拡張により、これは
let getInnerText = o -> ({
string: o -> o,
array: o -> o.map(getInnerText).join(""),
object: o -> getInnerText(o["childNodes"])
}[ typeis o ] || (->null) )(o);
var arr = []; typeof arr; // object
オブジェクトのプロパティを反復処理するときは、必ずhasOwnPropertyメソッドを使用してください。
for (p in anObject) {
if (anObject.hasOwnProperty(p)) {
//Do stuff with p here
}
}
これは、anObjectの直接プロパティにのみアクセスし、プロトタイプチェーンの下にあるプロパティを使用しないようにするために行われます。
パブリックインターフェイスを持つプライベート変数
これは、自己呼び出し関数の定義とともに、きちんとした小さなトリックを使用しています。返されるオブジェクト内のすべてはパブリックインターフェイスで使用できますが、その他はすべてプライベートです。
var test = function () {
//private members
var x = 1;
var y = function () {
return x * 2;
};
//public interface
return {
setx : function (newx) {
x = newx;
},
gety : function () {
return y();
}
}
}();
assert(undefined == test.x);
assert(undefined == test.y);
assert(2 == test.gety());
test.setx(5);
assert(10 == test.gety());