5

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

4

8 回答 8

13

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

  1. 对象的一般处理。例子:
    • 数字和字符串是不可变的(⇒ 常量)。没有定义任何方法来在内部更改它们——结果总是产生新的对象。虽然它们有一些先天方法,但您无法更改它们或添加新方法。任何这样做的尝试都将被忽略。
    • null并且undefined是特殊的对象。任何对这些对象使用方法或定义新方法的尝试都会导致异常。
  2. 适用的运算符。JavaScript 不允许(重新)定义运算符,所以我们坚持使用可用的。
    • 数字对算术运算符有一种特殊的方式:+, -, *, /
    • 字符串有一种特殊的方式来处理连接运算符:+.
    • 函数有一种特殊的方式来处理“调用”操作符:()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!

函数实际上做什么并不重要——它永远不会发挥作用,因为我们没有调用它!现在让我们定义它:

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
    

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

于 2008-12-16T21:03:55.237 回答
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
于 2008-12-16T18:47:58.070 回答
5

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

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

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

thisJavascript 函数在使用指针时的行为有点像其他 OOP 语言中的类。它们可以用 new 关键字实例化为对象:

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

现在这种从其他 OOP 语言到 Javascript 的映射将很快失败。例如,实际上 Javascript 中没有类之类的东西——对象使用原型链来代替继承。

如果你打算用 Javascript 进行任何类型的重要编程,我强烈推荐Javascript: The Good Parts by Crockford,你发邮件的那个人。

于 2008-12-16T18:41:14.573 回答
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”指针更改为您的新对象,因此它可以按预期工作。

于 2008-12-16T18:37:46.437 回答
4

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

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

因此,您可以将每个函数称为构造函数,即使它不理会this

那里有关于构造函数、原型等的非常好的教程……我个人从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.
于 2008-12-16T19:35:38.130 回答
1

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

当此行执行时:

var myFunctionVar = new myFunction();

然后thisinsidemyFunction()指的是您正在创建的这个新对象 - 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是没有引用named myFunction

于 2008-12-16T18:41:45.150 回答
0

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

于 2008-12-16T18:40:09.800 回答
0

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

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

于 2008-12-16T19:00:35.003 回答