繁体   English   中英

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

[英]Why in JavaScript is a function considered both a constructor and an 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

但是,这将有效:

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

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

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

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

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

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

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(xa); // 42 console.log(xb); // 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(ya); // undefined console.log(yb); // 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是我们从构造函数返回的“裸”对象。

你的理解是错误的:

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()”不同。 instanciang with new时你不需要“返回”:

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

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

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

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中进行任何重要的编程,我强烈推荐您通过电子邮件发送的Javascript: Crockford 的Good Parts

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”指针将变为您的新对象,因此这可以按预期工作。

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

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

首先,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失败的原因是没有名为myFunction 引用

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM