Maison  >  Article  >  interface Web  >  Comment implémenter les caractéristiques grammaticales des membres privés et implémenter les membres privés basés sur les compétences JavaScript_javascript

Comment implémenter les caractéristiques grammaticales des membres privés et implémenter les membres privés basés sur les compétences JavaScript_javascript

WBOY
WBOYoriginal
2016-05-16 15:34:511220parcourir

Avant-propos

Dans le paradigme de programmation orientée objet, l'encapsulation est un concept essentiel, et dans les langages orientés objet traditionnels tels que Java et C, les membres privés sont un moyen important de réaliser l'encapsulation. Cependant, en JavaScript, il n'y a en effet aucune prise en charge des membres privés en termes de fonctionnalités grammaticales, ce qui oblige également les développeurs à utiliser diverses astuces pour implémenter les membres privés dans JS. Ce qui suit présentera l'implémentation actuelle des fonctionnalités de membres privés dans JS. une comparaison de leurs avantages et inconvénients.

Quelques solutions de mise en œuvre existantes

Schéma de dénomination commun

Il est convenu que les noms de membres commençant par un trait de soulignement « _ » sont considérés comme des membres privés. Seules les méthodes membres de classe peuvent être consultées et appelées, et les membres privés ne sont pas autorisés à être consultés en externe. Le code simple est le suivant :

JavaScript

var MyClass = function () {
  this._privateProp = ‘privateProp';
};
MyClass.prototype.getPrivateProp = function () {
  return this._privateProp;
};
var my = new MyClass();
alert(my.getPrivateProp()); // ‘privateProp';
alert(my._privateProp); // 并未真正隐藏,依然弹出 ‘privateProp'

Avantages

Il ne fait aucun doute que la convention de dénomination est la solution la plus simple pour implémenter des membres privés sans aucun travail au niveau du code.
Le débogage est pratique et vous pouvez voir directement les membres privés de l'objet sur la console, ce qui facilite le dépannage.
Bonne compatibilité, supporté par ie6

Pas assez

Il n'y a aucun moyen d'empêcher l'accès externe et les modifications des membres privés. Si un développeur qui ne connaît pas ou ne respecte pas l'accord modifie les attributs privés, il n'y a rien à faire.
Tout le monde doit être forcé ou persuadé de respecter cet accord. Bien entendu, ce n'est pas un gros problème dans une équipe avec des normes de code.

Solution du symbole es6

Dans es6, une fonctionnalité Symbol a été introduite, qui a été introduite pour implémenter les membres privés.
L'idée principale est de générer une clé de chaîne aléatoire et unique pour le nom de chaque membre privé. Cette clé est invisible pour le monde extérieur. La visibilité interne est obtenue grâce à la variable de fermeture de js. L'exemple de code est le suivant :

.

JavaScript

(function() {
   var privateProp = Symbol(); // 每次调用会产生一个唯一的key
   function MyClass() {
     this[privateProp] = ‘privateProp'; // 闭包内引用到这个 key
   }
   MyClass.prototype.getPrivateProp = function () {
     return this[privateProp];
   };
})();
var my = new MyClass();
alert(my.getPrivateProp()); // ‘privateProp';
alert(my.privateProp); // 弹出 undefined,因为成员的key其实是随机字符串

Avantages

Cela compense les lacunes du système de convention de dénomination, et les parties externes ne peuvent pas obtenir accès aux membres privés par les canaux normaux.
La commodité du débogage est acceptable. Généralement, un paramètre de chaîne est transmis au constructeur de symbol et le nom de la propriété privée correspondante sur la console sera affiché comme : Symbol(key)

La compatibilité est bonne et les navigateurs qui ne prennent pas en charge Symbol peuvent être facilement supprimés.

Pas assez

L'écriture est un peu délicate. Une variable de fermeture doit être créée pour chaque membre privé afin que la méthode interne puisse y accéder.
En externe, vous pouvez toujours obtenir le nom de la propriété de symbole de l'instance via Object.getOwnPropertySymbols et obtenir les droits d'accès aux membres privés via ce nom. Ce scénario se produit relativement rarement, et le niveau des développeurs qui connaissent cette approche est considéré comme suffisamment capable de connaître l'impact de leurs actions, donc cette lacune n'est pas une véritable lacune.

Solution es6 WeakMap

Les conteneurs Map et WeakMap ont été introduits dans es6. La plus grande fonctionnalité est que le nom de clé du conteneur peut être n'importe quel type de données. Bien que l'intention initiale ne soit pas d'implémenter l'introduction de membres privés, il peut être utilisé de manière inattendue pour implémenter. fonctionnalités de membre privé.

L'idée principale est de créer un conteneur WeakMap au niveau de la classe pour stocker les membres privés de chaque instance. Ce conteneur est invisible du monde extérieur et visible de l'intérieur via des méthodes internes pour obtenir l'instance sous forme de nom de clé ; . Membres privés de l'instance correspondante sur le conteneur. L'exemple de code est le suivant :

.

JavaScript

(function() {
   var privateStore = new WeakMap(); // 私有成员存储容器
   function MyClass() {
     privateStore.set(this, {privateProp: ‘privateProp'}); // 闭包内引用到privateStore, 用当前实例做 key,设置私有成员
   }
   MyClass.prototype.getPrivateProp = function () {
     return privateStore.get(this).privateProp; 
   };
})();
var my = new MyClass();
alert(my.getPrivateProp()); // ‘privateProp';
alert(my.privateProp); // 弹出 undefined,实例上并没有 privateProp 属性

优点

弥补了命名约定方案的缺陷,外部无法通过正常途径获得私有成员的访问权。
对 WeakMap 做一些封装,抽出一个私有特性的实现模块,可以在写法上相对 Symbol 方案更加简洁干净,其中一种封装的实现可以查看参考文章3。
最后一个是个人认为最大的优势:基于 WeakMap 方案,可以方便的实现保护成员特性(这个话题会在其他文章说到:))

不足

不好调试,因为是私有成员都在闭包容器内,无法在控制台打印实例查看对应的私有成员
待确认的性能问题,根据 es6的相关邮件列表,weakmap 内部似乎是通过顺序一一对比的方式去定位 key 的,时间复杂度为 O(n),和 hash 算法的 O(1)相比会慢不少

最大的缺陷则是兼容性带来的内存膨胀问题,在不支持 WeakMap 的浏览器中是无法实现 WeakMap 的弱引用特性,因此实例无法被垃圾回收。 比如示例代码中 privateProp 是一个很大的数据项,无弱引用的情况下,实例无法回收,从而造成内存泄露。

现有实现方案小结

从上面的对比来看,Symbol方案最大优势在于很容易模拟实现;而WeakMap的优势则是能够实现保护成员, 现阶段无法忍受的不足是无法模拟实现弱引用特性而导致的内存问题。于是我的思路又转向了将两者优势结合起来的方向。

Symbol + 类WeakMap 的整合方案

在 WeakMap 的方案中最大的问题是无法 shim 弱引用,较次要的问题是不大方便调试。

shim 出来的 WeakMap 主要是无法追溯实例的生命周期,而实例上的私有成员的生命周期又是依赖实例, 因此将实例级别的私有成员部分放在实例上不就好了? 实例没了,自然其属性也随之摧毁。而私有存储区域的隐藏则可以使用 Symol 来做。

该方案的提供一个 createPrivate 函数,该函数会返回一个私有的 token 函数,对外不可见,对内通过闭包函数获得, 传入当前实例会返回当前实例的私有存储区域。使用方式如下:

JavaScript

(function() {
   var $private = createPrivate(); // 私有成员 token 函数,可以传入对象参数,会作为原型链上的私有成员
   function MyClass() {
     $private(this).privateProp = ‘privateProp' ; // 闭包内引用到privateStore, 用当前实例做 key,设置私有成员
   }
   MyClass.prototype.getPrivateProp = function () {
     return $private(this).privateProp; 
   };
})();
var my = new MyClass();
alert(my.getPrivateProp()); // ‘privateProp';
alert(my.privateProp); // 弹出 undefined,实例上并没有 privateProp 属性

代码中主要就是实现 createPrivate 函数,大概的实现如下:

JavaScript

// createPrivate.js
function createPrivate(prototype) {
  var privateStore = Symbol('privateStore');
  var classToken = Symbol(‘classToken');
  return function getPrivate(instance) {
     if (!instance.hasOwnProperty(privateStore)) {
       instance[privateStore] = {};
     }
    var store = instance[classToken];
     store[token] = store[token] || Object.create(prototype || {});
     return store[token];
   };
}

上述实现做了两层存储,privateStore 这层是实例上统一的私有成员存储区域,而 classToken 对应的则是继承层次之间不同类的私有成员定义,基类有基类的私有成员区域,子类和基类的私有成员区域是不同的。

当然,只做一层的存储也可以实现,两层存储仅仅是为了调试方便,可以直接在控制台通过Symbol(‘privateStore')这个属性来查看实例各个层次的私有部分。

奇葩的 es5 property getter 拦截方案

该方案纯粹是闲得无聊玩了玩,主要是利用了 es5 提供的 getter,根据 argument.callee.caller 去判断调用场景,如果是外部的则抛异常或返回 undefined, 如果是内部调用则返回真正的私有成员,实现起来比较复杂,且不支持 strict 模式,不推荐使用。 有兴趣的同学可以看看实现。

总结

以上几个方案对比下来,我个人是倾向 Symbol+WeakMap 的整合方案,结合了两者的优点,又弥补了 WeakMap 的不足和 Symbol 书写的冗余。 当然了,我相信随着 JS 的发展,私有成员和保护成员也迟早会在语法层面上进行支持,正如 es6 对 class 关键字和 super 语法糖的支持一样, 只是现阶段需要开发者使用一些技巧去填补语言特性上的空白。

Javascript私有成员的实现方式

总体来讲这本书还是可以的,但看完这本书还留了几个问题一直困扰着我,如js中私有变量的实现,prototype等,经过自己一系列测试,现在终于弄明白了。

很多书上都是说,Javascript是不能真正实现Javascript私有成员的,因此在开发的时候,统一约定 __ 两个下划线开头为私有变量。

后来,发现Javascript中闭包的特性,从而彻底解决了Javascript私有成员的问题。

 function testFn(){ 
    var _Name;//定义Javascript私有成员 
    this.setName = function(name){ 
     _Name = name; //从当前执行环境中获取_Name 
    } 
    this.getName = function(){ 
     return _Name; 
    } 
}// End testFn 
var test = testFn(); 
alert(typeof test._Name === "undefined")//true 
test.setName("KenChen"); 

test._Name 根本访问不到,但是用对象方法能访问到,因为闭包能从当前的执行环境中获取信息。

接下来我们看看,共有成员是怎样实现的

function testFn(name){ 
  this.Name = name; 
  this.getName = function(){ 
   return this.Name; 
  } 
} 
var test = new testFn("KenChen"); 
test.getName(); //KenChen 
test.Name = "CC"; 
est.getName();//CC 

接下来在看看类静态变量是怎样实现的

function testFn(){ 
} 
testFn.Name = "KenChen"; 
alert(testFn.Name);//KenChen 
testFn.Name = "CC"; 
alert(testFn.Name);//CC 

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn