美文网首页
JavaScript设计模式:构建模型(一)

JavaScript设计模式:构建模型(一)

作者: 魂斗驴 | 来源:发表于2021-02-11 12:21 被阅读0次

    学习JavaScript的最困难方面之一就是让您了解对象和所有相关的术语。原型,构造函数,构造属性,dunder原型,原型链等。当您尝试太快时,它很容易变得不知所措。

    本文将带您一步一步了解JavaScript的面向对象方法,以便您可以缓慢而逐步地构建心智模型。它是为那些刚接触JavaScript对象而又在与相关概念和术语苦苦挣扎的人们写的。

    建立模型

    我们将从可能很熟悉的图片开始我们的模型。在考虑与继承相关的类比之前,您可能已经拥有它或等效的产品。

    在这里,我们有一个Terrier从类继承的Dog类,而该类又从一个类继承Animal.现在,忘记有关基于类的面向对象编程的所有知识。这是不同的。我们没有类,只有对象。每个对象都链接到另一个对象。因此,我们可以重画上图来表示此“链接”概念。

    在这里,terrier对象链接到dog对象,而对象又链接到animal对象。但是它们如何联系?JavaScript中的每个对象都有一个内部[[prototype]]属性,可以直接由proto属性访问。此属性称为“ dunder proto”,本质上指向另一个对象。可以将其视为到另一个对象的链接。考虑到这一点,让我们重新绘制图表!

    好的,太好了。我们可以看到该proto属性指向另一个对象。但这对我们意味着什么?

    原型链

    原型链或原型继承是JavaScript如何从其他对象“继承”属性。如果我们尝试访问某个对象上的属性,而不是该对象直接拥有的属性,则下一个调用端口是该属性指向的对象proto。我们可以使用创建一个链接到另一个对象的对象Object.create(obj)。这将创建一个新对象,该对象的proto属性设置为指向obj,该对象作为参数传入。我们来看一些代码。

    var animal = {
      type: 'mammal',
      breathe: function() { 
        console.log("I'm breathing");
      },
    }
    
    var dog = Object.create(animal);
    
    console.log(dog);                      // {}
    console.log(dog.type);                 // "mammal"
    console.log(dog.__proto__);            // { type: 'mammal', breathe: ƒ }
    console.log(dog.__proto__ === animal); // true
    

    这样我们就可以清楚地看到dog和之间的关系animal。即使没有直接定义的属性dog,我们仍然可以访问type。让我们terrier从创建第三个对象dog。请注意,我们的terrier对象还将继承该animal对象上定义的属性。
    注意:从此处开始的代码块可能涉及很多重复。重复的代码已被注释掉,以帮助您专注于更改/添加。

    // var animal = {
    //   type: 'mammal',
    //   breathe: function() { 
    //     console.log("I'm breathing");
    //   },
    // }
    
    // var dog = Object.create(animal);
    var terrier = Object.create(dog);
    
    console.log(terrier.type);                 // "mammal"
    console.log(terrier.__proto__)             // {}
    console.log(terrier.__proto__ === dog);    // true
    console.log(terrier.__proto__ === animal); // false
    

    从上面我们可以看出,这terrier.proto显然是指向dog且dog单独指向的。有两种方法可以让我们查询一个对象与另一个对象之间的原型关系。
    第一个Object.prototype.isPrototypeOf()用于检查一个对象是否在另一个对象的原型链上的任何位置。所以对于我们的三个示例对象,我们可以从图中看到的上面animal是两个的原型链dog和terrier,同时dog是对的原型链terrier。让我们测试一下这个假设...

    // var animal = {
    //   type: 'mammal',
    //   breathe: function() { 
    //     console.log("I'm breathing");
    //   },
    // }
    
    // var dog = Object.create(animal);
    // var terrier = Object.create(dog);
    
    console.log(animal.isPrototypeOf(terrier)); // true
    console.log(animal.isPrototypeOf(dog));     // true
    console.log(dog.isPrototypeOf(terrier));    // true
    

    好吧,加起来。但是,如果我们要查找是否dog.proto直接链接到animal而不是位于其原型链上,该怎么办。好吧,我们可以使用dog.proto === animal,在这种情况下,它将返回true。proto但是,此属性直到最近才被标准化,由于某些潜在的问题行为,实际上已被列为已弃用。还有一种查询此关系的方法,即使用Object.getPrototypeOf(obj)。

    // var animal = {
    //   type: 'mammal',
    //   breathe: function() { 
    //     console.log("I'm breathing");
    //   },
    // }
    
    // var dog = Object.create(animal);
    // var terrier = Object.create(dog);
    
    console.log(Object.getPrototypeOf(terrier) === dog)    // true
    console.log(Object.getPrototypeOf(terrier) === animal) // false
    

    那么,如果dog在创建后向其中添加属性,会发生什么terrier?可以使用此属性terrier吗?

    // var animal = {
    //   type: 'mammal',
    //   breathe: function() { 
    //     console.log("I'm breathing");
    //   },
    // }
    
    // var dog = Object.create(animal);
    // var terrier = Object.create(dog);
    
    dog.speak = function() { 
      console.log("Woof Woof"); 
    };
    
    terrier.speak(); // "Woof Woof"
    

    创建对象后,是否将属性添加到对象的原型链并不重要。这里terrier.proto实际上指向dog,而不是的副本dog。原型链向上的任何更改都将进一步向下反映。

    链接到其他对象的对象(OLOO)

    链接到其他对象的对象(OLOO)是一种JavaScript设计模式,可让我们定义一个父对象,从中可以创建其他对象。所有共享属性都将在此父对象上定义。JavaScript中使用一种约定,该父对象的首字母将大写。就是这样,一个约定。归根结底,这只是另一个对象。让我们重新定义我们的思维模型以反映这一约定。


    现在假设我们要从创建两个不同的对象Animal。我们将创建一个reptile对象和一个mammal对象。以下是我们要实现的目标的概述:

    我们希望这些对象共享一个公共breathe属性,但是具有不同的type属性。一个将是.type = "mammal",另一个将是.type = "reptile”。让我们在心理模型中包括属性名称,以更好地了解我们正在尝试做的事情。

    现在让我们看一下代码…

    var Animal = {
      init: function(type) { 
        this.type = type;
      },
    
      breathe: function() {
        console.log("I'm breathing");
      },
    }
    
    var Dog = Object.create(Animal);
    var Terrier = Object.create(Dog);
    
    var mammal = Object.create(Animal);
    mammal.init("mammal");
    var reptile = Object.create(Animal);
    reptile.init("reptile");
    
    console.log(mammal.type);   // "mammal"
    mammal.breathe();           // "I'm breathing"
    console.log(reptile.type);  // "reptile"
    reptile.breathe();          // "I'm breathing"
    

    您会看到我们init在上定义了一个新方法Animal。此方法用于初始化我们新创建的对象中的值。因此,line 14我们创建一个新对象mammal。由于mammal是从创建的Animal,因此它的proto对象将指向Animal。反过来,这意味着它将有权访问在上定义的属性Animal,包括init方法。此方法没有什么特别的,可以随意调用,但要使用约定init。

    如果我们遵循的方法调用上line 15到line 3,需要注意的重要一点是,this将是该方法被调用,在这种情况下对象mammal。因此line 3将等同于mammal.type = "mammal","mammal"作为传入的参数。类似地,在line 17执行时,我们可以遵循line 3,即等同于reptile.type = "reptile"。

    因为我们的init方法定义的Animal,它现在也算是提供给Dog和Terrier由于原型继承。因此,如果我们从创建一个新对象Terrier,rex例如,rex也将使用此init方法。快速的视觉效果,然后我们将通过代码看到它。

    // var Animal = {
    //   init: function(type) { 
    //     this.type = type;
    //   },
    
    //   breathe: function() {
    //     console.log("I'm breathing");
    //   },
    // }
    
    // var Dog = Object.create(Animal);
    // var Terrier = Object.create(Dog);
    
    var rex = Object.create(Terrier);
    rex.init("canine");
    
    console.log(rex.type);   // "canine"
    rex.breathe();           // "I'm breathing"
    

    JavaScript的OLOO设计模式的基本要素就在这里。这种设计模式可能是最简单的设计模式,并且由于其原型继承而包含了JavaScript。

    还有另一种流行的设计模式,即伪古典模式,它使用函数来创建对象。新开发人员可能很难理解这种方法的细微差别。查看图表和心理模型可能看起来像蜘蛛网。因此,我们将逐步建立一个思维模型,每次一步。在继续前进之前,您必须完全掌握心智模型和代码段中的每一项内容,否则您很快就会发现更多的补充内容很重要。

    伪古典模式

    让我们保持简单,从基于类的继承的基本思维模型开始。

    这是伪古典模式尝试模仿的方法。它是通过使用函数而不是类来创建对象的。从下面的工厂函数中我们可以看出,从函数创建对象是多么容易。

    function createAnimal() {
      return {
        type: "mammal",
    
        breathe: function() {
          console.log("I'm breathing");
        }
      }
    }
    
    var animal = createAnimal();
    
    console.log(animal.type);                        // "mammal"
    animal.breathe();                                // "I'm breathing"
    console.log(Object.getOwnPropertyNames(animal)); // ["type", "breathe"]
    

    这种工厂功能方法有两个主要缺点。首先,我们无法识别创建特定对象的函数。在OLOO方法中,我们可以做到Object.getPrototypeOf(rex) === Terrier。在基于类的语言中,我们可以轻松地查询对象以找出其类。我们需要一种方法来识别哪些函数创建了我们的JavaScript对象。

    第二个缺点归结为系统资源的使用。在上面的代码片段中,我们可以看到,返回的每个对象createAnimal都有其自己的breathe()方法。对于上面的小片段来说,这一切都很好,但是如果您返回的对象有数百种方法,请想象一下。此外,您的函数可用于创建数百个此类对象。将每种方法复制到每个对象都浪费了很多资源。在OLOO方法中,我们有一个指向包含共享方法的父对象的链接。有一个类似的方法会很好。

    参考

    JavaScript Design Patterns: Building a Mental Model

    相关文章

      网友评论

          本文标题:JavaScript设计模式:构建模型(一)

          本文链接:https://www.haomeiwen.com/subject/kskkxltx.html