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)
  • 相关阅读:
    ASP.NET编程的十大技巧
    C#学习心得(转)
    POJ 1177 Picture (线段树)
    POJ 3067 Japan (树状数组)
    POJ 2828 Buy Tickets (线段树)
    POJ 1195 Mobile phones (二维树状数组)
    HDU 4235 Flowers (线段树)
    POJ 2886 Who Gets the Most Candies? (线段树)
    POJ 2418 Cows (树状数组)
    HDU 4339 Query (线段树)
  • 原文地址:https://www.cnblogs.com/wengxuesong/p/5584996.html
Copyright © 2011-2022 走看看