基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式_javascript技巧

前言

在面向对象的编程范式中,封装都是必不可少的一个概念,而在诸如 Java,C++等传统的面向对象的语言中, 私有成员是实现封装的一个重要途径。但在 JavaScript 中,确没有在语法特性上对私有成员提供支持, 这也使得开发人员使出了各种奇技淫巧去实现 JS 中的私有成员,以下将介绍下目前实现 JS 私有成员特性的几个方案以及它们之间的优缺点对比。

现有的一些实现方案

约定命名方案

约定以下划线'_'开头的成员名作为私有成员,仅允许类成员方法访问调用,外部不得访问私有成员。简单的代码如下:

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'

优点

毫无疑问,约定命名是最简单的私有成员实现方案,没有代码层面上的工作。
调试方便,能够在控制台上直接看到对象上的私有成员,方便排查问题。
兼容性好,ie6+都支持

不足

无法阻止外部对私有成员的访问和变更,如果真有不知道或者不遵守约定的开发人员变更私有属性,也是无能为力。
必须强制或说服大家遵守这个约定,当然这个在有代码规范的团队中不是什么太大的问题。

es6 symbol 方案

在 es6中,引入了一个 Symbol 的特性,该特性正是为了实现私有成员而引入的。
主要的思路是,为每一个私有成员的名称产生一个随机且唯一的字符串key,这个 key 对外不可见,对内的可见性则是通过 js 的闭包变量实现,示例代码如下:

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其实是随机字符串

优点

弥补了命名约定方案的缺陷,外部无法通过正常途径获得私有成员的访问权。
调试便捷程度上可以接受,一般是通过给 symbol 的构造函数传入一个字符串参数,则控制台上对应的私有属性名称会展示为:Symbol(key)

兼容性不错,不支持 Symbol的浏览器可以很容易的 shim 出来。

不足

写法上稍显别扭,必须为每一个私有成员都创建一个闭包变量让内部方法可以访问。
外部还是可以通过 Object.getOwnPropertySymbols的方式获取实例的 symbol 属性名称,通过该名称获得私有成员的访问权。这种场景出现得比较少,且知道这种途径的开发人员水平相信都是有足够的能力知道自己的行为会有什么影响,因此这个不足点也算不上真正意义的不足。

es6 WeakMap 方案

在 es6 中引入了 Map, WeakMap 容器,最大的特点是容器的键名可以是任意的数据类型,虽说初衷不是为了实现私有成员引入,但意外的可以被用来实现私有成员特性。

主要的思路是,在类的级别上创建一个 WeakMap 容器,用于存储各个实例的私有成员,这个容器对外不可见,对内通过闭包方式可见;内部方法通过将实例作为键名获取容器上对应实例的私有成员,示例代码如下:

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 

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索javascript语法
, javascript基本语法
javascript私有成员
javascript私有变量、javascript私有方法、javascript 私有属性、javascript 私有函数、子类访问父类私有成员,以便于您获取更多的相关知识。

时间: 2024-10-28 11:50:11

基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式_javascript技巧的相关文章

2016年最热门的15 款代码语法高亮工具,美化你的代码_javascript技巧

前言: 代码高亮很有用,特别是在需要在网站或者blog中显示自己编写的代码的时候,或者给其他人查看或调试语法错误的时候.我们可以将代码高亮,以便阅读者可以十分方便的读取代码块,增加用户阅读代码的良好体验. 语法高亮是文本编辑器用来显示文本的,特别是源代码,根据不同的类别来用不同的颜色和字体显示.这个功能有助于编写结构化的语言,比如编程语言,标记语言,这些语言的语法错误显示是有区别的.语法高亮并不会影响文本自身的意义,而且能很好的符合人们的阅读习惯. 目前,有很多免费而且有用的代码高亮脚本.这些脚

JavaScript高级程序设计(第三版)学习笔记6、7章_javascript技巧

第6章面向对象的程序设计 对象 1.数据属性 configurable表示能否通过delete删除属性从而重新定义属性能否修改属性的特性或能否把属性修改为访问器属性默认为true enumerbale表示能否通过for-in访问属性默认true writable表示能否修改属性值默认true value数据存储位置默认undefined 修改默认属性特性Object.defineProperty()接收三个参数属性所在对象属性名描述符对象描述符对象属性必须是configurable.enumer

探究JavaScript中的五种事件处理程序方式_javascript技巧

我们知道JavaScript与HTML之间的交互是通过事件实现的.事件最早是在IE3和Netscape Navigator 2中出现的,当时是作为分担服务器运算负载的一种手段. 通俗地理解,事件就是用户或浏览器自身执行的某种操作.而事件处理程序即为响应某个事件的函数.抽出主干,即事件处理程序为函数.  我们又把事件处理程序称为事件侦听器.  事件处理程序是以"on"开头的,因此对于事件on的时间处理程序即为onclick.时间处理程序在JavaScript中大致有五种,下面会根据这五种

javascript动态添加、修改、删除对象的属性与方法详解_javascript技巧

现在介绍如何为一个对象添加.修改或者删除属性和方法.在其他语言中,对象一旦生成,就不可更改了,要为一个对象添加修改成员必须要在对应的类中修改,并重新实例化,而且程序必须经过重新编译.JavaScript 中却非如此,它提供了灵活的机制来修改对象的行为,可以动态添加.修改.删除属性和方法.例如首先使用类Object来创建一个空对象user:var user=new Object(); 1.添加属性这时user 对象没有任何属性和方法,显然没有任何用途.但可以为它动态的添加属性和方法,例如:user

javascript超过容器后显示省略号效果的方法(兼容一行或者多行)_javascript技巧

javascript超过容器后显示省略号效果 在实际的项目中,由于文字内容的长度不确定性和页面布局的固定性,难免会出现文字内容超过div(或其他标签,下同)区域的情况,此时比较好的做法就是当文字超过限定的div宽度后自动以省略号(-)显示,这样,按照习惯,人们都会知道这儿有文字被省略了.css中有个属性叫做text-overflow:ellipsis;比如使用css可以这样写: {width:27em; white-space:nowrap; text-overflow:ellipsis; -o

基于OO的动画附加插件,可以实现弹跳、渐隐等动画效果 分享_javascript技巧

前言:前段时间一直都好忙也就好久没有写些东西了,最近手上的事刚好告些段落了,把以前空写的写插件都拿出来分享下吧,希望对大家有些帮助,也希望有高手能帮忙指点下我的写不足和错误,一直以来自己写的东西都是在用,性能方面个人只能尽量靠近问题还很多--真心求指点:插件简介:执行渐隐等动画效果,可以这个插件为一个附加插件 可以配合前面我发的一个弹出层 等等之类的东西用增加js展示的趣味性,使用方法:在下面的js代码里面前面写了,大家可以看看直接复制粘贴就可以用了有问题可以联系我</pJS代码如下 展示方式可

JavaScript截取字符串的Slice、Substring、Substr函数详解和比较_javascript技巧

在JavaScript中,提取子字符串主要是通过 Slice.Substring.Substr 三个方法之一. 复制代码 代码如下: // slice // 语法: string.slice(start [, stop])"Good news, everyone!".slice(5,9); // 'news' // substring // 语法: string.substring(indexA [, indexB])"Good news, everyone!".s

js语法学习之判断一个对象是否为数组_javascript技巧

1,真正的数组的判断方法 javascript中最简单的声明数组方法为: var a = []; 判断是否为数组的最直接的方法为: 复制代码 代码如下: a instanceof Array //true a .constructor == Array //true 这里涉及到一个instanceof语法,instanceof是一个云算符,与"+-*/"一样,它的语法如下: result = obj intanceof class 是用来判断一个对象是否是某个class的一个实例,运算

基于BootStrap Metronic开发框架经验小结【四】Bootstrap图标的提取和利用_javascript技巧

在前面的一篇随笔基于BootStrap Metronic开发框架经验小结[一]框架总览及菜单模块的处理,介绍了菜单模块的处理,主要介绍如何动态从数据库里面获取记录并构建菜单列表.其中菜单信息的图标样式,也是从数据库里面获取的,因此要求我们能够动态取得Bootstrap里面的各种图标定义了.本篇主要介绍如何提取Bootstrap的图标信息,存储到数据库里面为我所用. 1.菜单的显示及各种Bootstrap图标 我们从下图可以看到,为了菜单的美观,每个菜单项(这里分了三级菜单)都有一个图标,虽然大小