潍坊网站设计—JavaScript里的类和继承

2018.04.16 潍坊网站设计,JavaScript

54


  1、对象(Object)

 

  ECMA-262对对象的定义是:无序属性的集合,其属性可以包含基本值、对象或者函数。

 

  直观点描述,就是由多个键值对组成的散列表。

 

  JS创建对象的方法和其它语言大同小异:

 

  //通过构造函数创建

 

  var zhangsan=new Object();

 

  zhangsan.name="张三";

 

  zhangsan.age=20;

 

  zhangsan.sayHi=function(){

 

  alert("Hi,I'm"+this.name);

 

  };

 

  //通过对象字面量创建

 

  var lisi={

 

  name:"李四",

 

  age:21,

 

  sayHi:function(){

 

  alert("Hi,I'm"+this.name);

 

  }

 

  };

 

  当需要大量创建相同结构的对象时,可以使用对象工厂(Object Factory):

 

  //对象工厂

 

  function createPerson(name,age){

 

  return{

 

  name:name,

 

  age:age,

 

  sayHi:function(){

 

  alert("Hi,I'm"+this.name);

 

  }

 

  };

 

  }

 

  var zhangsan=createPerson("张三",20);

 

  var lisi=createPerson("李四",21);

 

  但通过这种方式创建出来的实例,不能解决类型识别问题,只知道它是一个对象,但具体什么?无法判断:

 

  zhangsan instanceof?

 

  lisi.constructor=?

 

  这时,“类”就登场了。



潍坊网站设计

 

  2、类(Class)

 

  2.1、构造函数模式

 

  事实上,JS中每个函数(function)本身就是一个构造函数(constructor),就是一个类:

 

  //构造函数模式

 

  function Person(name,age){

 

  this.name=name;

 

  this.age=age;

 

  this.sayHi=function(){

 

  alert("Hi,I'm"+this.name);

 

  };

 

  }

 

  var zhangsan=new Person("张三",20);

 

  var lisi=new Person("李四",21);

 

  alert(zhangsan instanceof Person);//true

 

  alert(lisi.constructor===Person);//true

 

  这里面其实有个问题:

 

  alert(zhangsan.sayHi===lisi.sayHi);//false

 

  多个实例中的同名方法并不相等,也就是说存在多个副本。而这些行为是相同的,应该指向同一个引用才对。

 

  为了解决这个问题,JS为每个函数分配了一个prototype(原型)属性,该属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。



潍坊网站设计

 

  2.2、原型模式

 

  原型(Prototype):指向一个对象,作为所有实例的基引用(base reference)。

 

  //构造函数+原型组合模式

 

  function Person(name,age){

 

  this.name=name;

 

  this.age=age;

 

  }

 

  Person.prototype.sayHi=function(){

 

  alert("Hi,I'm"+this.name);

 

  };

 

  var zhangsan=new Person("张三",20);

 

  var lisi=new Person("李四",21);

 

  alert(zhangsan.sayHi===lisi.sayHi);//true

 

  在Person中,sayHi是原型成员(prototype),name和age是特权成员(privileged),它们都是公共成员(public)。

 

  注:“特权”是道格拉斯提出的名词。道格拉斯·克罗克福德(Douglas Crockford),Web界人称道爷,JSON创立者,《Java语言精粹》作者,JSLint、JSMin、ADsafe开发者。

 

  类的原型带有一个constructor属性,指向该类的构造函数(如果重新分配了原型指针,需要手动添加constructor属性);类的实例上会自动生成一个属性指向该类原型(在Chrome上可以通过“__proto__”访问到该对象,而IE上该属性则是不可见的)。

 

  Person、Person的原型、Person的实例间的关系如下:

 

  需要注意的是,原型成员保存引用类型值时需谨慎:

 

  Person.prototype.friends=[];

 

  zhangsan.friends.push("王五");

 

  alert(lisi.friends);//["王五"]

 

  张三的基友莫名其妙就变成李四的基友了,所以friends应该添加为特权成员,而不是原型成员。

 

  2.3、类的结构

 

  综上所述,JS中的类的结构大致如下:

 

  类由构造函数和原型组成

 

  构造函数中可以声明私有成员和添加特权成员

 

  原型中可以添加原型成员

 

  私有成员可以被特权成员访问而对原型成员不可见

 

  特权成员和原型成员都是公共成员

 

  3、继承(Inherit)

 

  在JS中继承是如何实现的呢?

 

  3.1、拷贝继承

 

  最简单直接的方式莫过于属性拷贝:

 

  //拷贝继承

 

  function extend(destination,source){

 

  for(var property in source){

 

  destination[property]=source[property];

 

  }

 

  }

 

  extend(SubClass.prototype,SuperClass.prototype);

 

  这种方式虽然实现了原型属性的继承,但有一个非常明显的缺陷:子类实例无法通过父类的instanceof验证,换句话说,子类的实例不是父类的实例。

 

  3.2、原型继承

 

  在Chrome的控制台中查看HTMLElement的原型,大致如下:

 

  可以清晰看到,HTMLElement的原型是Element的实例,而Element的原型又是Node的实例,从而形成了一条原型链(Prototype-chain),JS的原生对象就是通过原型链来实现继承。

 

  这里顺道说下解释器对实例属性的查找过程:

 

  在特权属性中查找

 

  特权属性中没找到,再到原型属性中查找

 

  原型属性中没找到,再到原型的原型属性中查找

 

  直到根原型还没找到,返回undefined

 

  这就说明为什么我们自定义的类明明没有声明toString()方法,但仍然可以访问到,因为所有对象都继承自Object。

 

  因此,我们也可以通过原型链来实现继承:

 

  //原型链继承

 

  function User(name,age,password){

 

  //继承特权成员

 

  Person.call(this,name,age);

 

  this.password=password;

 

  }

 

  //继承原型

 

  User.prototype=new Person();

 

  //修改了原型指针,需重新设置constructor属性



潍坊网站设计公司

 

  User.prototype.constructor=User;

 

  var zhangsan=new User("张三",20,"123456");

 

  zhangsan.sayHi();//Hi,I'm张三

 

  运行正常,貌似没什么问题,但其实里面还是有些坑:

 

  父类的构造函数被执行了2次:继承特权成员时1次,继承原型时又1次。

 

  父类初始化两次,这有时会导致一些问题,举个例子,父类构造函数中有个alert,那么创建子类实例时,会发现有两次弹框。

 

  不仅如此,还导致了下面的问题。从控制台中查看子类的实例,结构如下:

 

  可以看到子类的原型中也包含了父类的特权成员(直接创建了一个父类实例,当然会有特权成员),只不过因为解释器的属性查找机制,被子类的特权成员所覆盖,只要子类的特权成员被删除,原型中相应的成员就会暴露出来:

 

  delete zhangsan.name;

 

  alert(zhangsan.name);//此时访问到的就是原型中的name

 

  那怎么办呢?对此道爷提供了一个很实用的解决方案——原型式寄生组合继承。

 

  3.3、原型式寄生组合继承

 

  我们的目的是子类原型只继承父类的原型,而不要特权成员,原理其实很简单:创建一个临时的类,让其原型指向父类原型,然后将子类原型指向该临时类的实例即可。实现如下:

 

  function inheritPrototype(subClass,superClass){

 

  function Temp(){}

 

  Temp.prototype=superClass.prototype;

 

  subClass.prototype=new Temp();

 

  subClass.prototype.constructor=subClass;

 

  }

 

  inheritPrototype(User,Person);  

 

  转载请注明:潍坊网站设计http://www.mfdream.com/newsshow/214.html


关键词

最新案例

寒枫总监

凌霄总监

来电咨询

400-6065-301

微信咨询

寒枫总监

凌霄总监

TOP