【js设计模式笔记---继承】

继承

在javascript中继承是一个非常复杂的话题,比其他任何面向对象语言的中的继承都复杂得多。在大多数其他面向对象语言中,继承一个类只需要使用一个关键字即可。与它们不同,在javascript中要想达到传承公用成员的目的,需要采取一系列措施。更有甚者,javascript属于使用原型式继承的少数语言之一。利益于这种语言的灵活性,你既可使用标准的基于类的继承,也可使用更微妙一些的原型式继承。

为什么需要继承

一般来说,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化对象间的耦合。使用继承符合前一个设计原则的需要。借助这种机制,你可以在现有类的基础上进行设计并充分利用它们已经具备的各种方法,而对设计进行修改也更为轻松。假设你需要让几个类都拥有一个按特定方式输出类结构的toString()方法,当然可以用复制加粘贴的办法把定义toString()方法的代码添加到每一个类中,但这样做的话,每当需要改变这个方法的工作方式时,你将不得不在每一个类中重复同样的修改。反之,如果你提供了一个ToStringProvider类,然后让那些类继承这个类,那么toString这个方法只需在一个地方声明即可。

让一个类继承另一个类可能会导致二者产生强耦合,也即一个类的依赖于另一个类的内部实现。我们将讨论一些有助于避免这种问题的技术,其中包括用掺元类为其他类提供方法这种技术。

 

类式继承

javascript可能被装扮成使用类式继承的语言。通过用函数来声明类、用关键字new来创建实例,javascript中的对象也能惟妙惟肖地模仿java或c++中的对象。下面是javascript中一个简单的类声明:

function Person(name){

     this.name = name;

}

Person.prototype.getName= function(){

     return this.name;

}

 

要创建该类的实例,只需要结合关键字new调用这个构造函数即可:

var reader = new Person(“John Smith”);

reader.getName();

 

原型链

创建继承Person的类则要复杂一些:

/*Class Author*/

function Author(name,books){

   Person.call(this,name); //call thesuperclass’s constructor in the scope of this.

   this.books = books;

}

Author.prototype =new Person(); //Set up the prototype chain.

Author.prototype.constructor= Author; //Set the constructor attribute to Author.

Author.prototype.getBooks= function(){//Add a method to Author

    return this.books;

}

让一个类继承另一个类需要用到许多行代码(不像大多别的面向对象的语言中那样只用一个关键字extend即可)。首先要做的是像前一个示例中那样创建一个构造函数。在构造函数中,调用超类的构造函数,并将name参数传给它。这行代码需要解释一下。在使用new运算符的时候,系统会为你做一些事。它先创建一个空对象,然后调用构造函数,在此过程这个空对象处于作用域链的最前端。而在Author函数中调用超类的构造函数时,你必须手工完成同样的任务。“Person.call(this,name)”这条语句调用了Person构造函数,并且在此过程中让那个空对象(this)处于作用域链的最前端,而name则被作为参数传入。

下一步是设置原型链。尽管相关代码比较简单,但这实际上是一个非常复杂的话题。前面已经说过,javascript没有extends关键字。但是在javascript中每个对象都有一个名为prototype的属性,这个属性要么指向一另一个对象,要么是null(这种说法并不正确,每个对象都有一个原型对象,但这并不意味着每个对象都有一个prototype属性(实际上只有函数才有这个属性)。在创建一个对象时,javascript会自动将原型对象设置为其构造函数的prototype属性所指的对象。应该注意的是,构造函数本身也是一个对象,它也有自己的原型对象,但这个原型对象并不是它的prototype属性所指向的那个对象。函数作为对象,其构造函数是Function。)。

在访问对象的某个成员时,如果这个成员未见于当前对象,那么javascript会在prototype属性所指的对象中查找它。如果在那个对象中也没有找到,那么javascript会沿着原型链向上逐一访问每个原型对象,直到找到这个成员。这意味着为了让一个类继承另一个类,只需将子类的prototype设置为指向超类的一个实例即可。这与其他语言中的继承机制迥然不同,可能会非常令人费解,而且有违直觉。

为了让Author继承Person,必须手工将Author的prototype设置为Person的一个实例,最后一步骤将prototype的constructornt属性重设为Author。

尽管本例中为实现继承需要使用三行代码,但是创建这个新的子类的实例与创建Person的实例没什么不同

var author = [];

author[0] = newAuthor(“Dustin Diza”,[‘javascript desing patterns’]);

author[1] = newAuthor(“Ross Harmes”,[‘javascript desing patterns’]);

author[1].getName();

author[1].getBooks();

 

由此可见,类式继承的所有复杂性只限于类的声明,创建新实例的过程仍然简单。

extend函数

为了简化类的声明,可以把派生子类的整个过程包装在一个名为extend的函数中。它的作用与其他语言中的extned关键字类似,即基于一个给定的类结构创建一个新的类

/*Extend function*/

//更全面的还是之前学的

function  inherit(p){

  if(p==null){throw TypeError();}

  if(Object.create)return Object.create(p);

  var t = type of p;

  if(t!="object" &&t!="function") throw TypeError();

 function f(){};

 f.prototype = p;

 return new f();

}

 

本书介绍的继承

function  extend(subClass,superClass){

var F = funtion(){};

F.prototype = superClass.prototype;

subClass.prototype = new F();

subClass.prototype.constructor = subClass;

}

这个函数所做的事与先前我们手工做的一样。它设置了prototype,然后再将其constructor重设为恰当的值。作为一项改进,它添加了一个空函数F,并将用它创建一个对象实例插入原型链中。这样做可以避免创建超类的新实例,因为它可能会较庞大,而且有时超类的构造函数有一些副作用,或者会执行一些需要大量计算的任务

使用extend函数后,前面的Person/Author例子变成了这个样子:

/*Class Person*/

function  Person(name){

   this.name = name;

}

Person.prototype.getName= function(){

   return this.name;

}

/*Class Author*/

function  Author(name,books){

    Person.call(this.name);

    this.books = books;

}

extend(Author,Person);

Author.prototype.getBooks= function(){

   this.books;

}

上例中的问题是超类Person的名称被固化在Author类的声明中。更好的方法是像下面这样用一种更具普适性的方式来引用父类。

/*改善*/

function  extend(subClass,superClass){

var F = funtion(){};

F.prototype = superClass.prototype;

subClass.prototype = new F();

subClass.prototype.constructor = subClass;

subClass.superclass =superClass.prototype

if(superClass.prototype.constructor==Object.prototype.constructor){

   superClass.prototype.constructor = superClass;

}

}

上例中它提供了superclass属性,这个属性可以用来弱化Author与Person之间的耦合。就可以有superclass属性来调用超类的构造函数

function  Author(name,books){

    Author.superclass.constructor.call(this,name);

   this.books = books;

}

extend(Author,Person);

Author.prototype.getBooks= function(){

   return this.books;

}

有了superClass属性,就可以直接调用超类中的方法。这在既要重定义超类的某方法而又想访问其在超类的中的实现时可以派上用场。例如,为了用一个新的getName方法重定义Person类中的同名方法,你可以先用Author.superclass.getName获取作者名,然后在此基础上添加其他信息

Author.prototype.getName= function(){

    var name = Author.superclass.getName.call(this);

    return name+”, Author of ”+this.getBooks().join(“,“);

}

原型式继承

原型式继承与类式继承截然不同。我们发现在谈到它的时候,最好忘掉关于类和实例的一切知识,只从对象的角度来思考。用基于类的办法来创建对象包含两个步骤:

首先,用一个类的声明定义对象的结构

第二,实例化该类以创建一个新的对象。

用这种方式创建的对象都有一套该类 所有实例属性的副本。每个实例方法都只存在一份,但每个对象都有一个指向它的连接。

使用原型式继承时,并不需要用类来定义对象的结构,只需要直接创建一个对象即可。这个对象随后可以被新的对象重用,这得益于原型链查找的工作机制。这个对象被称为原型对象,这是因为它为其他对象就有的模样提供了一个原型。这正原型式继承这个名称的由来。如下:

/*Person 原型对象*/

var Person = {

   name:”default name”,

   getName:function(){

    return this.name;

   }

}

这里并没有使用一个名为Person的构造函数来定义类的结构,Person现在是一个对象字面量。它是所要创建的其他种种类Person对象的原型对象。其中定义了所有类的Person对象都具备的属性和方法,并为它们提供了默认值。方法的默认值可能不会被改变,而属性的默认值一般都都会被改变:

var reader =clone(Person);

alert(reader.getName());

reader.name = “JohnSmith”;

alert(reader.getName());

clone函数可以用来创建新的Person对象。它会创建一个空对象,而该对象的原型被设置成为Person。这意味着在这个新对象中查找某个方法或属性时,如果找不到,那么查找过程会在其原型对象中继续进行。

你不必为创建Author而定义一个Person子类,只要执行一次克隆即可:

/*Author 原型对象*/

var Author =clone(Person);

Author.books = [];

Author.getBooks =function(){

     return this.books;

}

然后你可以重写义该克隆中的方法和属性。可以修改在Person中提供了默认值,也可以添加新的属性和方法。

这样一来就创建了一新的原型对象,你可以将其用于创建新的类Author对象。

对于继承而来的成员的读和写的不对等性

前面说过,为了有效地使用原型式继承,必须忘记有关类式继承的一切。这里就是一个例子。在类式继承中,Author的每个实例都有一份自己的books数组副本。你可以用代码author.books.push(“new book title”)为其添加新的元素。但是对于使用原型式继承方式创建的类Author对象来说,由于原型链接的工作方式,这种做法并非一开始就能行得通。一个克隆并非原型对象的一份完全独立的副本,它只是一个以那个对象为原型对象的空对象而已。克隆刚被创建时,author.name其实是一个返回最初的Person.name的链接。对于从原型对象继承而来的成员,其读写具有内在不对等性。在读取author.name的值时,如果你还没有直接为author实例定义name属性的话,那么所得到的是其原型对象的同名属性值。而在写入author.name的值时,你是在直接为author对象定义一个新属性。

如下

var authorClone =clone(Author);

alert(authorClone.name);//Linked to the primative Person.name, which is the string ‘default name’;

author.name = ‘newname’; //a new primative is created and added to the authorClone object itself.

alert(authorClone.name);//访问的是authorClone对象的name 属性

//the same follow

authorClone.books.push(“newbooks”); //链接的是Author.books

authorClone.books= []; //创建一个新的数组,并添加到authorClone对象上。

authorClone.books.push(“newbooks”); //现在改的是新数组

这也说明了为什么必须通过引用传递数据类型的属性创建新副本。在上面的例子中,向authorClone.books数组添加新元素实际上是把这个元素添加到Author.books数组中。这可不是什么好事,因为你对那个值的修改不仅会影响Author,而且会影响所有继承了Author但还未改写那个属性的默认值的对象。在这种场合中,可以使用hasOwnProperty方法来区分对象的实际成员和它继承而来的成员

有时原型对象自己也含有子对象。如果想覆盖子对象中的一个属性值,你不得不重新创建整个子对象。这可以通过将该子对象设置为一个空对象字面量,然后对其进行重塑而办到。但这意味着克隆出来的对象必须知道其原型对象的每一个子对象的确切结构和默认值。为了尽量弱化对象之间的耦合,任何复杂的子对象都应该使用方法来创建:

  var CompondObject = {

        string1:'default value',

        childObject:{

            bool:true,

            num:10

        }

    }

 

    var compoundObjectClone =clone(CompondObject);

    //不好的方式

    compoundObjectClone.childObject.num = 5;

    //好一些的,但compoundObjectClone必须知道对象的结构及默认值。这样可以使用CompondObject

    //与compoundObjectClone之解耦。

    compoundObjectClone.childObject = {

        bool:true,

        num:5

    }

在这个例子中,为compoundObjectClone对象添加一个childObject属性,并修改了它所指向的对象num属性。问题在于compoundObjectClone必须知道childObject具有两个默认值分别为true和10的属性。更好的办法是用一个工厂方法来创建childObject:

//best approach.Uses a method to create a new object,with the same structrue and default as theoriginal.

var CompoundObject= {}

CompoundObject.string1= ‘default value’;

CompoundObject.createChildObject= function(){

return {

   bool:true,

   num:10

}

};

CompoundObject.childObject= CompoundObject.createChildObject();

compoundObjectClone.childObject= CompoundObject.createChildObject();

compoundObjectClone.childObject.num= 5;

clone函数

在前面的例子中用来创建克隆对象的奇妙函数空间长什么样?如下:

function clone(object){

    function F(){}

   F.prototype = object;

   return new F;

}

clone函数首先创建了一个新的空函数F,然后将F的prototype属性设置为作参数ojbect传入的原型对象。由此可以体会到javascript最初设计者的用意。prototype属性就是用来指向原型对象的。通过原型链接机制,它提供了到有继承而来的成员链接。该函数最后通过new运算符作用于F创建出一个新对象,然后把这个新对象作为返回值返回。函数所返回的这个克隆结果是一个以给定对象为原型对象的空对象

 

掺元类

有一种重用代码的方法不需要用到严格的继承。如果想把一个函数用到多个类中,可以通过扩充 的方式让这个类共享该函数。其实实际做法大体为:先创建一个包含各种通用方法的类,然后再用它扩充其它类。这种包含通常方法的类称为掺元类。它们通常不会被实例化或直接调用。其存在的目的只是几其他类提供自己的方法。如下:

var Mixin =function(){};

Minix.prototype ={

     serialize:function(){

        var output = [];

        for(var key in this){

            output.push(key+”:”+this[key]);

        }

         return output.join(‘,’);

     }

}

这个方法可能在许多不同类型的类中都会用到,但没有必要让这些类都继承Mixin,把这个方法的代码复制到这个类中也并不明智。最好还是用augment函数把这个方法添加到每一个需要它的类中。

augment(Author,Mixin);

var author = new Author(“Ross Harmes”,[“javascript Design Patterns”]);

var serializedString = author.serialize();

在此我们用Mixin类中的所有方法扩充了Author类。Author类的实例现在就可以调用serialize方法了。这可以被视为多亲继承(multiple inheritance) 在javascript中的一种实例方式。C++和Python这类语言允许子类继承多个超类。这在js中是不允许的,因为一个对象只能拥有一个原型对象。不过由于一个类可以用多个掺元类加以扩展,所以这实际上实现了多继承的效果。

 

function arugment(receivingClass,givingClass){

      for(var methodName in givingClass){

         if(!receivingClass.prototype[methodName]){

             receivingClass.prototype[methodName] =givingClass.prototype[methodName];

          }

      }

  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
Node.js设计模式是一本详细解释了Node.js底层知识和设计方法的书籍。Node.js作为一个基于事件驱动、非阻塞I/O模型的JavaScript运行环境,具有许多独特的设计模式和最佳实践。这本书提供了关于如何使用这些设计模式来构建可扩展和高性能的Node.js应用程序的指导。 在Node.js中,装饰模式是一种通过继承的方式为一个基类对象扩展功能的方法。通过继承基类对象,我们可以在子类中添加新的功能以扩展原有功能。这种模式可以使得代码更具灵活性和可维护性,同时也遵循了面向对象的设计原则。 举个例子来说明装饰模式在Node.js中的应用。假设我们有一个基类对象代表一颗圣诞树,一开始它没有任何装饰品。然后,我们可以创建一个装饰器类来继承基类对象,并在子类中添加额外的装饰品功能。通过这种方式,我们可以动态地向圣诞树添加不同的装饰品,而不需要修改基类对象的代码。 在Node.js中实现装饰模式的一种方式是使用util模块的inherits方法来实现继承。我们可以创建一个装饰器类,并在该类中继承基类对象。然后,我们可以在装饰器类中重写基类对象的方法,并在方法中添加额外的功能。 总结起来,Node.js设计模式是一本详细解释了Node.js底层知识和设计方法的书籍。而装饰模式则是一种通过继承的方式为一个基类对象扩展功能的方法,在Node.js中可以使用util模块的inherits方法来实现继承。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [nodejs设计模式](https://download.csdn.net/download/ariflight/10460182)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [《Node.js开发实战详解》学习笔记](https://blog.csdn.net/diankan7855/article/details/101768936)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [NodeJS设计模式(一)](https://blog.csdn.net/It_sharp/article/details/89605464)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值