//拷贝继承functionextend{for{destination[property]=source[property];}}extend(SubClass.prototype,SuperClass.prototype)

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

可以看到子类的原型中也包含了父类的特权成员,只不过因为解释器的属性查找机制,被子类的特权成员所覆盖,只要子类的特权成员被删除,原型中相应的成员就会暴露出来:

3、继承

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

总结

修改后的代码整理如下:

3.1、拷贝继承

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

//用于子类继承父类原型的工具函数functioninheritPrototype{functionTemp(){}Temp.prototype=superClass.prototype;subClass.prototype=newTemp();subClass.prototype.constructor=subClass;}//父类functionPerson{//特权成员this.name=name;this.age=age;}//原型成员Person.prototype.sayHi=function(){alert("Hi,I''m"+this.name);};//子类functionUser{//继承父类特权成员Person.call;//添加新的特权成员this.password=password;}//继承父类原型inheritPrototype;//重写父类原型方法User.prototype.sayHi=function(){alert("Hi,mynameis"+this.name);};//扩展子类原型User.prototype.getPassword=function(){returnthis.password;};

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

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

//构造函数+原型组合模式functionPerson{this.name=name;this.age=age;}Person.prototype.sayHi=function(){alert("Hi,I''m"+this.name);};varzhangsan=newPerson;varlisi=newPerson;alert(zhangsan.sayHi===lisi.sayHi);//true

2.2、原型模式

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

//原型链继承functionUser{//继承特权成员Person.call;this.password=password;}//继承原型User.prototype=newPerson();//修改了原型指针,需重新设置constructor属性User.prototype.constructor=User;varzhangsan=newUser("张三",20,"123456");zhangsan.sayHi();//Hi,I''m张三

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

但每个类、每个子类、每个子类的子类,都要这么分几步写,也是很蛋疼的。对象有对象工厂,类当然也可以搞个类工厂,江湖上已有不少现成的类工厂,让我们可以从统一规范的入口来生成自定义类。

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

在Person中,sayHi 是原型成员,name 和 age
特权成员,它们都是公共成员

此时,子类实例的原型链大致如下:

functioninheritPrototype{functionTemp(){}Temp.prototype=superClass.prototype;subClass.prototype=newTemp();subClass.prototype.constructor=subClass;}inheritPrototype;

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

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

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

3.3、原型式寄生组合继承

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

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

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

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

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

原型中可以添加原型成员

这时,“类”就登场了。

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

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

再看下 zhangsan 的结构:

zhangsaninstanceof?lisi.constructor=?

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

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

3.2、原型继承

Person.prototype.friends=[];zhangsan.friends.push;alert;//["王五"]

1、对象

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

因为临时类的构造函数是空实现,子类在继承原型时自然不会执行到父类的初始化操作,也不会继承到一堆乱七八糟的特权成员。

前面我们已经学习了很多关于JavaScript的知识了,今天主要想谈的是关于JavaScript里的类和继承。JavaScript与大部分客户端语言有几点明显的不同:

//构造函数模式functionPerson{this.name=name;this.age=age;this.sayHi=function(){alert("Hi,I''m"+this.name);};}varzhangsan=newPerson;varlisi=newPerson;alert(zhangsaninstanceofPerson);//truealert(lisi.constructor===Person);//true

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

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

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

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

//对象工厂functioncreatePerson{return{name:name,age:age,sayHi:function(){alert("Hi,I''m"+this.name);}};}varzhangsan=createPerson;varlisi=createPerson;

2、类2.1、构造函数模式

//通过构造函数创建varzhangsan=newObject();zhangsan.name="张三";zhangsan.age=20;zhangsan.sayHi=function(){alert("Hi,I''m"+this.name);};//通过对象字面量创建varlisi={name:"李四",age:21,sayHi:function(){alert("Hi,I''m"+this.name);}};

类由构造函数和原型组成

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

JS是动态解释性语言,没有编译过程,它在程序运行过程中被逐行解释执行JS是弱类型语言,它的变量没有严格类型限制JS是面向对象语言,但没有明确的类的概念(虽然有class关键字,然而目前并没有什么卵用)JS虽然没有类,但可以通过一些方法来模拟类以及实现类的继承。一切皆对象,还先从对象说起。

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

2.3、类的结构

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

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

父类的构造函数被执行了 2 次:继承特权成员时 1 次,继承原型时又 1
次。父类初始化两次,这有时会导致一些问题,举个例子,父类构造函数中有个alert,那么创建子类实例时,会发现有两次弹框。不仅如此,还导致了下面的问题。从控制台中查看子类的实例,结构如下:

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

ECMA-262对对象的定义是:无序属性的集合,其属性可以包含基本值、对象或者函数。直观点描述,就是由多个键值对组成的散列表。

到此为止,我们已经比较完美地实现了类和类的继承。

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

相关文章

网站地图xml地图