42

我最近读到了一个事实,即有可能在 JavaScript 中定义 getter/setter。这似乎非常有帮助 - 设置器是一种“帮助器”,它可以在实际设置之前先解析要​​设置的值。

例如,我目前有以下代码:

var obj = function(value) {
    var test = !!value; // 'test' has to be a boolean
    return {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new obj(true);

此代码始终转换value为布尔值。所以如果你编码instance.test = 0,那么instance.test === false

但是,要使其正常工作,您必须实际返回一个object,这意味着新实例不是类型obj,而只是一个普通对象。这意味着更改的原型obj对实例没有影响。例如,这不起作用-未定义instance.func

obj.prototype.func = function() { console.log(this.value); };

因为instance不是类型obj。为了让原型函数工作,我想我不应该返回一个普通的对象,而是不返回任何东西,所以它instance只是 type obj,就像一个常规的构造函数一样。

那么问题是如何实现getter/setter?我只能找到描述如何将这些添加到对象的文章,而不是作为自定义类型的构造函数的一部分。

那么如何在构造函数中实现 getter/setter 以便既可以使用 getter/setter 又可以扩展原型呢?

4

6 回答 6

60

你不能那样做。

不过,您可以为对象的属性设置 setter/getter。不过我建议你使用 ES5 Object.defineProperties。当然这只适用于现代浏览器。

var obj = function() {
    ...
    Object.defineProperties(this, {
        "test": {
             "get": function() { ... },
             "set": function() { ... }
        }
    });
}

obj.prototype.func = function() { ... }

var o = new obj;
o.test;
o.func();
于 2011-03-07T16:37:44.573 回答
17

通常你需要方法。@Raynos 在 2011 年 5 月 7 日的回答完成了工作,但它定义了一个实例方法,而不是一个类方法。

下面说明了一个类定义,其中 getter 和 setter 是类的一部分。这个定义很像@Raynos 的答案,但在代码中有两个不同之处:(1)“defineProperties()”操作已从构造函数中移出。(2) “defineProperties()”的参数从实例对象“this”更改为构造函数的原型对象。

function TheConstructor(side) {
  this.side = side;
}

Object.defineProperties(TheConstructor.prototype, {
        area: {
             get: function()    { return this.side * this.side; }
            ,set: function(val) { this.side = Math.sqrt(val);   }
        }
});

// Test code:

var anInstance = new TheConstructor(2);
console.log("initial  Area:"+anInstance.area);
anInstance.area = 9;
console.log("modified Area:"+anInstance.area);

这会产生以下结果:

initial  Area:4
modified Area:9

尽管通常类与实例定义之间的区别只是样式问题,但良好的样式是有目的的,并且存在一种区分很重要的情况:memoized getter。此处描述了 memoized getter 的目的:Smart/self-overwriting/lazy getter

当记忆值与整个类相关时,在类级别定义 getter。例如,一个配置文件应该只读一次;然后,结果值应适用于程序的持续时间。以下示例代码在类级别定义了一个 memoized getter。

function configureMe() {
  return 42;
}

Object.defineProperties(TheConstructor.prototype, {
    memoizedConfigParam: {
        get: function() {
            delete TheConstructor.prototype.memoizedConfigParam;
            return TheConstructor.prototype.memoizedConfigParam = configureMe();
        }
        ,configurable:  true
    }
});

// Test code:

console.log("memoizedConfigParam:"+anInstance.memoizedConfigParam);

产生:

memoizedConfigParam:42

从示例中可以看出,memoized getter 的特点是 getter 函数会删除自己,然后用一个(可能)永远不会改变的简单值替换自己。请注意,“可配置”必须设置为“真”。

当记忆值取决于实例的内容时,在实例级别定义 getter。定义在构造函数内部移动,关注的对象是'this'。

function TheConstructorI(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            return this.memoizedCalculation = this.expensiveOperation();
        }
        ,configurable:  true
    }
  });
}

TheConstructorI.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instance2 = new TheConstructorI(2);
var instance3 = new TheConstructorI(3);

console.log("memoizedCalculation 2:"+instance2.memoizedCalculation);
console.log("memoizedCalculation 3:"+instance3.memoizedCalculation);

产生:

memoizedCalculation 2:8
memoizedCalculation 3:27

如果你想保证(而不是假设)记忆值永远不会改变,'writable' 属性需要改变。这使得代码有点复杂。

function TheConstructorJ(side) {

  this.side = side;

  Object.defineProperties(this, {
    memoizedCalculation: {
        get: function() {
            delete this.memoizedCalculation;
            Object.defineProperty( this, 'memoizedCalculation'
              ,{  value    : this.expensiveOperation()
                 ,writable : false
              });
            return this.memoizedCalculation;
        }
        ,configurable:  true
    }
  });
}

TheConstructorJ.prototype.expensiveOperation = function() {
  return this.side * this.side * this.side;
}

//Test code:

var instanceJ = new TheConstructorJ(2);

console.log("memoizedCalculation:"+instanceJ.memoizedCalculation);
instanceJ.memoizedCalculation = 42;  // results in error

产生:

memoizedCalculation:8
>Uncaught TypeError: Cannot assign to read only property 'memoizedCalculation' of object '#<TheConstructorJ>'

从 2011 年 3 月 7 日起,OP 的原始问题介绍了基本的 getter 和 setter 语法,指出它适用于对象但不适用于“this”,并询问如何在构造函数中定义 getter 和 setter。除了上面的所有示例之外,还有一种“便宜”的方法:在构造函数中创建一个新对象,就像 OP 所做的那样,然后将该对象分配为“this”中的成员。因此,原始代码如下所示:

var MyClass = function(value) {
    var test = !!value; // 'test' has to be a boolean
    this.data = {
        get test() { return test },
        set test(value) { test = !!value }
    };
};

var instance = new MyClass(true);

// But now 'data' is part of the access path
instance.data.test = 0;
console.log(instance.data.test);

产生:

false

信不信由你,我实际上遇到了这种“廉价”是最佳解决方案的情况。具体来说,当我将多个表中的记录封装在一个类中时,我使用了这种技术,并希望呈现一个统一的视图,就好像它们是一个称为“数据”的记录一样。

玩得开心。

IAM_AL_X

于 2016-11-19T21:57:21.333 回答
8

ES6 更新——查看 Alex Rauschmayer 的书Exploring ES6 http://exploringjs.com/es6/ch_maps-sets.html#sec_weakmaps-private-data的第19.3.1 节,它演示了如何将 WeakMaps 与 getter 和 setter 一起使用保存私人数据。结合第 16.2.2.3 节http://exploringjs.com/es6/ch_classes.html#leanpub-auto-getters-and-setters会导致类似

# module test_WeakMap_getter.js
var _MyClassProp = new WeakMap();
class MyClass {
    get prop() {
        return _MyClassProp.get( this ); 
    }
    set prop(value) {
        _MyClassProp.set( this, value );
    }
}
var mc = new MyClass();
mc.prop = 5 ;
console.log( 'My value is', mc.prop );

$ node --use_strict test_WeakMap_getter.js 
My value is 5
于 2015-10-20T16:25:16.850 回答
4
function Obj(value){
    this.value = !!value;
}

Obj.prototype = {
    get test () {
        return this.value;``
    },
    set test (value) {
        this.value = !!this.value;
    }
};
var obj = new Obj(true);
于 2016-04-10T14:49:27.463 回答
1

我知道这可能为时已晚,但我想出了一种不同的方法来完成你想要的事情,并且为了像我这样的人,在这里搜索答案。

function Constructor(input){
     this.input = input;
}
Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

var test = new Constructor(5);
alert(test.value) // 10

我已经在 chrome、safari、mobile safari、firefox 中对此进行了测试,它们都可以正常工作(当然是最新版本)

于 2011-08-09T16:58:41.947 回答
1

@Alex 我将其视为更多选择和更多功能,编程就是艺术,@Nat 与我们分享他的发现,为此我感谢他。也许有人想那样做。

我确定 setter 版本是相同的,但只是将 g 更改为 a s。

ig:

function Constructor(input){
     this.input = input;
}

Object.__defineGetter__.call(Constructor.prototype, "value", function(){
    return this.input * 2;
});

Object.__defineSetter__.call(Constructor.prototype, "bar", function(foo){
    return this.input *= foo;
});

var test = new Constructor(5);
console.log(test.value); // 10
test.bar = 5;
console.log(test.input); //25

话虽如此,此功能已被弃用,建议不要在生产编码中使用。

于 2014-06-11T02:15:21.817 回答