javascriptの基礎知識メモ

  • 78
    Like
  • 0
    Comment

普段javascriptを書かないので、たまに少し覚えてはすぐ忘れるを繰り返している。
今後のために最低限の知識を自分のメモとして残しておくことにした。

グローバルオブジェクトを使わない

関数内で宣言した変数でも、型名なしで宣言するとグローバルになるので必ずvarで宣言する。

var a = 0;
b = 1; // これはグローバルになる.

オブジェクトのメソッドはprototypeに書く

オブジェクトのコンストラクタの中にメソッドを書いてしまうと、オブジェクトが生成されるたびにメソッド用のメモリが追加される。なので汎用的なメソッドはプロトタイプに追加するのが良い。

Person.prototype.func = function(){
    return "aaa";
}

値は全て参照である

 オブジェクトAから継承してオブジェクトBを作る。
 Aのメンバを変更する。
 オブジェクトAから継承してオブジェクトCを作る。
 BとCの継承元メンバも変更されている。

<script>
    var A = { x : 1};
    var B = Object.create(A);
    A.x = 2;
    console.log(B.x);  // 結果は2
</script>

判定文の注意

割り当てていないオブジェクトはnullではなく、undefinedになっている。nullチェックではなく、undefinedかどうかを判定する必要がある。
また、「==」ではなく「===」を使うこと。

変数のスコープ

変数は定義された関数内でのみ参照可能である。
ただし、関数が入れ子の場合は外側の関数で定義された変数を参照できる。

function(){
    a = 1;
    var func = function(){
        b = 2;
        a += b;  // aは3
    }
    // ここではスコープを抜けたのでaは1。

    func();
    // ここでaは3になる。
}

存在確認

オブジェクトが存在するかどうかを調べるにはif()を書くだけ。
メソッドが実装されているかどうかもこれで確認できる。
プロパティの確認はinでできる。
独自プロパティかどうかはhasOwnProperty()で確認。

アクセサプロパティ

メンバ名を関数にすることで定義できる。
.rでアクセス可能。

var obj = {
    get r() {取得処理};
    set r() {書き込み処理};
 }

オブジェクト生成方法

●直接{}で書く。

クラスを定義する時などに用いる。
プロトタイプが定義できないので、プロトタイプを指定したい場合は後からプロトタイプを指定する。

myObj = { p : "value" };

myObj.prototype = protoObj;  // 定義した後にプロトタイプ指定。

●newで作成する

通常これを用いる。
クラス名を付けたコンストラクタメソッドをあらかじめ用意していおいて、それが呼び出される。

function MyClass(){
    MyClass.prototype.name = "ProtoTypeMyClass"; 
    return this;
}

●Object.Create()で作成する

継承させて作りたい時に使用する。
継承したいプロパティを持つオブジェクトを引数に渡す。
prototypeを渡すことも可能。

Object.Create(Object.prototype);

オブジェクトとプロパティの考え方まとめ

<script>

  function MyClass(){
    this.member = 0; // これはインスタンス毎に作られる
    MyClass.prototype.name = "ProtoTypeMyClass"; // これはクラス共通
    return this;
  }

  // コンストラクタからオブジェクトを作る.
  var o = new MyClass();

  // プロトタイプからオブジェクトを作る
  var a = Object.create(MyClass.prototype);

  // インスタンスから継承してオブジェクトを作る
  var b = Object.create(o);

  console.log(o.name);
  console.log(o.member);

  console.log(a.name);
  console.log(a.member); // undfined

  console.log(b.name);
  console.log(b.member);
</script>

結果

ProtoTypeMyClass
0
ProtoTypeMyClass
undefined
ProtoTypeMyClass
0

プロパティの禁止設定

書き込みなどを禁止にすることができる。

●書き込み可
●列挙可
●再定義可
●拡張可(クラスに対して)

var obj = Object.create(Object.prototype, 
  { x : {
    value : 1,
    writable : true,     // x の値が変更できる
    enumerable : true,   // 列挙か
    configurable : true  // 再定義可
  }}
)

これらはgetOwnPropertyDescriptor()で確認できる。
また、後からメソッドで変更可能である。

拡張不可にするメソッド
 ●Object.preventExtensions()で、拡張不可
 ●Object.seal()で、拡張不可+全ての独自プロパティを再定義不可
 ●Object.freeze()で、拡張不可+プロパティ再定義不可+プロパティ読み出し専用
●Object.freeze(Object.create( { } ) )のように作成時に使う事も可能。

プロパティの参照

.プロパティ名か、[文字列]で読み出す。
全て取り出す場合は、
keys()、またはgetOwnPropertyNames()。
前者は列挙可能属性のみを取り出す

関数の宣言方法

// 宣言。セミコロンはいらない
// 関数宣言の場合、関数宣言前でも実行ができる。ただし、あまり使わない方がいい。
function Add(){
  // 処理
}

// 式。セミコロンが必要
// 定義した後で実行可能。こちらの方が分かりやすい。
function Add(){
  // 処理
};

// 代入
// a()で実行可能。
var a = function Add(){
  // 処理
};

// 即時関数。すぐに実行される。
(function Add(){
  // 処理
})();

call()、apply()、bind()

関数は普通に実行する方法とはべつに、thisを指定して実行することができる。
それがcall()とapplyである。

//関数fをオブジェクトoのメソッドとして呼び出す。
f.call( o, 1, 2 );

最初の引数はthisとなり、二番目以降は関数の引数となる。

//applyは二番目以降を配列で渡すという違いがあるだけ。
f.apply( o, [1 ,2] );

// 現在実行中の関数に渡されたarguments配列を渡すことで全く同じ引数で関数を実行できる。
// あるオブジェクトoのメソッドmを置き換えるために渡す。
function func(o, m){
  var origin = o[m]; // 元のメソッドをクロージャで保存
  o[m] = function(){
    // 処理をする。
    var ret = origin.apply(this, arguments);  // 元のメソッド呼び出し。
    return ret;
  };
}

bindは、あるオブジェクトに関数を注入して、それを別名でクロージャ保存できる。

function f(y) {return this.x + y; } // バインドする関数
var o = { x : 1 }; // バインドされる側
var g = f.bind(o); // 「関数fをoで使えるように定義した」関数の名前をgにした。
g(2);  // 3になる

関数の戻り値

戻り値が指定されていない場合はundefinedが返る。
new演算子で呼び出されていて、戻り値がオブジェクトでなければ、thisが代わりに返される。つまり新しく生成されたオブジェクトが返される。

arguments引数

関数には直接指定する引数とは別に、いくつでも任意の引数を渡せる。それは関数内ではargumentsオブジェクトで参照できる。

function Myfunc(obj1, obj2){
    return obj1 + obj2 + arguments[2] + arguments[3];
};

// 呼び出す時. 3と4はargumentsに格納される。    
Myfunc(1,2,3,4);

プロトタイプを拡張する

プロトタイプにmethodメソッドを追加して、プロトタイプを直接触らずに関数を追加する。
ただし、for in文が影響を受けるのでhasOwnPropartyメソッドで継承されたものかどうかをチェックした方がいい。また、ライブラリがすでにこのメソッドを追加している可能性があるので、既に存在しているかどうかのチェックもした方がいい。

Function.prototype.method = function(name, func){
    this.prototype[name] = func;
    return this;
}

クロージャ

簡単に言うと、関数内で関数を定義して使うこと。実際に使われるのは内部の方の関数。

普通、関数は定義した時のコンテキストで実行される。
しかし、関数内で関数を定義することで、外側の関数の変数や引数を動的に扱えるようにすることができる。

function Add(x) {
  return function(y) {
    return x + y;
  };
}
var add = Add(1);
var add2 = Add(10);

console.log(add(2)); // 3になる.
console.log(add2(2)); // 12になる.

また、即時実行関数を使うことにより、プライベート変数を実現できる。
こちらのサイトの例が分かりやすかったです。
http://analogic.jp/closure/

var counter = (function () {
    var cnt = 0;

    return function (num) {
        cnt += num;
        console.log(cnt);
    };
}());

counter(-1); // 「-1」が出力される
counter(-1); // 「-2」が出力される

上記二つの違いは、定義した後にあらためて一度変数に格納してからその変数名で実行するか、定義と同時に実行してそのまま変数に格納して、その格納した変数名で内部の関数を実行するかの違い。
どちらにしろ実行されるのは内部の関数である。

さらに、内部のreturnの部分でオブジェクトを返すことにより、生成時に動的に動きを変えられるメソッドを持つオブジェクトを作成できる。

安全なオブジェクトについて

javascriptにクラスのような機能を疑似的に追加するのは難しい。
実際に書籍を読んでもいちまち理解できなかった。
今の時代では、クロージャの仕組みや疑似クラスを極めるより、素直にTypeScriptを使用するのが良いと思う。

TypeScript

生のjavascriptは人間の脳にあまりに優しくない。
TypeScriptはjavascriptの欠点をカバーしてくれる。

    // インターフェースを定義できる.
    interface IList<T> {
        push(item: T): void;
        get(index: number): T;
    }

    // クラスを宣言できる。ジェネリック型を使用できる.
    class List<T> implements IList<T>{

        // スコープとプリミティブ型が用意されていて宣言と同時に初期化可能
        private myName: string = "aaa";
        private myVal: number = 1;
        private myStatus: boolean = true;
        private data: T[];

        // コンストラクタは以下のように書く.
        constructor() {
            this.data = [];
        }

        push(item: T): void {
            this.data.push(item);
        }

        get(index: number): T {
            return this.data[index];
        }
    }

    var nameList = new List<string>();
    nameList.push("TEST");
    var n = nameList.get(0);