您的位置:首页 > Web前端 > JavaScript

JavaScript难点系列(六):原型链与继承

2017-09-28 16:09 295 查看

类和构造函数

JS中使用构造函数来定义类:

function Range(from, to) {
this.from = from
this.to = to
}
Range.prototype.includes = function(x) {
return this,from <= x && x <= this.to
}
Range.prototype.toString = function() {
return this.from + '...' + this.to
}

var r1 = new Range(1, 3)
console.log(r.includes(2))  // true
console.log(r.toString())  // 1...3


上述代码声明了一个Range构造函数,生成了类Range,使用new关键字能生成实例对象r1。

new Range(1, 3)这句表达式做了如下的事情:

var obj = {}
obj.__proto__ = Range.prototype
var result = Range.call(obj, 1, 3)
return typeof result === 'obj' ? result : obj


第一步新建一个空对象。

第二步把新对象的__proto__设置为Range的prototype,即Range的原型。

第三步给Range构造函数传参来给新对象的属性赋值。

第四步检测Range构造函数有没有返回一个新对象,如果有就返回它,如果没有就返回已经赋值好属性的obj。

其实new关键字就是一个语法糖,类似于一个函数而已,我们使用了它,就相当于写了上面这四句代码。



new完后我们就可以说r1对象是Range类的实例,但构造函数只是类的公有标识,并不是唯一标识。也就是说两个不同的构造函数的prototype属性可以指向同一个原型对象,那么它们创建的实例都是属于同一个类的。所以原型对象才是类的唯一标识,下面介绍的检测类型的方法也是依据实例对象是否继承自某个原型对象,而不是检测实例对象是由哪个构造函数初始化而来。

一共有三种检测对象的类的技术:

1. o instanceof Range

如果o继承自或者间接继承自Range.prototype,那么上面的表达式的结果为true

2. Range.prototype.isPrototypeOf(o)

如果o继承自或者间接继承自Range.prototype,那么上面的表达式的结果为true

原型链和继承

JS中实现继承主要是依靠原型链来实现的。其基本思想是让子类型的原型对象等于父类型的实例。而一般的继承实现有三种方式:原型链继承、借用构造函数继承、组合继承。

原型链继承

function super() {
this.colors = ["red", "blue", "green"];
}
function sub() {
}

sub.prototype = new super();

var instance1 = new sub();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new sub();
alert(instance2.colors); //"red,blue,green,black"


上述代码把sub的原型对象赋值为super父类的实例对象,于是super实例的属性就变为sub的原型属性了。但这样存在两个问题:

1. super实例属性上如果存在引用类型值,那么sub的原型对象属性上也会有拥有相同的引用类型值。于是sub的所有实例对象都会共享其原型对象上的引用类型属性。上述代码中对instance1的改动就影响到了instance2。

2. 在创建sub类型的实例时,不能向super类型的构造函数传递参数。也就是说super类型的构造函数如果需要用到参数,只能使用默认值。

借用构造函数

为了解决原型链继承的引用类型值所带来的问题,出现了一种称为借用构造函数的技术(经典继承)。其基本思想是在子类型构造函数的内部调用父类型的构造函数。

function super(name) {
this.colors = ["red", "blue", "green"];
this.name = name;
}
function sub() {
super.call(this, "jaja");
}

var instance1 = new sub();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
alert(instance1.name); // "jaja"

var instance2 = new sub();
alert(instance2.colors); //"red,blue,green"


虽然这种继承方法“继承”了父类的构造函数里的实例属性,但无法继承父类的原型中定义的方法。单单继承了构造函数中的属性而无法复用父类的原型方法,那么就不能称得上是真正的继承。

组合继承

为了发挥上面两张继承方式的优点,技术人员又发明了组合继承。其主要思想是使用原型链来实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

function super() {
this.colors = ["red", "blue", "green"];
}
function sub() {
super.call(this);
}

sub.prototype = new super();
sub.prototype.constructor = sub;

var instance1 = new sub();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new sub();
alert(instance2.colors); //"red,blue,green"


组合继承可以看作是对原型链继承的增强,它与原型链继承的主要不同是在子类的构造函数中引用了父类的构造函数。这样即使使用sub.prototype = new super()继承了父类的引用类型值,但后面在new sub()时通过sub的构造函数用实例属性覆盖了super的引用类型值,而sup
4000
er的原型对象的方法仍旧保留着没被覆盖。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript