【问题标题】:How to "properly" create a custom object in JavaScript?如何在 JavaScript 中“正确”创建自定义对象?
【发布时间】:2010-12-08 09:53:44
【问题描述】:

我想知道创建具有属性和方法的 JavaScript 对象的最佳方法是什么。

我见过一些例子,其中有人使用var self = this,然后在所有函数中使用self. 以确保范围始终正确。

然后我看到了使用.prototype 添加属性的示例,而其他人则使用内联。

谁能给我一个带有一些属性和方法的 JavaScript 对象的正确示例?

【问题讨论】:

  • 没有“最好”的方法。
  • self 不是保留字吗?如果不是,应该是;因为self 是一个引用当前窗口的预定义变量。 self === window
  • @Shaz:与浏览器对象模型中window 的其他属性(如documentframes)相比,它不再是保留字;您当然可以将标识符重新用作变量名。虽然,是的,在风格上我更喜欢var that= this 以避免任何可能的混淆。尽管window.self 最终毫无意义,所以几乎没有任何理由去触碰它。
  • 当 JS 被缩小时,将this 分配给一个局部变量(例如self)会减少文件大小。
  • Classjs 新链接:github.com/divio/classjs

标签: javascript


【解决方案1】:

当一个人在构造函数调用期间使用关闭“this”的技巧时,它是为了编写一个函数,该函数可以被其他不想调用某个对象的方法的对象用作回调。它与“使范围正确”无关。

这是一个普通的 JavaScript 对象:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

阅读Douglas Crockford 对 JavaScript 的评论,您可能会收获很多。 John Resig 也很棒。祝你好运!

【讨论】:

  • 呃,关闭this 与“使范围正确”有关。
  • 乔纳森是对的。 js 函数的范围是您设计的任何内容。 self=this 技巧是将它绑定到特定实例的一种方法,因此在另一个上下文中调用时它不会改变。但有时这正是你真正想要的。取决于上下文。
  • 我想你们其实都在说同样的话。 self=this 虽然不强制 this 持续存在,但很容易通过闭包“正确”确定范围。
  • 你这样做的原因=this 是为了让嵌套函数访问 this 的范围,因为它存在于构造函数中。当嵌套函数在构造函数内部时,它们的“this”作用域恢复到全局作用域。
【解决方案2】:

你也可以这样做,使用结构:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

然后:

var counter1 = createCounter();
counter1.increaseBy(4);

【讨论】:

  • 我不喜欢这样,因为空格很重要。 return 后的花括号必须在同一行,以实现跨浏览器兼容性。
【解决方案3】:

Douglas CrockfordThe Good Parts 中广泛讨论了这个话题。他建议避免使用 new 运算符来创建新对象。相反,他建议创建定制的构造函数。例如:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

在 Javascript 中,函数是一个对象,可以与 new 运算符一起用于构造对象。按照惯例,打算用作构造函数的函数以大写字母开头。您经常会看到以下内容:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

如果你在实例化一个新对象时忘记使用 new 操作符,你得到的是一个普通的函数调用,而 this 被绑定到全局对象到新对象。

【讨论】:

  • 是我还是我认为 Crockford 对新运营商的抨击毫无意义?
  • @meder:不只是你。至少,我认为新运营商没有任何问题。无论如何,var that = {}; 中有一个隐含的new
  • Crockford 是个脾气暴躁的老人,我在很多方面不同意他的观点,但他至少提倡批判性地看待 JavaScript,值得听听他所说的话。
  • @bobince:同意。大约 5 年前,他关于闭包的文章让我看到了很多东西,他鼓励采用深思熟虑的方法。
  • 我同意克罗克福德的观点。 new 运算符的问题在于 JavaScript 将使“this”的上下文与调用函数时的上下文非常不同。尽管有正确的大小写约定,但由于开发人员忘记使用 new、忘记大写等,在较大的代码库上会出现问题。为了务实,您可以在没有 new 关键字的情况下做所有需要做的事情 - 那么为什么要使用它呢?在代码中引入更多的故障点? JS 是一种原型语言,而不是基于类的语言。那么为什么我们希望它像静态类型语言一样工作呢?我当然不会。
【解决方案4】:

我经常使用这种模式——我发现它在我需要的时候给了我很大的灵活性。在使用中,它与 Java 风格的类非常相似。

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

这使用了一个在创建时调用的匿名函数,返回一个新的构造函数。因为匿名函数只被调用一次,你可以在其中创建私有静态变量(它们在闭包内,对类的其他成员可见)。构造函数基本上是一个标准的 Javascript 对象 - 您在其中定义私有属性,公共属性附加到 this 变量。

基本上,这种方法将 Crockfordian 方法与标准 Javascript 对象相结合,以创建更强大的类。

您可以像使用任何其他 Javascript 对象一样使用它:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

【讨论】:

  • 这看起来很有趣,因为它非常接近我的 C#“主场”。我也想我开始理解为什么 privateStaticVariable 真的是私有的(因为它是在函数的范围内定义的,并且只要有对它的引用就保持活动状态?)
  • 这里的问题是每个对象都有自己的所有私有和公共函数的副本。
  • @virtualnobi:这种模式不会阻止您编写原型方法:constructor.prototype.myMethod = function () { ... }
  • ECMA5 方式在哪里?函数 Class() {} 函数 SubClass() { Class.apply(this, arguments); } SubClass.prototype = Object.create(Class.prototype);
【解决方案5】:

在 JavaScript 中实现类和实例有两种模型:原型方式和闭包方式。两者都有优点和缺点,并且有很多扩展的变化。许多程序员和库有不同的方法和类处理实用程序函数来掩盖该语言的某些丑陋部分。

结果是,在混合公司中,您将拥有一堆杂乱无章的元类,它们的行为都略有不同。更糟糕的是,大多数 JavaScript 教程材料都很糟糕,并且提供了某种中间妥协来涵盖所有基础,让你非常困惑。 (可能作者也很困惑。JavaScript 的对象模型与大多数编程语言有很大不同,而且在很多地方直接设计得很糟糕。)

让我们从原型方式开始。这是您可以获得的最原生的 JavaScript:代码开销最少,instanceof 可以处理此类对象的实例。

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

我们可以将方法添加到new Shape 创建的实例中,方法是将它们写入此构造函数的prototype 查找:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

现在对它进行子类化,尽可能多地调用 JavaScript 进行子类化。我们通过完全替换那个奇怪的魔法prototype 属性来做到这一点:

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

在向其添加方法之前:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

这个例子可以工作,你会在很多教程中看到类似的代码。但是,伙计,new Shape() 很难看:我们正在实例化基类,即使没有创建实际的 Shape。它恰好在这种简单的情况下工作,因为 JavaScript 太草率了:它允许传入零个参数,在这种情况下 xy 变为 undefined 并分配给原型的 this.xthis.y .如果构造函数做了更复杂的事情,它就会一头雾水。

所以我们需要做的是找到一种方法来创建一个原型对象,其中包含我们在类级别想要的方法和其他成员,而无需调用基类的构造函数。为此,我们将不得不开始编写帮助代码。这是我所知道的最简单的方法:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

这会将基类原型中的成员转移到一个新的构造函数,该构造函数什么都不做,然后使用该构造函数。现在我们可以简单地写:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

而不是new Shape() 错误。我们现在有了一组可接受的构建类的原语。

我们可以在此模型下考虑一些改进和扩展。例如这里是一个语法糖版本:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

任何一个版本都存在构造函数不能被继承的缺点,就像在许多语言中一样。因此,即使您的子类在构造过程中没有添加任何内容,它也必须记住使用基所需的任何参数调用基构造函数。这可以使用apply 稍微自动化,但您仍然必须写出:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

所以一个常见的扩展是将初始化的东西分解成它自己的函数而不是构造函数本身。然后这个函数就可以从基类继承就好了:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

现在我们刚刚为每个类获得了相同的构造函数样板。也许我们可以把它移到它自己的辅助函数中,这样我们就不必继续输入它了,例如代替Function.prototype.subclass,把它转过来让基类的函数吐出子类:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

...它开始看起来有点像其他语言,尽管语法稍显笨拙。如果您愿意,可以添加一些额外的功能。也许您希望makeSubclass 获取并记住一个类名,并使用它提供一个默认的toString。也许您想让构造函数检测它何时在没有 new 运算符的情况下被意外调用(否则通常会导致非常烦人的调试):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

也许您想传递所有新成员并让makeSubclass 将它们添加到原型中,以节省您编写Class.prototype... 的时间。很多类系统都是这样做的,例如:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

在一个对象系统中有很多您可能认为需要的潜在功能,但没有人真正同意一个特定的公式。


关闭方式,然后。这避免了 JavaScript 基于原型的继承问题,根本不使用继承。而是:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

现在Shape 的每个实例都将拥有自己的toString 方法(以及我们添加的任何其他方法或其他类成员)的副本。

每个实例都有自己的每个类成员的副本的坏处是效率较低。如果您正在处理大量子类实例,原型继承可能会更好地为您服务。正如你所看到的,调用基类的方法也有点烦人:我们必须记住在子类构造函数覆盖它之前该方法是什么,否则它会丢失。

[也因为这里没有继承,instanceof操作符不起作用;如果需要,您必须提供自己的类嗅探机制。虽然您可以以与原型继承类似的方式来处理原型对象,但这有点棘手,而且不值得仅仅让instanceof 工作。]

每个实例都有自己的方法的好处是该方法可以绑定到拥有它的特定实例。这很有用,因为 JavaScript 在方法调用中绑定 this 的奇怪方式,如果你从它的所有者分离一个方法:

var ts= mycircle.toString;
alert(ts());

然后方法中的this 将不会是预期的Circle 实例(它实际上是全局window 对象,导致广泛的调试问题)。实际上,这通常发生在采用方法并将其分配给一般的setTimeoutonclickEventListener 时。

使用原型方式,您必须为每个此类分配包含一个闭包:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

或者,在未来(或者现在,如果你破解 Function.prototype)你也可以使用function.bind()

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

如果您的实例以闭包方式完成,则绑定是通过实例变量上的闭包免费完成的(通常称为thatself,尽管我个人建议不要使用后者,因为self 已经有了另一个,在 JavaScript 中的不同含义)。但是,您无法免费获得上述 sn-p 中的参数 1, 1,因此如果需要,您仍然需要另一个闭包或 bind()

闭包方法也有很多变体。您可能更愿意完全省略 this,创建一个新的 that 并返回它,而不是使用 new 运算符:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

哪种方式是“正确的”?两个都。哪个是“最好的”?这取决于你的情况。 FWIW 当我在做强 OO 的东西时,我倾向于为真正的 JavaScript 继承进行原型设计,并为简单的一次性页面效果使用闭包。

但是对于大多数程序员来说,这两种方式都非常违反直觉。两者都有许多潜在的混乱变化。如果您使用其他人的代码/库,您将同时遇到这两个(以及许多中间和通常损坏的方案)。没有一个普遍接受的答案。欢迎来到 JavaScript 对象的奇妙世界。

[这是为什么 JavaScript 不是我最喜欢的编程语言的第 94 部分。]

【讨论】:

  • 从“类” def 到对象实例化的非常好的渐进式步骤。并且很好地绕过new
  • 看起来 JavaScript 不是你最喜欢的语言,因为你想像使用它一样使用它。
  • 我当然愿意,每个人也一样:对于当今程序员面临的大多数常见问题,类和实例模型是更自然的模型。我确实同意,从理论上讲,基于原型的继承可能会提供一种更灵活的工作方式,但 JavaScript 完全没有兑现这一承诺。它笨重的构造函数系统给了我们两全其美的结果,使得类继承变得困难,同时没有提供原型所能提供的灵活性或简单性。简而言之,就是便便。
  • Bob 我认为这是一个很棒的答案 - 我一直在努力解决这两种模式,我认为你编写的代码比 Resig 更简洁,并且比 Crockford 更深入地解释了.我想不出更高的赞美......
  • 在我看来,将经典继承范例绘制到 javascript 等原型语言上总是一个方形钉子和一个圆孔。有时这是真的有必要吗,或者这只是人们将语言变成他们想要的方式而不是简单地使用语言的一种方式?
【解决方案6】:

另一种方式是http://jsfiddle.net/nnUY4/ (我不知道这种处理对象创建和显示函数是否遵循任何特定模式)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

【讨论】:

    【解决方案7】:

    Closure 用途广泛。 bobince 很好地总结了创建对象时的原型与闭包 方法。但是,您可以以函数式编程方式使用闭包来模仿 OOP 的某些方面。记住 函数是 JavaScript 中的对象;所以以不同的方式将函数用作对象。

    这里是一个闭包的例子:

    function outer(outerArg) {
        return inner(innerArg) {
            return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
        }
    }
    

    不久前,我看到了 Mozilla 关于 Closure 的文章。让我眼前一亮的是:“闭包让你可以将一些数据(环境)与对这些数据进行操作的函数相关联。这与面向对象编程有明显的相似之处,其中对象允许我们关联一些数据(对象的属性)具有一种或多种方法”。这是我第一次读到闭包和经典 OOP 之间的并行性,没有参考原型。

    怎么做?

    假设您要计算某些商品的增值税。增值税可能在申请的整个生命周期内保持稳定。在 OOP(伪代码)中执行此操作的一种方法:

    public class Calculator {
        public property VAT { get; private set; }
        public Calculator(int vat) {
            this.VAT = vat;
        }
        public int Calculate(int price) {
            return price * this.VAT;
        }
    }
    

    基本上,您将增值税值传递给您的构造函数,并且您的计算方法可以通过 closure 对其进行操作。 现在不要使用类/构造函数,而是将您的增值税作为参数传递给函数。因为你唯一感兴趣的就是计算本身,返回一个新函数,也就是calculate方法:

    function calculator(vat) {
        return function(item) {
            return item * vat;
        }
    }
    var calculate = calculator(1.10);
    var jsBook = 100; //100$
    calculate(jsBook); //110
    

    在您的项目中确定适合计算增值税的顶级值。作为一个经验法则,只要你不断地传递相同的参数,就有一种方法可以使用闭包来改进它。无需创建传统对象。

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

    【讨论】:

      【解决方案8】:

      在 JS 中基本上没有类的概念,所以我们使用函数作为类构造函数,这与现有的设计模式相关。

      //Constructor Pattern
      function Person(name, age, job){
       this.name = name;
       this.age = age;
       this.job = job;
       this.doSomething = function(){
          alert('I am Happy');
      }
      }
      

      到目前为止,JS 还不知道你想创建一个对象,所以这里出现了 new 关键字。

      var person1 = new Person('Arv', 30, 'Software');
      person1.name //Arv
      

      参考:面向 Web 开发人员的专业 JS - Nik Z

      【讨论】:

      • 拒绝投票已接受:如果有正当理由,会提供更多信息,并有机会改进。
      • 在 JS 中 class 的概念,就像您在标题中提到的使用 function 关键字一样。它不是一种设计模式,而是该语言的一个有意的特性。我没有对此投反对票,但由于简洁且与问题几乎无关,因此看起来像其他人一样。希望此反馈对您有所帮助。
      【解决方案9】:
      var Person = function (lastname, age, job){
      this.name = name;
      this.age = age;
      this.job = job;
      this.changeName = function(name){
      this.lastname = name;
      }
      }
      var myWorker = new Person('Adeola', 23, 'Web Developer');
      myWorker.changeName('Timmy');
      
      console.log("New Worker" + myWorker.lastname);
      

      【讨论】:

      • 这对已经提供的众多广泛答案有何补充?
      • 我喜欢这个答案,因为它简洁并显示了实现的三个部分:1)定义对象,2)实例化对象的实例,3)使用实例 - 它显示了一切一目了然,而不是通过上面所有冗长的答案进行解析(当然,这些都是非常好的答案,包含人们想要的所有相关细节)——这里是一个简单的总结
      【解决方案10】:

      继续bobince's answer

      在 es6 中,您现在可以实际创建 class

      所以现在你可以这样做了:

      class Shape {
          constructor(x, y) {
              this.x = x;
              this.y = y;
          }
      
          toString() {
              return `Shape at ${this.x}, ${this.y}`;
          }
      }
      

      所以延伸到一个圆圈(如在另一个答案中)你可以这样做:

      class Circle extends Shape {
          constructor(x, y, r) {
              super(x, y);
              this.r = r;
          }
      
          toString() {
              let shapeString = super.toString();
              return `Circular ${shapeString} with radius ${this.r}`;
          }
      }
      

      最终在 es6 中更简洁,更易于阅读。


      这是一个很好的例子:

      class Shape {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
      
        toString() {
          return `Shape at ${this.x}, ${this.y}`;
        }
      }
      
      class Circle extends Shape {
        constructor(x, y, r) {
          super(x, y);
          this.r = r;
        }
      
        toString() {
          let shapeString = super.toString();
          return `Circular ${shapeString} with radius ${this.r}`;
        }
      }
      
      let c = new Circle(1, 2, 4);
      
      console.log('' + c, c);

      【讨论】:

        【解决方案11】:

        除了 2009 年接受的答案。如果您可以定位现代浏览器,则可以使用 Object.defineProperty

        Object.defineProperty() 方法直接定义了一个新属性 对象,或修改对象上的现有属性,并返回 物体。 来源:Mozilla

        var Foo = (function () {
            function Foo() {
                this._bar = false;
            }
            Object.defineProperty(Foo.prototype, "bar", {
                get: function () {
                    return this._bar;
                },
                set: function (theBar) {
                    this._bar = theBar;
                },
                enumerable: true,
                configurable: true
            });
            Foo.prototype.toTest = function () {
                alert("my value is " + this.bar);
            };
            return Foo;
        }());
        
        // test instance
        var test = new Foo();
        test.bar = true;
        test.toTest();
        

        要查看桌面和移动兼容性列表,请参阅Mozilla's Browser Compatibility list。是的,IE9+ 支持它以及 Safari 移动版。

        【讨论】:

          【解决方案12】:

          你也可以试试这个

              function Person(obj) {
              'use strict';
              if (typeof obj === "undefined") {
                  this.name = "Bob";
                  this.age = 32;
                  this.company = "Facebook";
              } else {
                  this.name = obj.name;
                  this.age = obj.age;
                  this.company = obj.company;
              }
          
          }
          
          Person.prototype.print = function () {
              'use strict';
              console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
          };
          
          var p1 = new Person({name: "Alex", age: 23, company: "Google"});
          p1.print();
          

          【讨论】:

            【解决方案13】:

            创建一个对象

            在 JavaScript 中创建对象的最简单方法是使用以下语法:

            var test = {
              a : 5,
              b : 10,
              f : function(c) {
                return this.a + this.b + c;
              }
            }
            
            console.log(test);
            console.log(test.f(3));

            这非常适合以结构化方式存储数据。

            但是,对于更复杂的用例,创建函数实例通常会更好:

            function Test(a, b) {
              this.a = a;
              this.b = b;
              this.f = function(c) {
            return this.a + this.b + c;
              };
            }
            
            var test = new Test(5, 10);
            console.log(test);
            console.log(test.f(3));

            这允许您创建共享相同“蓝图”的多个对象,类似于您使用类的方式,例如。 Java。

            但是,通过使用原型,这仍然可以更有效地完成。

            当一个函数的不同实例共享相同的方法或属性时,您可以将它们移动到该对象的原型中。这样,函数的每个实例都可以访问该方法或属性,但不需要为每个实例复制。

            在我们的例子中,将方法 f 移动到原型是有意义的:

            function Test(a, b) {
              this.a = a;
              this.b = b;
            }
            
            Test.prototype.f = function(c) {
              return this.a + this.b + c;
            };
            
            var test = new Test(5, 10);
            console.log(test);
            console.log(test.f(3));

            继承

            在 JavaScript 中进行继承的一种简单但有效的方法是使用以下两行代码:

            B.prototype = Object.create(A.prototype);
            B.prototype.constructor = B;
            

            这类似于这样做:

            B.prototype = new A();
            

            两者的主要区别在于A的构造函数在使用Object.create时没有运行,这样更直观,更类似于基于类的继承。

            在创建B 的新实例时,您始终可以选择运行A 的构造函数,方法是将其添加到B 的构造函数中:

            function B(arg1, arg2) {
                A(arg1, arg2); // This is optional
            }
            

            如果要将B的所有参数传递给A,也可以使用Function.prototype.apply()

            function B() {
                A.apply(this, arguments); // This is optional
            }
            

            如果你想将另一个对象混入B的构造函数链中,你可以将Object.createObject.assign结合起来:

            B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
            B.prototype.constructor = B;
            

            演示

            function A(name) {
              this.name = name;
            }
            
            A.prototype = Object.create(Object.prototype);
            A.prototype.constructor = A;
            
            function B() {
              A.apply(this, arguments);
              this.street = "Downing Street 10";
            }
            
            B.prototype = Object.create(A.prototype);
            B.prototype.constructor = B;
            
            function mixin() {
            
            }
            
            mixin.prototype = Object.create(Object.prototype);
            mixin.prototype.constructor = mixin;
            
            mixin.prototype.getProperties = function() {
              return {
                name: this.name,
                address: this.street,
                year: this.year
              };
            };
            
            function C() {
              B.apply(this, arguments);
              this.year = "2018"
            }
            
            C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
            C.prototype.constructor = C;
            
            var instance = new C("Frank");
            console.log(instance);
            console.log(instance.getProperties());

            注意

            Object.create 可以在所有现代浏览器中安全使用,包括 IE9+。 Object.assign 不适用于任何版本的 IE 或某些移动浏览器。如果您想使用它们并支持未实现它们的浏览器,建议polyfill Object.create 和/或Object.assign

            你可以找到 Object.create here 的 polyfill 一个用于Object.assign here

            【讨论】:

              【解决方案14】:
              适合我的模式
              var Klass = function Klass() {
                  var thus = this;
                  var somePublicVariable = x
                    , somePublicVariable2 = x
                    ;
                  var somePrivateVariable = x
                    , somePrivateVariable2 = x
                    ;
              
                  var privateMethod = (function p() {...}).bind(this);
              
                  function publicMethod() {...}
              
                  // export precepts
                  this.var1 = somePublicVariable;
                  this.method = publicMethod;
              
                  return this;
              };
              

              首先,您可以更改向实例添加方法而不是构造函数的prototype 对象的偏好。我几乎总是在构造函数内部声明方法,因为我经常使用 构造函数劫持 来实现有关继承和装饰器的目的。

              以下是我决定哪些声明的位置:

              • 切勿直接在上下文对象上声明方法 (this)
              • var 声明优先于function 声明
              • 让基元优先于对象({}[]
              • public 声明优先于private 声明
              • 更喜欢Function.prototype.bind 而不是thusselfvmetc
              • 避免在另一个类中声明一个类,除非:
                • 显然两者是密不可分的
                • Inner 类实现命令模式
                • Inner 类实现了单例模式
                • Inner 类实现了状态模式
                • 内部类实现了另一个保证这一点的设计模式
              • 始终从闭包空间的词法范围内返回this

              以下是这些帮助的原因:

              构造函数劫持
              var Super = function Super() {
                  ...
                  this.inherited = true;
                  ...
              };
              var Klass = function Klass() {
                  ...
                  // export precepts
                  Super.apply(this);  // extends this with property `inherited`
                  ...
              };
              
              模型设计
              var Model = function Model(options) {
                  var options = options || {};
              
                  this.id = options.id || this.id || -1;
                  this.string = options.string || this.string || "";
                  // ...
              
                  return this;
              };
              var model = new Model({...});
              var updated = Model.call(model, { string: 'modified' });
              (model === updated === true);  // > true
              
              设计模式
              var Singleton = new (function Singleton() {
                  var INSTANCE = null;
              
                  return function Klass() {
                      ...
                      // export precepts
                      ...
              
                      if (!INSTANCE) INSTANCE = this;
                      return INSTANCE;
                  };
              })();
              var a = new Singleton();
              var b = new Singleton();
              (a === b === true);  // > true
              

              如您所见,我真的不需要thus,因为我更喜欢Function.prototype.bind(或.call.apply)而不是thus。在我们的Singleton 类中,我们甚至不将其命名为thus,因为INSTANCE 传达了更多信息。对于Model,我们返回this,以便我们可以使用.call 调用构造函数来返回我们传递给它的实例。多余地,我们将它分配给变量updated,尽管它在其他场景中很有用。

              此外,我更喜欢使用 new 关键字而不是 {brackets} 来构造对象文字:

              首选
              var klass = new (function Klass(Base) {
                  ...
                  // export precepts
                  Base.apply(this);  //
                  this.override = x;
                  ...
              })(Super);
              
              不喜欢
              var klass = Super.apply({
                  override: x
              });
              

              如您所见,后者无法覆盖其超类的“覆盖”属性。

              如果我确实向 Class 的 prototype 对象添加方法,我更喜欢对象字面量 -- 使用或不使用 new 关键字:

              首选
              Klass.prototype = new Super();
              // OR
              Klass.prototype = new (function Base() {
                  ...
                  // export precepts
                  Base.apply(this);
                  ...
              })(Super);
              // OR
              Klass.prototype = Super.apply({...});
              // OR
              Klass.prototype = {
                  method: function m() {...}
              };
              
              不喜欢
              Klass.prototype.method = function m() {...};
              

              【讨论】:

                【解决方案15】:

                我想提一下,我们可以使用 a Titlea String 来声明一个 Object。
                调用每种类型的方法都有不同的方法。见下文:

                var test = {
                
                  useTitle : "Here we use 'a Title' to declare an Object",
                  'useString': "Here we use 'a String' to declare an Object",
                  
                  onTitle : function() {
                    return this.useTitle;
                  },
                  
                  onString : function(type) {
                    return this[type];
                  }
                  
                }
                
                console.log(test.onTitle());
                console.log(test.onString('useString'));

                【讨论】:

                  猜你喜欢
                  • 2010-10-05
                  • 2012-05-08
                  • 2012-07-03
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-05-27
                  • 1970-01-01
                  相关资源
                  最近更新 更多