我能否在不使用new关键字的情况下构造JavaScript对象?

26

我想要做的是:

function a() {
  // ...
}
function b() {
  //  Some magic, return a new object.
}
var c = b();

c instanceof b // -> true
c instanceof a // -> true
b instanceof a // -> true

是否有可能实现这个目标?我可以通过将a挂入b的原型链中轻松地使b成为a的一个实例,但是这样我就必须执行new b(),而我正试图避免这种情况。我想要的东西可能吗?

更新:我觉得通过精确使用b.__proto__ = a.prototype可能是可行的。下班后我会继续尝试。

更新2:以下是似乎最接近你可以达到的内容,这对我来说已经足够好了。感谢所有有趣的答案。

function a() {
  // ...
}
function b() {
  if (!(this instanceof arguments.callee)) {
    return new arguments.callee();
  }
}
b.__proto__ = a.prototype

var c = b();
c instanceof b // -> true
c instanceof a // -> false
b instanceof a // -> true

更新3:我在一篇关于“强大构造函数”的博客文章中找到了我想要的东西,链接如下:blog post on 'power constructors',一旦我添加了必要的b.__proto__ = a.prototype这一行:

var object = (function() {
     function F() {}
     return function(o) {
         F.prototype = o;
         return new F();
     };
})();

function a(proto) {
  var p = object(proto || a.prototype);
  return p;
}

function b(proto) {
  var g = object(a(proto || b.prototype));
  return g;
}
b.prototype = object(a.prototype);
b.__proto__ = a.prototype;

var c = b();
c instanceof b // -> true
c instanceof a // -> true
b instanceof a // -> true
a() instanceof a // -> true

16
我很好奇:你为什么想避免新事物? - Upperstage
1
我正在创建Scala案例类的Javascript版本:github.com/pr1001/caseclass.js - pr1001
2
@pr1001:看不出来为什么这需要你绕过new关键字。而且__proto__对跨浏览器也没有帮助。 - T.J. Crowder
1
这只是一些语法糖,没有更多的东西。然而,我仍然认为这是一个有趣的挑战。 - pr1001
有用的 来源 - Boolean_Type
14个回答

35
你可以使用这个模式:
function SomeConstructor(){
   if (!(this instanceof SomeConstructor)){
        return new SomeConstructor();
   }
   //the constructor properties and methods here
}

之后你可以做以下事情:
var myObj = SomeConstructor();

[编辑2023(实际上:重新编辑)] 为了完全避免使用关键字new,您可以使用工厂函数。这是一个例子。这是道格拉斯·克罗克福德所称的无类面向对象解决方案
这个模式/思想在(包括其他)这个小的GitHub存储库中使用。

const Person = (name = 'unknown', age = 0, male = false) => {
  const value = () => `This is ${name}. ${
    !male ? `Her` : `His`} age is ${age} years.`;
    
  return {
    get whoIs() { return value(); },
    toString() { return value(); },
    addAge: years => age += years,
    setAge: newAge => age = newAge,
    rename: newName => name = newName,
  };
}
// usage
const [ jane, charles, mary ] =  [
  Person(`Jane`, 23), 
  Person(`Charles`, 32, 1), 
  Person(`Mary`, 16), ];

jane.addAge(17);
charles.setAge(76);
console.log(`${jane}`);
console.log(`${mary}`);
console.log(charles.whoIs);


我刚试了一下,看起来非常有前途。稍后会进一步开发它。 - pr1001
5
+1是允许构造函数在没有使用new的情况下工作的常见惯用语。 - bobince
6
评论 #1:但是……但是……但是……它仍然使用了 new。我不明白这里的意义,这只是一个工厂方法,允许用户使用 new。这只会导致团队中的一些成员使用 new,而另一些成员则不使用;这将是一个维护噩梦。要支持其中一个(例如,通过包含用于生产的移除调试代码的方式,在构造函数调用时如果作者没有--或已经使用了 new,则抛出异常),但不要两者都支持。 - T.J. Crowder
3
如果您想使用这个模式,请确保使用一个有名字的函数(如您的示例中所示),并使用函数的名称,而不是 arguments.callee,例如:if (!(this instanceof SomeConstructor))。使用 arguments.callee 将会极大地减慢函数的速度(根据 JavaScript 引擎的不同,速度会变慢 2 倍到 10 倍不等),并且在 JavaScript 的新 "strict" 模式下抛出异常。 - T.J. Crowder
1
抱歉,T.J.,如果我表达不清楚,我是在寻找代码,其中“最终用户”而不是库的创建者避免使用“new”。 - pr1001
这是一个有趣的技术问题,但我同意T.J.的观点,如果你希望你的库的用户能够避免使用new,那么你应该创建一个工厂方法,使它成为创建对象的唯一方式,而不是让用户选择是否使用new。理想情况下,只允许var x = new MyObject();var x = MakeMyObject();(或类似的方式)。 - nnnnnn

10

5
一定要读Crockford。他很有见识和教育意义。但不要把Crockford的话当成圣经,至少因为他早期的一些文章宣传了极其低效的编程实践。我并没有立即看出Crockford在JavaScript中模拟经典继承的文章(现在被他认为是“错误”)与OP提出的实际问题有何关联。 - T.J. Crowder
https://dev59.com/xnRC5IYBdhLWcg3wMeHf#381025 - Andreas Grech

5

是的。如果你不想使用“new”关键字,只需使用原型并在构造函数中返回某些内容。

使用“new”只是告诉构造函数:

  1. 构造函数中的“this”关键字应该引用函数本身,而不是父对象(通常情况下),如果此函数在全局范围内声明,那么父对象将是窗口对象。

  2. 对于所有失败的查找(未找到obj属性)在新创建的对象/实例上,检查原始构造函数的原型属性。

因此使用 new:

    function Point(x, y) {
       this.x = x;
       this.y = y;
    }
    Point.prototype.getDistance = function(otherPoint){
       var Dx = (this.x - otherPoint.x) ** 2;
       var Dy = (this.y - otherPoint.y) ** 2;
       var d = Dx + Dy;
       d = Math.sqrt(d);
       return d
    }
    var pointA = new Point(3, 6);
    var pointB = new Point(5, 8);
    var distanceAB = pointA.getDistance(pointB);

没有 new 关键字:

    function Point(x, y) {
       let d = Object.create(Point.prototype);
       d.x = x;
       d.y = y;
       return d
    }
    Point.prototype.getDistance = function(otherPoint){
       var Dx = (this.x - otherPoint.x) ** 2;
       var Dy = (this.y - otherPoint.y) ** 2;
       var d = Dx + Dy;
       d = Math.sqrt(d);
       return d
    }
    var pointA = Point(3, 6);
    var pointB = Point(5, 8);
    var distanceAB = pointA.getDistance(pointB);

尝试在第一个示例中添加或移除“new”,你会发现'this'不再指向构造函数,而是指向窗口对象。这样做将会给您的窗口添加x和y属性,这可能不是您想要的结果。

4

对于你的具体问题,简单的答案是:不需要。

如果你想避免使用new,可能其他答案提到的模式会有所帮助。但是,这些模式都不能使你的测试中的instanceof返回true。

new操作本质上是:

var x = (function(fn) { var r = {}; fn.call(r); return r;}(b);

然而,与之不同的是,使用某些内部属性将构建fn附加到对象上(是的,您可以使用constructor获取它,但设置它没有相同的效果)。唯一使instanceof按预期工作的方法是使用new关键字。


4

这是一个非常有趣的方法。它确实达到了去除 new (和 this) 的目的。虽然有点极端,但也展示了语言的强大之处。 - T.J. Crowder
1
但是instanceof无法返回问题所需的结果。 - AnthonyWJones

3

如果你想要继承和instanceof正常工作,通常情况下是无法避免使用new(除非像Crockford文章Zoidberg间接链接到的那样采取极端措施),但是为什么要这样做呢?

我能想到唯一需要避免使用new的原因是,当你试图将构造函数传递给另一段代码时,该代码不知道它是一个构造函数。在这种情况下,只需将其包装在工厂函数中:

function b() {
    // ...
}
function makeB() {
    return new b();
}
var c = makeB();

3

是的,您可以做到这一点。

var User = function() {
  var privateMethod = function() {
    alert('hello');
  }

  return {
    sayHello : function() {
      privateMethod();
      this.done = true;
    }
  }
}

var user1 = User();

这种方法有什么问题吗?

2

2
这不是关于一般目的的 new 使用,而是关于你可以避免使用它的特定场合,比如在 Object 中(使用 {} 代替),在 Array 中(使用 [] 代替),尤其是在 Function 中。 - T.J. Crowder
问题是,它是否可能。文章解释说,是的,它是可能的,并且还涵盖了一些为什么它可能有用的方面。 - nemisj
2
@nemisj:不,它没有。它谈论的是不要不适当地使用new(比如new function() { ... }),而不是完全避免使用它。事实上,这篇文章根本没有讨论继承。 - T.J. Crowder
@T.J. Crowder:我的错,没有注意到c实例a背后的想法 :) - nemisj

1
通过使用 Object.create 和经典的 Function.prototype,正确地设置原型链,可以在不使用 new 关键字的情况下保留 instanceof 关键字的正常功能。
function A() {
    return Object.create(A.prototype);
}

function B() {
    return Object.create(B.prototype);
}

B.prototype = Object.create(A.prototype);

var c = B();

assert(c instanceof A);
assert(c instanceof B);

1
唯一让 instanceof 起作用的方法是使用 new 关键字。instanceof 利用了由 new 建立的 ____proto____。

我能通过赋值 proto 做些什么吗? - pr1001
当然,proto 就是“下划线下划线proto下划线下划线”。 - Upperstage
是的,格式很糟糕。在Rhino和Chrome调试器中,__proto__似乎不是只读的。 - pr1001
很高兴知道这个消息;我在某处读到 proto 已经被弃用了。考虑到不同浏览器采取的不同方法,这可能是一个困难的策略。 - Upperstage

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