首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >每天一个小技巧:Javascript中定义私有属性(Private Properties) IIFE 实现构造函数实现Class实现原生实现

每天一个小技巧:Javascript中定义私有属性(Private Properties) IIFE 实现构造函数实现Class实现原生实现

作者头像
MudOnTire
发布2020-07-10 11:16:04
1.3K0
发布2020-07-10 11:16:04
举报
文章被收录于专栏:MudOnTireMudOnTireMudOnTire
logo
logo

和很多高级语言不同,JavaScript 中没有 publicprivateprotected 这些访问修饰符(access modifiers),而且长期以来也没有私有属性这个概念,对象的属性/方法默认都是public的。虽然目前 class 的私有属性特性已经进入了 Stage3 实验阶段(Spec),通过 Babel 已经可以使用,并且 Node v12 中也增加了对私有属性的支持,但这并不妨碍我们用 JS 的现有功能实现一个私有属性特性,以加深对这一概念的理解。

私有属性(方法)的意义在于将模块的内部实现隐藏起来,而对外接口只通过public成员进行暴露,以减少其他模块对该模块内部实现的依赖或修改,降低模块的维护成本。

IIFE 实现

IIFE(立即执行函数) 大家应该耳熟能详了,IIFE 经常被用来:

  1. 定义一个自执行的匿名函数
  2. 创建一个局部作用域,避免对全局产生污染

基于以上特性,用 IIFE 可以给一个对象实现简单的私有属性:

let person = (function () {
  // 私有属性
  let _name = "bruce"; 

  return {
    age: 30,
    // getter
    get name() {
      return _name;
    },
    // setter
    set name(val) {
      _name = val;
    },
    greet: function () {
      console.log(`hi, i'm ${_name} and i'm ${this.age} years old`);
    }
  };
})();

测试一下:

console.log(person.name); // 'bruce'
console.log(person._name); // undefined

person.name = "frank";

console.log(person.name); // 'frank'

console.log(Object.keys(person)); // ['age', 'name']

person.greet(); // hi, i'm frank and i'm 30 years old

IIFE 的实现简单易懂,但是只能作用于单个对象,而不能给 Class 或者构造函数定义私有属性。

构造函数实现

利用在构造函数中创建的局部变量可以作为 “私有属性” 使用:

function Person(name, age) {
  // 私有属性
  let _name = name; 
  
  this.age = age;
  this.setName = function (name) {
    _name = name;
  };
  this.getName = function () {
    return _name;
  };
}

Person.prototype.greet = function (){
  console.log(`hi, i'm ${this.getName()} and i'm ${this.age} years old`);
}

测试一下:

const person = new Person("bruce", 30);

console.log(person.getName()); // bruce

person.setName('frank');

console.log(person.getName()); // frank

person.greet(); // hi, i'm frank and i'm 30 years old

看起来还行,但是该实现方式需要在构造函数中定义 gettersetter 方法,这两个方法是绑定在实例上而不是原型上的,如果私有属性增加会导致实例方法暴增,对内存不太友好。

Class实现

Class中实现和构造函数类似,因为JavaScript中的class本质上是构造函数和原型的语法糖,实现如下:

class Person {
  constructor(name, age) {
    // 私有属性
    let _name = name; 
    
    this.age = age;
    this.setName = function (name) {
      _name = name;
    };
    this.getName = function () {
      return _name;
    };
  }

  greet() {
    console.log(`hi, i'm ${this.getName()} and i'm ${this.age} years old`);
  }
}

Class中的实现也会存在和构造函数中一样的问题,而且在 greet() 方法中无法访问 _name,需要通过调用 getter 方法。这和一般意义上的私有属性还是有差别的,真正的私有属性在class内部应该是可以正常访问的,而不仅仅是在构造函数内部可以访问。

原生实现

以上三种实现或多或少都有一些问题,还好在ES2019中已经增加了对 class 私有属性的原生支持,只需要在属性/方法名前面加上 '#' 就可以将其定义为私有,并且支持定义私有的 static 属性/方法。例如:

class Person {
  // 私有属性
  #name; 

  constructor(name, age) {
    this.#name = name;
    this.age = age;
  }

  greet() {
    console.log(`hi, i'm ${this.#name} and i'm ${this.age} years old`);
  }
}

测试一下:

const person = new Person("bruce", 30);

console.log(person.name); // undefine

person.greet(); // hi, i'm bruce and i'm 30 years old

更多语法可以参考 MDN: Private class field

我们可以去babel里面将原生的代码转换一下,看看babel的polyfill是怎么实现的:

babel
babel

发现主要思路居然使用 WeakMap。。好吧,还是太年轻。格式化后的polyfill代码贴在下面,有兴趣的同学可以研究一下:

"use strict";

function _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = privateMap.get(receiver);
  if (!descriptor) {
    throw new TypeError("attempted to get private field on non-instance");
  }
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}

function _classPrivateFieldSet(receiver, privateMap, value) {
  var descriptor = privateMap.get(receiver);
  if (!descriptor) {
    throw new TypeError("attempted to set private field on non-instance");
  }
  if (descriptor.set) {
    descriptor.set.call(receiver, value);
  } else {
    if (!descriptor.writable) {
      throw new TypeError("attempted to set read only private field");
    }
    descriptor.value = value;
  }
  return value;
}

var _name = new WeakMap();

class Person {
  constructor(name, age) {
    _name.set(this, {
      writable: true,
      value: void 0,
    });

    _classPrivateFieldSet(this, _name, name);

    this.age = age;
  }

  greet() {
    console.log(
      "hi, i'm "
        .concat(_classPrivateFieldGet(this, _name), " and i'm ")
        .concat(this.age, " years old")
    );
  }
}

每天一个小技巧(Tricks by Day),量变引起质变,希望你和我一起每天多学一点,让技术有趣一点。所有示例将会汇总到我的 tricks-by-day github 项目中,欢迎大家莅临指导 ?

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • IIFE 实现
  • 构造函数实现
  • Class实现
  • 原生实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档