JavaScript如何创建一个对象属性的引用?

16
我理解在JavaScript中,原始类型按值传递,对象按引用传递
我有兴趣创建某种解决方法,使我能够获取包含原始类型的对象属性的引用。例如,我希望以下内容能够正常工作:
var someObject = {a: 1, b: 2};
var myRef = someObject.b;
myRef ++;
someObject.b #=> 3

当然,这是不起作用的。我知道你可以创建一个getter和setter函数,或使用一个对象引用另一个对象,但我真正想要的是一种解决方法,允许我将变量定义为对另一个对象属性的引用,到目前为止似乎这是不可能的。
所以,我的问题很简单:这是否可能,如果可能,怎么做?

2
你尝试的方法只适用于可变对象。 - thefourtheye
我理解了,但是似乎你设置为属性或变量的任何东西都至少有一个指向它的引用...因此似乎应该有一种方法去创建一个指向引用而不是值的引用...我想这不是JS的工作方式,但目前对我来说并不明显为什么。 - Andrew
在JavaScript中,你只能获取对象或数组的引用。因此,获取其他类型的引用的唯一方法是将它放在对象或数组中,并传递/分配包含对象或数组。这就是JS的工作方式。 - jfriend00
2
可能没有好的方法来做到这一点,即使有解决方法,你也不应该使用它,因为这有点违背了通过“复制-引用”传递参数的初衷(JavaScript 不是按引用传递参数)。 - adeneo
@Andrew 你正在增加一个不可变的数字。因此,在增加后,将创建一个新的不可变对象,其值为增加后的值,并且引用myRef将引用该新创建的对象。因此,无论如何,您都将失去旧引用。 - thefourtheye
另请参阅:https://dev59.com/RWsz5IYBdhLWcg3wpZvy 基本上是相同的根本问题/难题。 - Andrew
4个回答

7

原始类型是不可变的,所以不行。您可以将原始类型包装在对象中,像这样:

function MyNumber(n) { this.n = n; }
MyNumber.prototype.valueOf = function() { return this.n; }
var someObject = { a: 1, b: new MyNumber(2) };
var myRef = someObject.b;
MyNumber.call(myRef, myRef + 1);
console.log(+someObject.b); // convert to number with +

或者

var someObject = {
    a: { value: 1 },
    b: { value: 2 },
};
var myRef = someObject.b;
my_inc(myRef); // function my_inc (obj) { obj.value++; }
// someObject.b.value == 3

React框架使用非常简单的模式来封装值。
function Link(value, requestChange)
{
    this.value = value;
    this.requestChange = requestChange;
}

您可以传递对象,通过检查对象的value属性访问当前值,如果您想要更改它,则可以使用新值调用requestChange方法来更改值。优势在于将实际“存储位置”和更改值的逻辑与值读取和写入访问分离。请注意,值还可以是复杂对象。
您也可以使用闭包实现类似的效果:
var someObject = {
    a: 1,
    b: 2
};

function property(object, prop) {
    return {
        get value () {
            return object[prop]
        },
        set value (val) {
            object[prop] = val;
        }
    };
}

var ref = property(someObject, "b");
ref.value; // 2
++ref.value; // 3
someObject.b; // 3

这是因为 getter 和 setter 函数可以访问它们创建时所处作用域中的任何绑定(objectprop)。现在,您可以传递 ref,将其存储在数据结构中等等。


是的,这正是我认为必要的。从语法上讲,它不如创建快捷引用那样好看,但没关系。感谢提供示例! - Andrew
你可以为可引用的值定义一个class - Константин Ван

1
如果你想要"链接"或同步两个不同对象的属性,那么可以像这样实现:
var someObject = {
    a: 1,
    b: 2
};
var linkedObject = {
    a:1, 
    b:2
}
function property(object, prop) {
    return {
        get value () {
            return object[prop]
        },
        set value (val) {
            object[prop] = val;
        }
    };
}
var s_prop = 'b'
var o_ref = property(someObject, s_prop);
var tmp = linkedObject[s_prop]; 

Object.defineProperty(
    linkedObject,
    s_prop,
    {

        set: function(value) {
            o_ref.value = value;
        },
        get: function() {
            return o_ref.value
        }
    }
);
linkedObject[s_prop] = tmp 

someObject.b = 333 /// linkedObject.b is also 333 now
console.log(someObject.b) //  333 
console.log(linkedObject.b)// 333

linkedObject.b = {"test": 2}
console.log(someObject.b) //  {test:2}
console.log(linkedObject.b)// {test:2}

someObject.b.test = 3 
console.log(someObject.b) // {test:3}
console.log(linkedObject.b)//{test:3}

在绝大多数情况下,最好的方法是简单地重用包含共享属性的对象,作为享元对象,即将该对象设置为其他变量/对象的属性。这样你就不需要做任何getter/setter类型的工作了。当然,在某些更复杂的问题中,可能需要那种类型的代码,但简单的享元模式通常更可取。 - Andrew

0

不,没有一个好方法可以做到。

如果你想的话,可以使用一个变通方法。比如说,用单元素数组包装所有主要数据类型:

var someObject = {a: [1], b: [2]};
var myRef = someObject.b;
myRef[0]++;
someObject.b[0]; // 3

虽然这不是最理想的,因为你必须一直使用[0]来访问属性。但在某些情况下,它仍然很有用。单个元素数组的默认toString就是其元素的toString,因此您可以直接在字符串上下文中使用该属性:

console.log('My value: ' + someObject.b); // 'My value: 3'

有一些不错的方法可以实现。首先,您可以将someObject存储为引用,然后使用someObjectRef.b;这并不坏。另一种好的方法可能是创建函数(或者如果需要,使用工厂模式创建函数工厂),如果您需要对引用执行更复杂或冗余的操作。 - Andrew
最后,如果您的主要目标是避免大量语法(一种“好”的方式),您可以考虑使用 ProxyObject.defineProperty()(可能使用 getter、setter 和/或 apply(用于 object.property() 语法)。 - Andrew

0

我不知道这是否令人满意,但如果您愿意将所需对象包装在对象中,则可以这样做:

var a = {a:{a:1},b:2};
var b = a.a;
b.a++;
a.a.a //=> 2

这不完全是你要求的,但它可以工作。


1
使用这个逻辑,你可以只做 someObject.b++,但那不是重点。 - adeneo
我猜想你可以像@gotnull的回答中那样在函数指针中使用它。所以,如果需要的话,fptr(a.a)可以更改你的a.a.a。 - Mobius
@adeneo 这有点道理,或者说可以这么认为。这种方法和类似的方法实际上非常好地解决了问题;请参见我之前的评论。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接