フロントエンドで定数を定義する方法

4590 ワード

多くのプログラミング言語はconstキーワード宣言定数を提供し、ES 6でもconstを提供しているが、フロントエンドのconstは他のプログラミング言語とは異なり、宣言された変数が定数であることを意味しない.const b = {}を使用して定数bを宣言したが、b.a = 1を使用してオブジェクトbを変更してもエラーはなく、定数と思っていた実際に変数のオブジェクトを変更した.
どうしてこんなことになったの?
実際には、constで定義された変数は、実際のデータを指すポインタで保存され、基本データ型String、Boolean、Number、undefined、null、Symbolでは、スタックメモリに格納された単純なデータセグメントは、値によってアクセスされ、定数に等しい.しかし、参照データ型に対してconstは、スタックメモリに保存されているオブジェクトへのポインタが一定に保たれることを保証するだけでなく、言い換えればconstは変数が常に同じオブジェクトを指すことを保証することができ、オブジェクトの修正はできない.
だから、フロントエンドでいったいどのように定数を実現するのか!
Object.freeze Object.freezeは、オブジェクトをフリーズすることができ、属性を追加および削除することはできません.また、オブジェクトの既存の属性は、列挙できない、構成できない、書き込み不可です.なお,この方法ではオブジェクトを浅くフリーズさせるしかなく,その内部属性がオブジェクトである場合でも改ざん可能であり,完全フリーズを実現するには,以下のような操作が必要である.
function deepConst(data){
  Object.freeze(data);
  for(let key in data){
    let prop = data[key];
    if(!data.hasOwnProperty(key) || !(typeof prop === "object") || Object.isFrozen(prop)){
      continue;
    }
    deepConst(prop);
  }
}

Object.defineProperty、Object.preventExtensions、Object.seal
Object.preventExtensions
この方法では、オブジェクトを拡張不可能にすることができます.すなわち、オブジェクトは新しい属性を追加できませんが、オブジェクトの元の属性は削除または変更できます.また、属性の値がオブジェクトの場合、属性を追加できないように設定されていますが、その属性値はオブジェクトの属性であり、以前から属性を追加できます.
例を挙げます.
let obj = {a:1,b:2,c:{d:3}};
Object.preventExtensions(obj);
obj.d = 1;
obj.a = 2;
delete obj.b;
obj.c.e = 10;
//  {a:1,c:{d:3,e:10}
console.log(obj);

Object.seal Object.preventExtensionsと比較して、この方法は同様にオブジェクトを新しい属性を追加できないようにすることができ、この方法はオブジェクトの属性の削除を禁止する.同様に、属性の値がオブジェクトの場合も、属性の値は新しい属性を追加したり削除したりすることができます.
例を挙げる
let obj = {a:1,b:2,c:{d:3}};
Object.seal(obj);
obj.e = 10;
delete obj.a;
delete obj.c.d;
obj.c.f = 10;
//  {a:1,b:2,c:{f:10}
console.log(obj);

Object.defineProperty Object.defineProperty(obj, prop, descriptor)はMVVMで異彩を放ち、それを使って対象を完全に凍結することもできる.コードを書く前に、writable、Configurableのすべての内容を知る必要があることを理解しておくことが、今回の凍結の鍵です.
writable
オブジェクト属性の値が書き換えられるかどうかはtrueで許可され、falseで禁止され、デフォルトではfalseです.属性の値がオブジェクトである場合、書き換えられないように設定されているにもかかわらず、その属性がオブジェクトである値は書き換えられます.
例を挙げます.
let obj = {a:1,b:2,c:{d:3}};
Object.defineProperty(obj,"a",{writable:true});
Object.defineProperty(obj,"b",{writable:false});
Object.defineProperty(obj,"c",{writable:false});
Object.defineProperty(obj,"e",{writable:false});
obj.a = 2;
obj.b = 3;
obj.c.d = 4;
//   2, a        
console.log(obj.a);
//     2, b         
console.log(obj.b);
//     {d:4},         ,          ,               。
console.log(obj.c);

Configurable configurableプロパティは、オブジェクトのプロパティが削除できるかどうか、およびwritableプロパティ以外のプロパティが変更できるかどうかを示します.trueに対してfalseの変更を許可することは変更を禁止することを示し、デフォルトはfalseであり、属性の値がオブジェクトである場合、属性が変更できないにもかかわらず、その属性がオブジェクトである属性は変更することができる.例を挙げる
let obj = {a:1,b:2,c:{d:3}};
Object.defineProperty(obj,"a",{configurable:true});
Object.defineProperty(obj,"b",{configurable:false});
Object.defineProperty(obj,"c",{configurable:false});
delete obj.a;
delete obj.b;
delete obj.c;
//   {b:2,c:{}},         ,            ,                。
console.log(obj);

上記の3つの方法を単独で出すと、オブジェクトを定数に変えることは完璧ではありませんが、組み合わせて定数を生成することができます.
function deepConst(data){
  if (!data || typeof data !== 'object') {
    return;
  }
  //Object.preventExtensions(data);     
  Object.seal(data);
  Object.keys(data).forEach(function(key) {
    unWriteConfig(data, key, data[key]);
  });
}
function unWriteConfig(data, key, val) {
  deepConst(val);
  Object.defineProperty(data, key, {
    writable:false,
    configurable:false
  });
}

Proxy Proxyターゲットオブジェクトの前にブロックが行われ、外部からのオブジェクトへのアクセスと変更はこのブロックを通過する必要があるため、ブロックを操作してオブジェクトへのアクセスと変更を制御することができます.Proxyでサポートされているブロック操作は多く、以下に文章に関連する操作のみを列挙します.Proxyをもっと詳しく知りたい場合は、この文章を参照してください.
function createDeepProxy(target) {
  function makeHandler() {
    return {
      set(target, key, value, receiver) {
        return false;
      },
      deleteProperty(target, key) {
        return false;
      }
    }
  }
  
  function proxify(obj, path) {
    for(let key of Object.keys(obj)) {
      if(typeof obj[key] === 'object') {
        obj[key] = proxify(obj[key], [...path, key]);
      }
    }
    let p = new Proxy(obj, makeHandler());
    return p;
  }
  return proxify(target, []);
}