最近在看《JavaScript设计模式》,然后开篇复习了JavaScript中的几种继承方式,自己似乎也没有怎么仔细探究过,目前自己没怎么碰到过应用的场景(噗),所以借这次机会好好来屡屡思路。
方式1 类式继承
例子
function Person() {
this.telephone = ['000-0000-0000'];
}
function Student(className) {
this.className = className;
}
Student.prototype = new Person();
var Haha = new Student(1);
var Xixi = new Student(2);
创建好父类和子类。联系他们的方式是把学生的prototype指向一个人的实例。
问:prototype是什么?
几乎任何对象有一个[[prototype]]属性,在标准中,[[prototype]]一个隐藏属性,指向的是这个对象的原型。而它的指向是由构造该对象的方法决定的:
1.对象字面量构造:其[[prototype]]指向Object.prototype。
var person = {};
2.构造函数构造:new操作符调用的函数就是构造函数。其[[prototype]]和其构造函数的prototype指向相同。而构造函数prototype属性指向的对象带有constructor属性,指向函数自身。
function Person(){}
var person = new Person();
此图为Person的prototype内容,可以看到constructor属性实际指向的就是Person()函数。(小绿色框框内和外面绿色框框其实是同一个内容)。
3.Object.create构造的。
var person = {};
var Haha = Object.create(person);
这里对象Haha的[[prototype]]指向对象person。也可以写null,此时对象Haha就没有原型。
首先要分清楚类和实例,在控制台显示中,只有类才会有prototype属性,而实例是拥有一个名为_proto_的属性,它会指向构造它函数的原型,两者本质都是一个指针。
function Person() {
this.telephone = ['000-0000-0000'];
}
var Hehe = new Person();
console.log(Person.prototype);
console.log(Hehe);
以上代码运行结果:
可以瞧见,这里Hehe的_proto_
是指向了Person.prototype
。
问:new关键字的作用是什么?
new关键字运作的过程如下,引用自《JavaScript》高级程序设计:
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。 2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this。
简单来说,它创建了一个空对象,指定了原型,把属性方法进行拷贝,并把this指向进行了改变。假如我们把上面的代码改成:
function Person() {
this.telephone = ['000-0000-0000'];
}
var Hehe = Person();
console.log(Hehe.telephone);
去掉new关键词赋予Person(),会报错,而输出window.telphone
得到的就是['000-0000-0000']
。因为函数的返回值(没有返回值所以是undefined)赋予给了Hehe,尝试去读取undefined的属性,报错了。而此时函数运行中的this是全局变量window。
So,回归类式继承,仔细看看诞生的嘻嘻和哈哈两位同学
会发现,各自都有自己的班级名属性,但是原型指向的是同一个Person实例,所以如果嘻嘻有两个号码,或者他要更改自己的号码,那哈哈的电话号码也会发生变化,他们只能共享这个电话号码。
方式2 构造函数继承
例子
function Person(name) {
this.name = name;
}
Person.prototype.showName = function() {
console.log(this.name);
}
function Student(name, className) {
this.className = className;
Person.call(this, name);
}
var Haha = new Student('Haha', 1);
var Xixi = new Student('Xixi', 2);
问:call函数的运作过程?
call函数和apply函数的作用相同,不同之处就是apply函数只能传入2个参数,而call函数可以有多个。F.call(thisArg,[arg1……]) 函数的运作过程如下(来源网络):
1.先判断F是否为一个函数,如果不是一个函数,那么将抛出TypeError异常。
2.创建一个内部类型空列表list
3.然后如果参数除去thisArg外还有其他参数的话,就将这些值添加到list中
4.thisArg和list作为F内部属性[[Call]]的参数传入调用进行函数的执行操作
简而言之就是它把一个函数的对象上下文改成了由 thisArg指定的新对象。
So,回归构造函数继承,仔细看看诞生的嘻嘻和哈哈两位同学
可以看到两个实例都拥有了className和name两个属性,因为call方法的运行类似于执行了Haha.name='Haha'
和Xixi.name='Xixi'
。
但是因为没有与父类的原型相联系,所以父类原型中的方法,不能得到继承。运行Haha.showName()会得到报错。
方式3 组合继承
例子
function Person(name) {
this.name = name;
}
Person.prototype.showName = function() {
console.log(this.name);
}
function Student(name, className) {
this.className = className;
Person.call(this, name);
}
Student.prototype = new Person();
Student.prototype.showClassName = function() {
console.log(this.className);
}
var Haha = new Student('Haha', 1);
var Xixi = new Student('Xixi', 2);
组合继承综合了类式继承和构造函数继承,在把父类的属性继承后,把子类的原型指向了父类实例,这样就可以继承父类原型的方法了。
但是这里相当于使用了两次父类函数,并且子类不是父类的实例,子类的原型是父类的实例,所以还会有更好的方法。
方式4 原型继承
function inheritObject(o) {
function F() {}
F.prototype = o;
return new F();
}
var person = {
name: "unknown",
telephone: ["000-0000-0000"]
}
var Xixi = inheritObject(person);
Xixi.name = "Xixi";
Xixi.telephone.push("111-1111-1111");
var Haha = inheritObject(person);
Haha.name = "Haha";
仔细看看诞生的嘻嘻和哈哈两位同学
这里.name给Xixi实例添加了一个自己的name属性,而push操作是直接影响原型中引用变量,所以改进之后又有了下面这种方式。
在这里我产生了一个疑问,为什么name属性是自己添加新的,而telephone是采用原来的。于是添加了一个age属性,执行Xixi.age++操作。
这里可以看到实例重新添加了一个age属性,所以我们可以说只要是改变原型属性的值,就会把新的属性加在实例上,引用不改变是因为引用的地址还没有改变。
方式5 寄生式继承
寄生式继承是在原型继承的基础之上,我们需要再添加一下代码:
function createPerson(obj) {
var o = inheritObject(obj);
o.getName = function(){
console.log(name);
}
return o;
}
这样就给得到的对象添加了公共方法。
方式6 寄生组合式继承
寄生组合式继承是为了弥补组合式继承的缺点,是在寄生式继承+构造函数继承组合而成的:
function inheritObject(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subClass, superClass) {
//复制一份父类原型
var p = inheritObject(superClass.prototype);
//修正重写子类原型导致constructor属性被修改
p.constructor = subClass;
//设置子类原型
subClass.prototype = p;
}
function Person(name) {
this.name = name;
}
Person.prototype.showName = function() {
console.log(this.name);
}
function Student(name, className) {
this.className = className;
Person.call(this, name);
}
inheritPrototype(Student, Person);
Student.prototype.showClassName = function() {
console.log(this.className);
}
var Xixi = new Student('Xixi',2);
var Haha = new Student('Haha',1);
以下为嘻嘻和哈哈的内容:
可以对比一下组合式继承的结果:
不同的地方在于把子类原型的构造函数改成了实例对应的构造函数,在组合继承中子类原型直属并没有constructor属性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。