関数の概要
関数はどんな言語でも核となる概念です。関数は任意の数のステートメントをカプセル化することができ、いつでもどこでも呼び出して実行することができます。javascriptでは、関数はオブジェクトであり、プログラムは関数を自由に操作することができます。関数は他の関数の中に入れ子にすることができ、定義されたスコープ内の変数にアクセスすることができます。
関数の宣言
JavaScriptには関数を宣言する3つの方法があります。
関数の宣言文
functionコマンドで宣言されたコードブロックは関数です。functionコマンドの後には関数名が続き、その関数に渡される引数が括弧で括られます。関数の本体は中括弧の中に置かれます。
function hello(a){
console.log(a);
}
上記のコードでは、hello関数に名前を付け、後でhello()という形を使って、適切なコードを呼び出すことができます。これを関数の宣言と呼びます。
関数式
functionコマンドで関数を宣言するだけでなく、変数に代入する書き方もあります。
var hello = function(a){
console.log(a)
}
この書き方では、無名関数を変数に代入します。このとき、無名関数は関数式とも呼ばれます。なぜなら、代入文の等号の右側に置けるのは式だけだからです。
関数式を使って関数を宣言する場合、関数コマンドの後に関数名は続きません。関数名が付加されている場合、それは関数本体の内部でのみ有効であり、外部では有効ではありません。
var hello = function x(a){
console.log(typeof x);
}
console.log(x);// ReferenceError: x is not defined
hello(); //function
個人的には、名前付き関数式の場合、関数名は関数オブジェクトの形式パラメータに相当し、関数の内部でのみ使用でき、変数名は関数オブジェクトの実パラメータに相当し、関数の内部でも外部でも使用できると理解しています。
var test = function fn(){
return fn === test;
}
console.log(test());//true
console.log(test === fn);//ReferenceError: fn is not defined
Functionコンストラクタ
var add = new Function(
'x',
'y',
'return x + y'
);
//
function add(x,y){
return x + y;
}
上記のコードでは、Functionコンストラクタは3つのパラメータを受け取りますが、最後のパラメータはadd関数の "関数本体 "であり、他のパラメータはadd関数のパラメータです。
最後の引数のみが関数本体として扱われ、引数が1つの場合はその引数が関数本体となります。
var foo = new Function(
'return "hello world"'
);
//
function foo() {
return 'hello world';
}
Functionコンストラクタは、newコマンドを使わなくても全く同じ結果を返すことができます。
全体として、このような関数の宣言方法は直感的でないため、ほとんど誰も使っていません。
関数の返り値
関数内のreturn文は、関数呼び出しの戻り値を返すために使用されます。return文は関数本体内にのみ記述することができ、そうでない場合は構文エラーとなります。
return 1;//SyntaxError: Illegal return statement
return文がない場合、関数呼び出しは関数本体内の各文を関数の終わりまで順番に実行し、最後に呼び出し元のプログラムに戻ります。この場合、呼び出し側の式の結果は未定義です。
var test = function fn(){}
console.log(test());//undefined
関数は return 文に達すると終了します。
var test = function fn(){
return 2;
};
console.log(test());//2
注意:return文以降の関数内のすべての文が実行されないわけではありませんが、finally文は例外で、return文がfinally句の実行を妨げることはありません。
function testFinally(){
try{
return 2;
}catch(error){
return 1;
}finally{
return 0;
}
}
try{
//一般的に言って、ここにあるコードは最初から最後まで問題なく動くだろう。
//しかし、throw文によって直接、あるいはメソッドを呼び出すことによって間接的に、例外がスローされることもある
}catch(e){
//ここでのコードは、try文ブロックが例外をスローした場合にのみ実行される。
//ここでは、ローカル変数eを通して投げられたErrorオブジェクトやその他の値への参照を得ることができる。
//ここでのコードブロックは、何らかの理由で例外を処理するか、無視するか、あるいはthrow文で例外を再スローすることができる。
}finally{
//finally内のロジックは、try文が例外をスローするかどうかに関係なく常に実行され、try文ブロックを終了する方法がある:
//ステートメントブロックの最後のステートメントを実行した後、通常の終了を行う。
//break文、continue文、return文による終了
//例外がスローされ、catch句で例外がキャッチされる
//例外がスローされ、例外はキャッチされず、上方への伝播が続く
}
関数のreturn文は1つだけではありません。
function compare(x,y){
if(x > y){
return x - y;
}else{
return y - x;
}
}
return文は式なしで単独で使用でき、呼び出し元のプログラムには未定義を返します。
var test = function fn(){
return;
}
console.log(test()); //undefined;
return文は関数の最後の文としてよく登場します。returnが実行されると、関数は残りのステートメントを実行せずに直ちに戻ります。
//をポップアップさせない。
var test = function fn(){
return;
alert(1);
};
console.log(test());//undefined
関数が new 接頭辞付きで呼び出され、返り値がオブジェクトでない場合、あるいは返り値がない場合は、this が返されます。
function fn(){
this.a = 2;
return 1;
}
var test = new fn();
console.log(test);//{a:2}
console.log(test.constructor);//fn(){this.a = 2;return 1;}
戻り値がオブジェクトの場合、そのオブジェクトを返します。
関数のコール
関数は、呼び出されたときにのみ実行されます。呼び出し演算子は、関数値を生成する式に続く括弧の組で、括弧の中には0個以上のカンマ区切りの式を入れることができます。各式はパラメータ値を生成し、各パラメータ値には、関数が宣言されたときに定義された正式なパラメータ名が割り当てられます。
javascriptには、関数呼び出しモード、メソッド呼び出しモード、コンストラクタ呼び出しモード、間接呼び出しモードの4つの呼び出しモードがあります。
1関数の呼び出しパターン
関数がオブジェクトの属性でない場合は、関数として呼び出されます。通常の関数呼び出しの場合、関数の戻り値は呼び出し元の式の値です。
function add(x,y){
return x+y;
}
var sum = add(3,4);
console.log(sum)//7
関数呼び出しモードを使って関数を呼び出す場合、非厳密モードでは thisはグローバル・オブジェクトにバインドされ、厳密モードでは thisは未定義になります。
function add(x,y){
console.log(this);//window
}
add();
function add(x,y){
'use strict';
console.log(this);//undefined
}
add();//window
リライト
関数呼び出しパターンの thisはグローバル・オブジェクトにバインドされているため、グローバル・プロパティはオーバーライドされます。
var a = 0;
function fn(){
this.a = 1;
}
fn();
console.log(this,this.a,a);//window 1 1
そのため、グローバル・プロパティのオーバーライドに関連する問題を避けるために、以下の点に注意してください。
2メソッドの呼び出しパターン
関数がオブジェクトのプロパティに格納されている場合、それはメソッドと呼ばれます。メソッドが呼び出されるとき、this はオブジェクトにバインドされます。呼び出し式にプロパティを取り出すアクションが含まれている場合、それはメソッドとして呼び出されます。
var o = {
m:function(){
console.log(111);
}
}
p.m();
メソッドは thisを使って、そのメソッドが属するオブジェクトにアクセスすることができます。this "のオブジェクトへのバインディングは呼び出し時に行われます。this "を使って所属するオブジェクトのコンテキストを取得するメソッドは、パブリックメソッドと呼ばれます。
var p = {
a:1,
m:function(){
console.log(111);
},
n:function(){
this.a = 2;
}
}
console.log(p.m()); //1
o.n();
console.log(o.m().a);//2
コンストラクタ呼び出しパターン
関数やメソッドの呼び出しにキーワード new がある場合、それはコンストラクタ呼び出しになります。
function fn(){
this.a = 1;
};
var obj = new fn();
console.log(obj.a);//1
コンストラクタ呼び出しに括弧内の実パラメータ・リストが含まれている場合、まずこれらの実パラメータ式を計算し、それを
function fn(x){
this.a = x;
};
var obj = new fn(2);
console.log(obj.a);//2
javascriptのコンストラクタ呼び出しの構文では、コンストラクタに正式なパラメータがない場合、実パラメータのリストと括弧を省略することができます。正式なパラメータを持たないコンストラクタの呼び出しでは、括弧を省略することができます。
var o = new Object();
//
var o = new Object;
コンストラクタは通常 return キーワードを適用しません。通常は新しいオブジェクトを初期化し、コンストラクタの関数本体が実行を終了したときに明示的に返します。この場合、コンストラクタ呼び出し式の結果は、この新しいオブジェクトの値になります。
function fn(){
this.a = 2;
}
var test = new fn();
console.log(test);//{a:2}
コンストラクタが return 文を使用しているが戻り値を指定していない場合、または生の値を返す場合、戻り値は無視され、呼び出しの結果として新しいオブジェクトが使用されます。
function fn(){
this.a = 2;
return;
}
var test = new fn();
console.log(test);//{a:2}
コンストラクタがreturnステートメントを使用して明示的にオブジェクトを返す場合、呼び出し式の値はオブジェクトになります。
var obj = {a:1};
function fn(){
this.a = 2;
return obj;
}
var test = new fn();
console.log(test);//{a:1}
4間接コールモード
call()メソッドやapply()メソッドを使用すると、関数を間接的に呼び出すことができます。
どちらのメソッドでも、呼び出しに必要な this 値を明示的に指定することができます。つまり、どの関数でも、そのオブジェクトのメソッドでなくても、そのオブジェクトのメソッドとして呼び出すことができます。call() では関数の実パラメータとして自身の実パラメータリストを使用しますが、 apply() ではパラメータを配列として渡す必要があります。
var obj = {};
function sum(x,y){
return x+y;
}
console.log(sum.call(obj,1,2));//3
console.log(sum.apply(obj,[1,2]));//3
call() メソッドと apply() メソッドについては、後の章の関数のプロパティとメソッドで取り上げます。
関数の引数
javascriptの関数の引数は、他のほとんどの言語の関数の引数とは異なります。関数はいくつの引数を渡そうが、どのようなデータ型を渡そうが、あるいは引数をまったく渡さなくとも気にしません。
arguments
javascriptの関数定義では、関数の形式パラメータの型は指定されず、関数呼び出しでは、渡された実パラメータの値の型チェックは行われません。実際、javascriptの関数呼び出しでは、渡された形式パラメータの数さえチェックされません。
function add(x){
return x+1;
};
console.log(add(1));//2
console.log(add('1'));//'11'
console.log(add());//NaN
console.log(add(1,2));//2
同じ名前の正式なパラメータ
非制限モードでは、同じ名前の正式パラメータが関数内に出現し、最後に出現したその名前の正式パラメータにのみアクセスできます。
function add(x,x,x){
return x;
}
console.log(add(1,2,3));//3
厳密モードでは、同じ名前の正式パラメータがあると構文エラーが発生します。
function add(x,x,x){
'use strict';
return x;
}
console.log(add(1,2,3));//SyntaxError: Duplicate parameter name not allowed in this context
引数の数
実パラメータが関数宣言で指定された形式パラメータの数より少ない場合、残りの形式パラメータは未定義値に設定されます。
function add(x,y){
console.log(x,y);//1 undefined
}
add(1);
実パラメーターが形式パラメーターより多い場合、残りの実パラメーターを直接取得する方法はありません。
javascriptの引数は、内部的には配列として表現されます。どの引数が配列の中にあっても、関数が受け取るのは常にこの配列です。この配列は、関数内から arguments オブジェクトを介してアクセスすることができます。このオブジェクトは Array のインスタンスではありませんが、配列に似たオブジェクトであり、その要素には角括弧構文を使ってアクセスすることができます。
function add(x){
console.log(arguments[0],arguments[1],arguments[2])//1 2 3
return x+1;
}
add(1,2,3);
引数オブジェクトのlength属性は実パラメータの数を表し、関数のlength属性は形式パラメータの数を表します。
function add(x,y){
console.log(arguments.length)//3
return x+1;
}
add(1,2,3);
console.log(add.length);//2
正式な参考文献は便利ですが、必須ではありません。
function add(){
return arguments[0] + arguments[1];
}
console.log(add(1,2));//3
関数のオーバーロード
javascriptの関数は、伝統的な意味でのオーバーロードはできません。他の言語では、2つの定義のシグネチャが異なる限り、1つの関数に対して2つの定義を書くことが可能です。
javascriptの関数は、引数が0個以上の値を含む配列で表されるため、シグネチャを持ちません。関数のシグネチャがなければ、真のオーバーロードはできません。
//後の宣言が前の宣言を上書きする
function addSomeNumber(num){
return num + 100;
}
function addSomeNumber(num){
return num + 200;
}
var result = addSomeNumber(100);//300
メソッドのオーバーロードは、入力される関数の引数の型と数を調べ、異なる反応をすることによってのみ模倣することができます。
function doAdd(){
if(arguments.length == 1){
alert(arguments[0] + 10);
}else if(arguments.length == 2){
alert(arguments[0] + arguments[1]);
}
}
doAdd(10);//20
doAdd(30,20);//50
パラメータの受け渡し
javascriptの関数の引数はすべて値で渡されます。つまり、関数の外から関数内のパラメータに値をコピーすることは、ある変数から別の変数に値をコピーすることと同じです。
1 基本タイプの値
基本型の値をパラメータに渡す場合、渡される値はローカル変数にコピーされます。
function addTen(num){
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
console.log(count);//20,変更しない
console.log(result);//30
2 リファレンス・タイプの値
参照型の値をパラメータに渡す場合、メモリ上のこの値のアドレスはローカル変数にコピーされるため、このローカル変数の変更は関数の外部に反映されます。
function setName(obj){
//obj 関数の内部にはローカル変数がある
obj.name = 'test';
}
var person = new Object();
setName(person);
console.log(person.name);//'test'
参照型の形式パラメータが関数内でオーバーライドされると、その変数はローカル・オブジェ クトを参照します。このローカル・オブジェクトは、関数が実行されると同時に破棄されます。
function setName(obj){
obj.name = 'test';
console.log(person.name);//'test'
//関数は内部的に参照型の正式なパラメータを書き換え、このobj変数はローカルオブジェクトとなる。関数は実行され、すぐに破棄される。つまり、人.nameやグローバルオブジェクトの属性値を分割することができる。
obj = new Object();
obj.name = 'white';
console.log(person.name);//'test'
}
var person = new Object();
setName(person);
関数のプロパティとメソッド
関数はjavascriptの特別なオブジェクトで、通常のオブジェクトがプロパティやメソッドを持つように、プロパティやメソッドを持つことができます。Function()コンストラクタを使って新しい関数オブジェクトを作成することもできます。
属性
length
前のセクションで述べたように、引数オブジェクトのlength属性は実パラメータの数を示し、パラメータのlength属性は形式パラメータの数を示します。
function add(x,y){
console.log(argument.length);//3
console.log(add.length);//2
}
add(1,2,3);
name
関数は非標準のname属性を定義しており、この属性を通して与えられた関数に指定された名前にアクセスすることができます。この属性の値は、常にfunctionキーワードに続く識別子と等しくなります。
function fn(){};
console.log(fn.name);//'fn'
var fn = function(){};
console.log(fn.name);//'fn'
var fn = function abc(){};
console.log(fn.name);//'abc'
prototype
すべての関数には、プロトタイプ・オブジェクトと呼ばれるオブジェクトへの参照を指すプロトタイプ属性があります。各関数には異なるプロトタイプ・オブジェクトが含まれます。コンストラクタとして関数を使用する場合、新しく生成されるオブジェクトはプロトタイプオブジェクトのプロパティを継承します。
function fn(){};
var obj = new fn;
fn.prototype.a = 1;
console.log(obj.a);//1
プロトタイプ・オブジェクトは、後で取り上げるオブジェクト指向プログラミング・コースでも非常に重要なレッスンです。
メソッドを使用します。
apply()と call()
すべての関数には、apply() メソッドと call() メソッドの 2 つの継承されないメソッドがあります。このふたつのメソッドの目的は、特定のスコープで関数をコールすることです。
オブジェクト o のメソッドで関数 f() を呼び出すには、call() と apply() を使用します。
f.call(o);
f.apply(o);
oにmメソッドがないと仮定すれば、これは等価です:
o.m = f;//fをoとして格納する一時的なメソッド
o.m();//引数を渡さずに呼び出す
delete o.m; //一時的なメソッドを削除する
例
window.color = "red";
var o = {color: "blue"};
function sayColor(){
console.log(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
//sayColor.call(o) :
o.sayColor = sayColor;
o.sayColor(); //blue
delete o.sayColor;
apply()メソッドは、関数を実行するスコープと引数の配列の2つの引数を取ります。第二引数には Array のインスタンスか引数オブジェクトを指定します。
function sum(num1, num2){
return num1 + num2;
}
//実行中の関数のスコープはグローバルなので、thisはウィンドウオブジェクトを表す。
function callSum1(num1, num2){
return sum.apply(this, arguments);
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]);
}
console.log(callSum1(10,10));//20
console.log(callSum2(10,10));//20
call() メソッドの動作は apply() メソッドと同じで、唯一の違いは引数の受け取り方です。call() メソッドの場合、最初のパラメータである this の値は変わりません。言い換えると、call() メソッドを使用する場合は、関数に渡される引数を 1 つずつリストする必要があります。
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
console.log(callSum(10,10)); //20
apply()とcall()のどちらを使用するかは、関数に引数を渡す際にどちらの方法を取るのが最も便利かに完全に依存します。引数オブジェクトを直接渡すことを意図している場合や、 含まれる関数が最初に配列も受け取る場合は、apply() を使用したほうが便利なのは間違いありません。
非厳密モードでは、関数の call() メソッドや apply() メソッドを使用する際に、NULL 値や undefined 値がグローバル・オブジェクトに変換されます。厳格モードでは、関数の this 値は常に指定された値になります。
var color = 'red';
function displayColor(){
console.log(this.color);
}
displayColor.call(null);//red
var color = 'red';
function displayColor(){
'use strict';
console.log(this.color);
}
displayColor.call(null);//TypeError: Cannot read property 'color' of null
アプリケーション
1 配列の最大要素を求めなさい
javascriptには配列の最大要素を求める関数がありません。applyメソッドとMath.maxメソッドを組み合わせることで、配列の最大要素を返すことができます。
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a);//15
2 クラス数の配列を実データに変換
Array.prototype.slice.apply({0:1,length:1});//[1]
3配列の値を別の配列にプッシュ
var a = [];
Array.prototype.push.apply(a,[1,2,3]);
console.log(a);//[1,2,3]
Array.prototype.push.apply(a,[2,3,4]);
console.log(a);//[1,2,3,2,3,4]
4 コールバック関数のオブジェクトのバインド
applyメソッドは関数が実行されるオブジェクトをバインドするだけでなく、関数を即座に実行するため、バインド文は関数本体内に記述する必要がありました。
<body>
<button id="btn"> </button>
</body>
<script src="https://..com/jquery/..1/..js"></script>
<script type="text/javascript">
var o = {};
o.f = function() {
console.log(this === o);
}
$('#btn').on('click',function(){
o.f.apply(o);
});
</script>
bind()
bind() は es5 の新しいメソッドで、このメソッドの主な目的は関数をオブジェクトにバインドすることです。
関数 f() で bind() メソッドを呼び出し、引数としてオブジェクト o を渡すと、 メソッドは新しい関数を返します。新しい関数を関数呼び出しとして呼び出すと、元の関数 f() があたかも o のメソッドであるかのように呼び出されます。
function f(){
return this.x + y;//これがバインドされる関数である
}
var o = {x : 1};//バインドされるオブジェクト
var g = f.bind(o);//を呼び出すことで、関数の引数を分割することができる。.f(x);
g(2);//3;
bind()メソッドは、単に関数をオブジェクトにバインドするだけではありません。bind()に渡される実パラメータは、最初の実パラメータに加えて、thisにもバインドされます。コリエリゼーション
var sum = function(x,y){
return x+y;
}
var succ = sum.bind(null,1);
succ(2); //3,x1にバインドし、2を実パラメータyとして渡す
function f(y,z){
return this.x + y + z;
}
var g = f.bind({x:1},2);
g(3); //6,this.xbind()を1に、y bind()を2に、z bind()を3にバインドする。
bind() メソッドを使用してコリアライズを実装すると、 関数の引数を分割することができます。
function getConfig(colors,size,otherOptions){
console.log(colors,size,otherOptions);
}
var defaultConfig = getConfig.bind(null,'#c00','1024*768');
defaultConfig('123');//'#c00 1024*768 123'
defaultConfig('456');//'#c00 1024*768 456'




