◆ ES5 までって言語機能自体はすごくシンプルでわかりやすかった
◆ シンプルというならこれくらいのシンプルさがいい ということでまとめてみた
◆ 基本構文って C 系のよくあるものが多くて他言語経験者だと関数やプロトタイプ周りくらいわかれば十分使えると思う
◆ ES2015 以降は触れないので 今の JavaScript を知りたい人には向いてない記事です

最近普段使わない言語の使い方紹介記事とかを見ていたのですが 「すごくシンプルで覚えることは少ない」 なんて書かれているのに読んでみたらすごく長くて前編後編別れていたり……
組み込みの関数やクラスが多いとかなら別に問題ないのですが 構文自体がいっぱいあると知らないと (勘でしか) 読めないですし 言語自体はシンプルなもののほうが気軽に使い始められて良いなと思います

JavaScript って最近こそ ES2015~ のどんどん機能追加で構文も増えて複雑になりつつありますが それまでの ES5 まではとても単純な言語だったと思います
DOM 操作したり WebAPI を操作したりいろいろ出来て Node.js というサーバサイドまであるくらいでしたが それはいろいろな関数が用意されていてるだけで これを呼び出したら◯◯できる のようなものであって言語自体の機能ではないです

私自身 他言語いくつか使ってみて「構文とか書き方自体がありすぎてよくわからないし覚えるのも面倒」と思ってた頃に JavaScript を使ってみて言語自体はシンプルで簡単に拡張できる言語というのが気に入って好きになったというのもあります


今回は一昔前の ES5 までの JavaScript 自体の機能を書いてみます
(ES2015 以降の機能がつい入ってしまわないように気をつけます)
言語自体のシンプルさをアピールするならこれくらいシンプルにしてよねと言いたいのが目的なので 新たに JavaScript を勉強する人が読むのには向かないと思います
外部非公開だからと未だに IE8 や 9 対応で jQuery で操作する程度で ES2015~ の新機能なんて別世界って人にはもしかすると役に立つかもしれません
あれ? IE8 って ES5 動くんだっけ?
あと JavaScript 固有じゃなくて他言語でもよくあるものについては詳しい説明しないのでプログラミング初心者向けでもないです

コメント

基本的な構文は C 系のよくある感じです
コメントも // と /* */ の 2 種類です

// コメント

/*
コメント
*/

基本はこんな使い方することはないですが
無意味な式があってもエラーにならないので Python のドキュメントみたいな文字列を置いておいてもコメント的な動きにはできます

"コメント"

計算

よくある演算子で計算できます

1 + 1
// 2

3 / 2
// 1.5

3 % 2
// 1

int/float のような区別がないので割り算は割り切れないなら小数ができます

変数

var を付けて宣言するとローカルスコープに宣言します
ブロックスコープはなく関数単位です
スコープ作るために関数を作って即実行という使われ方もします

何もつけないとすでにある変数を書き換え 存在しない場合はグローバルに変数を宣言します

var a = 10
var b = "abc"
a = true
b = null

グローバル

一番外側の空間です
それぞれの script タグで実行するスクリプトの関数の外側部分です

window がグローバルオブジェクトでグローバルに宣言したものは window のプロパティとしても参照できます

基本の型はこれだけです

Number
String
Boolean
Object (Array, Function)
null
undefined

Number に整数も小数も入ります
文字ひとつでも文字列で String になります
配列や関数はオブジェクトの一種なので Object 扱いです
null は例外的な値として使う値です
「typeof null」 で null の型を取得すると "object" となる謎仕様があります
JavaScript では未定義のところにアクセスするとエラーの代わりに undefined という値が取得できます
null みたいなものです
取得できる場所は配列のインデックスが範囲外やオブジェクトの存在しないプロパティアクセスや関数に渡した引数が足りないときなどがあります

リテラル

それぞれ次のような方法で値を作れます

// Number
var a = 1
var a = 1.
var a = 1.2
var a = .3

// String
var a = "str"
var a = 'str'

// Boolean
var a = true
var a = false

// Array
var a = []
var a = [1]
var a = [1, 2, 3]

// Function
var a = function(){ return 1 }
function a(x, y){ return x + y }

// Object
var a = {}
var a = {a: 1}
var a = {a: "abc", b: true}

// null
var a = null

// undefined
var a = undefined

オブジェクト

キーは文字列固定でバリューには好きなデータを入れることが出来ます
キーは変数名に使える文字と数字のみの場合にはクオートが不要です
(正式な JSON は常にダブルクオートが必要です)

var b = {x: 1, y: "text", あ: true, 30: false, "a-b": 0}

オブジェクトのそれぞれのデータはプロパティと呼ばれます
プロパティのアクセスは . を使うか [] を使います

b.x
b["y"]
b[30]
b.あ
b.z
b["a-b"]

. の場合は変数に使える文字のみで 数字や記号を含むときは [] の必要があります

オブジェクトの値にはオブジェクトを入れることも可能なのでネストすることできます

var c = {d: {e: {"!": {z: 1}}}}

c.d.e["!"].z
// 1

配列と組み合わせることも可能です

var d = [{a: 1}, {a: 2, b: {c: [{d: 1}]}}]

d[0].a
// 1

d[1].b.c[0].d
// 1

プロパティを消す場合には delete を使います

var e = {a: 1, b: 2}
delete e.b
e
// {a: 1}

prototype

オブジェクトにはプロトタイプとなるオブジェクトを設定することができます
アクセスされたプロパティが自身のオブジェクトに存在しない場合はプロトタイプのオブジェクトを探します
プロトタイプのオブジェクトにもプロトタイプのオブジェクトが存在し プロトタイプのチェーンの内どこかで見つかればそのプロパティを取得できます
プロパティに書き込む場合はプロトタイプは関係なく直接そのオブジェクトのプロパティに書き込まれます

var a = {x: 10}
var b = {__proto__: a, y: 20}
b.x
// 10
b.y
// 20

__proto__ という名前の特殊なプロパティにプロトタイプとなるオブジェクトへの参照が含まれています

var a = {x: 10}
var b = {__proto__: a}

b.x
// 10
b.x = 100
b.x
// 100
b.__proto__.x
// 10

自身にプロパティがある場合にプロトタイプのオブジェクトのプロパティを見たい場合には __proto__ を経由することができます
デフォルトで Object.prototype というオブジェクトが {} リテラルでオブジェクトを作ったときのプロトタイプと設定されています
ここには toString などが用意されていて

var c = {}
c.toString()
// "[object Object]"

と呼び出せます
__proto__ を null にするとそれもできなくなります

var d = {__proto__: null}
d.toString()
// エラー d.toString is not a function

__proto__ プロパティを使う以外にも指定のオブジェクトをプロトタイプとして新しいオブジェクトを作る関数も用意されています

var e = Object.create({x: 1})
e.x
// 1
e.__proto__
// {x: 1}

関数

宣言と呼び出し

関数呼び出しは C 系のよくあるものです
関数名の後に括弧で引数を渡します

eval("2 + 3")
String.fromCharCode(0x3042)
parseInt("10", 2)

JavaScript の場合は引数の数をチェックしないので多くても少なくても問題ありません
少ないと受け取る側で undefined を受け取ります

関数は文で宣言する方法と 式を代入する方法の 2 つ作り方があります

function x(a, b){
return a + b
}
x(100, 200)

一般的な関数宣言です
ここでいう x は関数という特殊な扱いはなくただの変数 x に関数が入ってるのと一緒です
他の変数に代入したり上書きしたりできます
特殊な部分は呼び出すことができる部分と スコープの最初で宣言されるので この宣言文より上で関数を使用できるところです


次は関数を代入するものです

var f = function(d){
return d + 1
}
f(10)

この場合関数の名前は省略可能です
付けた場合でもその名前で変数は作られません
代入した先の名前で呼び出しができます
こっちの場合は代入した時点で変数に関数が入るのでこの文より上で関数を使用できません

ちょっと複雑なのですが この書き方は式としてみなせる場合のみで関数宣言文とみなせる場合はそっちが優先されます
代入せず

function(d){
return d + 1
}

と書くと 「代入されなかった意味のない式」 ではなく関数宣言扱いされて その場合は名前が必須なので構文エラーになります
式扱いさせるには () で囲んだり ! や + など演算子から始めるなど式の途中に入れる必要があります

スコープを作るために関数を作って即時呼び出すときは

(function(){
alert(1)
})()



!function(){
alert(1)
}()

などの書き方が使われることが多いです

発展

関数の中で関数を作ることも出来ます

function p(v){
function q(a){
return a + 1
}
return q(v)
}
p(1)
// 2

配列やオブジェクトを返せるので複数のデータを受け渡しできます

function s(){
return[1, 2]
}
s()
// [1, 2]

function t(){
return {a: 1, b: 2}
}
t()
// {a: 1, b: 2}

関数自体を返すことも出来ます

function s(k){
return function(l){
return k + l
}
}
s(10)(20)
// 30

()() のように () をつなげて返された関数をそのまま実行することもできます

関数を返す場合は関数が終了しても返された関数から参照可能な変数は保持されます

function m(i){
return function(){
return i++
}
}
var x = m(10)
x()
// 10
x()
// 11
x()
// 12
var y = m(100)
y()
// 100
y()
// 101
x()
// 13

関数の中で arguments 変数を参照すると可変引数として受け取れます
配列のようで配列ではないオブジェクトです

function n(a, b){
console.log(a, b)
console.log(arguments)
}
n(1, 2, 3, 4)
// 1 2
// [1, 2, 3, 4]


オブジェクトのプロパティに関数を入れて使うことも出来ます

var functions = {
add: function(a, b){return a + b},
sub: function(a, b){return a - b},
}
functions.add(100, 10)
// 110
functions.sub(100, 10)
// 90

this

JavaScript では this という特別な値があります
基本はグローバルオブジェクトの window を参照します

関数の中では this が特別な値に変わることがあります
まずオブジェクトのプロパティの関数を呼び出すと そのオブジェクトを参照します

var a = {x: 100}
a.b = function(){return this}
a.b()
// {x: 100, b: ƒ}

a.b で呼び出した時 b の中の this は a になっています
これは a.b() で呼び出したときのみで a.b を c などのプロパティではないただの変数に入れて c() のように呼び出したときには何も起きません
通常どおり window が参照されます


また this を呼び出す時に指定する方法があります

function f(a){
return [a, this]
}
f.call({x: 200}, 10)
// [10, {x: 200}]

関数の call プロパティには this を指定して関数を呼び出すための関数が入っています
f.call() のように使い 1つ目の引数には this を何にするかを渡して 2 つ目以降に関数に渡す引数を入れます
call の代わりに apply を使うと 2 つ目の引数には関数に渡す引数を配列で渡せます

function f(a, b){
return [a + b, this]
}
f.apply({x: 300}, [1, 2])
// [3, {x: 300}]

先に this を設定しておくこともできます

function f(){
return this
}
f2 = f.bind({x: 400})
f2()
// {x: 400}

関数の bind プロパティには this を設定した関数を作る関数が用意されています
bind の引数に this に設定したい値を渡して返ってくる関数は 渡した値が this に bind 済みです
普通に呼び出しても this は設定した値になります

制御構文

制御構文は C 系ほぼそのままです

if/else

if(1){
// 実行される
}else{
// 実行されない
}

if(0){
// 実行されない
}else if(1){
// 実行される
}

elseif や elif みたいなものはないです

switch

switch(1){
case 0:
// 実行されない
break
case 1:
// 実行される
break
default:
// 実行されない
break
}

while/do-while

while(1){
if(1){
break
}
}

do{
if(1){
continue
}
}while(1)

for

for(var i=0; i < 10; i++){
alert(i)
}

for-in

var array = [10, 20, 30]
for(var i in array){
console.log(i + ": " + array[i])
}
// 0: 10
// 1: 20
// 2: 30

オブジェクトのキーをループします
配列の場合はインデックスです
プロトタイプも見るので自身のオブジェクトのみにするなら hasOwnProperty を使ってそれが自身のものかチェックする必要があります

operator

単純な足し算や引き算以外の演算子です

代入

a = 1

変数に代入するやつです

ほか演算子と組み合わせると計算結果を代入できます

a = 1
a += 2
// 3
a -= 10
// -7

比較

"10" == 10
"10" === 10

イコールの数が 2 つと 3 つがあります
== は自動で型変換した上で比較します
自動変換がどう変換されるかがわかりづらいことやオブジェクトの場合にはユーザが作った関数を通して変換されたりでバグのもとになるので理由がない限り === を使って変換せずそのまま比較することが推奨されてます
=== は null と undefined も別扱いするのですがこれらは同じとみなしたいことが多いので例外で == null や == undefined では == も使うというケースは多いです

1 < 2
2 <= 2
3 > 2
3 >= 3

大小比較です
true/false が取得できます
↑のは全部 true になります

インクリメント・デクリメント

a++
--a

変数の値を 1 増やしたり減らしたりします
++ は増やして -- は減らします
左側についているとその式自体の値が増減後のもので 右側についてると増減前のものになります
数値以外が入ってる変数に行うと数値に変換可能なら変換され 不可能なら NaN になります

文字列結合

文字列を + で計算すると文字列が結合されます
片方が文字列ならもう一方も文字列に変換されて結合されるので数値と文字列を + で結合することもできます
数値計算を先にしたいなら括弧が必要です

"a" + 1 + 1
// a11

"a" + (1 + 1)
// a2

ビット演算

3 & 1
// 1

1 | 2
// 2

1 ^ 3
// 2

1 << 10
// 1024

32 >> 2
// 8

~0
// -1

キャスト

JavaScript では演算子での変換機能を使ってキャストすることが多いです
C 系の (string) みたいな機能はありません

+"10.5"
// 10.5

~~"10.5"
// 10

!!1
// true

""+3
// "3"

+ を使うと数値化します
Number 関数に入れるのと一緒です
数値にならない場合は NaN です

~~ を使うと 32bit integer として数値化します
+ では小数や 32bit の範囲を超えて変換できます
~~ ではビット演算してるので 32bit integer までになります
小数点以下がいらないときや 変換できない時に NaN ではなく 0 になるので未定義を初期化した上でそのまま計算に使いたい時に便利です

""+ で空文字列と足し算することで文字列化できます
String 関数に入れるのと一緒です

!! を使うと boolean 型に変換して反転を 2 回するので boolean 型に変換したのと同じになります
Boolean 関数に入れるのと一緒です

and/or

基本は if や while の条件など boolean 型を組み合わせたい時に使うものです

true && false
// false

false && false
// false

true && true
// true

true || false
// true

false || false
// false

true || true
// true

1 ? 2 : 3
// 2

0 ? 4 : 5
// 5

JavaScript の場合は && と || を通しても型変換されないので

obj && obj.prop
value || 0

みたいに obj があればそのプロパティを参照したり value がないならデフォルト値として 0 をつかうといったことが出来ます

その他

void は常に undefined を返します

void 0
// undefined

in を使うとオブジェクトがそのプロパティを持っているか判断できます

"a" in {a: 1}
// true

"b" in {a: 1}
// false

typeof を使うと型を文字列で取得できます

typeof 1
// "number"

typeof "a"
// "string"

typeof function(){}
// "function"

typeof []
// "object"

typeof {}
// "object"

typeof null
// "object"

typeof undefined
// "undefined"

instanceof を使うと指定関数をコンストラクタに持つオブジェクトか判定できます

({}) instanceof Object
// true

/a/ instanceof RegExp
// true

"a" instanceof String
// false

文字列や数値など非オブジェクトは instanceof で true になることはありません

new とオブジェクト指向

関数がオブジェクトを返せるので 値と関数をセットにして返せばメンバとメソッドのあるインスタンスの出来上がりです

function f(){
return {
x: 10,
up: function(){ this.x++ },
down: function(){ this.x-- }
}
}
var v = f()
v.x
// 10
v.up()
v.x
// 11

ですが 実行のたびに新しい関数を作るのは無駄があります
値は各インスタンスごとにもつ必要がありますが関数は全体でひとつで十分です
オブジェクトにはプロトタイプ機能があるので

var p = {
up: function(){ this.x++ },
down: function(){ this.x-- }
}
function g(){
return {
x: 10,
__proto__: p
}
}
var v = g()
v.x
// 10
v.up()
v.x
// 11

とすることができます

これでもいいのですが JavaScript では他の言語のクラスのように new 演算子を使ってプロトタイプを自動で設定してくれる機能があります

function h(){
this.x = 10
}
h.prototype.up = function(){ this.x++ }
h.prototype.down = function(){ this.x-- }
var v = new h()
v.x
// 10
v.up()
v.x
// 11

すべての関数は prototype というプロパティを持っています
関数を new をつけて呼び出すと 返り値はその prototype プロパティをプロトタイプと設定されたオブジェクトになります
また関数実行時の this は window の代わりに返り値として返すオブジェクトになり return は不要です
new をつける前提の関数では this のプロパティに値を入れて初期化すればそれが返り値になってインスタンスが作られます


new がやってることを JavaScript の処理で書いてみると

function new_h(){
var new_object = Object.create(h.prototype)
return h.apply(new_object, arguments)
}

となります

var v = new_h()
v.x
// 10
v.up()
v.x
// 11

同じ結果が得られます


デフォルトで用意されてる型には Date や RegExp があります

new Date().getFullYear()
// 2018

new RegExp("^[a-z]{4}$").test("abcd")
// true

try

try ブロックの中でエラーが起きた時に catch ブロックが呼び出されます
他言語のよくある try と同じで機能はシンプルです

try{
throw new Error("")
} catch (err) {
console.log(err)
}

catch で受け取る変数は省略出来ませんし catch は一つだけです
finally ブロックは使えますが 使ってるところはめったに見るものではないです
私の場合は年に 1 回見るかどうかくらいかもしれないくらいのレア度です

use_strict

ストリクトモードという少し動きが変わるモードがあります
最近の JavaScript でこそクラス構文やモジュールで強制的にこのモードになりますが 過去のものでは強制されることはなく使いたい人が使えばよく そこまで使ってる人が多いわけでもなかったので省略します

まとめ

途中で言うほど構文や言語機能でもないところもついでに説明入れたりで思ったよりちょっと長くなりましたが このくらいの機能です
漏れがないか ES5 で書かれたコードをいくつか見てみましたがこれでカバーできてると思います
長いコードでも たいていは関数やメソッド呼び出しばかりですからね

実際のコード見て思い出したのが try-catch です
言語によっては多用しますが JavaScript だと比較的少なめです
エラーがあってもタイマーやリスナなどのエントリポイントが最上位になるので 途中で止まってもあまり困らない作りですからね
本当の初期化中ならエラーが起きるのと以降何も起きなくなりますが ユーザの操作が入ってこないところなので 開発中にエラーが起きないようになってれば起きることはそうないですし

あと with は外してます
うまく使えば便利ではありますが ほぼ使われないですし非推奨ですし

と これだけなのに十分に拡張性があっていろいろできたのがいいところです
シンプルなものだと機能足りなすぎ と思うものはありますが JavaScript だと自分で十分補える物が多く 実際 ES2015 以降の機能も一部構文以外は Promise なども polyfill でできる (言語自体を拡張しないでも関数を作ればできる) 部分が多いです
ブラウザで使える=気軽に使える という良さもありますが このシンプルさかつ拡張性も好きなところでメインに使い始めた理由です