有没有更好的方法来模拟JavaScript中的指针?

时间:2012-04-23 13:05:08

标签: javascript pointers closures simulate dynamic-scope

我正在使用dynamic scoping在JavaScript中模拟pointers follows

var ptr = (function () {
    var ptr = "(" + String(function (value) {
    if (value === void 0) return upvalue;
    else upvalue = value;
}) + ")";

    return function (upvalue) {
        return ptr.replace(/upvalue/g, upvalue);
    };
}());

function swap(xptr, yptr) {
    var t = xptr();
    xptr(yptr());
    yptr(t);
}

var x = 2;
var y = 3;

alert([x, y]);
swap(eval(ptr("x")), eval(ptr("y")));
alert([x, y]);

有没有其他方法可以达到相同的效果(即不使用eval)?它看起来似乎太多样板了。

5 个答案:

答案 0 :(得分:3)

因为您使用指针的唯一方法是取消引用它以访问另一个变量,您可以将其封装在属性中。

function createPointer(read, write) {
  return { get value() { return read(); }, set value(v) { return write(v); } };
}

要创建指针,请传递读取和写入指向的变量的访问器方法。

var i;
var p = createPointer(function() { return i; }, function(v) { i = v; });
// p is now a "pointer" to i

要取消引用指针,请访问其值。换句话说,在C中你可以在这里写*p,你可以写p.value

i = "initial";
alert(p.value); // alerts "initial"
p.value = "update";
alert(i); // alerts "update"
p.value += "2";
alert(i); // alerts "update2"

您可以创建指向同一变量的多个指针。

var q = createPointer(function() { return i; }, function(v) { i = v; });
// q is also a "pointer" to i
alert(q.value); // alerts "update2"
q.value = "written from q";
alert(p.value); // alerts "written from q"

您可以通过简单地用另一个指针覆盖指针变量来更改指针指向的内容。

var j = "other";
q = createPointer(function() { return j; }, function(v) { j = v; });
// q is now a "pointer" to j

您可以通过指针交换两个变量。

function swap(x, y) {
    var t = x.value;
    x.value = y.value;
    y.value = t;
}

让我们使用他们的指针交换ij的值。

swap(p, q);
alert(i); // alerts "other"
alert(j); // alerts "written from q"

您可以创建指向局部变量的指针。

function example() {
    var myVar = "myVar as local variable from example";
    var r = createPointer(function() { return myVar; }, function(v) { myVar = v; });
    swap(p,r);
    alert(i); // alerts "myVar as local variable from example"
    alert(myVar); // alerts "other"
}
example();

通过闭包的魔力,这为您提供了一种模拟malloc的方法。

function malloc() {
    var i;
    return createPointer(function() { return i; }, function(v) { i = v; });
}
var p = malloc(); // p points to a variable we just allocated from the heap
p.value = 2; // write a 2 into it

你的魔法也有效:

var flowers = new Misdirection(
       createPointer(function() { return flowers; }, function(v) { flowers = v; }));
flowers.abracadabra();
alert(flowers);

function Misdirection(flowers) {
    this.abracadabra = function() {
        flowers.value = new Rabbit;
    };
}

function Rabbit() {
    this.toString = function() { return "Eh... what's up doc?" };
}

答案 1 :(得分:2)

不幸的是,在Javascript中引用变量的唯一方法是直接访问它(我们不想要的东西,因为它执行静态绑定)或将其名称以字符串形式传递给eval。

如果你真的想避免使用eval,你可以做的是尝试将变量放在作为作用域的对象中,因为这样可以使用[]下标符号来访问给定名称的变量。请注意,如果您创建的所有指针都是全局变量,那么情况已经如此,因为全局变量也是全局window对象的属性。


function pointer(scope, varname){
    return function(x){
        if(arguments.length <= 0){ //The explicit arguments.length lets us set the pointed variable to undefined too.
            return scope[varname];
        }else{
            return (scope[varname] = x);
        }
    }
};

var vars = {
    x: 1
};

var y = 2; // "normal" variables will only work if they are global.

swap( pointer(vars, 'x'), pointer(window, 'y') );

答案 2 :(得分:0)

类似的东西?

function swap(a,b,scope) {
    var t = scope[a];
    scope[a] = scope[b];
    scope[b] = t; 
}

x = 2;
y = 3;
alert([x,y]);
swap('x', 'y',this);
alert([x,y]);

答案 3 :(得分:0)

以下是使用对象执行此操作的一种方法:

var obj = {
    x:2,
    y:3
},
swap = function(p1, p2){
    var t = obj[p1];
    obj[p1] = obj[p2];
    obj[p2] = t;
};

console.log( obj.x, obj.y );
swap('x', 'y');
console.log( obj.x, obj.y );

答案 4 :(得分:0)

修改

@Tomalak - 考虑以下JavaScript程序:

var flowers = new Misdirection;
flowers.abracadabra();
alert(flowers);

function Misdirection() {
    this.abracadabra = function () {
        this = new Rabbit;
    };
}

function Rabbit() {
    this.toString = function () {
        return "Eh... What's up, doc?";
    };
}

上述程序抛出ReferenceError: Cannot assign to 'this'。指针可用于解决此问题;虽然它不会更新this指针,但它会做下一个最好的事情 - 更新对this指针的唯一引用。

我们可以在不使用指针的情况下通过将this替换为flowers来使上述程序正常工作。但是,通过这样做,误导魔术技巧只适用于构造函数的一个实例。指针允许我们使其适用于任何数量的实例。

使用Function.callFunction.applyArray.map无法获得相同的结果。另外,如果构造函数返回一个显式值,那么无论如何都无法覆盖this指针。即使我从abracadabra返回Misdirection函数并调用flowers()而不是flowers.abracadabra(),我在下面编写的程序(使用指针)也会有效。

<强>原始

在JavaScript中模拟指针是一个非常强大的黑客。例如,它可用于执行follows

的魔术
var flowers = new Misdirection(&flowers);
flowers.abracadabra();
alert(flowers);

function Misdirection(flowers) {
    this.abracadabra = function () {
        *flowers = new Rabbit;
    };
}

function Rabbit() {
    this.toString = function () {
        return "Eh... What's up, doc?";
    };
}

这一切都很好看,但是当我们real power时,JavaScript中的push the envelope模拟指针会闪耀:

var Square = new Class(function (ctor, uber) {
    *ctor = constructor;

    var side;

    function constructor(length) {
        side = length;
    }

    this.area = function () {
        return side * side;
    };

    return &uber;
});

var Cube = new Class(function (ctor, uber) {
    *ctor = constructor;

    function constructor(side) {
        uber(side);
    }

    this.area = function () {
        return 6 * uber.area();
    };

    return &uber;
}, Square);

var cube = new Cube(5);
alert(cube.area());

function Class(claus, Uber) {
    Claus.__proto__ = Uber === void 0 ? Class.prototype : Uber;

    return Claus;

    function Claus() {
        var self = this;
        var called;

        var uber = Uber === void 0 ? function () {
            throw new Error("No uber class specified.");
        } : function () {
            if (!called) {
                called = "Cannot call uber class constructor more than once.";

                var args = Array.prototype.slice.call(arguments);
                args = Array.prototype.concat.call([null], args);
                var base = new (Function.prototype.bind.apply(Uber, args));

                self.__proto__.__proto__ = base;
                self.__proto__.__proto__.constructor = Claus;

                *uber = base;
            } else throw new Error(called);
        };

        var constructor = new Function;
        uber = claus.call(this, &constructor, uber);
        constructor.apply(this, arguments);
    };
}

显然这是一个太多的样板,但它确实证明了闭包有多强大。真正令人惊奇的是,我们可以使用这个样板来模拟JavaScript中的经典面向对象编程。例如,以下代码可以转换为上述程序(尽管我们需要编写一个完整的解析器来执行此操作):

class Square {
    var side;

    function constructor(length) {
        side = length;
    }

    this.area = function () {
        return side * side;
    };
}

class Cube extends Square {
    function constructor(side) {
        uber(side);
    }

    this.area = function () {
        return 6 * uber.area();
    };
}

var cube = new Cube(5);
alert(cube.area());

请注意,行*ctor = constructor;return &uber;已被删除。这只是使构造函数和继承工作所必需的冗余代码。此外,Class函数未写入源代码,因为它由转换编译器自动添加。

在上面的程序中演示了在JavaScript中模拟指针的美妙之处,其中类uber中的变量Cube最初是基类构造函数。然而,当它被调用时,它被基类的实例取代,后者成为this的原型。

这也意味着Cube的实例不会是Square的实例,除非直到从Cube调用超级类构造函数。