为什么JavaScript中的函数既是构造函数又是对象?

时间:2008-12-16 18:23:38

标签: javascript object constructor

我最近一直在做很多研究,但还没有得到一个非常好的答案。我在某处读到当JavaScript引擎遇到函数语句时会创建一个新的Function()对象,这会让我相信它可能是一个对象的子对象(从而成为一个对象)。所以我给Douglas Crockford发了电子邮件,答案是:

  

不完全是因为一个功能   语句不会调用编译器。

     

但它会产生类似的结果。

另外,据我所知,除非已将实例化为新对象,否则不能在函数构造函数上调用成员。所以这不起作用:

function myFunction(){
    this.myProperty = "Am I an object!";
}
myFunction.myProperty; // myFunction is not a function
myFunction().myProperty; // myFunction has no properties

然而,这将有效:

function myFunction(){
    this.myProperty = "Am I an object!";
}
var myFunctionVar = new myFunction();
myFunctionVar.myProperty;

这只是一个语义问题......在整个编程世界中,对象何时真正成为一个对象,以及它如何映射到JavaScript?

8 个答案:

答案 0 :(得分:13)

关于函数和构造函数没有什么神奇之处。 JavaScript中的所有对象都是......好吧,对象。但是有些对象比其他对象更特殊:即内置对象。差异主要在于以下几个方面:

  1. 物体的一般处理。例子:
    • 数字和字符串是不可变的(⇒常量)。没有定义任何方法在内部更改它们 - 总是生成新对象作为结果。虽然它们有一些固有的方法,但您无法更改它们或添加新方法。任何这样做的尝试都将被忽略。
    • nullundefined是特殊对象。任何在这些对象上使用方法或定义新方法的尝试都会导致异常。
  2. 适用的运营商。 JavaScript不允许(重新)定义运算符,因此我们坚持使用可用的东西。
    • 数字对算术运算符有特殊的处理方式:+-*/
    • 字符串有一种特殊的方式来处理连接运算符:+
    • 函数有一种特殊的方式来处理“call”运算符:()new运算符。后者具有如何使用构造函数的prototype属性的先天知识,构造一个具有适当内部链接的对象,并在其上调用构造函数正确设置this
  3. 如果您查看ECMAScript标准(PDF),您将看到所有这些“额外”功能被定义为方法和属性,但其中许多功能不能直接供程序员使用。其中一些将在标准ES3.1的新版本中公开(截至2008年12月15日的草案:PDF)。一个属性(__proto__)是already exposed in Firefox

    现在我们可以直接回答您的问题。是的,函数对象具有属性,我们可以随意添加/删除它们:

    var fun = function(){/* ... */};
    fun.foo = 2;
    console.log(fun.foo);  // 2
    fun.bar = "Ha!";
    console.log(fun.bar);  // Ha!
    

    这个功能实际上做了什么并不重要 - 它永远不会发挥作用,因为我们不称之为!现在让我们来定义它:

    fun = function(){ this.life = 42; };
    

    它本身不是构造函数,它是一个在其上下文中运行的函数。我们可以很容易地提供它:

    var context = {ford: "perfect"};
    
    // now let's call our function on our context
    fun.call(context);
    
    // it didn't create new object, it modified the context:
    console.log(context.ford);           // perfect
    console.log(context.life);           // 42
    console.log(context instanceof fun); // false
    

    如您所见,它为现有对象添加了一个属性。

    为了将我们的函数用作构造函数,我们必须使用new运算符:

    var baz = new fun();
    
    // new empty object was created, and fun() was executed on it:
    console.log(baz.life);           // 42
    console.log(baz instanceof fun); // true
    

    正如您所看到的,new使我们的函数成为构造函数。以下操作由new完成:

    1. 创建了新的空对象({})。
    2. 其内部原型属性设置为fun.prototype。在我们的例子中,它将是一个空对象({}),因为我们没有以任何方式修改它。
    3. 使用此新对象作为上下文调用
    4. fun()
    5. 我们的功能是修改新对象。通常它会设置对象的属性,但它可以做任何它喜欢的事情。

      有趣的琐事:

      • 因为构造函数只是一个对象,我们可以计算它:

        var A = function(val){ this.a = val; };
        var B = function(val){ this.b = val; };
        var C = function(flag){ return flag ? A : B; };
        
        // now let's create an object:
        var x = new (C(true))(42);
        
        // what kind of object is that?
        console.log(x instanceof C); // false
        console.log(x instanceof B); // false
        console.log(x instanceof A); // true
        // it is of A
        
        // let's inspect it
        console.log(x.a); // 42
        console.log(x.b); // undefined
        
        // now let's create another object:
        var y = new (C(false))(33);
        
        // what kind of object is that?
        console.log(y instanceof C); // false
        console.log(y instanceof B); // true
        console.log(y instanceof A); // false
        // it is of B
        
        // let's inspect it
        console.log(y.a); // undefined
        console.log(y.b); // 33
        
        // cool, heh?
        
      • 构造函数可以返回覆盖新创建的对象的值:

        var A = function(flag){
          if(flag){
            // let's return something completely different
            return {ford: "perfect"};
          }
          // let's modify the object
          this.life = 42;
        };
        
        // now let's create two objects:
        var x = new A(false);
        var y = new A(true);
        
        // let's inspect x
        console.log(x instanceof A); // true
        console.log(x.ford);         // undefined
        console.log(x.life);         // 42
        
        // let's inspect y
        console.log(y instanceof A); // false
        console.log(y.ford);         // perfect
        console.log(y.life);         // undefined
        

        正如你所看到的xA原型和所有,y是我们从构造函数返回的“裸”对象。

答案 1 :(得分:12)

你的理解是错误的:

myFunction().myProperty; // myFunction has no properties

它不起作用的原因是因为“。myProperty”应用于“myFunction()”的返回值,而不是对象“myFunction”。即:

$ js
js> function a() { this.b=1;return {b: 2};}
js> a().b
2
js> 

请记住,“()”是一名运营商。 “myFunction”与“myFunction()”不同。当使用new:

实例时,你不需要“返回”
js> function a() { this.b=1;}
js> d = new a();
[object Object]
js> d.b;
1

答案 2 :(得分:5)

要回答您的具体问题,技术上的功能始终是对象。

例如,您可以随时执行此操作:

function foo(){
  return 0;
}
foo.bar = 1;
alert(foo.bar); // shows "1"

当Javascript函数使用this指针时,其行为与其他OOP语言中的类有点类似。它们可以实例化作为具有new关键字的对象:

function Foo(){
  this.bar = 1;
}
var foo = new Foo();
alert(foo.bar); // shows "1"

现在,从其他OOP语言到Javascript的这种映射很快就会失败。例如,Javascript中实际上没有类这样的东西 - 对象使用原型链来进行继承。

如果您要在Javascript中进行任何重要的编程,我强烈推荐Crockford的Javascript: The Good Parts,那是您通过电子邮件发送的。

答案 3 :(得分:4)

Javascript的“全局”范围(至少在浏览器中)是window对象。

这意味着当您执行this.myProperty = "foo"并将该功能调用为普通myFunction()时,您实际上正在设置window.myProperty = "foo"

myFunction().myProperty的第二点是,您在这里查看myFunction()返回值,所以很自然地,它不会有任何属性,因为它返回null

你在考虑的是:

function myFunction()
{
    myFunction.myProperty = "foo";
}

myFunction();
alert(myFunction.myProperty); // Alerts foo as expected

这几乎与

相同
var myFunction = new Function('myFunction.myProperty = "foo";');
myFunction();

new上下文中使用它时,“返回值”是您的新对象,“this”指针将变为您的新对象,因此这可以按预期工作。

答案 4 :(得分:4)

事实上,职能是“一等公民”:他们是一个对象。

每个对象都有一个Prototype,但只能直接引用一个函数的原型。当使用函数对象作为参数调用new时,使用函数对象的原型作为原型构造新对象,并在输入函数之前将this设置为新对象。

所以你可以调用每个函数一个构造函数,即使它只留下this

有关于构造函数,原型等的非常好的教程......我个人从Object Oriented Programming in JavaScript学到了很多东西。它显示了“继承”其原型的函数的等价性,但使用this来填充新对象的属性,以及使用特定原型的函数对象:

function newA() { this.prop1 = "one"; } // constructs a function object called newA
function newA_Too() {} // constructs a function object called newA_Too
newA_Too.prototype.prop1 = "one";

var A1 = new newA();
var A2 = new newA_Too();
// here A1 equals A2.

答案 5 :(得分:1)

首先,JavaScript与C ++ / Java的行为方式不同,因此您需要将这些想法抛出窗口,以便能够理解javascript的工作原理。

执行此行时:

var myFunctionVar = new myFunction();

然后this内的myFunction()引用您正在创建的这个新对象 - myFunctionVar。因此这行代码:

 this.myProperty = "Am I an object!";

基本上有

的结果
 myFunctionVar.myProperty = "Am I an object!";

您可以帮助您查看new运算符上的一些文档。在JS中,new运算符实际上允许您从函数中创建对象 - 任何普通的旧函数。与new运算符一起使用的函数没有什么特别之处,它将它标记为构造函数,就像在C ++或Java中一样。正如文档所说:

  

创建用户定义的对象类型需要两个步骤:

     
      
  1. 通过编写函数来定义对象类型。
  2.   
  3. 使用new。
  4. 创建对象的实例   

所以你用代码做了什么

function myFunction(){
    this.myProperty = "Am I an object!";
}

是创建一个可用作构造函数的函数。代码myFunction.myProperty失败的原因是没有名为myFunction引用

答案 6 :(得分:0)

JavaScript基于ECMA脚本。它的规范使用原型模型为OOP。但是,ECMA脚本不强制执行严格的数据类型。 该对象需要实例化,原因与ECMA脚本需要一个“new”调用一样,该调用将为该属性分配内存,否则它将保持一个函数,如果你愿意,你可以调用它,在这种情况下,属性将初始化然后在函数结束时被销毁。

答案 7 :(得分:0)

只有在使用new关键字进行实例化时,该函数才会充当构造函数。

结果是一个可以使用“this”关键字访问成员属性的对象。当以任何其他方式使用该函数时,该方法中的this关键字没有任何意义。