【问题标题】:Why in JavaScript is a function considered both a constructor and an object?为什么在 JavaScript 中函数既是构造函数又是对象?
【发布时间】:2010-09-27 04:43:58
【问题描述】:

我最近对此进行了大量研究,但还没有得到一个非常好的可靠答案。我在某处读到,当 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?

【问题讨论】:

    标签: javascript object constructor


    【解决方案1】:

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

    1. 对象的一般处理。例子:
      • 数字和字符串是不可变的(⇒ 常量)。没有定义任何方法来在内部更改它们——结果总是产生新的对象。虽然它们有一些先天方法,但您无法更改它们或添加新方法。任何这样做的尝试都将被忽略。
      • nullundefined 是特殊对象。对这些对象使用方法或定义新方法的任何尝试都会导致异常。
    2. 适用的运算符。 JavaScript 不允许(重新)定义运算符,所以我们坚持使用可用的。
      • 数字与算术运算符有一种特殊的方式:+-*/
      • 字符串有一种特殊的方式来处理连接运算符:+
      • 函数有一种特殊的方式来处理“调用”运算符:()new 运算符。后者拥有关于如何使用构造函数的prototype 属性、构造具有与原型的适当内部链接的对象以及调用构造函数正确设置this 的先天知识。

    如果您查看 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. fun() 是用这个新对象作为上下文调用的。

    由我们的函数来修改新对象。通常它设置对象的属性,但它可以为所欲为。

    有趣的琐事:

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

      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
      

      如您所见,x 是带有原型的A,而y 是我们从构造函数返回的“裸”对象。

    【讨论】:

      【解决方案2】:

      你的理解是错误的:

      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
      

      【讨论】:

      • 嘿,这是一个命令行 javascript 解释器吗?我在哪里可以买到?似乎比使用 firebug 控制台测试东西容易得多
      • 刚刚做了“yum install js”;它实际上是 spidermonkey,Firefox js 引擎编译为命令行可执行文件。如果您使用较小的操作系统,则始终可以使用 Java 版本的 rhino。
      【解决方案3】:

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

      例如,您总是可以这样做:

      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

      【讨论】:

        【解决方案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”指针变为您的新对象,因此它可以按您的预期工作。

        【讨论】:

        • 这可能是我读过的对该概念的最佳描述。感谢发帖!
        【解决方案5】:

        确实,函数是“一等公民”:它们是一个对象。

        每个对象都有一个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.
        

        【讨论】:

          【解决方案6】:

          首先,JavaScript 处理对象的方式与 C++/Java 不同,因此您需要抛开这些想法,才能理解 javascript 的工作原理。

          当这行执行时:

          var myFunctionVar = new myFunction();
          

          那么myFunction() 内部的this 指的是您正在创建的这个新对象-myFunctionVar。因此这行代码:

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

          基本上有

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

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

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

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

          那么你对代码做了什么

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

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

          【讨论】:

            【解决方案7】:

            JavaScript 基于 ECMA 脚本。它的规范使用原型模型使其成为 OOP。然而,ECMA 脚本并不强制执行严格的数据类型。 需要实例化对象的原因与 ECMA 脚本需要“新”调用的原因相同,该调用将为属性分配内存,否则它将保持为函数,您可以根据需要调用它,在这种情况下,属性将初始化然后在函数结束时销毁。

            【讨论】:

              【解决方案8】:

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

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

              【讨论】:

              • hmm ... javascript:newobj=function(){return {}}(); alert(newobj) 创建 [object Object] ... gnu(而不是牛肉)在哪里?
              • @Ekim 我看不出你的演示是如何关联的。一个函数可以返回一个对象是你所展示的?如果你做了一个new newobj(),这可能会让谈论变得有趣。您只是返回一个对象,而不是创建自己的类型。
              猜你喜欢
              • 2016-05-08
              • 1970-01-01
              • 1970-01-01
              • 2018-01-29
              • 2011-10-22
              • 1970-01-01
              • 2018-06-12
              • 2018-07-31
              • 2022-08-16
              相关资源
              最近更新 更多