Open12

JavaScript勉強

haseyuyhaseyuy

不変オブジェクト

  • 生成後に状態が変更されないオブジェクト
メソッド名 プロパティの追加 プロパティの削除 プロパティの削除
preventExtensions ⭕️ ⭕️
sead ⭕️
freeze
// Object.preventExtensionsの動き
var obj = {x: 1, y: 2};
Object.preventExtensions(obj);

// プロパティは追加できない
obj.z = 3;
Object.keys(obj);

// プロパティの削除は可能
delete obj.y;
Object.keys(obj);
console.log(obj);

// Object.freezeの動き
var obj = {x: 1, y: 2};
Object.freeze(obj);

// プロパティ値は変更できない
obj.x = 2;
console.log(obj);

オブジェクト型の定数値にはObject.freeze()を使うと良いみたい
オブジェクト型の場合、constで保持されるのは変数が指し示すアドレスであり、値は変更可能だから

const a = 1
a = 2 // => TypeError: Assignment to constant variable.

const obj = {
    hoge: 1
};
obj.hoge = 2;
console.log(obj.hoge);
haseyuyhaseyuy

スコープ

  • スコープとは名前の有効範囲のこと
  • グローバルスコープと関数スコープ(ローカルスコープとも呼ぶ)がある
var x = 1;
function f (){
    console.log(x); // => undefined
    var x = 2;
    console.log(x); // => 2
}
f();
haseyuyhaseyuy
  • let宣言で定義した変数はブロックスコープになる
function f(){
    let x = 1; 
    console.log(x); // 1
    {
        let x = 2;
        console.log(x); // 2
    }
    console.log(x); // 1
}
f();

function f1(){
    let x = 1; 
    {
        console.log(x); // 1を出力、ブロックを外側に向かって探索
    }
}
f1();
haseyuyhaseyuy

レキシカルスコープ

  • シンボルを表す実体が、
var x = 7;
function g(){ return x; }
function f(){ 
    var x = 9;
    return g();
}
console.log(f());    // => 7
haseyuyhaseyuy

シャドーイング

スコープの小さい同名の変数でスコープの大きい名前を隠すこと

var n = 1;
function f() {
    n = 2;
    console.log(n); // 2
}
f();
haseyuyhaseyuy

this参照

  • トップレベルのコードのthis参照はグローバルオブジェクトを参照
  • 関数内のthisの参照は関数の呼び出し方法で異なる
関数の呼び出し方法 this参照の参照先オブジェクト
コンストラクタ呼び出し 生成したオブジェクト
メソッド呼び出し レシーバオブジェクト
applyあるいはcall呼び出し applyまたはcall引数で指定したオブジェクト
それ以外の呼び出し グローバルオブジェクト
var obj = {
    x:3,
    doit:function(){
        console.log(this.x);
    }
}
// メソッド呼び出し
obj.doit(); //3
haseyuyhaseyuy

applyとcall

  • 呼び出した関数内のthis参照を指定した任意のオブジェクト参照にできる
function f() {
  console.log(this.x);
}

var obj = {
    x: 4,
}

f.apply(obj); // 4
f.call(obj);  // 4

function f1(a, b) {
  console.log(this.x, a, b);
}
// 第二引数の配列要素が関数f1の引数になる
f1.apply({x: 4}, [1, 2]); // 4 1 2
// 第二引数以降の引数が関数f1の引数になる
f1.call({x: 4}, 1, 2); // 4 1 2
haseyuyhaseyuy

プロトタイプ汚染

  • JavaScriptでは基本的にどのオブジェクトも凍結されておらず、好きプロパティを追加できる
  • オブジェクトが直接所有するプロパティとプロトタイプチェーンの中に持つプロパティとの区別が付きづらい理由から
    バグを引き起こす原因となる
  • この特徴を有益な形で使用すると、例えば、polifillとして古いブラウザーに存在しない機能を供給することができます。
// Object.prototypeをを汚染する
Object.prototype.foo = 1;
const obj = { bar: 2};

// objがプロトタイプチェーンの中にObject.prototypeを含んでいる
console.log(Object.prototype.isPrototypeOf(obj)); // true
console.log(obj instanceof Object); // true

// 値を取得する際にプロトタイプを辿る
console.log(obj.foo); // 1
console.log(obj.bar); // 2

// in演算子ではオブジェクトが直接所有するプロパティか判定できない
console.log("foo" in obj); // true
console.log("bar" in obj); // true

// Object#hasOwnPropertyではオブジェクトが直接所有するプロパティか判定できる
const hasOwnProperty = Object.prototype.hasOwnProperty;
console.log(hasOwnProperty.call(obj, "foo")); // false
console.log(hasOwnProperty.call(obj, "bar")); // true
haseyuyhaseyuy

通常関数とアロー関数の違い

  • 通常関数はfunctionキーワードを使って定義する
function () {}
  • アロー関数は名前の通り=>を使って定義する
() => {}
haseyuyhaseyuy

thisの指すもの

  • 通常関数の場合、呼び出し元によってthisは変わる
  • ブラウザー内ではwindowオブジェクトがグローバルオブジェクト
  • Node.jsで実行されるスクリプトの場合、globalと呼ばれるオブジェクトがグローバルオブジェクト

通常関数

function regular() {
    return this
}

// グローバルスコープで実行した場合、thisはグローバルオブジェクトを指す
console.log(regular() === global) //=> true

// メソッドとして実行した場合、thisはオブジェクトを指す
const obj = { method: regular }
console.log(obj.method() === obj) //=> true

アロー関数

const arrow = () => {
    return this
}

const lexicalThis = this

console.log(arrow() === lexicalThis) // => true

// メソッドとして実行しても、thisはメソッドが属するオブジェクトを指さない
const obj = { method: arrow}
console.log(obj.method() === obj) // => false
console.log(obj.method() === lexicalThis) // => true

newできるかどうか

  • arrow関数の場合newできない
function regular(){}
const arrow = () => {}

new regular()
new arrow() // => TypeError: arrow is not a constructor

call, apply, bindの振る舞い

  • 通常関数は、call, apply, bindメソッドの第一引数で、その関数のthisを指すオブジェクトを指定することができる
  • アロー関数では、指定しても無視される
function regular() {
    return this
}

const arrow = () => { return this }

const obj = {}

console.log(regular.bind(obj)() === obj) // => true
console.log(arrow.bind(obj)() === obj) // => false

console.log(regular.apply(obj) === obj) // => true
console.log(arrow.apply(obj) === obj) // => false

console.log(regular.call(obj) === obj) // => true
console.log(arrow.call(obj) === obj) // => false

prototypeプロパティの有無

  • 通常関数にはprototypeプロパティがあるが、アロー関数にはない
function regular() {
    return this
}

const arrow = () => { return this }

console.log(typeof regular.prototype) // => obj
console.log(typeof arrow.prototype) // => undefined

argumentsの有無

  • 通常関数は、argumentsで引数リストを参照できる
  • アロー関数は引数リストを参照できない
const arguments = 'hoge'

function regular() {
    console.log(arguments)
}

const arrow = () => {
    console.log(arguments)
}

regular(1, 2, 3) // => [Arguments] { '0': 1, '1': 2, '2': 3 }
// レキシカルスコープ変数のargumentsを参照するだけ
arrow(1, 2, 3) // => hoge
  • アロー関数で引数リストを参照するには、可変長引数を定義する方法がある
const arrow = (...arguments) => {
    console.log(arguments)
}

arrow(1, 2, 3) // => [ 1, 2, 3 ]

引数名の重複

  • 通常関数は引数名の重複は許されるが、アロー関数は許されない
  • 通常関数でもStrictModeの場合は、引数名の重複がエラーになる
function regular(x, x) {}
const arrow = (x, x) => {} // => SyntaxError: Duplicate parameter name not allowed in this context

巻き上げ(hoisting)

  • 通常関数は巻き上げが起きる。つまり関数定義前に呼び出しコードを書いても、関数が実行できる
  • アロー関数は巻き上げが起きない
regular()
function regular(){}

arrow()
const arrow = () => {} // => ReferenceError: Cannot access 'arrow' before initialization
haseyuyhaseyuy

同じ関数名での再定義

  • 通常関数は、同じ名前の関数を定義できる。最後の関数で上書きされる
function regular() {
    console.log(1)
}

function regular() {
    console.log(2)
}

// 同じな目の関数を定義できる。最後の関数で上書きされる
regular()

// varの振る舞いと同じ
var a = 1
var a = 2
console.log(a)
  • アロー関数はletconstの仕様上、同じ関数名で定義を上書きできない
let arrow = () => {}
let arrow = () => {} // => SyntaxError: Identifier 'arrow' has already been declared
haseyuyhaseyuy

カリー化

  • カリー化された関数は、任意の引数を固定した別の関数を作成することができる。これを部分適用という
const add = (x, y) => x + y;
console.log(add(1, 2)); // => 3

// Curryingはf(a, b, c)と呼び出し可能なものをf(a)(b)(c)と呼び出すように変換
// The curried version (both with and without the ES6 syntax)
const add1 = x => y => x + y;
console.log(add1(1)(2)); // => 3

// 2を足す関数で部分的用できる(毎回2を書かなくてい)
const plustTwo = add1(2);
console.log(plustTwo(3)); // => 5
console.log(plustTwo(4)); // => 6