深入理解对象的私有和静态属性

87 阅读3分钟

JavaScript 的对象构建和面向对象的编程模式。

image.png

  • 先看一个正常的属性创建有三种方式: 类 class, 对象字面量, 函数构造式
// 示例1:类class
class WidgetA {
  constructor() {
    this.appName =  "天气应用"
  }
  getName(){
    return this.appName;
  }
}
var widget1 = new WidgetA();
console.log(widget1.appName); // 返回 “天气应用”
console.log(widget1.getName()); // 返回 “天气应用”

// 示例2:对象字面量
var WidgetB = {
  appName : "天气应用",
  getName : function (){
    return this.appName;
  }
}

console.log(WidgetB.appName); // 返回 “天气应用”
console.log(WidgetB.getName()); // 返回 “天气应用”

// 示例3:函数构造式
function WidgetC(){
  this.appName = "天气应用";
  this.getName = function (){
    return "天气应用";
  };
}

var widget3 = new WidgetC();
console.log(widget3.appName); // 返回 “天气应用”
console.log(widget3.getName()); // 返回 “天气应用”
  • 如何创建私有属性?
  1. 用 # 符号创建私有属性 根据最新的ES13(262.ecma-international.org/13.0/) 规范,我们可以通过 # 符号,来定义一个私有的属性。
class WidgetD {
  #appName;
  constructor(){
      this.#appName = "天气应用";
  }
  getName(){
    return this.#appName;
  } 
}

var widget4 = new WidgetD();
console.log(widget4.appName); // 返回 undefined
console.log(widget4.getName()); // 返回 “天气应用”
  1. 在 # 问世之前,工程师们是怎么实现私有属性的。主要有闭包、WeakMap 和 Symbol 这三种方式 2.1 用闭包和 IIFE 创建私有属性
// 对象字面量
var WidgetE;
(function(){
  var appName = "天气应用";
  WidgetE = {
    getName: function(){
      return appName;
    }
  };
}());
WidgetE.appName; // 返回 undefined
WidgetE.getName(); // 返回 “天气应用”

2.2 通过构造函数的方式,构造私有属性

// 构造函数
function WidgetF() {
  var appName = "天气应用";
  this.getName = function(){
    return appName;
  }
}
var widget6 = new WidgetF();
console.log(widget6.appName); // 返回 undefined
console.log(widget6.getName()); // 返回 “天气应用”

上面 例子中还有一个问题,就是我们每次在创建一个新对象的时候,私有属性都会被重新创建一次,这样就会造成重复工作和冗余内存。解决这个问题的办法就是把通用的属性和功能赋值给 prototype,这样通过同一个构建者创建的对象,可以共享这些隐藏的属性

function WidgetG() {
  var appName = "天气应用";
  this.getName = function(){
    return appName;
  }
}
//通用属性和功能赋值给 prototype, 防止私有属性重新创建,造成重复工作和内存冗余
WidgetG.prototype = (function(){
  var model = "支持安卓";
  return {
    getModel: function(){
      return model;
    }   
  }
}());
var widget7 = new WidgetG();
console.log(widget7.getName()); // 返回 “天气应用”
console.log(widget7.getModel()); // 返回 “支持安卓”

2.3 用 WeakMap 创建私有属性 WeakMap,它的特点是只接受对象作为键名,键名是弱引用,键值可以是任意的。

var WidgetH;
{
  let privateProps = new WeakMap();
  
  WidgetH = function(){
    privateProps.set(this,{appName : "天气应用"});
  }
  
  WidgetH.prototype.getName = function(){
    return privateProps.get(this).appName;
  }
}
  
var widget8 = new WidgetH();
console.log(widget8.appName); // 返回 undefined
console.log(widget8.getName()); // 返回 “天气应用”

2.4 用 Symbol 创建私有属性 Symbol 也是在 ES6 引入的一个新的数据类型,我们可以用它给对象的属性的键名命名

var WidgetI;
{
  let privateProps = Symbol();
  
  WidgetI = function(){
    this[privateProps] = {appName : "天气应用"};
  }
  
  WidgetI.prototype.getName = function(){
    return this[privateProps].appName;
  }
}
  
var widget9 = new WidgetI();
console.log(widget9.getName()); // 返回 “天气应用”
  • 如何创建静态属性? 静态的属性是属于构造函数的属性,而不是构造对象实例的属性
  1. 创建公开静态属性
class WidgetJ {
  static appName = "天气应用";
  static getName(){
    return this.appName;
  } 
}

console.log(WidgetJ.appName); // 返回 “天气应用”
console.log(WidgetJ.getName()); // 返回 “天气应用”

var widget10 = new WidgetJ();
//公开静态属性只能作用于 class 本身
console.log(widget10.appName); // 返回 undefined
console.log(widget10.getName()); // 返回 undefined
  1. 创建私有静态属性
class WidgetM {
  // 通过 static 和 # 组合 创建私有静态属性
  static #appName = "天气应用";
  static staticGetName(){
    return WidgetM.#appName; 
  }
  instanceGetName(){
    return WidgetM.#appName; 
  }
}

console.log(WidgetM.staticGetName()); // 返回 “天气应用”
console.log(WidgetM.appName()); // 返回 “天气应用”

var widget13 = new WidgetM();
console.log(widget13.instanceGetName()); // 返回 “天气应用”