关于JavaScript的工作原理有几个问题

5

最近我一直在深入研究JavaScript,以充分了解这门语言,但我有一些令人困扰的问题似乎找不到答案(具体涉及面向对象编程)。

假设有以下代码:

function TestObject()
{
    this.fA = function()
    {
        // do stuff
    }

    this.fB = testB;

    function testB()
    {
        // do stuff
    }
}

TestObject.prototype = {
    fC : function 
    {
        // do stuff
    }
}

函数 fAfB 有什么区别?它们在作用域和潜在能力方面是否完全相同?这只是惯例还是一种技术上更好或更合适的方式?

如果任何时候都只会有一个对象实例,那么像 fC 这样向原型中添加一个函数是否值得?这样做有什么好处?原型仅在处理多个对象实例或继承时才真正有用吗?

从技术上讲,将方法添加到原型的“正确”方式是像我上面所述的那样还是每次调用 TestObject.prototype.functionName = function(){}

我希望保持我的 JavaScript 代码尽可能清晰易读,但同时也非常关注语言中对象的正确约定。我来自 Java 和 PHP 背景,正在努力不做任何关于 JavaScript 工作方式的假设,因为我知道它是基于原型的,与我以前接触过的语言非常不同。


JavaScript不等同于面向对象编程。正如您(正确地!)所观察到的,JavaScript可以说是一种“原型”语言。强烈推荐一本书:JavaScript权威指南,道格拉斯·科克福德。在我看来... - paulsm4
好问题,但不适合在这里提问。你最好到http://programmers.stackexchange.com上提问这类问题。 - Jeremy J Starcher
paulsm4:我确实理解,只是出于完整性的考虑,我也想从这个角度理解JavaScript。还有感谢你的书籍建议!@JeremyJStarcher:谢谢!老实说,我甚至不知道那本书的存在。 - KayoticSully
3
为什么JavaScript不是面向对象的语言?一个语言不必拥有类和基于类的继承来被认为是面向对象的。 - nnnnnn
@KayoticSully,你不应该在多个Stack Exchange网站上发布同样的问题。选择一个网站并坚持下去。"购物式提问"是不被赞同的。你也在这里发布了这个问题:http://programmers.stackexchange.com/questions/164029/a-few-questions-about-how-javascript-works - George Stocker
@GeorgeStocker 对不起,我只是按照上面的建议做了。 - KayoticSully
4个回答

6

fA和fB函数有什么区别?

实际上没有什么区别。函数表达式(fA)和函数声明(fB)的主要区别在于函数何时被创建(声明函数在执行任何代码之前可用,而函数表达式直到表达式实际执行才可用)。您可能会遇到与function expressions相关的各种怪癖。

在这个例子中,我会使用函数表达式,因为声明一个函数表达式,然后分配结果似乎有点抽象。但是,任何一种方法都没有"正确"或"错误"之分。

如果任何时候只会有一个对象实例,添加fC这样的原型函数是否值得?

不值得。几乎所有进行继承的人都发现纯对象通常更简单,因此更好。但是,原型继承对于修补内置对象非常方便(例如,在缺少Array.prototype.each时添加它)。

从技术上讲,向原型添加方法的"正确"方法是什么...

没有特定的对象。用其他对象替换默认原型似乎有点浪费,但使用字面量创建的对象赋值可能更整洁易读,比顺序赋值更好。对于一个或两个赋值,我会使用:

 Constructor.prototype.method = function(){…}

对于许多方法,我会使用对象字面量。有些人甚至使用经典的扩展函数并执行以下操作:

myLib.extend(Constructor.prototype, {
    method: function(){…}
});

如果已经定义了一些方法,那么哪种方法更适合添加方法?

看看一些库,并决定你喜欢什么,有些可以混搭使用。根据特定情况做出决策,通常只需要让足够数量的代码看起来相同,然后无论选择哪种模式,它都会看起来整洁。


3

fAfB实际上是相同的,只是一个惯例问题。

如果只有一个对象实例,我甚至不会使用构造函数,而是使用对象字面量,例如:

var o = {
   fA: function () { ... },
   fB: function () { ... },
   fC: function () { ... }
};

关于将它添加到实例或原型中,如果只有一个实例,则将其添加到实例比将其添加到原型略微更有效,但是,如我所说,请使用文字而非函数。

我避免在构造函数中声明函数,因为每次调用构造函数都会创建代表每个函数的新对象。这些对象不是很大,但如果创建了许多对象,它们往往会累加。如果可以将函数移到原型中,则更加高效。

至于添加到原型中,我更喜欢使用

TestObject.prototype.functionName = function () { };

样式,但这是偏好的问题。我喜欢以上的方法,因为无论你是扩展原型还是创建初始原型,它看起来都一样。


感谢您的建议,您提出了一个非常好的观点,关于将函数添加到原型中的建议风格。在原型中有没有办法拥有“私有”函数?或者有没有一种保护函数的方式,不需要在每个实例中都复制一遍?我认为没有这样的方法,我一直认为这是一种权衡,但还是想问一下。 - KayoticSully
尝试阅读Douglas Crockford的《JavaScript中的私有成员》(http://javascript.crockford.com/private.html)。 - RobG

0
我回答第一部分:没有区别,当您将函数声明为非变量时,它的声明会在块中上升,因此。
...
func();
...    
function func () { ... }

等于

var func = function () { ... };
...
func();
...

所以你的代码

function TestObject () {
  this.fA = function() { // do stuff };   
  this.fB = testB;    
  function testB() { // do stuff }
}

等于

function TestObject () {
    var testB = function () { // do stuff };    
    this.fA = function () { // do stuff };
    this.fB = testB;
}

声明优先处理与作用域无关。 - RobG
好的,就您所说的英语而言,“scope”在ECMAScript中有非常具体的含义。 :-) - RobG

0
还有没有权威的 JavaScript 风格指南或文档,介绍 JavaScript 在低层级操作的相关内容?
任何 JavaScript 程序员都不应该错过"《JavaScript高级程序设计》。这是一本深入探讨 JavaScript 的绝佳书籍。它解释了对象、类模拟、函数、作用域等等内容,并且也是一个 JS 参考书。
至于添加方法到原型的“正确”方式,是我上面提到的 TestObject.prototype.functionName = function(){} 还是每次调用?对于定义类的方式,我建议看看各种 JS MVC 框架(比如轻量级的 Spine.js)。你不需要使用整个框架,只需使用它们的类模拟库即可。主要原因是 JS 没有类的概念,而只由对象和原型组成。另一方面,类可以完美地模拟(请不要认为这是缺陷)。由于这需要程序员遵守某些规定,因此最好使用类模拟库来完成工作并使代码更清晰。

程序员应该期望一个类仿真库具有的标准方法包括:

// define a new Class
var AClass = Class.create({
   // Object members (aka prototype), 
   // usually an initialize() method is called if it is defined 
   // as the "constructor" function when a class object is created
}, {
   // Static members
}); 

// create a child class that inherits methods and attributes from parent
var ChildClass = AClass.extend({
   // Child object members
},{
   // Child static members
}); 

AClass.include({
   // Object amendments (aka mixin), methods and attributes 
   //   to be injected to the class object
},{
  // Statics amendments, methods and attributes to be 
  //  injected as class static members
});


// check object type
var aObj = new AClass();
aObj instanceof AClass; //  true
aObj instanceof ChildClass; //  false


var cObj = new ChildClass();
cObj instanceof AClass; //  true
cObj instanceof ChildClass; //  true

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接