使用私有成员创建对象的技术差异

2024-03-29

Here http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html我发现了一种启用对象私有成员的 javascript 模块模式。如果我没猜错的话,可以这样写:

var myObject1 = (function(){
  var privateMember = 42;
  return {
    publicMember: function() {
      return privateMember;
    }
  }
})();

但还有更有效的方法:

var myObject2 = new function() {
  var privateMember = 42;
  this.publicMember = function() {
    return privateMember;
  }
}

这两者有什么区别吗?还有其他实现私有成员的可能性吗?

这是 Chrome 调试器的结果:


这两者之间只有一些真正的技术差异(伯吉在评论中指出)。除非你做了成千上万次这样的事情,否则这些差异可能并不重要。有一个style差异,这是主观的,但技术差异很小。请参阅下面的细分以了解血淋淋的细节。

在较高层次上,两种形式都创建一个新对象,它们都依赖于私有成员的闭包。第二种方式使用更多的内存,因为该函数在使用后无法进行垃圾收集,而第一种方式可以。类似地,创建为函数的对象prototype属性在第一个版本中是可回收的,但在第二个版本中则不行。对于某些人来说,第二种方法可能不如第一种方法那么清楚(请参阅@djechlin 下面的问题),尤其是因为隐式返回值,但这是一个风格点。

关于你的第二个问题:

还有其他实现私有成员的可能性吗?

对于像你这样的一次性对象,你这样做的方式可能是最好的(无论是哪种形式,真的)。但现在,有sort of另一种方式,从 ES.Next(ECMAScript 的下一个版本)开始,有一种新的真正私有的方式。不过,在这两种情况下,当您处理对象类而不是一次性对象时,它更有用。

More in 我博客上的这篇文章 http://blog.niftysnippets.org/2013/05/private-properties-in-es6-and-es3-and.html(其中还讨论了 ES.Next 附带的内容),但我将在这里讨论最重要的细节。

对象类中私有成员的典型模式如下所示:

function Foo(toBePrivate) {
    var privateMember = toBePrivate;

    this.method1 = function() {
        // ...do something with the truly private `privateMember`
    };
}
Foo.prototype.method2 = function() {
    // ...doesn't have access to the truly private `privateMember`
};

var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() cannot

它的问题是通过创建的每个对象new Foo得到它的own method1功能。我们没有得到重用method2,那里只有one其中由创建的所有对象共享new Foo。 (这对于现代引擎来说不一定是大问题,因为它们能够重复使用code of method1即使一个新的method1 object为每个创建Foo目的。但有一些动态改变原型的开发模式,显然无法作用method1 above.)

这是我们今天可以做的近乎私有的模式:

var Foo = (function() {
    // Create a private name object for our private property
    var privateKey = makeRandomString();

    // Our constructor    
    function Foo(toBePrivate) {
        this[privateKey] = toBePrivate;
    }

    // Define the private property so it's non-enumerable
    Object.defineProperty(Foo.prototype, privateKey, {
        writable: true
    });

    // Methods shared by all Foo instances
    Foo.prototype.method1 = function() {
        // ...use this[privateKey] here...
    };
    Foo.prototype.method2 = function() {
        // ...use this[privateKey] here...
    };

    return Foo;
})();

var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() can too!
// Both `method1` and `method2` are *reused* by all `Foo` objects

...在哪里makeRandomString正是这样做的,每次我们调用它时它都会给我们一个新的随机字符串。

我们创建的财产不是私有的,但它确实是晦涩难懂的。它没有出现在for-in循环(因为我们创建的属性是不可枚举的)并且它的名称每次代码运行时都会改变。因此,任何尝试使用私有数据的代码都必须首先找出属性名称,这是一个不简单的练习,因为该代码无法获取对象的不可枚举属性名称的列表。当然,在调试器中看一眼该对象就会显示该属性及其值。该物业是真的很晦涩难懂,但并不是真正的私密。

ES.Next(ECMAScript 的下一个版本)中的内容显着改进了这种模式。我们会得到私有名称对象 http://wiki.ecmascript.org/doku.php?id=harmony%3aprivate_name_objects,它们本身很有用,也被新课程 http://wiki.ecmascript.org/doku.php?id=harmony%3aclasses.

以下是私有名称如何应用于上述内容:

// **ES.Next, not yet available in the wild**
import Name from "@name";
var Foo = (function() {
    // Create a private name object for our private property
    var privateKey = new Name();

    function Foo(toBePrivate) {
        this[privateKey] = toBePrivate;
    }
    Foo.prototype.method1 = function() {
        // ...use this[privateKey] here...
    };
    Foo.prototype.method2 = function() {
        // ...use this[privateKey] here...
    };

    return Foo;
})();

var f = new Foo("private stuff");
// f.method1() can use the private data
// f.method2() can too!
// Both `method1` and `method2` are *reused* by all `Foo` objects

使用私有名称对象创建的属性永远不会出现在for-in枚举at all,并且它们的名称不是字符串。代码cannot访问名称为私有名称对象的属性,而无需该特定名称对象。自从privateKey上面的变量完全私有Foo类,没有其他代码可以使用该属性。这是完全私人的。当然,这些将显示在调试器中,但是调试器中没有任何内容是私有的。


@djechlin 要求详细说明每个私人会员表格的具体工作方式,这有助于我们理解 Bergi 强调的它们之间的区别:

你的第一个例子:

var myObject1 = (function(){
  var privateMember = 42;
  return {
    publicMember: function() {
      return privateMember;
    }
  }
})();

(此列表遗漏了一些我认为不相关的细节。)

  1. 一个名为myObject1在当前变量绑定对象上创建(可能是window如果这是全局的)其值undefined.

  2. 计算函数表达式:
    A) A Function要创建的对象。
    B) 创建一个空白对象并将其分配给新函数的prototype财产。
    C) A constructor属性是在该对象上创建的,并给出对该函数的引用。
    D) 对当前变量绑定对象的引用存储在函数上。

  3. 调用该函数,为调用的执行上下文创建(除其他外)一个变量绑定对象。

  4. 一个名为privateMember创建并分配给步骤 3 中的变量绑定对象。

  5. 值 42 被分配给privateMemberVBO 的财产。

  6. 创建一个空白对象并给出原型Object.prototype.

  7. 计算内部函数表达式,aFunction创建对象(带有一个空白对象prototype财产和constructor属性放在该对象上,并带有对当前变量绑定对象的引用[第 3 步中的对象])。

  8. 该函数被分配给步骤 5 中空白对象的属性,如下所示publicMember.

  9. 从主匿名函数返回对步骤 6 中对象的引用。

  10. 该对象引用存储在myObject1在步骤 1 中创建的属性。

  11. 主匿名函数(来自步骤 2)没有未完成的引用,因此可以被 GC 回收;所以它所引用的对象prototype属性也可以通过GC回收。

你的第二个例子:

var myObject2 = new function() {
  var privateMember = 42;
  this.publicMember = function() {
    return privateMember;
  }
}

(再次省略一些不相关的细节。)

  1. 一个名为myObject2在当前变量绑定对象上创建(可能是window如果这是全局的)其值undefined.

  2. 计算函数表达式:
    A) A Function要创建的对象。
    B) 创建一个空白对象并将其分配给新函数的prototype财产。
    C) A constructor属性是在该对象上创建的,并给出对该函数的引用。
    D) 对当前变量绑定对象的引用存储在函数上。

  3. 创建一个新的空白对象并从匿名函数的原型中分配其原型prototype财产。

  4. 使用步骤 3 中传入的对象来调用该函数this,为调用的执行上下文创建(除其他外)变量绑定对象。

  5. 一个名为privateMember创建并分配给步骤 4 中的变量绑定对象。

  6. 值 42 被分配给privateMemberVBO 的财产。

  7. 计算内部函数表达式,aFunction创建对象(带有一个空白对象prototype财产和constructor属性放在该对象上,并带有对当前变量绑定对象的引用[第 4 步中的对象])。

  8. 该函数被分配给步骤 5 中空白对象的属性,如下所示publicMember.

  9. 该函数返回,并且因为它不返回对象,所以结果new表达式是对步骤 3 中创建的对象的引用。

  10. 该对象引用存储在myObject2在步骤 1 中创建的属性。

  11. 主匿名函数(来自步骤 2)无法被 GC 回收,因为myObject2的底层原型在constructor属性(因此函数和分配给其的对象prototype属性保留在内存中)。

您可以释放该函数(但不能释放分配给其的对象)prototype属性),在其中添加以下行:

delete this.constructor.prototype.constructor;

这将从分配给其的对象中删除对该函数的引用prototype财产。该对象仍然是myObject2的底层原型,但它不再引用该函数,因此该函数有资格进行 GC。

但到那时,我们就已经进入了默默无闻的境地。 :-)

结论

所以它们几乎是一样的,只是主要的匿名函数和它的对象有一点小小的区别。prototype财产不符合 GC 资格。在现实世界中,需要成千上万的这样的东西才能发挥作用。

(旁注:某些实现可能会推迟创建函数的某些步骤,例如为其创建一个空白对象prototype属性并设置其constructor— 直到/除非prototype使用属性,因为当然在vast大多数情况下,它从未被使用过,因为绝大多数函数从未被用作构造函数。所以你的第一个表格可能是一点点效率更高,因为它可以跳过这些步骤。除非你做了数千次这样的事情,否则这种差异不太重要。)


FWIW,如果您关心的是行数、括号数量或不喜欢对象字面量等,第一个也可以这样写:

var myObject1 = function(){
  var obj = {};
  var privateMember = 42;
  obj.publicMember = function() {
    return privateMember;
  };
  return obj;
}();

作为一个问题style,我更喜欢显式返回,但这是风格问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用私有成员创建对象的技术差异 的相关文章

  • 滚动播放 Html5 视频

    我正在一个 WordPress 网站上工作 下面的代码可以按照我想要的方式工作 只播放一次 最后出现 重播 按钮 我希望它在滚动到 ie 时开始播放 在视口中 我在这里看到了几种不同的方法来实现这一点 但我无法让它们与我当前的代码很好地配合
  • 每个对象? [复制]

    这个问题在这里已经有答案了 我在 JavaScript 中有对象 var object someobject Object aaa true bbb true ccc true 我该如何使用每一个呢 object each function
  • 使用 AngularJS ng-bind-html 时从 json 数据获取 img src

    所以我有一个有趣的问题 但我还没有找到答案 假设我从 JSON 文件中获得了一堆数据 但不知何故 其中一个主要字段如下所示 description img src http o aolcdn com hss storage midas 37
  • 支持 Javascript 链式关系运算符吗?

    我只是尝试了一些 JS 核心原则 发现引擎评估链式关系运算符时不会抛出错误 相反 他们以一种我自己无法理解的方式进行评估 console log 1 lt 2 lt 3 lt 4 lt 5 true expected console log
  • 向回调函数添加附加参数

    我正在 Node js 中构建一个系统 该系统应该查找文件夹数组中的所有文件 统计它们 然后使用该信息执行一些其他工作 我使用 fs readdir 从每个文件夹同步获取所有文件 我的代码如下所示 for i 0 max paths len
  • Mongoose VersionError:保存文档时找不到 id 的匹配文档

    通过 sync API 请求同步用户购物车时 我反复看到以下错误 每当用户更改购物车的内容时就会调用此函数 VersionError 找不到 id 的匹配文档 2y4b1hq601cd013e0af25e32 版本4修改路径 购物车 car
  • 如何解释这个正则表达式 /[\W_]/g

    我的代码是 var result2 result replace W g replace replace 该代码有效 我得到了我需要完成的工作 但我不明白正则表达式如何 W g有效 但我找不到任何我理解的文档 g这是一个全局正则表达式 因此
  • 使用 window.print 内容将网页下载为 pdf

    我想要一个链接 当单击该链接时 会自动开始下载网页的可打印版本 我正在使用Moodle 我想要的内容是完全相同的如果我使用 ctrl p 下载页面并保存为 pdf 或使用 a href Download web page a 我正是想要该内
  • GWT 与 ScriptSharp 的优缺点 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • npm install --legacy-peer-deps 到底做了什么?何时推荐/潜在的用例是什么?

    刚刚遇到这个错误 npm ERR code ERESOLVE npm ERR ERESOLVE unable to resolve dependency tree npm ERR npm ERR While resolving email
  • JavaScript 中使用 eval() 执行用户输入的安全风险

    我计划为我的学生制作一个快速网页来教他们 JavaScript 编程 在此页面上 我想为他们提供一个文本框并允许他们运行 JavaScript 以便他们可以看到该语言在工作中的动态特性 然而 我很清楚 对用户输入使用 eval 通常是一个非
  • 使用 Asynchronous ReadableStream 和 Response 从 Service Worker 的 fetch 事件返回 HTML

    这个问题类似于我的另一个问题 https stackoverflow com questions 62457644 use readablestream with response to return html from fetch eve
  • 在 Javascript 中获取类的所有实例

    我以为这个问题已经有了答案 但我似乎找不到答案 如何在 Javascript 中的此类的所有实例上运行特定的类方法 这必须在我不知道实例名称的情况下完成 我想我可以在类中使用某种静态变量来存储所有实例 但这在 JS 中似乎不存在 那么如何在
  • 在我自己的文件夹结构中的 Sequelize 中的迁移

    我是新来的Sequelize我当前的项目要求我将它与迁移一起使用 我熟悉迁移的内容和方式 我来自Django背景 每个子应用程序在同一文件夹中都有模态 视图 api url 和迁移 我喜欢这个结构 并希望在我的 Nodejs 应用程序中保持
  • 测试方法的存在性

    我正在尝试使一些现有的 JS 向后兼容 如果一个方法不存在 我需要重写它 否则只返回现有的方法 这是我到目前为止的代码 this grid getDataSource function if getDataSource undefined
  • Javascript 子字符串方法帮助

    长话短说 我正在开发一个 Web 应用程序并在其中使用 AJAX 我试图禁用点击时链接的默认操作 将哈希值附加到链接 然后从网址中删除 我遇到的问题是 虽然哈希值被相应地附加 但子字符串方法并没有提取 而是提取了它后面的字母 这是我的代码
  • 查找数组中是否有任何项目符合条件

    我是 JavaScript 新手 现在 我有一个包含多个对象的数组 因此 我想迭代它 如果任何对象与条件匹配 那么我想返回一个值并停止该循环 我的 obj 数组就像 var obj type numberOfQuestions techno
  • 如何使用 Chart.js 在堆积条形图中显示内联值?

    我正在使用 Chart js 库在堆叠条形图中显示一些值 但我正在努力找出如何显示条形图中的值 即 现在 我有以下代码 可以在条形顶部显示数字 但我想知道如何在条形内部显示它们 var numberWithCommas function x
  • 使用两个键执行自动完成 - Material UI with React

    当使用两个值之一搜索时 我试图自动完成输入 title and year 但是 它仅在我搜索时才有效title 当我搜索时year 它不显示任何选项 示例代码 export default function ComboBox return
  • 在 JavaScript 中将带有哈希值的十六进制字符串转换为带有 0x 的十六进制值的最佳方法?

    这个问题不是问如何将哈希字符串十六进制值转换为其相反的颜色 这个问题询问如何将哈希字符串十六进制值转换为常规十六进制值 如下所述 我从元素的存储样式中获取元素的十六进制颜色值 我需要将它们的十六进制值 带有像 FFFFFF 这样的哈希值的字

随机推荐