English 中文(简体)
为什么在JavaScript中函数被认为是构造函数和对象?
原标题:
  • 时间:2008-12-16 18:23:37
  •  标签:

我最近一直在进行大量的研究,但仍然没有得到一个非常好的坚实答案。我读到有一个地方说,当 JavaScript 引擎遇到一个函数语句时,会创建一个新的 Function() 对象,这会让我相信它可以成为一个对象的子级(从而变成一个对象)。所以我给 Douglas Crockford 发了一封电子邮件,他的回答是:

Not exactly, because a function statement does not call the compiler.

但它产生了类似的结果。

此外,据我的了解,除非它被实例化为新对象,否则无法在函数构造函数中调用成员。因此这不起作用:

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?

最佳回答

你的理解是错误的。

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时不需要“return”。

js> function a() { this.b=1;}
js> d = new a();
[object Object]
js> d.b;
1
问题回答

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

  1. General treatment of objects. Examples:
    • Numbers and Strings are immutable (⇒ constants). No methods are defined to change them internally — new objects are always produced as the result. While they have some innate methods, you cannot change them, or add new methods. Any attempts to do so will be ignored.
    • null and undefined are special objects. Any attempt to use a method on these objects or define new methods causes an exception.
  2. Applicable operators. JavaScript doesn t allow to (re)define operators, so we stuck with what s available.
    • Numbers have a special way with arithmetic operators: +, -, *, /.
    • Strings have a special way to handle the concatenation operator: +.
    • Functions have a special way to handle the "call" operator: (), and the new operator. The latter has the innate knowledge on how to use the prototype property of the constructor, construct an object with proper internal links to the prototype, and call the constructor function on it setting up this correctly.

如果您查看 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. New empty object ({}) was created.
  2. Its internal prototype property was set to fun.prototype. In our case it will be an empty object ({}) because we didn t modify it in any way.
  3. fun() was called with this new object as a context.

这取决于我们的功能来修改新对象。通常它会设置对象的属性,但它可以做任何它想做的事情。

有趣的小知识:

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

    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 是我们从构造函数返回的“裸对象”。

回答你的具体问题,技术上说函数总是对象。

例如,您可以随时这样做:

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》(中文名为《JavaScript语言精粹》),他是您发送电子邮件的那个人。

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"指针会更改为您的新对象,因此它能够按您的期望工作。

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

每个对象都有一个原型,但只有函数的原型可以直接引用。当使用函数对象作为参数调用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.

首先,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运算符实际上允许您从任何普通的函数中创建对象。与C++或Java中标记为构造函数的函数不同,您使用new运算符的函数没有任何特殊之处。正如文档所说:

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

  1. Define the object type by writing a function.
  2. Create an instance of the object with new.

所以你对代码做了什么?

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

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

JavaScript is based on the ECMA script. Its specification uses the prototyping model for it to be OOP. How ever, ECMA script does not enforce strict data types. The object needs to be instantiated for the same reason that ECMA script requires a new call which will allocate memory for the property, Otherwise it will remain a function and you can call it if you like, in which case, the property will initialize and then be destroyed when the function ends.

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

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





相关问题
热门标签