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

5 javascript constructor object

我最近一直在做很多研究,但还没有得到一个非常好的答案.我在某处读到当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
Run Code Online (Sandbox Code Playgroud)

但是,这将有效:

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

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

Eug*_*kin 13

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

  1. 物体的一般处理.例子:
    • 数字和字符串是不可变的(⇒常量).没有定义任何方法在内部更改它们 - 总是生成新对象作为结果.虽然它们有一些固有的方法,但您无法更改它们或添加新方法.任何这样做的尝试都将被忽略.
    • null并且undefined是特殊对象.任何在这些对象上使用方法或定义新方法的尝试都会导致异常.
  2. 适用的运营商.JavaScript不允许(重新)定义运算符,因此我们坚持使用可用的东西.
    • 数字与算术运算符一种特殊的方式:+,-,*,/.
    • 字符串有一种特殊的方式来处理连接运算符:+.
    • 函数有一种特殊的方式来处理"call"操作符:()new操作符.后者具有如何使用prototype构造函数的属性的先天知识,构造一个具有与原型的适当内部链接的对象,并在其上调用构造函数this正确设置.

如果您查看ECMAScript标准(PDF),您将看到所有这些"额外"功能被定义为方法和属性,但其中许多功能不能直接供程序员使用.其中一些将在标准ES3.1的新版本中公开(截至2008年12月15日的草案:PDF).一个属性(__proto__)已在Firefox中公开.

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

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

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

fun = function(){ this.life = 42; };
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

如您所见,它为已存在的对象添加了一个属性.

为了使用我们的函数作为构造函数,我们必须使用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
Run Code Online (Sandbox Code Playgroud)

正如你所看到的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?
    
    Run Code Online (Sandbox Code Playgroud)
  • 构造函数可以返回覆盖新创建的对象的值:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    正如你所看到的那样xA原型和所有,而y我们是从构造函数返回的"裸"对象.


niX*_*Xar 12

你的理解是错误的:

myFunction().myProperty; // myFunction has no properties
Run Code Online (Sandbox Code Playgroud)

它不起作用的原因是因为".myProperty"应用于"myFunction()"的返回值,而不是对象"myFunction".以机智:

$ js
js> function a() { this.b=1;return {b: 2};}
js> a().b
2
js> 
Run Code Online (Sandbox Code Playgroud)

请记住,"()"是一个运营商."myFunction"与"myFunction()"不同.instanciang with new时你不需要"返回":

js> function a() { this.b=1;}
js> d = new a();
[object Object]
js> d.b;
1
Run Code Online (Sandbox Code Playgroud)

  • 刚刚做了"yum install js"; 它实际上是spidermonkey,Firefox js引擎编译为命令行可执行文件.如果使用较小的操作系统,则可以始终使用Java版本的rhino. (3认同)

Tri*_*ych 5

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

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

function foo(){
  return 0;
}
foo.bar = 1;
alert(foo.bar); // shows "1"
Run Code Online (Sandbox Code Playgroud)

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

function Foo(){
  this.bar = 1;
}
var foo = new Foo();
alert(foo.bar); // shows "1"
Run Code Online (Sandbox Code Playgroud)

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

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