JavaScriptオブジェクトなど(私的メモ)

目次
  1. JavaScriptの特徴
  2. イミュータブルとミュータブル
    1. JavaScriptにおけるデータ型
    2. 変数をコピー(代入)した時の挙動
    3. オブジェクトの複製
    4. 引数の扱い
  3. スコープ
  4. コンストラクタ関数
    1. プロトタイプ
    2. クラスとクラス継承
  5. thisキーワードについて
    1. thisの参照先
    2. thisのコンテキストを制御する
  6. Object.defineProperty()
  7. for in と for of
  8. MapとSet

JavaScriptの特徴

  • JavaScript ではほとんどのものはオブジェクトです
    関数を含むほとんど全てのものがオブジェクトとして扱われます
    関数は第一級オブジェクトであり、配列も特別な種類のオブジェクトです
    プリミティブ値(数値、文字列、真偽値、null、undefined、シンボル、BigInt)はオブジェクトではありませんが、オブジェクトのように扱うことができます
  • JavaScript はオブジェクトですプロトタイプベースの言語です
    オブジェクトはプロトタイプ(他のオブジェクト)を継承することで振る舞いを獲得します
    オブジェクトは、プロトタイプチェーンを持ち、これを通じてメソッドやプロパティを継承します
  • JavaScript は動的型付け言語です
    変数に対して具体的なデータ型を事前に指定する必要はなく、プログラムの実行中に任意の型の値をその変数に代入できます

イミュータブルとミュータブル

JavaScriptにおけるデータ型

JavaScriptにおけるデータ型は大きくプリミティブ型(オブジェクト以外)とオブジェクト型に分けられます

  • プリミティブはオブジェクト以外のデータのことです
    文字列・数値・BigInt・真偽値・undefined・Symbol ・null
    *JavaScriptのプリミティブ型(nullとundefinedを除く)はオブジェクトではないので本来はプロパティやメソッドを持つことができません
    しかし、プログラミング上の利便性を高めるため、JavaScriptはこれらのプリミティブ値に対しても、一時的に対応するラッパーオブジェクトを生成することで、オブジェクトのように扱うことを可能にします
    *例えば、数値に対する操作ではNumberオブジェクト、文字列に対する操作ではStringオブジェクトが生成されます
  • オブジェクトはプリミティブ以外のデータであり、以下のようなものが含まれます
    オブジェクトリテラル({}を使用したオブジェクト)
    配列
    関数
    日付(Dateオブジェクト)
    正規表現
    *配列はArrayオブジェクト(0から整数を添字として値にアクセス)ですし、関数もまた実行可能なオブジェクトです

プリミティブはイミュータブル(値の変更不可能
オブジェクトはミュータブル(値の変更可能

プリミティブ
*プリミティブ値は代入(=)することで新しい値になります(変更されたわけではない)

let a = "hoge";
console.log(a); // hoge
a.toUpperCase();
console.log(a) // hoge
//代入する
a = a.toUpperCase();
console.log(a) // HOGE

オブジェクトはミュータブル(値の変更ができる)
プロパティやメソッドの値は変更できます(変数には値ではなく参照が保存されているため)
*オブジェクト自体ではなくプロパティの変更なので、constで宣言できます

let a = {
    prop: 'hoge',
}
console.log(a.prop)//hoge
a.prop = 'hoge2'
console.log(a.prop)//hoge2

変数をコピー(代入)した時の挙動

プリミティブ型の値がコピーされる場合は値そのものが複製される(値渡し)
オブジェクトがコピーされる場合は参照が複製される(参照渡し)

プリミティブ型:値そのものが複製されます

let a = 10;
let b = a; // 'a'の値が'b'にコピーされる
b = 20; // 'b'の値を変更しても'a'には影響しない
console.log(a); // 10
console.log(b); // 20

オブジェクト型:参照がコピーされますが、オブジェクト自体は複製されません(変更がコピー元にも影響します)

let obj1 = { value: 10 };
let obj2 = obj1; // 'obj1'の参照が'obj2'にコピーされる
obj2.value = 20; // 'obj2'を通じてオブジェクトを変更すると、'obj1'にも影響する
console.log(obj1.value); // 20
console.log(obj2.value); // 20

オブジェクトの複製

オブジェクトを複製する主な目的は、変更がコピー元に影響しないようにするためです

  • シャローコピー
    オブジェクトの一番上のレベルのプロパティのみを新しいオブジェクトにコピーします
    ネストされたオブジェクトや配列などは、元のオブジェクトと新しいオブジェクトで共有されます
  • ディープコピー
    オブジェクトに含まれるすべてのプロパティ(ネストされたオブジェクトや配列を含む)を完全に新しいオブジェクトにコピーします
    *ディープコピーには一般的に外部ライブラリ(Immerなど)や再帰的な関数が使用されることが多い
//オブジェクトを新しく作成
let ary = []
lel obj = {}

スプレッド構文
スプレッド演算子は配列やオブジェクトを個別の要素に展開するために使用されます
この演算子を使うと、オブジェクトまたは配列のシャローコピーを簡単に作成できます

//配列の複製
let ary = [1, 2];
let newAry = [...ary]; // 'ary'の浅いコピーを作成
let newAry2 = [...ary, 3]; // 'ary'を複製し、新たに3を追加
console.log(ary, newAry2); // [1, 2] [1, 2, 3]

//オブジェクトの複製
let obj = { a: 1 };
let newObj = { ...obj, b: 2 }; // 'obj'の浅いコピーを作成し、新たに'b:2'を追加
console.log(obj, newObj); // {a: 1} {a: 1, b: 2}

スプレッド構文を使ったネストされたオブジェクトの複製
ネストされたプロパティも独立してコピーするためには、ネストされた各レベルでスプレッド構文を適用する必要があります

const original = {
    level1: {
        level2: {
            level3: 'value'
        }
    }
};

const copy = {
    ...original,
    level1: {
        ...original.level1,
        level2: {
            ...original.level1.level2
        }
    }
};

console.log(copy); // { level1: { level2: { level3: 'value' } } }

// 元のオブジェクトを変更
original.level1.level2.level3 = 'new value';

console.log(original.level1.level2.level3); // 'new value'
console.log(copy.level1.level2.level3); // 'value'

引数の扱い

関数の引数がプリミティブ型の場合イミュータブルであるため関数内での変更が元のオブジェクトに影響を及ぼさない
関数の引数がオブジェクト型の場合ミュータブルであるため関数内での変更が元のオブジェクトに反映される

引数がプリミティブの場合
関数内で引数argに新しい値を代入しても、外部の変数aには影響しません

let a = 0;
function fn1(arg) {
    arg = 1;
    console.log(a, arg);
}
fn1(a);
// 結果: 0, 1

引数がオブジェクトの場合
関数にオブジェクトを渡すと、そのオブジェクトへの参照がコピーされます
したがって、関数内でオブジェクトを変更すると、その変更は外部のオブジェクトにも反映されます

const b = {
    prop: 0
};
function fn2(arg) {
    arg.prop = 1;
    console.log(b.prop, arg.prop);
}
fn2(b); // 結果: 1, 1

分割代入
分割代入は配列やオブジェクトから値を取り出して、それを個別の変数に割り当てるJavaScriptの表現です
プリミティブ値のプロパティを分割代入で取り出した場合は、その値はプリミティブ値自体(数値、文字列、真偽値など)であるため、新しい変数に割り当てられるのは元のオブジェクトのプロパティの値のコピーです

const a = {
    prop: 0
}
//分割代入
let { prop } = a;
prop = 1;

console.log(a.prop, prop);
// 0 1

分割代入を使用してオブジェクトのpropプロパティを直接引数として扱います
ここでpropはプリミティブ値であり、関数内での再代入は外部のオブジェクトbには影響しません

const b = {
    prop: 0
};
function fn2({prop}) {
    prop = 1;
    console.log(b.prop);
}
fn2(b); // 結果: 0

レストパラメーター
レストパラメーターは関数に渡された複数の引数を一つの配列にまとめる役割です
オブジェクトに対する操作は「参照渡し」として扱われ、関数内での変更が元のオブジェクトに反映されます

const b = {
    prop: 0
};
function fn2(...arg) {
    arg[0].prop = 1; //オブジェクトへの参照を通じてプロパティを変更
    console.log(b.prop, arg[0].prop);
}
fn2(b); // 結果: 1, 1

レストパラメーターは通常、関数に渡された引数のうち、指定された引数以外の「残りの引数」を配列として扱うために使用されます
関数がどのくらいの数の引数を受け取るか事前に分からない場合や、複数の引数を一括で処理したい場合に有用です

function myFun(a, ...manyArgs) {
  console.log(a)//1
  console.log(manyArgs)//[2, 3, 4, 5]
}
myFun(1,2,3,4,5)

余談:スプレッド構文は、関数呼び出し時の引数として配列の要素を個別の引数として渡すためにも使用されます

function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];

console.log(sum(...numbers)); // 結果:6 スプレッド演算子が配列を個別の引数に展開

スコープ

スコープとは、「変数」や「関数の引数」などを参照できる範囲のことです
例えば、名前の衝突を防いだり、特定の変数や関数をスコープ内に閉じ込めることで外部から直接アクセスされるのを防ぎます
*種類:グローバルスコープ、ローカルスコープ(関数スコープ、ブロックスコープ)、レキシカルスコープ、モジュールスコープ

  • ローカルスコープには主に関数スコープブロックスコープの2種類があります
    それぞれのスコープ内で宣言された変数は、そのスコープ内および内側のスコープから参照することができますが、外側からは参照できません
    内側のスコープからは、自分のスコープを含む、より外側のスコープにある変数や関数を参照することが可能です
  • プログラムの最上位に定義された変数や関数はグローバルスコープに属し、どこからでも参照可能です
  • レキシカルスコープとは、コードを書く時点で変数がどのスコープに属しているかが決まるスコープです(関数がどこで宣言されたかに基づいて、その関数がアクセスできる変数が決定されます)
    *コールバック関数は、他の関数に引数として渡され、特定のイベントや処理の完了後に実行される関数です
    コールバック関数がどの変数にアクセスできるかは、そのコールバック関数が定義された場所によって決まります
    コールバック関数内で使用される変数は、コールバック関数が定義されたスコープ内、またはその外側のスコープ(親スコープ)で見つかるものにアクセスできますが、コールバック関数が実行されるスコープ(呼び出し元のスコープ)内の変数には直接アクセスできません(もちろん、グローバルスコープの変数にはどこからでもアクセス可能です)
  • モジュールスコープとは、そのモジュールファイル内で宣言された変数、関数、クラスなどが、デフォルトではモジュール外部からアクセスできないスコープを持っているという意味です

クロージャーとは、関数が外側の関数のスコープにある変数を参照している状態を指します
*内部状態を外部から直接アクセスできないように隠蔽したり、関数が受け取るべき引数の一部を事前に「固定」したりできます

//クロージャーの典型的な例、外側の関数の引数xを内側の関数が利用しています
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

varの挙動と関数宣言と関数式

  • 「var」で宣言された変数はブロックスコープ(if文やループ文など)は無視して一番近い外側のスコープに変数をひもづけます
  • 「var」は宣言の前に使用しても「undefined」となりエラーになりません(ホイスティングがあるためです)
  • 「関数宣言」もホイスティングがあり実行より後で宣言してもエラーになりません
    「関数式」は実行前に宣言する必要があります
func(){
//関数スコープ
}
if(省略){
//ブロックスコープ
}
for(省略){
//ブロックスコープ
}
//関数式
const func = function() {};
//関数宣言
function func() {}

コンストラクタ関数

  • コンストラクタ関数は新しいオブジェクトインスタンスを作成するための関数です
    同じ構造を持つオブジェクトを簡単に複数作成することができます
  • 慣例的に変数名の先頭は大文字です
  • newキーワードでオブジェクトを作成します(インスタンス化)
  • newで作成したオブジェクトをインスタンスと呼びます
  • コンストラクタ関数内での「this」はこれから新しく作るインスタンスを指します
function User(name) {
  // this = {};  (暗黙で書く必要はない)
  // this へプロパティを追加
  this.name = name;
  // return this;  (暗黙で書く必要はない)
}
let user1 = new User("花子")
console.log(user1) //User {name: '花子'}

returnの挙動
通常、コンストラクタ関数ではreturn文を省略します(newキーワードによって自動的に新しいオブジェクト(thisが指すオブジェクト)が返される)
*プリミティブ値をreturnした場合、またはreturn文がない場合は、暗黙的にthisによって表される新しいオブジェクトが返されます(return this)

ただし、コンストラクタ関数から明示的にオブジェクトをreturnした場合、そのオブジェクトが新しいインスタンスとして返されます

function User(name) {
  this.name = name;
  return {}
}
let user1 = new User("花子")
console.log(user1) // {}

プロトタイプ

メソッドをコンストラクタ関数のprototypeプロパティに追加することは、すべてのインスタンスがそのメソッドを共有することを可能にします
これに対して、コンストラクタ内でメソッドを定義すると、インスタンスごとにメソッドのコピーが作成され、余計なメモリを消費します

function User(name) {
    this.name = name;
    //コンストラクタ内で定義
    // this.func = function() {
    //  console.log( this.name);
    // }
}
User.prototype.func = function() {
    console.log(this.name);
}
const user1 = new User('花子');
user1.func()//花子

プロトタイプオブジェクトとは?

ほぼすべてのオブジェクトは「プロトタイプオブジェクト」を持っています
プロトタイプオブジェクトは、他のオブジェクトにメソッドやプロパティを提供する役割を持っています
あるオブジェクトが特定のメソッドやプロパティにアクセスしようとした時、そのオブジェクト自身にそのメソッドやプロパティが定義されていない場合、JavaScriptはプロトタイプオブジェクトを検索し、そこに定義されているメソッドやプロパティを利用します

  • プロトタイプの継承
    JavaScriptのすべてのオブジェクトは、別のオブジェクトのプロトタイプを継承します
    この継承されたオブジェクトは、プロトタイプオブジェクトと呼ばれます
    オブジェクトがあるプロパティやメソッドを呼び出す時、そのオブジェクト自身にそのプロパティやメソッドが存在しない場合、JavaScriptはプロトタイプチェーンをたどって探します
  • プロトタイプチェーン
    あるプロパティやメソッドが現在のオブジェクトに存在しない場合、JavaScriptエンジンが__proto__プロパティを辿って親のプロトタイプオブジェクトを検索し、必要なプロパティやメソッドを最終的にObject.prototypeに到達するまで探し、見つからない場合undefinedが返されます
  • JavaScriptでは、関数もオブジェクトです
    コンストラクタ関数はprototypeプロパティを持っており、これはそのコンストラクタによって生成されるすべてのインスタンスが共通で継承するプロトタイプオブジェクトを指します
    このプロトタイプオブジェクトにメソッドやプロパティを定義することで、それらをすべてのインスタンスで共有できます
    *例えば、Userコンストラクタ関数から生成されるすべてのインスタンスは、User.prototypeに定義されたプロパティやメソッドにアクセスできます

__proto__プロパティ
__proto__プロパティあるオブジェクトが継承しているプロトタイプオブジェクトへの内部リンクを提供します

新しいインスタンスが作成されると、そのインスタンスの__proto__プロパティはコンストラクタ関数のprototypeプロパティを参照します
__proto__プロパティを通じて、オブジェクトはプロトタイプチェーン上にあるメソッドやプロパティにアクセスできます

instanceof演算子は、あるコンストラクターの「prototype」が、プロトタイプチェーンの中のどこかに現れるか判定します(型の判定などに使います)
hasOwnProperty() メソッドは、オブジェクト自身がそのプロパティを持っているかを判定します(プロトタイプチェーンのプロパティは無視したいときなどに使います)

const obj = {
 prop : 'hoge'
};

console.log(obj instanceof Object); //true

console.log(obj.hasOwnProperty('prop')) //true

プロトタイプ継承
Userオブジェクトの一部を利用(継承)してAdminオブジェクトを作る

function User(name) {
    this.name = name;
}
User.prototype.func = function() {
    console.log(this.name);
}
//call()
function Admin(name, group) {
    User.call(this, name);
    this.group = group
}
// Object.create()
Admin.prototype = Object.create(User.prototype);
Admin.prototype.func2 = function() {
    console.log(this.group + this.name);
}

let user1 = new User("花子")
let admin1 = new Admin("太郎", "Aグループ")

console.log(user1)//User {name: '花子'}
console.log(admin1)//Admin {name: '太郎', group: 'Aグループ'}
user1.func() //花子
admin1.func() //太郎
admin1.func2() //Aグループ太郎

クラスとクラス継承

クラスとクラス継承の構文は、コンストラクタ関数やプロトタイプ継承のシンタックスシュガー(簡単に記述できるようにしたもの)です

constructorメソッド:インスタンス化されたときに自動的に呼び出される特別なメソッドで、インスタンスの初期化を行います

class User{
    constructor(name) {
        this.name = name;
    }
   func() {
        console.log(this.name);
    }
}
let user1 = new User("花子")

Userクラスを継承したAdminクラスを作る
extendsキーワード:クラス継承を実現するためにextendsキーワードを使用します
これにより、既存のクラスから新しいサブクラスを作成し、メソッドやプロパティを継承できます
superキーワード:オブジェクトの親を呼び出すために使用でき、コンストラクターで使用する場合「this」が使われる前にsuperを呼び出す必要があります

class User{
    constructor(name) {
        this.name = name;
    }
   func() {
        console.log(this.name);
    }
}

class Admin extends User {
    constructor(name, group) {
        super(name);
        this.group =group;
    }
   func2 = function() {
    console.log(this.group + this.name);
  }
}

let user1 = new User("花子")
let admin1 = new Admin("太郎", "Aグループ")

console.log(user1)//User {name: '花子'}
console.log(admin1)//Admin {name: '太郎', group: 'Aグループ'}
user1.func() //花子
admin1.func() //太郎
admin1.func2() //Aグループ太郎

メソッドチェーン

メソッドは「return this」 でインスタンスを返すことでメソッドチェーンで繋ぐことができます

thisキーワードについて

動的にオブジェクトを生成する際(例えばコンストラクタ関数やクラスのコンストラクタ内で)、thisは新しく作成されるインスタンスを指し、それを使用してプロパティやメソッドをそのインスタンスにバインドするのに効果的です

thisの基本的な挙動

  • オブジェクトのメソッドとして
    メソッドの中でthisを使用した場合、thisはそのメソッドを呼び出したオブジェクトにバインドされます
  • 単独の関数として
    関数がオブジェクトのメソッドとしてではなく、ただの関数として呼び出される場合、thisはグローバルオブジェクト(ブラウザではwindow、Node.jsではglobal)にバインドされます
    *ただし、厳格モード(’use strict’;)が有効な場合、thisはundefinedになります
  • コンストラクタ関数やクラスのコンストラクタ内
    thisは新しく作成されるインスタンスを指します
    これにより、コンストラクタ内でインスタンスにプロパティやメソッドを追加することができます
  • call、apply、bindメソッドを使うとthisの値を明示的に指定することができます

thisの参照先

  • オブジェクト.メソッド()で実行した場合は、「this」の参照先はそのオブジェクト
  • 関数名()で実行した場合は
    • strictモードでは「this」の参照先は「undefined」
    • strictモードでない場合は「this」の参照先はグローバルオブジェクト
"use strict"
const obj = {
    prop: 'hoge',
    func: function() {
        console.log(this);
    }
}
obj.func()//結果 hoge

//obj.func()を変数に代入
const test = obj.func
//関数名()で実行
test()//結果undefined

メソッドをコールバック関数として渡した場合、関数が呼ばれた場所で「this」が参照するオブジェクトは変わります

window.prop = 'グローバル';
const obj = {
    prop: 'obj',
    func: function() {
        console.log(this.prop);
    }
}
function fn(ref) {
    ref();
}
fn(obj.func);//グローバル

入れ子になった関数の「this」の参照先はグローバルオブジェクトです

const func = {
    func1: function(){
        console.log(this);
        const func2 = function(){
            console.log(this);
        }()
    }
}
func.func1()//{func1: ƒ} Window

まとめると
thisの参照先は、関数の呼び出し方や実行コンテキストによって動的に変わります
メソッドを変数に代入したり、メソッドをコールバック関数として引数に渡す場合、および入れ子になった関数の中では、thisの参照先は基本的にグローバルオブジェクトになります(strictモードではundefined)

thisのコンテキストを制御する

thisを固定する
bindメソッドはthisの値を固定した新しい関数を返します
関数をすぐには実行しません
thisが指定したオブジェクトに永続的にバインドされた関数のコピーを作成します
この新しい関数は後でいつでも呼び出すことができます
bind(強制したいオブジェクト、第二引数以降は引数として引き継ぐ)

window.prop = 'グローバル';
const obj = {
    prop: 'obj',
    func: function() {
        console.log(this.prop);
    }
}
const test1 = obj.func
test1() //グローバル
//bindでオブジェクトを固定
const test2 = obj.func.bind(obj);
test2()  //obj

//fn1関数の引数にbindした新たな関数を渡す
function fn1(ref) {
    ref();
}
fn1(test2);//obj

//fn2関数の引数を強制
function fn2(arg) {
    console.log(arg);
}
const test3 = fn2.bind(null, 'aaa');
test3()//aaa

thisの参照先を変更する
callメソッドapplyメソッド」はthisの値を一時的にバインドして関数を実行します
*applyは第二引数は配列になり展開されて引数に渡されます

function greet(message) {
    console.log(`${message}, my name is ${this.name}`);
}
const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };
// callメソッドを使用して、person1オブジェクトをgreet関数の実行コンテキストにバインド
greet.call(person1, 'Hello'); // 出力: Hello, my name is Alice
// callメソッドを使用して、person2オブジェクトをgreet関数の実行コンテキストにバインド
greet.call(person2, 'Hi'); // 出力: Hi, my name is Bob


function introduce(message, city) {
    console.log(`${message}, my name is ${this.name} and I live in ${city}`);
}
const person1 = { name: 'Alice' };
// applyメソッドを使用して、person1オブジェクトをintroduce関数の実行コンテキストにバインド
// 引数は配列として渡される
introduce.apply(person1, ['Hello', 'New York']); // 出力: Hello, my name is Alice and I live in New York

thisのレキシカルな挙動(アロー関数)について
アロー関数は自身のthisを持たず、外側の(レキシカルスコープの)thisを「継承」します
この性質により、アロー関数はthisが変わることなく、周囲のコンテキストに従います
アロー関数は入れ子にしても、参照されている 「this」が指すものは変わりません

window.prop = 'グローバル';
const obj = {
    prop: 'obj',
    method : function() {
        const a = () => console.log(this.prop);
        a();
    }
}
obj.method(); //obj

//アロー関数が使えない状況では、thisをあらかじめ(_this)に代入してthisの参照先を安全に保持
window.prop = 'グローバル';
const obj = {
    prop: 'obj',
    method : function() {
        const _this = this;
        const a = function(){console.log( _this.prop)}
        a();
    }
}
obj.method(); //obj

備考:オブジェクトのメソッド内でthisを参照する必要がある場合、通常の関数式を使用することが推奨されます
*アロー関数を使用すると、thisがレキシカルスコープに基づくため、期待通りのオブジェクトを指さない可能性があります

const obj = {
  prop: 'こんにちは',
  greet: () => {
    console.log(this.prop);
  }
};

obj.greet(); // undefined

const obj = {
  prop: 'こんにちは',
  greet: function() {
    console.log(this.prop);
  }
};

obj.greet(); // "こんにちは"

Object.defineProperty()

Object.defineProperty()メソッドは、オブジェクトに新しいプロパティを直接定義するか、既存のプロパティを変更するために使用されます

オブジェクトのプロパティは「value(プロパティの値)」以外のプロパティの設定値を保持しています(ディスクリプター)
これらの値を変更することで挙動を変更できます

  • value:プロパティの値。
  • configurable :設定変更の可否
  • enumerable :列挙の可否
  • writable :値の変更の可否
  • setとgetはオプションなので、設定されない場合はundefinedになります

Object.defineProperty()はオブジェクトに新しいプロパティを直接定義できます
getOwnPropertyDescriptor(obj, 'prop')はオブジェクトのプロパティのディスクリプターを返します

const obj = {};

Object.defineProperty(obj, 'prop', {
       value: 0,
       configurable: true,
       enumerable: false,
       writable: true
});

console.log(obj.prop); //0

const descriptor = Object.getOwnPropertyDescriptor(obj, 'prop');

console.log(descriptor);
//{value: 0, writable: true, enumerable: false, configurable: true}

GetterやSetterを設定
ゲッターやセッターを定義することで、プロパティへのアクセスや値の変更に追加のロジックを組み込むことが可能になります

//definePropertyを使った場合
let user = {
  name: "太郎",
};
Object.defineProperty(user, "newName", {
  get: function () {
    return user.name;
  },
  set: function (value) {
    this. name = value;
  },
});

//オブジェクトに書いた場合
// let user = {
//   name: "太郎",
//   get newName() {
//     return this.name;
//   },
//   set newName(value) {
//     this.name = value;
//   },
// };

user.name = "花子";

console.log(user.newName);//花子
console.log(user.name)//花子
console.log(user)//{name: '花子'}

for in と for of

for…inループ

  • 出力されるのはキーです
  • オブジェクトのすべての列挙可能なプロパティ(キー)を通してループします
    これにはオブジェクト自身のプロパティのみならず、そのオブジェクトのプロトタイプチェーン上にある列挙可能なプロパティも含まれます
  • プロトタイプチェーン上のプロパティも列挙しますが、通常はオブジェクト自身が持つプロパティだけに注目したい場合が多いため、obj.hasOwnProperty(key)を使ってフィルタリングすることが推奨されます
  • 処理の順序が保証されていません(ただし、実際のブラウザやJavaScriptエンジンでは、ある程度の順序が守られることが多いです)
const obj = {
	prop1: 'v1',
	prop2: 'v2',
	prop3: 'v3',
}

for(let key in obj) {
	// if(obj.hasOwnProperty(key)) {
		console.log(key, obj[key]);
	// }
}
//prop1 v1
//prop2 v2
//prop3 v3

for…ofループ

  • 値が出力されます
  • String、Array、NodeList、Map、Setなど、反復可能オブジェクト(iterableオブジェクト)を通してループします
    *これらはSymbol.iteratorプロパティを持っているため、for…ofループで使用できます
  • 通常、for…ofループは直接オブジェクトには使用できません(オブジェクトはデフォルトでiterableではないため)
    *Object.keys()、Object.values()、Object.entries()などのメソッドを使ってオブジェクトのキー、値、またはキー/値のペアを配列として取得し、その配列をfor…ofループで反復処理することができます
const arry = ['v1', 'v2', 'v3'];

for(let v of arry) {
	console.log(v)
}
//v1
//v2
//v3
for (let [key, value] of Object.entries(someObject)) {
  console.log(key, value);
}

MapとSet

ObjectとMapの違い

  • キーの種類
    Objectのキーは文字列またはシンボルに限定されます
    Mapでは任意の値(関数やオブジェクトを含む)をキーとして使用できます
  • 反復処理
    Objectはfor…inループ(またはObject.keys()、Object.values()、Object.entries()を使った反復処理)で反復処理され
    Mapはfor…ofループを直接使用して反復処理できます

Mapのメソッド

//new Map() 新しいmapを作ります
const map = new Map();
const key1 = {};
// map.set(key, value)キーで、値を格納します.
map.set(key1, 'value1');
// map.get(key)指定されたキーの値を返却
console.log(map.get(key1));//value1
//map.has(key) – キーが存在する場合にtrue
console.log(map.has(key1)); //true
//map.delete(key) – キーで値を削除します.
//map.clear() – mapをクリアします.
//map.size – 要素の数

Mapのループ処理

let myMap = new Map()
myMap.set('key1', 'v1')
myMap.set('key2', 'v2')
myMap.set('key3', 'v3')
myMap.entries()

//上はこれと同じ
// let myMap = new Map([
//   ['key1', 'v1'],
//   ['key2', 'v2'],
//   ['key3', 'v3']
// ]);

// キーの反復
for (let key of myMap.keys()) {
  console.log(key); // key1 key2 key3
}
//値の反復
for (let v of myMap.values()){
console.log(v);//v1 v2 v3
}
//[key, value]の反復
for (let entry of myMap){
   console.log(entry);// ['key1', 'v1'] ['key2', 'v2']['key3', 'v3']
}
//forEach
myMap.forEach( (value, key)=>console.log(`${key} ${value}`));
//key1 v1, key2 v2 ,key3 v3

ArrayとSetの違い

  • 重複の扱い
    Setは重複する値を保持しません
    同じ値を複数回addしても、Setには一度だけ保持されます
  • 反復処理
    Arrayはfor…inとfor…ofの両方で反復処理できますが、for…ofが推奨されます
    *for…inは配列のインデックス以外のプロパティも列挙する可能性があります
    Setはfor…ofループでその要素を反復処理できます。
const mySet = new Set()
mySet.add(1)
mySet.add(5)
mySet.add(5)
console.log(mySet)// Set [ 1, 5 ]
console.log(mySet.has(1)) //true
console.log(mySet.size)//2

for (let item of mySet){
 console.log(item) // 1 5
}

// Setオブジェクトを  Array.fromでArrayに変換
const arry = Array.from(mySet);
console.log(arry); //[1, 5]