【CSS3】ドロップシャドウやグロー(光彩)をCSSで(box-shadow)
これも折角だから改めて調べてみためもめも
ちゃんとAndriod、iPhoneで確認もした!
問題なく表示されたのぜ!(・∀・)
ドロップシャドウ(box-shadow)の基本的な書き方
「box-shadow」は名前の通りボックスに影をつけてくれるプロパティ
省略形が存在するので記述方法にはいくつかパターンがあるけど
まずは基本形から(・∀・)
box-shadow: 3px 3px 5px 0 #777;
box-shadow:横方向のずれ 横方向のずれ ぼかしの大きさ 影の広がり 影の色;
てな感じで指定できる!
- ぼかしの大きさ
- 値が大きい程ぼんやり、値が小さい程くっきり
- 影の広がり
- シャドウ全体の大きさの拡張・縮小
- 「ぼかし部分の内側に作られるpadding」というイメージ
- 負の値を設定することも可能
4つ目の値を省略すると影の広がりが0pxなのと同じ表示に
3つ目、4つ目の値を省略するとぼかしの大きさが0px扱いになり
くっきりした影になりますのぜ
ちなみに数値1つでは動作しません
実際に使用するときはこんな感じで‥
.shadow{ -webkit-box-shadow: 3px 3px 5px 0 #777; /* Safari, Chrome用 */ -moz-box-shadow: 3px 3px 5px 0 #777; /* Firefox用 */ box-shadow: 3px 3px 5px 0 #777; /* CSS3 */ }
これ便利ねー
ちなみに光彩は!
おわかりかもしれませんが
横方向のずれ、横方向のずれを0にすると実現できるのぜ
.shadow{ -webkit-box-shadow: 0 0 5px 0 #777; /* Safari, Chrome用 */ -moz-box-shadow: 0 0 5px 0 #777; /* Firefox用 */ box-shadow: 0 0 5px 0 #777; /* CSS3 */ }
更に内側のシャドウも(inset)
「inset」という値を追加するだけ!
.shadow{ -webkit-box-shadow: inset 3px 3px 5px 0 #777; /* Safari, Chrome用 */ -moz-box-shadow: inset 3px 3px 5px 0 #777; /* Firefox用 */ box-shadow: inset 3px 3px 5px 0 #777; /* CSS3 */ }
きゃーかんたーん!(・∀・)
更にさらに複数のシャドウも
値を「,」で区切ることで複数のシャドウを設定することが可能なのぜ
なにそれ複数ってどういうこと?ってなった方
影の外にさらに影、みたいになります
.shadow{ -webkit-box-shadow: 0 0 4px 0 #F00, 0 0 12px 0 #777, 5px 5px 5px 0 #777; -moz-box-shadow: 0 0 4px 0 #F00, 0 0 12px 0 #777, 5px 5px 5px 0 #777; box-shadow: 0 0 4px 0 #F00, 0 0 12px 0 #777, 5px 5px 5px 0 #777; }
あと外側の影+内側の影なんてのもできる
.shadow{ -webkit-box-shadow: 3px 3px 5px 0 #777, inset 0 0 10px 0 #fff; -moz-box-shadow: 3px 3px 5px 0 #777, inset 0 0 10px 0 #fff; box-shadow: 3px 3px 5px 0 #777, inset 0 0 10px 0 #fff; }
これつかえばボタンとかCSSでなんとかなっちゃうね、すごいね
【Android】【iPhone】viewport
久しぶりにスマホページのコーディング依頼があったので
JavaScriptのお勉強を一旦お休みしてSPページ関連のメモをかくよ
今更だけどちゃんと調べたのでviewport
viewportとは
- ウィンドウのサイズのようなもの
- 初期値は横幅980pxに指定されている
- ちなみにiPhoneの横幅は320px、約3倍の幅が指定されてる
- この初期値が指定されているからPCページをスマホで見ると縮小された状態で表示される
ふむふむ、なるほどですね
viewportの指定方法
metaタグで指定する
例
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
プロパティ | 説明 | 初期値 | 許容範囲 | 備考 |
---|---|---|---|---|
width | デバイスの横幅 | 980px | 200~10,000px | device-width(デバイスの横幅に合わせる)の指定が可能 |
height | デバイスの縦幅 | 横幅とアスペクト比から計算される値 | 200~10,000px | device-height(デバイスの縦幅に合わせる)の指定が可能 |
initial-scale | ページが最初に読み込まれた時の拡大率 | 表示範囲から計算される値 | minimum-scale~maximum-scale | 乗数で指定(例:120%の場合は1.2) |
minimum-scale | 拡大率の下限 | 0.25 | 0~10 | 乗数で指定 |
maximum-scale | 拡大率の上限 | 1.6 | 0~10 | 乗数で指定 |
user-scalable | ユーザーに拡大縮小を許可するか | yes | yes or no | no(拡大縮小不可)にすることでフォーム入力時のスクロールも不可 |
結局どう設定したらいいの?
端末の横幅を最大限に活かすべきなので
特に理由が無ければ大抵の場合は、viewportはdevice-widthに指定
縮小拡大はできないようにしてあるのが普通
あとは臨機応変に
注意点
仕様に関して詳しく検証しているサイトを見つけたので
貼っておくます
http://ipn3g.com/web/study3.html
以下引用
- コンテンツより小さいviewport width/heightを設定すると無視される
- viewportでinitial-scaleを設定せずにwidth/heightを設定すると全画面表示となる
- viewportでinitial-scaleもwidth/heightも設定しないとwidth=980px/height=1091pxとなる
- 表示域とコンテンツのサイズが一致しない場合にinitial-scaleを設定すると、意図しないレイアウトが発生することがある
【JavaScript】高度な関数のテーマ①
さてさて関数もここまでやってきました最後の章
関数を利用したより高度なトピックの紹介とのことで
基本を!っていう方は後でもいいよ!ってなってたんだけれど
なんか周りでクロージャクロージャって
よく聞くからやってみることにするよ
補足:名前付き引数でコードを読みやすくする
名前付き引数とは、
呼び出し時に名前を明示的に指定できる引数のこと
triangle ({ base:5; height:4; })
- 名前付き引数を用いることのメリット
- 引数が多くなっても、コードの意味が分かりやすい
- 省略可能な引数をスマートに表現できる
- 引数の順番を自由に変更できる
名前付き引数を利用すれば先ほどのtriangle関数は
以下のように呼び出すことも可能
triangle ({ height:4; }) //前方の引数だけ省略 triangle ({ height:4; base:5; }) //引数の順番の変更
もちろん呼び出しに際して
明示的に名前を指定しなければならないので
コードが冗長になるというデメリットもあるが
- そもそも引数の数が多い
- 省略可能な引数が多く、省略パターンにもさまざまな組み合わせがある
ようなケースには有効な方法である
その時々の文脈に応じて使い分けるようにすること
名前付き引数の具体的実装方法
function triangle(args) { if (args.base == undefined) { args.base = 1; } if (args.height == undefined) { args.height = 1; } return args.base * args.height / 2; } document.writeln(triangle({base:5, height:4})); //10
名前付き引数と云っても何ら難しいものではなく
引数を匿名オブジェクト(ここでは仮引数args)で
受け取っているだけである
メソッドの中でもオブジェクトのプロパティとして
それぞれの引数にアクセスしている点に注意
呼び出しのタイミングでも引数を「{..}」と記述しているのは
匿名オブジェクトを表しているためである
関数の引数も関数(高階関数)
「JavaScriptの関数はデータ型の一種」そうね何度もやったね
つまり、関数そのものもまた、他の数値型や文字列型などと同様
関数の引数として渡したり
戻り値として返したりすることができるということ
べんりー(・∀・)
そして、そのように
「関数を引数、戻り値として扱う関数」のことを
高層関数と呼ぶ、うむ
//高階関数arrayWalkを定義 function arrayWalk(data, f) { for (var key in data) { f(key, data[key]); } } //配列を処理するためのユーザ定義関数 function showElement(key, value) { document.writeln(key + ':' + value); } var ary = [1, 2, 4, 8, 16]; arrayWalk(ary, showElement);
0:1
1:2
2:4
3:8
4:16
arrayWalk関数は、引数に与えられた配列dataの内容を
指定されたユーザ定義関数fの規則に従って
順番に処理するための高階関数
さてここでちょっとfor..in命令の復習はさんでいいですかw
配列内の要素を順に処理する(for..in命令)
for(仮変数 in 配列/オブジェクト) { ループ内で実行する命令(群) }
- 指定された配列/連想配列やオブジェクト配下の要素/メンバに対して先頭から順番に繰り返し処理を行う
- 仮変数には、配列/連想配列やオブジェクトから取り出された要素のインデックス番号やキー名、メンバ名が格納され、for..inブロックの中で要素値を参照する際に使用することができる
- 仮変数に格納されるのが要素値そのものではないことに注意
- for..inループを利用するのは連想配列、オブジェクトのキーを走査する場合に留め通常配列を走査する場合は原則としてforループを利用するようにする
- for..inループは配列のインデックス番号を取り出すだけなのでコードがあまりシンプルにならない(値そのものではないので、却って誤解を招く)
- 通常配列の内容を取り出す処理はコードによっては正しく動作しない場合がある
おうおう思い出した順番に取り出してくるやつね
よしわかったところで続き
高階関数arrayWalkにおける引数の意味は
- data
- 処理対象の配列
- f
- 配列の各要素を処理するためのユーザ定義関数
となっている
なお、ユーザ定義関数fは引数として
配列のキー名(仮引数key)、値(仮引数value)を受け取り
与えられた配列要素に対して任意の処理を行うものとする
(これは高階関数というよりもarrayWalk関数としての決まりである)
えーっと‥?(・∀・;)
ここでは、ユーザ定義関数showElementは与えられた引数に基づいて
「<キー>:<値>」のような形式で出力する
そのため、arrayWalk関数はそれ全体として
配列内のキー名と値をリスト形式で出力することになる
えーっと‥?えーっと‥?(;∀;)
もちろんユーザ定義関数は自由に差し替えることができる
‥‥(・∀・)
ちょっとここまで理解できてないから
一旦次に行かずストップして考える(・∀・)
//高階関数arrayWalkを定義 function arrayWalk(data, f) { for (var key in data) { f(key, data[key]); } } //配列を処理するためのユーザ定義関数 function showElement(key, value) { document.writeln(key + ':' + value); } var ary = [1, 2, 4, 8, 16]; arrayWalk(ary, showElement);
- arrayWalk関数を実行する
- 引数ary、showElementを渡す
- ってことは「function arrayWalk(ary, showElement) {」みたいな
- ってことは「f(key, data[key]);」⇒「showElement(key, data[key]);」みたいな
- for..in命令で配列内の要素を順に処理する
- そのたびにshowElementも実行される
- 「document.writeln(key + ':' + value);」される
- ここの引数keyは「f(key, data[key]);」のkeyが代入されたものか?
あーあーわかった
引数がkeyばっかりだから
なにがなんだかわからないんだ
書きなおしてみた
//高階関数arrayWalkを定義 function arrayWalk(data, f) { for (var key1 in data) { f(key1, data[key1]); } } //配列を処理するためのユーザ定義関数 function showElement(key2, value) { document.writeln(key2 + ':' + value); } var ary = [1, 2, 4, 8, 16]; arrayWalk(ary, showElement);
はあく!
じゃあ続き!
もちろんユーザ定義関数は自由に差し替えることができる
//高階関数arrayWalkを定義 function arrayWalk(data, f) { for (var key in data) { f(key, data[key]); } } //配列を処理するためのユーザ定義関数 var result = 0; function sumElement(key, value) { result += value; //与えられた配列要素でresultを加算 } var ary = [1, 2, 4, 8, 16]; arrayWalk(ary, sumElement); document.writeln('合計値:' + result);
合計値:31
ユーザ定義関数sumElementは与えられた値valueを
グローバル変数resultに足しこんでいる
(ここでは引数keyは使用しない)
そのため、arrayWalk関数はそれ自体として
配列要素の合計値を求めることになる
ここでおおもとのarrayWalk関数は
一切書き換えていない点に注目
このように高階関数を利用することで
ベースとなる機能はそのまま
(ここでは配列の内容を順番に取り出すこと)に
具体的な処理内容だけを自由に差し替えることができる
ふむーなるほどー
さっきの二つを私なりに書きなおしてみると
こんな感じかな?
//高階関数arrayWalkを定義 function arrayWalk(data, f) { for (var key1 in data) { f(key1, data[key1]); } } //配列を処理するためのユーザ定義関数① function showElement(key2, value) { document.writeln(key2 + ':' + value); } //配列を処理するためのユーザ定義関数② var result = 0; function sumElement(key2, value) { result += value; //与えられた配列要素でresultを加算 } var ary = [1, 2, 4, 8, 16]; arrayWalk(ary, showElement); //ユーザ定義関数①呼び出し arrayWalk(ary, sumElement); //ユーザ定義関数②呼び出し document.writeln('合計値:' + result);
0:1
1:2
2:4
3:8
4:16
合計値:31
二つ並べても問題ない、ってことですな、べんり!
keyのあたり分かりにくいから
わざと引数名変えていますです(・∀・)
私がばかなだけなんだけれども
「使い捨ての関数」は匿名関数で
匿名関数と高階関数は密接な関係を持っている、とな
というのも、高階関数においては引数として与えられる関数が
「その場限り」でしか利用されないことがよくあるため
このような「使い捨ての関数」は
あえて名前付きの関数として定義するよりも
匿名関数(関数リテラル)として記述した方がコードがシンプルになる
//高階関数arrayWalkを定義 function arrayWalk(data, f) { for (var key1 in data) { f(key1, data[key1]); } } var ary = [1, 2, 4, 8, 16]; arrayWalk( ary, function(key2, value) { document.writeln(key2 + ':' + value); } );
さっきのshowElement関数を匿名関数で書き換えたもの
匿名関数(関数リテラル)を利用することで
関数呼び出しコードに関数を直接指定することができる
このような記法は下記のようなメリットがある
- コードが短くなる
- 関連する処理が一つの文で記述できるため、呼び出し元のコードと実際の処理を規定している関数との関係がわかりやすくなる
- 一度限りしか利用しない関数に名前(しかもグローバルスコープの名前)を付けずにすむため、「意図せぬ名前の重複を回避できる」
今後より高度なスクリプト(特にAjaxのコールバック関数)を
記述する上で重宝する
また、多くのJavaScriptプログラマが好んで利用しているので
外部ライブラリなどを読み解く際にも有効、ふむー
まだもう少し高度な~続きそうなので記事分けよう
【JavaScript】引数情報を管理する(argumentsオブジェクト)
JabaScriptは引数の数をチェックしない
function showMessage(value) { document.writeln(value); } showMessage(); //undefined showMessage('山田'); //山田 showMessage('山田', '鈴木'); //山田
ユーザ定義関数ShowMessageは引数をひとつ受け取る
このような関数に対して上記コードのように
それぞれ0、1、2個の引数を渡してやると
0個、2個の場合エラーが出るように感じるが
実際は正しく動作する
JavaScriptでは
与える引数の数が関数側で要求する数と異なる場合も
これをチェックしない
したがって引数0個の場合、仮引数valueの値は
undefined(未定義)であるものとして処理される
また、引数が多かった場合、多かった2つ目の引数('鈴木')は
とりあえず無視され結果として
引数を1個渡した場合と同様の結果が得られた
もっとも多かった引数は切り捨てられるわけではない
内部的には「引数情報のひとつ」として保持されて
あとからりようできる状態になっている
この引数情報を管理するのがargumentオブジェクトである
argumentオブジェクトは関数配下(関数を定義する本体部分)でのみ
利用できる特別なオブジェクトである
argumentオブジェクトは関数呼び出しのタイミングで生成されて
呼び出し元から与えられた引数の値を保持する
このargumentオブジェクトを利用することで、たとえば
「実際に与えられた引数の数と要求する引数の数を比較し、
異なる場合にはエラーを返す」といった処理も記述できる
function showMessage(value) { if (arguments.length != 1) { throw new Error('引数の数が違うのぜ(・∀・)' + arguments.length + '個じゃ多いのだわ'); } document.writeln(value); } try { showMessage('山田', '鈴木'); } catch(e) { window.alert(e.message); }
あれ?try..catch命令とかthrow命令って
どんなだっけってなったので復習
例外処理(try..catch..finally命令)
try { 例外が発生するかもしれない命令(群) } catch(例外情報を受け取る変数) { 例外が発生した際に実行する命令(群) } [finally { 例外の有無に関わらず、査収的に実行される命令(群) }]
- 例外情報はcatchブロックにErrorオブジェクトとして引き渡される
throw new Error(エラーメッセージ)
- 例外はプログラム中に発生したものを補足するばかりではなく自分で発生させることもできる
- 例外を発生させることを「例外をスローする」ともいう
- throw命令は多くの場合if命令のような条件分岐命令と一緒に使用される
うむ、そうでしたこんなんやりました
この例で注目すべきはif命令のあたり
lengthは、argumentsオブジェクトに属するプロパティの一つで
実際に関数に渡された引数の個数を表す
つまりここでは
「実際に渡された引数が1個でない場合に例外をスローしている」
のである
例では呼び出し元の引数が2個なので例外が発生し
エラーメッセージをダイアログ表示する
ここでは、単に日引数の個数をチェックしているだけだが
引数のデータ型や値の有効範囲などの妥当性をチェックすることもできる
argumentsとArgumentsどちらが正しい?
argumentsオブジェクトの実体は、厳密には
「Argumentsオブジェクトを参照するargumentsプロパティ」である
入門書によってはArgumentsオブジェクトと記載されているものも多い
しかし、関数のなかで「Arguments.length」と記述することはできない
Argumentsオブジェクトはあくまで関数内で暗黙的に生成されるもので
プログラマが意識することすらない存在である
なるほどー
この本では混乱しないように
argumentsオブジェクトという記述で統一しているみたいね、ふむ
補足:引数のデフォルト値を設定する
引数の数をチェックしない、ということはJavaScriptでは
すべての引数は省略可能であるということになる
ただし多くのケースでは、引数がただ省略されただけでは
正しく動作しないことがほとんどである
デフォルトで引数を設定しておく必要がある
function triangle(base, height) { if (base == undefined) { base = 1; } if (height == undefined) { height = 1; } return base * height / 2; } document.writeln(triangle(5)); //2.5
上記のtriangle関数において引数base、heightのデフォルト値はいずれも1である
JavaScriptでは、引数のデフォルト値を表現するための構文はない
そのため、例のように引数の内容をチェックし
undefined(未定義値)だった場合にそれぞれの値をセットしている
「document.writeln(triangle(5));」では引数が一つしか設定されていない
そのため、後方の引数heightが省略されているものとみなされ
「5×1÷2」で2.5という結果が得られる
引数baseだけを省略することはできない
「省略できるのは、あくまで後ろの引数だけである」という点に注意
可変長引数の関数を定義する
argumentsオブジェクトの用途は
なにも引数の妥当性チェックだけにあるわけではない
可変長引数の関数という重要な機能がある
可変長引数の関数とは「引数の個数があらかじめ決まっていない関数」のこと
たとえば、Functionコンストラクタを思い出してみるとわかりやすいかもしれない
Functionコンストラクタでは、生成する関数オブジェクトが
要求する引数の個数に応じて引数を自由に変更できる
Functionコンストラクタでは
生成する関数オブジェクト要求する引数の個数に応じて、引数を自由に変更できる
var showMessage = Function('msg', 'document.writeln(msg);'); //与える引数は2個(仮引数と処理内容が一つずつ) var triangle = new Function('base', 'height', 'return base * height / 2;'); //与える引数は3個(仮引数が2個と処理内容が一つ)
このように、呼び出し元の都合で引数の個数が変動する可能性がある
宣言時に引数の個数が確定できない関数が可変長引数の関数である
可変数引数の関数を利用することで柔軟に処理を記述することができる
下記は引数に与えられた数値を合計するsum関数を定義する例である
function sum() { var result = 0; //与えられた引数を順番に取り出し、順に加算処理 for (var i = 0; i < arguments.length; i++) { var tmp = arguments[i]; if (isNaN(tmp)) { throw new Error('指定値が数値じゃないよ' + tmp); } result += tmp; } return result; } try { document.writeln(sum(1, 3, 5, 7, 9));//25 } catch(e) { window.alert(e.message); }
この例ではforループの中でarugmentsオブジェクトから
すべての要素(引数の値)を取り出しその合計値を求める
arugmentsオブジェクトからi番目の要素を取り出すには
「arugments[i]」のように記述する
また、ここではグローバル関数isNaNで
取得した要素が数値であるかどうかを確認している点に要注目である
isNaNがtrueを返す(要素が数値でない)場合には
Errorオブジェクトを呼び出し元にスローし処理を中断する
明示的に宣言された引数と可変長数を混在させる
function printf(format) { //引数の2番目以降を順番に処理 for (var i = 0; i < arguments.length; i++) { var pattern = new RegExp('\\{' + (i - 1) + '\\}', 'g'); format = format.replace(pattern, arguments[i]); } document.writeln(format); } printf('こんにちは、{0}さん、私はみこむらです', '山田', 'みこむら'); //こんにちは、山田さん、私はみこむらです
printf関数は、第1引数で指定された書式文字列に含まれる
プレイスホルダ(パラメータの置き場所:{0}、{1}、{2}..)を
第2引数以降の値で置き換えたものを出力するための関数
printf関数で第1引数だけが仮引数formatとして明示的に宣言されており
第2引数以降がいわゆる可変長引数として扱われている点に注目
このようなケースでもargumentsオブジェクトには
明示的に宣言された引数→可変長引数の順番ですべての引数が格納される
可変長引数だけがargumentsオブジェクトに管理されるわけじゃない点に注意
つまり、ここではargumentsオブジェクトから
可変長引数の部分(arguments[1]..[n])を取り出し
順に対応するプレイスホルダ({0}、{1}、{2}..)と置き換えている
あ、一応replaceメソッドの復習
正規表現で文字列を置き換える
- String.replaceメソッド
- 正規表現でマッチした文字列を置換する
- 置換後の文字列には「$1..$9」といった特殊変数を埋め込める
- この特殊変数はサブマッチ文字列を保存するための変数
置き換え対象の文字列.replace(正規表現オブジェクト, 置換後の文字列)
うむー何とか意味が理解できた気がする
無名引数は必要最低限に
明示的に宣言された引数に対しては、
変数名formatで直接アクセスするほか
argumentsオブジェクト経由で
「arguments[0]」のようにアクセスすることもできる
つまり先ほどの例のような場合でも(構文的には)
すべての引数を可変長引数として記述できる
しかし、これはコードの可読性という観点から推奨できない
インデックス番号で引数を管理するargumentsオブジェクトよりも
名前で管理する引数の方が
直観的に引数の内容を把握しやすいからである
なにからなにまでargumentsオブジェクトに委ねるのではなく
内容や個数があらかじめ想定できる引数については
できるだけ明示的に名前を付けておくこと
ふむふむ了解である
可変長引数にも仮の名前を付ける
「コードの可読性」という意味では
可変長引数にも仮の名前を付けておくのが望ましい
たとえば先ほどの例だと以下のようになる
function printf(format, var_args) {
これによって後からコードを読んだ人間は
printf関数の末尾に可変長関数を指定できることを理解できる
ただし、この引数var_argsはあくまで便宜的な名前なので
関数の中ではこれまでと同じく
argumentsオブジェクト経由でアクセスする必要がある
(引数var_argsにすべての可変長引数が格納されるわけではない)
再起呼び出しを定義する(calleeプロパティ)
argumentsオブジェクトでもう一つ重要なのが
現在実行中の関数自身を参照するために用意されている
calleeプロパティである
argumentsオブジェクトのその他のプロパティに比べると
calleeプロパティを目にする機会はそれほど多くはない
しかし、calleeプロパティを利用すれば、
関数などの特定の処理の中で自分自身を呼び出す
再帰呼び出し(Recursive Call)の処理を
容易に記述できるようになる
これによりたとえば階乗計算のように
同種の手続きを階層的に何度も呼び出すような処理を
シンプルなコードで表現できる
function factorial(n) { if (n != 0) { return n * arguments.callee(n - 1); } return 1; } document.writeln(factotial(5)); //120
factorial関数は与えられた自然数nの
階乗を求めるためのユーザ定義関数である
ここでは自然数nの階乗が「n×(n-1)!」で
求められることに着目している
これを再帰的に表現しているのが
「return n * arguments.callee(n - 1);」である
つまり与えられた数値から1を差し引いたもので
自分自身を再帰的に呼び出しているのである
冒頭で述べたように、
関数自身を表すのはarguments.calleeプロパティである
これを念頭において先ほどのコードをみてみると
内部的には以下のような手順で処理が行われていることになる
factorial(5) → 5 * factorial(4) → 5 * 4 * factorial(3) → 5 * 4 * 3 * factorial(2) → 5 * 4 * 3 * 2 * factorial(1) → 5 * 4 * 3 * 2 * 1 * factorial(0) → 5 * 4 * 3 * 2 * 1 * 1 → 5 * 4 * 3 * 2 * 1 → 5 * 4 * 3 * 2 → 5 * 4 * 6 → 5 * 24 → 120
このように何段階にわたる処理も再帰呼び出しを利用すれば
これだけ短いコードで記述できる
なお、階乗計算の場合、
「自然数nが0である場合に戻り値を1とすること」を
忘れないように気を付ける
さもないとこの再帰呼び出しは無限ループになってしまう
また、この例であれば下記記述を
return n * arguments.callee(n - 1);
calleeプロパティを使わずに以下のように記述することもできる
return n * factorial(n - 1);
しかしこのように記述すると、
もともとの関数名が変更された場合に
関数本体の記述も変更しなければならないことから
あまり好ましくない
また、以下のように匿名関数として記述された場合には、
そもそも再帰呼び出しするための関数名がないため
calleeプロパティを利用「しなければならない」
function (n) { if (n != 0) { return n * ???(n - 1); //←関数名が指定できない } return 1; } document.writeln(factotial(5)); //120
再帰呼び出しとcalleeプロパティ
いずれも重要な事項なので
ここで合わせてきちんと理解する必要がある、うむ
うむ!
使う機会があまり想像できないのでその辺は先生に!
【JavaScript】変数はどの場所から参照できるか(スコープ)
さて引き続き関数の勉強です!(`・ω・´)
グローバルスコープとローカルスコープがある
スコープとは
「変数が作りぷとの中のどの場所から参照できるか」
を決める概念である
JavaScriptのスコープは以下の2つに分類できる
- グローバルスコープ
- スクリプト全体から参照できる
- ローカルスコープ
- 定義された関数の中でのみ参照できる
ここまではトップレベルで定義する
(関数の外で定義する)変数だけ見てきたので
スコープを意識する必要はほぼなかったが
いよいよ関数が登場したところで
このスコープについても理解しておく必要がある
はーい!がんばりまーす!
グローバル変数とローカル変数の違い
グローバルスコープを持つ変数のことをグローバル変数
ローカルスコープを持つ変数のことをローカル変数という
とりあえず一旦は
- グローバル変数
- 関数の外で宣言した変数
- ローカル変数
- 関数の中で宣言した変数
と覚えておく
(これ本当はやや嘘も混ざっているらしい‥後述を待つ!)
var scope = 'Global Variable'; function getValue() { var scope = 'Local Variable'; return scope; } document.writeln(getValue()); //Local Variable document.writeln(scope); //Global Variable
関数の外で宣言された1行目の変数scopeはグローバル変数
関数getValue内で宣言された変数scopeは
ローカル変数とみなされる
結果としてgetValue関数を介して変数scopeを参照した場合は
ローカル変数scopeの値が、
最後の行のように直接変数scopeを参照した場合には
グローバル変数scopeの値が、それぞれ返されることが確認できる
スコープが異なる場合、それぞれの変数は
(同名であっても)別ものとして認識されている
変数宣言にvar命令は必須
scope = 'Global Variable'; function getValue() { scope = 'Local Variable'; return scope; } document.writeln(getValue()); //Local Variable document.writeln(scope); //Local Variable
さっきのコードの変数宣言から
var命令を取り除いたコードである
JavaScriptにおいて変数宣言を表すvar命令は
省略可能なためこのコードは正しく動作する
しかし、結果はどちらも「Local Variable」を返す
JavaScriptではvar命令を使わずに宣言した変数は
すべてグローバル変数とみなすため
getValue関数が実行された段階で
変数scopeが「scope = 'Local Variable';」によって
上書きされてしまうことになる
ほほーなるほど、これが
とりあえず一旦は
- グローバル変数
- 関数の外で宣言した変数
- ローカル変数
- 関数の中で宣言した変数
と覚えておく
と云っていたのをやや嘘と述べた理由なのか!
ローカル変数を定義するには必ず
var命令を使用する必要がある
ふむふむー!
以上の理由から
「関数内でグローバル変数を書き換える」ような用途を除いて
原則としてvar命令を省略すべきではない
グローバル変数を宣言する場合も
「グローバル変数にはvar命令をつけず、
ローカル変数にはvar命令を付ける」というのは
かえって混乱のもとになるため
原則、「変数宣言はvar命令で行う」癖をつけておくことで
無用なバグの混入を防ぐことができる
はーい!きをつけます!(・∀・)
ローカル変数の有効範囲はどこまで?
ローカル変数は「宣言された関数の中でのみ有効な変数」と述べたが
より厳密には「宣言された関数全体で有効な変数」である
var scope = 'Global Variable'; function getValue() { document.writeln(scope); //undefined var scope = 'Local Variable'; return scope; } document.writeln(getValue()); //Local Variable document.writeln(scope); //Global Variable
これは
- JavaScriptではローカル変数は「関数全体で有効」である
- getValue関数内の「document.writeln(scope);」を実行するときすでに変数scopeは有効になっている
- しかしローカル変数scopeは確保されているだけでvar命令は実行されていない
- つまりローカル変数scopeの中身は未定義(undefined)である
というように処理をしているためである
JavaScriptのこのような挙動が思わぬ不具合の原因になる
これを避けるという意味でも
ローカル変数は関数の先頭で宣言するように
心がけるべきである
仮引数のスコープ(基本型と参照型の違いに注意する)
仮引数とは
「呼び出し元から関数に対して渡されたパラメータを受け取る変数」
function triangle(base, height) {..}
上記のようなtriangle関数であれば
仮引数はbase、heightとなる
仮引数は基本的にローカル変数として処理される
var value = 10; function decrementValue(value) { value--; return value; } document.writeln(decrementValue(100)); //99 document.writeln(value); //10
上記のコードでは
- グローバル変数valueに10がセットされる
- 「document.writeln(decrementValue(100));」でdecrementValue関数が呼び出される
- decrementValue関数内部で使用されている仮引数valueはローカル変数とみなされるため、これをいくら操作してもグローバル変数valueに影響はない
- 「document.writeln(decrementValue(100));」でdecrementValue関数に100を仮引数valueに渡しても、仮引数valueをデクリメントしても、グローバル変数valueが書き換えられることはない
結果として「document.writeln(value);」では
もともとの値である10が返される
仮引数に渡される値が参照型の場合はどうなるのか
具体的なコードをみてみる
var value = [1, 2, 4, 8, 16]; function decrementElement(value) { value.pop(); //末尾の要素を削除 return value; } document.writeln(decrementElement(value)); //1,2,4,8 document.writeln(value); //1,2,4,8
参照型とは「値そのものでなく値を格納した
メモリ上の場所(アドレス)だけ格納している型」である
そして参照型の値を受け渡しする場合には渡される値も
(値そのものではなく)メモリ上のアドレスだけになる
(このような渡し方を参照渡しという)
つまりここでは
- 一行目で定義されたグローバル変数valueと二行目で定義された仮引数(ローカル変数)とは変数としては別物である
- しかし「document.writeln(decrementElement(value));」でグローバル変数valueの値が仮引数valueに渡された時点で「結果的に」実際に参照しているメモリ上の場所が等しくなる
したがってdecrementElement関数の中で配列を操作した場合
結果はグローバル変数valueにも反映される
‥(・∀・;)
わかってるような、わかってないような?←
ちょっとこれは
ちゃんと先生に聞いておいた方がよさそうだー
ブロックレベルのスコープは存在しない
JavaやC#のようなプログラム言語では
文ブロック({..})の範囲内でのみ
有効とするスコープ(ブロックスコープ)が存在する
以下はJavaによるごく簡単なサンプルである
if (ture) { int i = 5; } System.out.println(i); //エラー
Javaの世界ではブロック単位でスコープが決定するため
この場合「変数iの有効範囲はifブロックの内部だけ」になる
つまり「System.out.println(i);」の時点で
「変数iが未定義である」とみなされエラーとなる
一方JavaScriptの世界では、同様のコードが正しく動作する
if (ture) { var i = 5; } documet.writeln(i); //5
JavaScriptではブロックレベルのスコープが存在せず
ブロック(ここではifブロック)を抜けた後も
変数iが有効であり続けるためこのような結果になる
疑似的にブロックスコープを定義する
「変数の意図せぬ競合を防ぐ」という意味でも
変数のスコープをできるだけ必要最低限に留めることは重要である
JavaScriptでも下記のような記述で疑似的に
ブロックスコープを実現することができる
with ({i:0}) { if (ture) { i = 5; } } documet.writeln(i); //変数iはスコープ外なのでエラー
ここではwith命令を利用することで
withブロックの中でのみ参照可能な変数i
(実際には匿名オブジェクトのiプロパティ)を定義している
かなりトリッキーなコードなため
日常的に使用することはおすすめしないが
どうしてもブロックスコープを実装したいという場合には
このような記述を試してみるのも良い
ふむふむー
関数リテラル/Functionコンストラクタにおけるスコープの違い
関数リテラルとFunctionコンストラクタは
いずれも匿名関数を定義するための機能を提供するものだが
実は関数の中でこれらを利用した場合
スコープの解釈が異なる
var scope = 'Global Variable' function checkScope() { var scope = 'Local Variable' var f_lit = function() { return scope; }; document.writeln(f_lit()); //Local Variable var f_con = new Function('return scope;'); document.writeln(f_con()); //Global Variable } checkScope();
関数リテラルf_litも、Functionコンストラクタf_conも
関数内部で定義している
そのためいずれもローカル変数scopeを参照するように見えるが
結果を見てもわかるように
Functionコンストラクタはグローバル変数を参照している
これは直観的にもわかりにくい挙動だが
仕様書を確認すると正しい挙動であると書かれている
なんかながーい名前の仕様書だから一旦置いておこう‥←
Functionコンストラクタは
原則として利用しないことを前提とすれば
このような混乱が生じるケースも少ないかもしれないが
ここで改めて
「関数の3つの記法は必ずしも意味的に等価ではない」
ということを確認しておくべきである
はーい!
スコープ長かった‥
それだけ大事ということですよねわかります
【JavaScript】例題やってみる②【配列、制御命令などなど】
そういえば先生にもう一問例題を出されたりした(`・ω・´)
var items = [ { "code": "pink_shirt", "name": "ピンク色のシャツ", "description": "ピンク色のシャツの説明", "price": 2000, "category": "tops" }, { "code": "red_shirt", "name": "赤いキャミソール", "description": "赤いキャミソールの説明", "price": 1000, "category": "tops" }, { "code": "green_keyholder", "name": "グリーンのキーホルダー", "description": "グリーンのキーホルダーの説明", "price": 1500, "category": "accessory" }, { "code": "pink_bottoms", "name": "ピンクのスカート", "description": "ピンクのスカートの説明", "price": 3000, "category": "bottoms" }, { "code": "green_bottoms", "name": "みどりのパンツ", "description": "みどりのパンツの説明", "price": 2500, "category": "bottoms" } ];
前回の配列そのままです
今回は、1000円以下のアイテムの個数を求めよ
割とすぐできそう!(・∀・)
var cnt = 0; for(var i = 0; i<items.length; i++){ if (items[i].price <= 1000){ cnt++; } };
こんなかな!
OKいただきました!(`・ω・´)
あとあとさらにただ数字だすだけじゃなく
そのあともなんか使う可能性があるデータなら
配列作ってその個数をみるって形もいいよと
べんきょうべんきょう、と考えてみた
var ary = []; for(var i = 0; i<items.length; i++){ if (items[i].price <= 1000){ ary.push(items[i].code); } }; console.log(ary.length);
こうか!(`・ω・´)
ちゃんとわかるってうれしい‥(´;ω;`)
【JavaScript】関数定義における4つの注意点
JavaScriptにおける関数の定義はシンプルだが
実際にコーディングに取り組んでみると
思わぬ不具合に悩まされることもある
よくある誤りを避けるヒントを4つ紹介する
はーいよろしくです
return命令は途中で改行しない
JavaScriptでは「基本的に」セミコロンで文末を認識する
ただしセミコロンを省略した場合にも
適宜、前後の文脈から分の末尾を判断する
つまりJavaScriptでは文末に
セミコロンを付けることが好ましいが「必須ではない」
このような寛容さは
基本的にJavaScriptのハードルを下げる要因になるものだが
時として要らぬ混乱をもたらす原因にもなる
var triangle = function(base, height) { return base * height / 2; } document.writeln('三角形の面積:' + triangle(5, 2)); //5
これは呼び出し元に三角形の面積、式「base * height / 2;」の結果を
戻すことを意図したコードだが
実際に呼び戻してみると意図したような結果は得られない
おそらく実行結果は「三角形の面積:undefined」となるはず
うん、なりました!(・∀・)
上のコードは実際にはセミコロンが自動的に補完されて
下記のような解釈がされている
return; base * height / 2;
結果triangle関数は戻り値として(デフォルトの)undefinedを返し
後続の式「base * height / 2;」は無視されている
意図したように動作させるには途中の改行を削除する必要がある
関数はデータ型の一種
JavaScriptにおいて関数はデータ型の一種である
var triangle = function(base, height) { return base * height / 2; } document.writeln('三角形の面積:' + triangle(5, 2)); //5 triangle = 0; document.writeln(triangle); //0
ほかのプログラミング言語を学んだことがある方ならば
上記のコードが直観的に「間違いである」と感じるはず
うーん‥私ほかの全然学んだことないからなー(・∀・;)
まあいいや続きやる
- 関数と同名の変数が定義されたことに問題があるならば「triangle = 0;」はエラーになるはず
- 関数をあたかも変数のように呼び出していることが問題ならば「document.writeln(triangle);」で問題となるはず
と思うかもしれない
(私は全く思わなかったけど‥ははは)
しかし、これは正しいコードである
JavaScriptにおいて関数は「データ型の一種」
そのため、triangle関数を定義すると
じつは「triangleという変数に関数型のリテラルを格納する」ことと
同じ意味なのである
したがって「triangle = 0;」で変数triangleに
改めて数値型の値をセットしても間違いではない
また、数値型に書き換えられた変数を参照している
「document.writeln(triangle);」のコードも正しい
関数のこの性質を利用して、あからさまに
以下のようなコードを書くこともできる
var triangle = function(base, height) { return base * height / 2; } document.writeln(triangle);
function (base, height) {
return base * height / 2;
}
ここではtriangleを変数として参照しているので
triangleに格納された関数定義がそのまま文字列として出力されている
(厳密にはFunctionオブジェクトのtoStringメソッドが呼び出されて
文字列表現に変化されたものが出力されている)
function命令は静的な構造を宣言する
もっとも、function命令による関数定義は
いわゆる代入演算子(=)による変数への代入とは
異なる点もあるため要注意
document.writeln('三角形の面積:' + triangle(5, 2)); //5 function triangle(base, height) { return base * height / 2; }
「関数定義が変数定義である」という前提に基づけば
1行目の時点でまだtriangle関数(関数定義を格納した変数triangle)は
宣言されていないはずなので
このコードはエラーにならなければならない
しかし、実際にはこのコードを実行してみると
正しくtriangle関数が実行されて結果も表示されることが確認できる
これはfunctionが動的に実行される命令ではなく
静的な構造を宣言するためのキーワードであるためである
「静的な構造」と言ってしまうとわかりにくいかもしれないが
要は
「function命令はコードを解析/コンパイルするタイミングで、関数を登録している」
ということである
したがって実行時にはすでにコード内の構造の一部として
triangle関数をどこからでも呼び出すことができる
関数リテラル/Functionコンストラクタは実行時に評価される
さっきの例を関数リテラル/Functionコンストラクタで
書き換えたらどうなるだろうか
document.writeln('三角形の面積:' + triangle(5, 2)); //5 var triangle = function(base, height) { return base * height / 2; }
結果は実行エラーになる
function命令とは異なり
関数リテラル/Functionコンストラクタ実行時(代入時)に評価される
ということである
したがって関数リテラル/Functionコンストラクタで
関数を定義する場合には
「呼び出し元のコードよりも先に記述する」必要がある
なるほどりょうかいです