js的对象系统并没有特别鼓励或强制信息隐藏。所有的属性名都是一个字符串,任意一个程序都可以简单地通过访问属性名来获取相应的对象属性。例如,for...in循环、ES5的Object.keys()和Object.getOwnPropertyNames()函数等特性都能轻易地获取对象的所有属性名。

怎么处理私有属性

编码规范

js程序员诉诸于编码规范,而不是任何绝对的强制机制。例如,一些程序员使用命名规范给私有属性名前置或后置一个下划线字符(_)。这并没有强制隐藏,而只是表明对对象的正确行为操作的一个建议。用户不应该检查或修改该属性,以便对象仍然能自由地改变其实现。

使用闭包

程序员需要更高强度的信息隐藏。例如,一些安全敏感的平台或应用程序框架。它们希望发送对象到未授信的、缺乏对该对象内部风险干预的应用程序。强制信息隐藏能够派上用场的另外一个情形是频繁使用的程序库。在这些程序中,当粗心的用户不小心地依赖或干扰了实现细节,就会引入不可预知的BUG。
上面这些情况,js为信息隐藏提供了一种非常可靠的机制-闭包。
闭包是一种简朴的数据结构。它们将数据存储到封闭的变量中而不提供对这些变量的直接访问。获取闭包内部结构的唯一方式是该函数显式地提供获取它的途径。换句话说,对象和闭包具有相反的策略:对象的属性会被自动暴露出去,闭包中的变量会被自动隐藏起来。
可以利用闭包的特性在对象中存储真正的私有数据。不是将数据作为对象的属性来存储,而是在构造函数中以变量的方式来存储它,并将对象的方法转变为引用这些变量的闭包。

  1. function User(name,pwd){
  2. this.toString=function(){
  3. return '[User '+name+']';
  4. };
  5. this.checkPwd=function(pwd){
  6. return hash(pwd)===pwd;
  7. }
  8. }

当使用上面的代码如

  1. var u=new User('wengxuesong','asdfasdf');
  2. u.toString();//'[User wengxuesong]'

执行环境、活动对象及作用域链

首先是构造函数的运行,这时将创造了两个环境:全局环境、User环境。每个环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。在WEB浏览器中全局环境关联的是window对象。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
第一次调用User函数时,会创建一个包含this,arguments,name,pwd的活动对象。全局执行环境的变量对象在User执行环境的作用域链中处于第二位。当使用new时,this指向生成的实例对象。
对应的图示如下:

与其它的实现不同,该实现的toString和checkPwd方法是以变量的方式来引用name和pwd变量的,而不是以this属性的方式来引用。现在,User的实例根本不包含任何实例属性,因此外部代码不能直接访问User实例的name和pwd变量。

缺点:

为了让构造函数中的变量在使用它们的方法的作用域内,这些方法必须置于实例对象中。这将导致实例包含过多的方法副本,占用内存。但信息隐藏如果更重要,这占额外的代价是值得的。

提示

  • 闭包变量是私有的,只能通过局部引用获取

  • 将局部变量作为私有数据从而通过方法实现信息隐藏

附录一:for...in循环

for...in语句是一种精准的迭代语句,可以用来枚举对象的属性。

语法

  1. for(property in expression) statement

示例

  1. var obj={a:10,b:20,c:30}
  2. for(var prop in obj){
  3. console.log(prop);
  4. }
  5. /*
  6. 'a'
  7. 'b'
  8. 'c'
  9. */

上面代码显示了所有obj对象的属性,每次执行循环时,都会将obj对象中存在的一个属性名赋值给变量prop。这个过程一直持续到对象中的所有属性都被枚举一遍为止。
其中var不是必需的,但最好加上,可以确保变量是局部的。
如果迭代对象是null或undefined,会抛出错误。
ES5中不会抛出错误,而只是不执行循环体。

建议

在使用for...in循环前先检测该对象的值不是null或undefined。

附录二:Object.keys方法

来自mozilla.org社区
Object.keys()方法返回对象的可枚举属性数组,顺序和for...in循环一样(不同之处在于for...in循环也会遍历原型对象中的属性)。

语法

  1. Object.keys(obj);

参数

obj:想要枚举属性的对象

示例

  1. var arr=['a','b','c'];
  2. console.log(Object.keys(arr));//['0','1','2']
  3.  
  4. //类数组
  5. var obj={0:'a',1:'b',2:'c'};
  6. console.log(Object.keys(obj));//['0','1','2']
  7.  
  8. //类数组,包含随机索引
  9. var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
  10. console.log(Object.keys(an_obj)); //['2', '7', '100']
  11.  
  12. //getFoo为非可枚举属性
  13. var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } });
  14. my_obj.foo = 1;
  15.  
  16. console.log(Object.keys(my_obj)); // console: ['foo']

  

注意

在ES5中,如果参数不是一个object类型,会导致一个类型错误。ES6中,则会把不是object的参数转化为obj

  1. Object.keys("foo");
  2. // TypeError: "foo" is not an object (ES5 code)
  3. Object.keys("foo");
  4. // ["0", "1", "2"] (ES6 code)

兼容版本

  1. if (!Object.keys) {
  2. Object.keys = (function() {
  3. 'use strict';
  4. var hasOwn = Object.prototype.hasOwnProperty,
  5. hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
  6. dontEnums = [
  7. 'toString',
  8. 'toLocaleString',
  9. 'valueOf',
  10. 'hasOwnProperty',
  11. 'isPrototypeOf',
  12. 'propertyIsEnumerable',
  13. 'constructor'
  14. ],
  15. dontEnumsLength = dontEnums.length;
  16. return function(obj) {
  17. if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
  18. throw new TypeError('Object.keys called on non-object');
  19. }
  20. var result = [], prop, i;
  21. for (prop in obj) {
  22. if (hasOwn.call(obj, prop)) {
  23. result.push(prop);
  24. }
  25. }
  26. if (hasDontEnumBug) {
  27. for (i = 0; i < dontEnumsLength; i++) {
  28. if (hasOwn.call(obj, dontEnums[i])) {
  29. result.push(dontEnums[i]);
  30. }
  31. }
  32. }
  33. return result;
  34. };
  35. }());
  36. }

附录三:Object.getOwnPropertyNames方法

来自mozilla.org社区
Object.getOwnPropertyNames方法返回对象的所有属性(可枚举,不可枚举)

语法

  1. Object.getOwnPropertyNames(obj)

参数

obj:要返回所有属性的对象

示例

  1. var arr = ['a', 'b', 'c'];
  2. console.log(Object.getOwnPropertyNames(arr).sort());
  3. // logs ["0", "1", "2", "length"]//上面的length是不可枚举的属性
  4.  
  5. // 类数组对象var obj = { 0: 'a', 1: 'b', 2: 'c' };
  6. console.log(Object.getOwnPropertyNames(obj).sort());
  7. // logs ["0", "1", "2"]
  8.  
  9. // 使用Array.prototype.forEach方法来遍历
  10. Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
  11. console.log(val + ' -> ' + obj[val]);
  12. });
  13. // logs// 0 -> a// 1 -> b// 2 -> c
  14.  
  15. // 不可枚举属性示例var my_obj = Object.create({}, {
  16. getFoo: {
  17. value: function() { return this.foo; },
  18. enumerable: false
  19. }
  20. });
  21. my_obj.foo = 1;
  22.  
  23. console.log(Object.getOwnPropertyNames(my_obj).sort());
  24. // logs ["foo", "getFoo"]

  

如果只想得到可枚举的属性,可以使用Object.keys()或for...in循环(这个要配合Object.prototype.hasOwnProperty()方法),见附录1,2。

原型链中的属性不会列举

  1. function PClass(){}
  2. PClass.prototype.m=function(){};
  3.  
  4. function SClass(){
  5. this.prop=5;
  6. this.sm=function(){};
  7. }
  8. SClass.prototype=new PClass();
  9. SClass.prototype.constructor=SClass;
  10. SClass.prototype.pm=function(){};
  11.  
  12. console.log(Object.getOwnPropertyNames(
  13. new SClass() // ["prop", "sm"]
  14. ))

  

只获取不可枚举属性

  1. var target = [1,2,3,4];
  2. var enum_and_nonenum = Object.getOwnPropertyNames(target);//获取所有属性
  3. var enum_only = Object.keys(target);//获取可枚举属性
  4. //去除所有可枚举属性
  5. var nonenum_only = enum_and_nonenum.filter(function(key) {
  6. var indexInEnum = enum_only.indexOf(key);
  7. if (indexInEnum == -1) {
  8. return true;
  9. } else {
  10. return false;
  11. }
  12. });
  13.  
  14. console.log(nonenum_only);//["length"]

  

注意

同附录2中的Object.keys的参数。ES5不是object类型报错,ES6先转化再执行。

  1. Object.getOwnPropertyNames('foo');
  2. // TypeError: "foo" is not an object (ES5 code)
  3. Object.getOwnPropertyNames('foo');
  4. // ["0", "1", "2", "length"] (ES6 code)

[Effective JavaScript 笔记]第35条:使用闭包存储私有数据的更多相关文章

  1. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  2. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  3. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  4. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  5. [Effective JavaScript 笔记] 第11条:熟练掌握闭包

    理解闭包三个基本的事实 第一个事实:js允许你引用在当前函数以外定义的变量. function makeSandwich(){ var magicIngredient=”peanut butter”; ...

  6. [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

    构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...

  7. [Effective JavaScript 笔记] 第13条:使用立即调用的函数表达式创建局部作用域

    function wrapElements(a){ var res=[],i,n; for(i=0,n=a.length;i<n;i++){ res[i]=function(){return a ...

  8. [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

    对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...

  9. [Effective JavaScript 笔记]第50条:迭代方法优于循环

    "懒"程序员才是好程序员.复制和粘贴样板代码,一但代码有错误,或代码功能修改,那么程序在修改的时候,程序员需要找到所有相同功能的代码一处处进行修改.这会使人重复发明轮子,而且在别人 ...

随机推荐

  1. 如何部署Iveely.Computing分布式实时计算系统

    Iveely.Computing是参考Storm的分布式实时计算系统的部分原理,用纯Java实现的轻量级.迷你型,适合于搜索引擎的实时计算系统, Iveely 搜索引擎是一款基于Iveely.Comp ...

  2. 页面无法访问 css文件加载问题

    1.青奥项目的web配置: 后缀为.html和.vm的请求会被控制器拦截.  控制器没设置目标资源,所以无法访问到资源! 2.文件不能放在vm文件夹下,因为设置了视图解析,vm文件夹下的文件只有后缀为 ...

  3. JWPlayer中字幕文件的配置

    最近应项目要求研究JWPlayer,视研究进度可能会将解决的问题或者一些配置方法写在这里. jwplayer支持vtt和srt格式的字幕文件,在视频中可以选择加载多个字幕文件(常用于多语言字幕),并且 ...

  4. 【项目开发】LigerUI+MVC的应用

    1.RazorJS 2.@Html.Raw     表示不对输出进行转义

  5. Spring MVC框架

    这个Spring Web MVC 框架提供了模型视图控制器的架构,这种结构能够被用来开发灵活的和松耦合的Web应用程序. 这种MVC模式能够将应用程序分离成不同的层面,(输入逻辑,业务逻辑,UI逻辑) ...

  6. Java基础-父类-子类执行顺序

    代码解析 子类 package com; /** * 子类 * @author huage * */ public class Test extends Test1{ public static vo ...

  7. Session的异常

    既然这一天就这么废了,那就多说一些吧!其实session也是有潜在的问题的.Session销毁的三种情况: (1)超时:超过30分钟 (2)服务器非正常关闭,如果自己手动stop service而不是 ...

  8. Oracle自定义函数

    核心提示:函数用于返回特定数据.执行时得找一个变量接收函数的返回值; 语法如下: create or replace function function_name ( argu1 [mode1] da ...

  9. Windows下一些配置信息

    VC.VS和.NetFramework版本对应关系 VC6.0对应VS 6.0 VC7.0对应VS 2002 VC7.1对应VS 2003 .Net Framework1.0/1.1 VC8.0对应V ...

  10. BZOJ-3228 棋盘控制 线段树+扫描线+鬼畜毒瘤

    3228: [Sdoi2008]棋盘控制 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 23 Solved: 9 [Submit][Status][D ...