使用"new"关键字声明JavaScript对象的方法是否符合规范?

3

我最近几个月一直在学习JavaScript,有时候感觉自己写代码比理解语言的细节更快,所以请您见谅。

无论如何,我的问题与我一直在使用的某种方法的行为有关。基本的要点是能够设计出可以构建和使用对象而不使用“new”关键字。

以下是一个例子:

<!DOCTYPE html>
<html>
    <head>
        <script>

        function example()
        {
            if(!(this instanceof example))
                return new example();
        }

        example.prototype.test = function()
        {
            var
                result = example();
            if(result instanceof example)
            {
                console.log("Type: example");
                if(result === this)
                    console.log("Unexpected: (result === this)");    
                else
                    console.log("Expected: (result !== this)");    

            }
            else 
                console.log("Type: ?");
            return result;
        }    

        function run()
        {
            var
                one = new example(), 
                two = example(), 
                three = one.test(),
                four = two.test();    
            three.test();
            four.test();
        }

        </script>
    </head>
    <body onload = "run()">
    </body>
</html>

我得到的输出是:
Type: example
Expected: (result !== this)
Type: example
Expected: (result !== this)
Type: example
Expected: (result !== this)
Type: example
Expected: (result !== this)

这正是我想要看到的。我的问题具体来说是:它是否:

(1) 标准行为

(2) 所有浏览器都支持

(3) 倾向于任何不良副作用


1
你有使用过jQuery吗?你是否注意到它不使用new关键字? - Barmar
从实际情况来看,我的Javascript经验相当有限。你有什么想法,jQuery是如何完成这个任务的呢? - Sir Galahad
1
非常类似于这个:jQuery = function(selector, context) { return new jQuery.fn.init( selector, context ); } - Barmar
当然,你正在使用 new,只是在隐藏它。一个行为是否“标准”是基于个人观点的,因为有些人喜欢经典的面向对象编程方式,使用 new 创建对象,而另一些人则更喜欢函数式编程风格。 - Heretic Monkey
只是为了澄清,我所说的“标准”,指的是符合ECMAScript标准。是的,我意识到new仍然被调用,但不一定是由调用者调用。 - Sir Galahad
1
是的,这是一个相当常见的习语。 - Bergi
2个回答

1
你的模式
function Foo() {
  if (!(this instanceof Foo)) {
    return new Foo();
  }
}

这是相当普遍和众所周知的(是合法的)。但它有一些缺点,主要是围绕处理传递参数而展开。此外,它是不必要的,JavaScript 有高阶函数,因此没有必要为每个函数添加样板代码:

let wrapConstructor = fn => (...args) => {
  return new (fn.bind.apply(fn, [fn, ...args]));
};

let Foo = function Foo() {
  if (!(this instanceof Foo)) {
    throw new TypeError('Not a Foo...');
  }
};

let wrappedFoo = wrapConstructor(Foo);

let foo = wrappedFoo(); // doesn't throw

这样做的好处是可以与内置构造函数一起使用,例如Date
let wrappedDate = wrapConstructor(Date);
let myDate = wrappedDate(2016, 11, 8); // today's date

关于浏览器兼容性,显然写法并不好,但只要环境中有Function.prototype.bindFunction.prototype.apply(IE 9+),稍加babel魔法后就可以运行。
至于是否符合标准行为,如果你的意思是符合规范,那么这肯定是。如果你的意思是“这是常见做法吗”,那么这取决于你与哪些JavaScript开发者交往。如果你经常使用许多高阶函数(例如我小小的wrapConstructor实用程序),那么new就是一个麻烦,需要避免。
对于包括此类功能的库,请查看ramda
更新
请注意,您也可以通过对助手进行轻微修改来轻松地结合这两种模式。
let construct = (fn, args) => {
  return new (fn.bind.apply(fn, [fn, ...args]));
};

function Foo() {
  if (!(this instanceof Foo)) {
    return construct(Foo, Array.from(arguments));
  }
}

现在,Foo可以带有或不带有 "new" 调用,并且函数的参数会自动传递,无需提前知道有多少个参数。这种方法适用于任何计数的构造函数,包括可变数量参数的构造函数。

有趣!但这种习惯用法的一个问题是,它只有在构造函数之外使用才有意义(否则显然会导致无限递归),这有点违背初衷。我只希望调用者能够在不必使用“new”的情况下调用构造函数。 - Sir Galahad

0

从技术角度来看,它是有效的。但是,除非你有充分的理由,否则你应该小心更改预期的语义,而在这种情况下(无意冒犯),我认为你没有。特别是如果你的代码被其他开发人员使用-最好遵循惯例,例如将构造函数大写,并使常规函数不大写。

如果函数没有使用new调用,难道你不能只是throw一个错误吗?


这是一个相当常见的模式(正如Barmar在评论中指出的那样)。如果您正在编写应用程序,应该有一个样式指南来涵盖此内容,但如果您正在编写库,则为API的使用者提供此选项实际上非常好(这可能是jQuery等库这样做的原因)。 - Jared Smith
1
@sweaver2112 不,我不想抛出异常,因为我自然而然地想要启用那种使用方式。无论如何,考虑到它似乎不会破坏任何东西,我真的不认为在库中添加这个功能会有什么危害。开发人员可以选择使用该语法或不使用,这只是一个偏好问题。 - Sir Galahad

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