【问题标题】:can you use call or apply with new?你可以用打电话或申请新的吗?
【发布时间】:2015-03-04 06:26:45
【问题描述】:

How can I call a javascript constructor using call or apply?相关

但不一样,我正在尝试在未正确调用时将 SO 答案应用于John Resig's forcing a constructor

function User(first, last){ 
  if ( !(this instanceof User) )
    // the line I want to replace, or remove the redundancy from:
    return new User(first, last);

  this.name = first + " " + last; 
} 

var name = "Resig"; 
var user = User("John", name); 

assert( user, "This was defined correctly, even if it was by mistake." ); 
assert( name == "Resig", "The right name was maintained." );

目标代码行意味着每次构造函数更改时,都必须记住更改内部自调用参数。在过去的 3 天里,我已经对这个问题进行了 3 次项目之旅。

链接问题中的所有示例都谈到传递constructor,但在这种情况下constructor 是什么?它甚至还没有完成定义。

但到目前为止,所有尝试都没有通过测试,或者抛出了 stackoverflow。

我如何确保调用的构造函数产生正确响应 instanceof User 的结果,即使在没有 new 关键字的情况下调用,同时消除参数参数的重复?

【问题讨论】:

  • 我不明白为什么您链接的问题(或者更确切地说,它的答案)不适用于您的场景...?
  • 我已经编辑了您的代码(略微,如编辑注释所述);如果您愿意,请随时回滚。
  • 这只是伤害了我的眼睛。为什么要这样做?只需要求程序员使用new。这就是它的目的。
  • @T.J.Crowder 也许我正在阅读我的联赛讨论,我不知道如何以这种方式应用它们。
  • @Pinoniq 这不一定是一个好习惯,但我确实想了解如何将这些概念一起应用。

标签: javascript resig


【解决方案1】:

为您提供一些选项,全部使用Object.create

选项 1:

function User(first, last){ 
  var rv;

  if ( !(this instanceof User) ) {
    // They called us without `new`: Create an object backed by `User.prototype`:
    rv = Object.create(User.prototype);

    // Now, call this function applying the arguments
    User.apply(rv, arguments);

    // Return the object
    return rv;
  }

  // Normal constructor stuff    
  this.name = first + " " + last; 
} 

当然,不必为您创建的每个构造函数重复所有这些逻辑,您可以使用辅助函数:

function constructWith(obj, ctor, args) {
    if (obj instanceof ctor) {
        return null;
    }
    obj = Object.create(ctor.prototype);
    ctor.apply(obj, args);
    return obj;
}

然后

function User(first, last){ 
  var rv;

  if ((rv = constructWith(this, User, arguments)) != null) {
      return rv;
  }

  // Normal constructor stuff    
  this.name = first + " " + last; 
} 

选项 2:不要使用 this 太多:

function User(first, last){ 
  var rv;

  if (this instanceof User) {
    // They (probably) used `new`, all is good, use `this`
    rv = this;
  } else {
    // They didn't use `new`, create an object backed by `User.prototype`
    rv = Object.create(User.prototype);
  }

  // ...use `rv`, not `this`, from here on

  rv.name = first + " " + last; 

  // This is important for the case where they didn't use `new`, and harmless
  // in the case where they did.
  return rv;
} 

如您所见,这要简单得多,但如果您真的喜欢语法突出显示(说真的,我有一个客户,this 跳出来对他来说真的很重要)等等...

当然,您可以将其封装在一个助手中:

function useOrConstruct(obj, ctor) {
    return obj instanceof ctor ? obj : Object.create(ctor.prototype);
}

然后

function User(first, last){ 
  var rv = useOrConstruct(this, User);

  // ...use `rv`, not `this`, from here on

  rv.name = first + " " + last; 

  // This is important for the case where they didn't use `new`, and harmless
  // in the case where they did.
  return rv;
} 

选项 3:constructOMatic

当然,如果我们要定义助手,也许我们应该全力以赴:

function User() {
    return constructOMatic(this, User, arguments, function(first, last) {
        this.name = first + " " + last;
    });
}

...constructOMatic 是:

function constructOMatic(obj, ctor, args, callback) {
    var rv;
    if (!(obj instanceof ctor)) {
        obj = Object.create(ctor.prototype);
    }
    rv = callback.apply(obj, args);
    return rv !== null && typeof rv === "object" ? rv : obj;
}

现在,您可以在回调中使用this 来满足您的需求。最后在return 中摆弄rvobj 是为了模拟new 的行为(new 表达式的结果是由new 运算符创建的对象 除非构造函数返回非null对象引用,在这种情况下优先)。


Object.create 是所有现代浏览器上的 ES5 功能,但上面使用的单参数版本可以为过时的浏览器填充:

if (!Object.create) {
    Object.create = function(proto, props) {
        if (typeof props !== "undefined") {
            throw "The two-argument version of Object.create cannot be shimmed.";
        }
        function ctor() { }
        ctor.prototype = proto;
        return new ctor; // Yes, you really don't need () (but put them on if you prefer)
    };
}

【讨论】:

  • 您的解决方案有效,谢谢。我可以在 6 分钟内接受
  • @Maslow:很高兴有帮助。我添加了一些注释以避免重复。
  • 在选项 1 中,为什么 rvif 之外声明?只是为了更清楚地表明,如果跳过if,它将在范围内?它不能在if 之外访问。
  • @Maslow:我总是在函数的顶部声明变量,因为那才是真正声明 thanks to hoisting 的地方。 (我想你已经知道链接文章中的内容,但如果不知道......)
  • 是的,我很熟悉它,试图在我的评论中暗示这一点
【解决方案2】:

复制和粘贴非常简单,代码很干净。你不需要改变它。

如果你接受eval,你可以这样做:

function User(first, last){ 
  if ( !(this instanceof arguments.callee) ) {
    var name = arguments.callee.name;
    var param = [].map.call(arguments,function(e,i){return 'arguments['+i+']';});
    return eval('new '+name+'('+ param +')');
  }

  this.name = first + " " + last;
}

//test
var user1 = User("John", "Resig");
var user2 = new User("John", "Resig");

没有eval,你可以这样做:

function instantiate(C,a){
    switch(a.length){
        case 0: return new C();
        case 1: return new C(a[0]);
        case 2: return new C(a[0],a[1]);
        case 3: return new C(a[0],a[1],a[2]);
        case 4: return new C(a[0],a[1],a[2],a[3]);
        default : throw("too many arguments");
    }
}

function User(first, last){ 
  if ( !(this instanceof arguments.callee) ) {
    return instantiate(arguments.callee, arguments);
  }

  this.name = first + " " + last;
}


//test
var user1 = User("John", "Resig");
var user2 = new User("John", "Resig");

在 ECMAScript 6 中,您可以使用扩展运算符将带有 new 关键字的构造函数应用于参数数组:

"use strict";
function User(first, last){ 
  if ( !(this instanceof User) ) {
    return new User(...arguments);
  }

  this.name = first + " " + last;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-15
    • 2015-01-16
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    • 2020-05-27
    • 2017-12-21
    • 2018-05-01
    相关资源
    最近更新 更多