Những điều dễ nhầm lẫn trong javascript

2 minute read

Những điều dễ nhầm lẫn trong javascript

this

this là đại diện cho object gọi hàm. Tuy nhiên, với trường hơp không chỉ định object gọi hàm thì mặc định object này sẽ là window object. Nhưng mà trường hợp bạn bật mode use strict lên thì kết quả sẽ là undefined

    const foo = {
      bar: function() {
        // caller is foo.bar()
        console.log(this); // foo object
          
        const baz = function() {
          // due to caller is baz(), so object wasn't set
          console.log(this); // window object
        }
        baz();
        
        const qux = function() {
          'use strict';
          // due to caller is qux(), so object wasn't set + strict mode on
          console.log(this); // undefined
        }
        qux();
      }
    }
    foo.bar();

また、classの場合はuse strictの指定なしでも厳格モードになる。以下は引用

this に値が付けられずに静的メソッドまたはプロトタイプメソッドが呼ばれると、this の値はメソッド内で undefined になります。たとえ “use strict” ディレクティブがなくても同じふるまいになります。なぜなら、class 本体の中のコードは常に Strict モードで実行されるからです。 ```javascript class Foo { bar() { // 呼び出し元はfoo.bar() console.log(this); // Foo object

    const baz = function() {
      // 呼び出し元はbaz()でオブジェクトの指定がないかつクラス内なので厳格モード
      console.log(this); // undefined
    }
    baz();
  }
}
const foo = new Foo();
foo.bar(); ```

call、apply、bindでthisを指定することができる

    const foo = {
      bar: function() {
        const baz = function() {
          // 呼び出し元はbaz.apply(this)。
          // applyに指定しているthisの呼び出し元はfoo.bar()なのでfooとなる
          console.log(this) // foo object
        }
        baz.apply(this)
    
        const qux = function() {
          // 呼び出し元はqux()で、quxにはbind(this)が指定されている。
          // bindに指定されているthisの呼び出し元はfoo.bar()なのでfooとなる
          console.log(this) // foo object
        }.bind(this)
        qux();
      }
    }
    foo.bar();

また、アロー関数内のthisはアロー関数が定義されているスコープに基づく。以下は引用

アロー関数は、そのアロー関数が定義されているスコープに基づいて “this” を確立する

    const foo = {
      bar: function() {
        const baz = () => {
          // 呼び出し元はbaz()
          // bazが宣言されているbar()の呼び出し元はfoo.bar()なのでfooとなる
          console.log(this); // foo object
        }
        baz();
      }
    }
    foo.bar();

シャローコピー

以下は引用の翻訳。引用にあるコピー操作ではシャローコピー(浅いコピー)となることがあり、コピー先を変更することでコピー元に影響を与えることがある

JavaScript では、標準的な組み込みのオブジェクトコピー操作 (スプレット構文, concat(), slice(), Array.from(), Object.assign(), Object.create()) はすべて、ディープコピーではなくシャローコピーを作成します。

以下の例では、nameは変わらないが、schoolは書きかわる

    const user = {
      name: "user",
      school: { name: "school" }
    }
    
    const user2 = {...user}
    
    user2.name = "user2"
    user2.school.name = "school2"
    
    console.log(user); // {name: "user", school: {name: "school2"} }
    console.log(user2); // {name: "user2", school: {name: "shool2"} }

ディープコピー(深いコピー)を使用することで回避する。以下は引用

Javascript のオブジェクトのディープコピーを作成する一つの方法は、そのオブジェクトが シリアライズ 可能であれば JSON.stringify() でオブジェクトを JSON 文字列に変換し、 JSON.parse() で文字列から(完全に新しい) Javascript のオブジェクトに変換することです。

new演算子

クラスでなく、関数に対しても使うことができる。関数がnew演算子で呼ばれるとき、コンストラクタ関数という。コンストラクタ関数として呼び出すとオブジェクトが生成される

    const Foo = function() {
      this.foo = "bar"
      return 1;
    }
    
    // 通常の関数として呼び出す
    console.log(Foo()); // 1 
    
    // コンストラクタ関数として呼び出す
    console.log(new Foo()); // Foo{foo: "bar"}オブジェクト

以下はnew演算子のドキュメント

構文の引用

new constructor[([arguments])]
引数 constructor
オブジェクトインスタンスの型を指定するクラスまたは関数です。

解説の引用

  1. 空のプレーンな JavaScript オブジェクトを生成します。
  2. 新しいオブジェクトにプロパティ (proto) を追加し、コンストラクター関数のプロトタイプオブジェクトに結びつけます。
  3. 新しく生成されたオブジェクトインスタンスを this コンテキストとして結びつけます。 (すなわち、コンストラクター関数内の this へのすべての参照は、最初のステップで作成されたオブジェクトを参照するようになります。)
  4. 関数がオブジェクトを返さない場合は this を返します。

コンストラクタ関数の戻り値として、オブジェクトを返す場合とそうでない場合で挙動が異なる

オブジェクトを返さない例

    function Foo() {
      this.bar = 1;
    }
    
    const foo = new Foo();
    console.log(foo); // Foo { bar: 1 }

オブジェクトを返す例

    function Foo() {
      this.bar = 1;
      return { baz: 1 };
    }
    
    const foo = new Foo();
    console.log(foo); // { baz: 1 }
    console.log(foo.bar) // undefined

プロトタイプベース

objectやarrayをconsole.logしたときに見かける[[Prototype]]__proto__の正体。知らないと困るというケースがあるかどうかはわからないが、以下あたりを1度読んでおくと雰囲気がわかる

図で理解するJavaScriptのプロトタイプチェーン - Qiita

[継承とプロトタイプチェーン - JavaScript MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#inheritance_with_the_prototype_chain)

ただ、classから作ったオプジェクトもプロトタイプベースであることは変わらないので理解はしておくべき

変数や関数の巻き上げ(Hoisting)

以下はドキュメントの引用

変数や関数の宣言が物理的にコードの先頭に移動されることを示唆していますが、実際にはそうではありません。変数や関数の宣言はコンパイル時にメモリに格納されますが、コード内で入力された場所は変わりません

ただし

定義のみが巻き上げられ、初期化はそうでありません。変数が使用された後に定義や初期化された場合、値は undefined になります

関数の場合、宣言の前に読んでもエラーにはならない

    foo();
    
    function foo() {
      console.log("foo");
    }

変数の場合

    foo(); // Uncaught TypeError: foo is not a function
    
    var foo = () => console.log("foo");

引用のとおり定義のみが巻き上げられるので、実行時は以下のイメージとなり、変数としては存在するが、関数として呼ぼうとするとエラーになる

    var foo;
    
    foo(); // この時点でfoo は undefined
    
    foo = () => console.log("foo")

その他

オブジェクトを返すアロー関数の書き方。以下は引用

オブジェクトリテラル式を返す場合は、式の周りに括弧が必要です。 ```javascript // NG const foo = () => {bar: “baz”}

// OK
const foo = () => ({bar: "baz"}) ```    
Theo zenn

Leave a Comment