对象字面量/初始化器中的自引用

2024-11-02 21:00:00
admin
原创
32
摘要:问题描述:有没有什么方法可以让下面的内容在 JavaScript 中运行?var foo = { a: 5, b: 6, c: this.a + this.b // Doesn't work }; 在当前形式下,此代码显然会引发引用错误,因为this不引用foo。但...

问题描述:

有没有什么方法可以让下面的内容在 JavaScript 中运行?

var foo = {
    a: 5,
    b: 6,
    c: this.a + this.b  // Doesn't work
};

在当前形式下,此代码显然会引发引用错误,因为this不引用foo。但是,没有办法让对象文字的属性中的值依赖于先前声明的其他属性?


解决方案 1:

好吧,我唯一能告诉你的是getter:

var foo = {
  a: 5,
  b: 6,
  get c() {
    return this.a + this.b;
  }
}

console.log(foo.c) // 11

运行代码片段Hide results展开片段

这是 ECMAScript 第五版规范引入的语法扩展,大多数现代浏览器(包括 IE9)都支持该语法。

解决方案 2:

你可以做类似的事情:

var foo = {
   a: 5,
   b: 6,
   init: function() {
       this.c = this.a + this.b;
       return this;
   }
}.init();

这将是对象的某种一次性初始化。

请注意,您实际上正在将的返回值分配init()foo,因此您必须return this

解决方案 3:

缺少明显而简单的答案,因此,为了完整起见:

但是没有办法让对象文字的属性值依赖于先前声明的其他属性?

不。这里的所有解决方案都将其推迟到创建对象之后(以各种方式),然后再分配第三个属性。最简单的方法就是这样做:

var foo = {
    a: 5,
    b: 6
};
foo.c = foo.a + foo.b;

所有其他方法都只是更间接地完成相同任务的方法。(Felix 的方法特别聪明,但需要创建和销毁临时函数,这增加了复杂性;并且要么在对象上留下额外的属性,要么 [如果您有delete该属性]影响该对象后续属性访问的性能。)

如果您需要将其全部放在一个表达式中,则可以不使用临时属性来做到这一点:

var foo = function(o) {
    o.c = o.a + o.b;
    return o;
}({a: 5, b: 6});

或者当然,如果你需要多次执行此操作:

function buildFoo(a, b) {
    var o = {a: a, b: b};
    o.c = o.a + o.b;
    return o;
}

然后你需要在哪里使用它:

var foo = buildFoo(5, 6);

解决方案 4:

只需实例化一个匿名函数:

var foo = new function () {
    this.a = 5;
    this.b = 6;
    this.c = this.a + this.b;
};

解决方案 5:

现在,在 ES6 中,您可以创建延迟缓存属性。首次使用时,该属性将求值一次,成为普通静态属性。结果:第二次跳过数学函数开销。

魔法就在吸气者身上。

const foo = {
    a: 5,
    b: 6,
    get c() {
        delete this.c;
        return this.c = this.a + this.b
    }
};

在箭头中,getterthis获取周围的词法范围。

foo     // {a: 5, b: 6}
foo.c   // 11
foo     // {a: 5, b: 6 , c: 11}  

解决方案 6:

应该采取一些措施来解决这个问题;

var foo = function() {
    var a = 5;
    var b = 6;
    var c = a + b;

    return {
        a: a,
        b: b,
        c: c
    }
}();

在 中声明的所有变量foo都是私有的foo,正如您对任何函数声明所期望的那样,并且由于它们都在范围内,因此它们都可以相互访问而无需引用this,就像您对函数所期望的那样。不同之处在于此函数返回一个公开私有变量的对象并将该对象分配给foo。最后,您只需使用语句将要公开的接口作为对象返回即可return {}

然后在最后执行该函数,这()会导致整个 foo 对象被评估,实例化其中的所有变量并将返回对象添加为的属性foo()

解决方案 7:

你可以这样做

var a, b
var foo = {
    a: a = 5,
    b: b = 6,
    c: a + b
}

当我必须引用最初声明函数的对象时,该方法对我来说非常有用。以下是我如何使用它的一个简单示例:

function createMyObject() {
    var count = 0, self
    return {
        a: self = {
            log: function() {
                console.log(count++)
                return self
            }
        }
    }
}

通过将 self 定义为包含打印函数的对象,您可以允许该函数引用该对象。这意味着如果您需要将打印函数传递到其他地方,则无需将其“绑定”到对象。

如果你愿意,可以使用this如下图所示的方法

function createMyObject() {
    var count = 0
    return {
        a: {
            log: function() {
                console.log(count++)
                return this
            }
        }
    }
}

然后下面的代码将记录 0、1、2,然后给出错误

var o = createMyObject()
var log = o.a.log
o.a.log().log() // this refers to the o.a object so the chaining works
log().log() // this refers to the window object so the chaining fails!

通过使用 self 方法,您可以保证 print 始终返回相同的对象,无论函数在哪个上下文中运行。使用 self 版本时,上述代码将运行良好并记录 0、1、2 和 3 createMyObject()

解决方案 8:

为了完成,在 ES6 中我们有了类(在撰写本文时只有最新的浏览器支持,但可以在 Babel、TypeScript 和其他转译器中使用)

class Foo {
  constructor(){
    this.a = 5;
    this.b = 6;
    this.c = this.a + this.b;
  }  
}

const foo = new Foo();

解决方案 9:

只是为了思考-将对象的属性置于时间线之外:

var foo = {
    a: function(){return 5}(),
    b: function(){return 6}(),
    c: function(){return this.a + this.b}
}

console.log(foo.c())

上面也有更好的答案。这是我修改您提出的问题的示例代码的方法。

更新:

var foo = {
    get a(){return 5},
    get b(){return 6},
    get c(){return this.a + this.b}
}
// console.log(foo.c);

解决方案 10:

你可以使用模块模式来实现。就像:

var foo = function() {
  var that = {};

  that.a = 7;
  that.b = 6;

  that.c = function() {
    return that.a + that.b;
  }

  return that;
};
var fooObject = foo();
fooObject.c(); //13

通过这种模式,您可以根据需要实例化多个 foo 对象。

http://jsfiddle.net/jPNxY/1/

解决方案 11:

有几种方法可以实现这一点;这是我将使用的方法:

function Obj() {
 this.a = 5;
 this.b = this.a + 1;
 // return this; // commented out because this happens automatically
}

var o = new Obj();
o.b; // === 6

解决方案 12:

get属性效果很好,并且您还可以对只应运行一次的“昂贵”函数使用绑定闭包(这仅适用于var,不适用于constlet

var info = {
  address: (function() {
    return databaseLookup(this.id)
  }).bind(info)(),

  get fullName() {
    console.log('computing fullName...')
    return `${this.first} ${this.last}`
  },

  id: '555-22-9999',
  first: 'First',
  last: 'Last',
}

function databaseLookup() {
  console.log('fetching address from remote server (runs once)...')
  return Promise.resolve(`22 Main St, City, Country`)
}

// test
(async () => {
  console.log(info.fullName)
  console.log(info.fullName)
  console.log(await info.address)
  console.log(await info.address)
  console.log(await info.address)
  console.log(await info.address)
})()

运行代码片段Hide results展开片段

解决方案 13:

只是为了大家娱乐一下:

var foo = (                        (This={
    a: 5,
    b: 6,                          })=>({...This,
    c: This.a + This.b             }))(
);

console.log(foo);

运行代码片段Hide results展开片段

解决方案 14:

在对象文字上创建新函数并调用构造函数似乎与原始问题完全不同,而且没有必要。

在对象文字初始化期间不能引用同级属性。

var x = { a: 1, b: 2, c: a + b } // not defined 
var y = { a: 1, b: 2, c: y.a + y.b } // not defined 

计算属性的最简单解决方案如下(无堆,无函数,无构造函数):

var x = { a: 1, b: 2 };

x.c = x.a + x.b; // apply computed property

解决方案 15:

我使用以下代码作为替代方案,它有效。并且变量也可以是数组。(@ Fausto R.)

var foo = {
  a: 5,
  b: 6,
  c: function() {
    return this.a + this.b;
  },

  d: [10,20,30],
  e: function(x) {
    this.d.push(x);
    return this.d;
  }
};
foo.c(); // 11
foo.e(40); // foo.d = [10,20,30,40]

解决方案 16:

此处发布的其他答案更好,但这里有一个替代方案:

  • 在初始化时设置值(不是 getter,也不是派生的,等等)

  • 不需要init()对象字面量之外的任何类型的代码

  • 是一个对象文字,而不是工厂函数或其他对象创建机制。

  • 不应有任何性能影响(初始化除外)

自执行匿名函数和窗口存储

var foo = {
    bar:(function(){
        window.temp = "qwert";
        return window.temp;
    })(),
    baz: window.temp
};

保证(bar之前) 的订单baz

当然,这会造成污染window,但我无法想象有人会编写需要window.temp持久性的脚本。也许tempMyApp如果你是个偏执狂的话。

它也很丑陋,但偶尔有用。例如,当您使用具有严格初始化条件的 API 时,不想进行重构以使范围正确。

当然,它是干燥的。

解决方案 17:

这是对象中“this”行为的一个例子。

this.prop = 'external';
global.prop = 'global.prop';
const that = this;

const a = {
  prop: 'internal',
  prop1: this.prop, //external

  log() {
    return this.prop //internal
  },
  log1: () => {
    return this.prop //external
  },
  log2: () => {
    return function () {
      return this.prop; //'global.prop' in node; 'external' in chrome
    }()
  },
  log3: function () {
    return (() => {
      return this.prop; //internal
    })()
  },
}

解决方案 18:

所有这一切的关键是范围

您需要将要定义的属性的“父级”(父对象)封装为其自己的实例化对象,然后可以使用关键字引用兄弟属性this

非常非常重要的一点是要记住,如果你在没有先这样做的情况下引用this,那么this将引用外部范围......即对象window

var x = 9   //this is really window.x
var bar = {
  x: 1,
  y: 2,
  foo: new function(){
    this.a = 5, //assign value
    this.b = 6,
    this.c = this.a + this.b;  // 11
  },
  z: this.x   // 9 (not 1 as you might expect, b/c *this* refers `window` object)
};

解决方案 19:

如果你的对象被写成一个返回对象的函数,并且你使用 ES6 对象属性“方法”,那么就有可能:

const module = (state) => ({
  a: 1,
  oneThing() {
    state.b = state.b + this.a
  },
  anotherThing() {
    this.oneThing();
    state.c = state.b + this.a
  },
});

const store = {b: 10};
const root = module(store);

root.oneThing();
console.log(store);

root.anotherThing();
console.log(store);

console.log(root, Object.keys(root), root.prototype);

解决方案 20:

这是一个简洁的 ES6 方法:

var foo = (o => ({
    ...o,
    c: o.a + o.b
  }))({
    a: 5,
    b: 6
  });
  
console.log(foo);

运行代码片段Hide results展开片段

我用它来做这样的事情:

const constants = Object.freeze(
  (_ => ({
    ..._,
    flag_data: {
      [_.a_flag]: 'foo',
      [_.b_flag]: 'bar',
      [_.c_flag]: 'oof'
    }
  }))({
    a_flag: 5,
    b_flag: 6,
    c_flag: 7,
  })
);

console.log(constants.flag_data[constants.b_flag]);

运行代码片段Hide results展开片段

解决方案 21:

这个解决方案怎么样?它也可以用于带有数组的嵌套对象

      Object.prototype.assignOwnProVal
     = function (to,from){ 
            function compose(obj,string){ 
                var parts = string.split('.'); 
                var newObj = obj[parts[0]]; 
                if(parts[1]){ 
                    parts.splice(0,1);
                    var newString = parts.join('.'); 
                    return compose(newObj,newString); 
                } 
                return newObj; 
            } 
            this[to] = compose(this,from);
     } 
     var obj = { name : 'Gaurav', temp : 
                  {id : [10,20], city:
                        {street:'Brunswick'}} } 
     obj.assignOwnProVal('street','temp.city.street'); 
     obj.assignOwnProVal('myid','temp.id.1');

解决方案 22:

由于我没有看到涵盖这种确切场景,因此添加一个选项。如果您不想在何时或更新c时更新,那么 ES6 IIFE 可以很好地工作。a`b`

var foo = ((a,b) => ({
    a,
    b,
    c: a + b
}))(a,b);

为了我的需要,我有一个与数组相关的对象,该对象最终将在循环中使用,所以我只想计算一次一些常见的设置,所以这就是我所拥有的:

let processingState = ((indexOfSelectedTier) => ({
    selectedTier,
    indexOfSelectedTier,
    hasUpperTierSelection: tiers.slice(0,indexOfSelectedTier)
                         .some(t => pendingSelectedFiltersState[t.name]),
}))(tiers.indexOf(selectedTier));

因为我需要设置一个属性,indexOfSelectedTier并且在设置属性时需要使用该值hasUpperTierSelection,所以我首先计算该值并将其作为参数传递给 IIFE

解决方案 23:

我认为以下是可维护性最好的代码,即使它不是对象文字语法:

var foo = function() {
   this.a = 5;
   this.b = 6;
   this.c = this.a + this.b;
   return this;
}.call({});

这将创建一个新的空对象{},然后使用匿名函数设置其属性(使用 执行call())。我认为唯一不好的部分是需要return this,感觉就像一行额外的代码。不幸的是,我找不到更好的方法将对新创建的匿名对象的引用移动到foo

我认为这比语法更好var foo = new function() {...},因为这不会在原型链中创建一个额外的级别,正如@Bergi 在现有答案之一的评论中所解释的那样。

但是,如果这是真正的字面意思,没有其他逻辑,只有一个补充,那么写出来会更有意义

const foo = {
    a:5,
    b:6,
    c:11, // sum of a + b
};

因为不需要在运行时甚至编​​译时计算该总和。

解决方案 24:

虽然对象声明不允许您引用先前的属性,但函数声明中的默认值可以。

因此,您可以这样做:

const foo = ((a=5, b=6, c=a+b) => ({a,b,c}))()

console.log(foo)

运行代码片段Hide results展开片段

解决方案 25:

两个偷懒的解决方案

这里已经有了很好的答案,虽然我不是这方面的专家,但我是懒惰方面的专家,而且以我的专业眼光来看,这些答案似乎不够懒惰。

第一:从匿名函数返回对象

TJ Crowder,Henry Wrightson和Rafael Rocha的答案略有不同:

var foo = (() => {

  // Paste in your original object
  const foo = {
    a: 5,
    b: 6,
  };
  
  // Use their properties
  foo.c = foo.a + foo.b;

  // Do whatever else you want

  // Finally, return object
  return foo;
})();

console.log(foo);

运行代码片段Hide results展开片段

这里的轻微优势是只需按原样粘贴原始对象,而不必担心参数等。(恕我直言,这样包装函数就会变得非常透明)。

第二种:使用setTimeout

如果你不需要foo.c立即使用,那么这个可能会有用:

var foo = {
  a: 5,
  b: 6,
  c: setTimeout(() => foo.c = foo.a + foo.b, 0)
};

// Though, at first, foo.c will be the integer returned by setTimeout
console.log(foo);
// But if this isn't an issue, the value will be updated when time comes in the event loop
setTimeout( () => console.log(foo), 0);

运行代码片段Hide results展开片段

解决方案 26:

好吧,我想到了另一个解决方案。在这里我想初始化一个表示每个时间单位的毫秒数的对象。事实证明,typescript 中的枚举在我的情况下无法使用,因此我声明了多个变量并将其分配给一个对象,如下所示:

const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
const WEEK = 7 * DAY

export const TimeInMS = {
  SECOND,
  MINUTE,
  HOUR,
  DAY,
  WEEK
}

该方法的缺点是:

  • 即使我们不需要变量,它们也会被定义为常量。因此它需要无用的内存。

  • 对象的每个值都必须声明为独立变量

解决方案 27:

具有相当好可维护性的替代语法:

let a = 5;
let b = 6;
let foo = {
  a,
  b,
  c: a+b,
};

return这是可行的,因为如果您没有明确指定名称,JavaScript 将使用变量名称作为新创建对象的属性名称。对于像这样的短数组,如果这在函数内部,我个人会使用单行语法:

let a = 5;
let b = 6;
return { a, b, c:a+b };

解决方案 28:

另一种方法是先声明对象,然后再向其分配属性:

const foo = {};
foo.a = 5;
foo.b = 6;
foo.c = foo.a + foo.b;  // Does work
foo.getSum = () => foo.a + foo.b + foo.c;  // foo.getSum() === 22

这样,你就可以使用对象变量名来访问已分配的值。

最适合config.js文件。

解决方案 29:

如果您想使用原生 JS,其他答案提供了很好的解决方案。

但是如果你愿意编写自引用对象,例如:

{ 
  a: ...,
  b: "${this.a + this.a}",
}

我编写了一个名为self-referenced-object的 npm 库,它支持该语法并返回一个本机对象。

解决方案 30:

注意:此解决方案使用 Typescript(如果需要,您可以使用 TS 编译为的 vanilla JS)

class asd {
    def = new class {
        ads= 'asd';
        qwe= this.ads + '123';
    };
    
    // this method is just to check/test this solution 
    check(){
        console.log(this.def.qwe);
    }
}

// these two lines are just to check
let instance = new asd();
instance.check();

这里我们使用类表达式来获取我们想要的嵌套对象字面量接口。在我看来,这是除了在创建期间引用对象属性之外最好的方法。

需要注意的是,使用此解决方案时,您拥有的界面与对象文字完全相同。而且语法非常接近对象文字本身(与使用函数等相比)。

比较以下

我提出的解决方案

class asd {
    def = new class {
        ads= 'asd';
        qwe= this.ads + '123';
    };

如果对象字面量就足够了,解决方案

var asd = {
    def : {
        ads:'asd',
        qwe: this.ads + '123';, //ILLEGAL CODE; just to show ideal scenario
    }
}

另一个例子

在这个类中,您可以将多个相对路径组合在一起,而使用对象文字则无法做到这一点。

class CONSTANT {
    static readonly PATH = new class {
        /** private visibility because these relative paths don't make sense for direct access, they're only useful to path class
         *
         */
        private readonly RELATIVE = new class {
            readonly AFTER_EFFECTS_TEMPLATE_BINARY_VERSION: fs.PathLike = '\\assets\\aep-template\\src\\video-template.aep';
            readonly AFTER_EFFECTS_TEMPLATE_XML_VERSION: fs.PathLike = '\\assets\\aep-template\\intermediates\\video-template.aepx';
            readonly RELATIVE_PATH_TO_AFTER_EFFECTS: fs.PathLike = '\\Adobe\\Adobe After Effects CC 2018\\Support Files\\AfterFX.exe';
            readonly OUTPUT_DIRECTORY_NAME: fs.PathLike = '\\output';
            readonly INPUT_DIRECTORY_NAME: fs.PathLike = '\\input';
            readonly ASSETS_DIRECTORY_NAME: fs.PathLike = '\\assets';
        };
    }
}
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用