Open12
JavaScript勉強
不変オブジェクト
- 生成後に状態が変更されないオブジェクト
メソッド名 | プロパティの追加 | プロパティの削除 | プロパティの削除 |
---|---|---|---|
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);
スコープ
- スコープとは名前の有効範囲のこと
- グローバルスコープと関数スコープ(ローカルスコープとも呼ぶ)がある
var x = 1;
function f (){
console.log(x); // => undefined
var x = 2;
console.log(x); // => 2
}
f();
- 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();
レキシカルスコープ
- シンボルを表す実体が、
var x = 7;
function g(){ return x; }
function f(){
var x = 9;
return g();
}
console.log(f()); // => 7
シャドーイング
スコープの小さい同名の変数でスコープの大きい名前を隠すこと
var n = 1;
function f() {
n = 2;
console.log(n); // 2
}
f();
this参照
- トップレベルのコードのthis参照はグローバルオブジェクトを参照
- 関数内のthisの参照は関数の呼び出し方法で異なる
関数の呼び出し方法 | this参照の参照先オブジェクト |
---|---|
コンストラクタ呼び出し | 生成したオブジェクト |
メソッド呼び出し | レシーバオブジェクト |
applyあるいはcall呼び出し | applyまたはcall引数で指定したオブジェクト |
それ以外の呼び出し | グローバルオブジェクト |
var obj = {
x:3,
doit:function(){
console.log(this.x);
}
}
// メソッド呼び出し
obj.doit(); //3
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
プロトタイプ汚染
- 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
通常関数とアロー関数の違い
- 通常関数は
function
キーワードを使って定義する
function () {}
- アロー関数は名前の通り
=>
を使って定義する
() => {}
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
同じ関数名での再定義
- 通常関数は、同じ名前の関数を定義できる。最後の関数で上書きされる
function regular() {
console.log(1)
}
function regular() {
console.log(2)
}
// 同じな目の関数を定義できる。最後の関数で上書きされる
regular()
// varの振る舞いと同じ
var a = 1
var a = 2
console.log(a)
- アロー関数は
let
やconst
の仕様上、同じ関数名で定義を上書きできない
let arrow = () => {}
let arrow = () => {} // => SyntaxError: Identifier 'arrow' has already been declared
カリー化
- カリー化された関数は、任意の引数を固定した別の関数を作成することができる。これを部分適用という
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