zoukankan      html  css  js  c++  java
  • [Effective JavaScript 笔记]第35条:使用闭包存储私有数据

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

    怎么处理私有属性

    编码规范

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

    使用闭包

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

    function User(name,pwd){
      this.toString=function(){
         return '[User '+name+']';
      };
      this.checkPwd=function(pwd){
         return hash(pwd)===pwd;
      }
    }
    

    当使用上面的代码如

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

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

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

    1465898519862

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

    缺点:

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

    提示

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

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

    附录一:for...in循环

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

    语法

    for(property in expression) statement
    

    示例

    var obj={a:10,b:20,c:30}
    for(var prop in obj){
      console.log(prop);
    }
    /*
    'a'
    'b'
    'c'
    */

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

    建议

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

    附录二:Object.keys方法

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

    语法

    Object.keys(obj);
    

    参数

    obj:想要枚举属性的对象

    示例

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

      

    注意

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

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

    兼容版本

    if (!Object.keys) {
      Object.keys = (function() {
        'use strict';
        var hasOwn = Object.prototype.hasOwnProperty,
            hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
            dontEnums = [
              'toString',
              'toLocaleString',
              'valueOf',
              'hasOwnProperty',
              'isPrototypeOf',
              'propertyIsEnumerable',
              'constructor'
            ],
            dontEnumsLength = dontEnums.length;
    
        return function(obj) {
          if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
            throw new TypeError('Object.keys called on non-object');
          }
    
          var result = [], prop, i;
          for (prop in obj) {
            if (hasOwn.call(obj, prop)) {
              result.push(prop);
            }
          }
    
          if (hasDontEnumBug) {
            for (i = 0; i < dontEnumsLength; i++) {
              if (hasOwn.call(obj, dontEnums[i])) {
                result.push(dontEnums[i]);
              }
            }
          }
          return result;
        };
      }());
    }
    

    附录三:Object.getOwnPropertyNames方法

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

    语法

    Object.getOwnPropertyNames(obj)
    

    参数

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

    示例

    var arr = ['a', 'b', 'c'];
    console.log(Object.getOwnPropertyNames(arr).sort()); 
    // logs ["0", "1", "2", "length"]//上面的length是不可枚举的属性
    
    // 类数组对象var obj = { 0: 'a', 1: 'b', 2: 'c' };
    console.log(Object.getOwnPropertyNames(obj).sort()); 
    // logs ["0", "1", "2"]
    
    // 使用Array.prototype.forEach方法来遍历
    Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
      console.log(val + ' -> ' + obj[val]);
    });
    // logs// 0 -> a// 1 -> b// 2 -> c
    
    // 不可枚举属性示例var my_obj = Object.create({}, {
      getFoo: {
        value: function() { return this.foo; },
        enumerable: false
      }
    });
    my_obj.foo = 1;
    
    console.log(Object.getOwnPropertyNames(my_obj).sort()); 
    // logs ["foo", "getFoo"]
    

      

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

    原型链中的属性不会列举

    function PClass(){}
    PClass.prototype.m=function(){};
    
    function SClass(){
      this.prop=5;
      this.sm=function(){};
    }
    SClass.prototype=new PClass();
    SClass.prototype.constructor=SClass;
    SClass.prototype.pm=function(){};
    
    console.log(Object.getOwnPropertyNames(
        new SClass() // ["prop", "sm"]
    ))
    

      

    只获取不可枚举属性

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

      

    注意

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

    Object.getOwnPropertyNames('foo');
    // TypeError: "foo" is not an object (ES5 code)
    
    Object.getOwnPropertyNames('foo');
    // ["0", "1", "2", "length"]  (ES6 code)
  • 相关阅读:
    HTML+CSS简单实现导航栏二级下拉菜单
    原创 | 我的个人微信公众号
    原创 | 喂,在吗?
    NodeJs实现邮箱验证
    JS排序算法(二)冒泡排序
    JS排序算法(一) 快速排序
    前端常见的布局方式
    JS继承方式
    前端Node实现简易的文件上传下载
    原生js实现深度克隆
  • 原文地址:https://www.cnblogs.com/wengxuesong/p/5584996.html
Copyright © 2011-2022 走看看