原型
在javascript中每个对象(除null外)创建的时候,就会关联另一个对象,这个关联对象就是原型,每一个对象都会从原型中继承属性。
1.prototype
function Person(age){
this.age = age;
}
Person.prototype.Name = 'test';
var person = new Person();
console.log(person.Name) // 'test'
从这里可以看到,每个函数都有一个prototype属性,这个属性指向的是函数的原型对象。函数的原型可以通过.prototype属性获取到,可以读取或设置其原型对象的属性。
2.__ proto__
function Person(){}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
每个对象(除null外)都会有一个__proto__属性,这个属性可以获取到该对象的原型
3.constructor
function Person(){}
console.log(Person === Person.prototype.constructor); //true
每个原型都有constructor属性,指向关联它的构造函数。
所以原型在函数和对象之间的关系如下:
原型链
4.拓展 原型的原型
由上面几点已经得知,原型是一个对象,那么原型的原型是什么呢?
function Person(){}
console.log(typeof(Person.prototype.__proto__)) //Object
从这里可以看出,原型的原型也是一个对象,那么套娃一下,原型对象的原型的原型又是啥呢
var obj = new Object()
console.log(obj.__proto__.__proto__); //null
所以对象的原型的原型是null,到这里为止这个关联就结束了。所以整个原型链的关系图如下
几种继承方式和优缺点
假设一个公共父类Person,其属性方法如下:
function Person(name){
this.name = name || 'noName';
this.sleep = function(){
console.log(this.name + '好想睡觉');
}
}
Person.prototype.eat = function(food){
console.log(this.name + '好想干饭,特别是' + food);
}
1.原型链继承
function Woman(){}
Woman.prototype = new Person();
Woman.prototype.Name = "美少女";
let woman = new Woman();
- 优点:
- 这样将父类的示例作为子类的原型,容易实现。
- 父类后续新增的属性或方法,子类中都可以访问。
- 操作子类即可修改原型中的属性。
- 缺点:
- 要新增原型中属性或方法,必须要先new一个实例。
- 无法多继承
- 创建子类实例时,无法向父类构造函数传参
2.构造函数继承
function Woman(name){
Person.call(this,name);
this.name = name || 'No Name';
}
let woman = new Woman('xixi');
console.log(woman.name, woman.sleep(), woman.eat()); // xixi, undefined, 报错(women.eat is not a function)
- 优点
- 解决了原型链继承中不可传递参数的缺点。
- 子类可以继承多个父类(使用多个call或apply)
- 缺点
- 这里的woman是Woman的实例,但不是Person的实例,因为Woman的原型并没有指向Person。
- 父类Person原型中的属性和方法(eat)无法被继承。
- 子类实例woman虽然可以继承父类Person的属性和方法,但是父类Person中的函数无法被复用,如上代码中的sleep()。
3.实例继承
function Woman(name){
let instance = new Person();
instance.name = name || 'no name';
return instance
}
var women = new Woman();
- 优点
- 不限制调用方式,无论是new Woman()还是Woman()都可以实现继承。
- 缺点
- woman是Person的实例,而不是Woman的实例
- 不支持多继承
4.组合继承
function Woman(name){
Person.call(this,name);
this.name = name || 'no name';
}
// 将整个原型链更改
Woman.prototype = new Person();
Woman.prototype.constructor = Woman;
let woman = new Woman();
- 优点
- woman实例既能获取Woman的属性和方法,也能获取到Person的原型对象的属性和方法(eat)
- woman即是子类Woman的实例,也是父类Person的实例
- 弥补了单纯的构造继承的缺点,可以向父类Person传值
- 可以复用所有对象的函数
- 缺点
- 调用了两次父类构造函数,生成了两份实例,多了一些些内存消耗。
6.寄生组合继承
function Woman(name){
Person.call(this,name);
this.name = name ||'No Name';
}(function(){
// Super无实例方法,避免了上一种继承方式的创建两次实例
var Super = function(){};
Super.prototype = Person.prototype;
Woman.prototype = new Super();
})
Woman.prototype.constructor = Woman;
let woman = new Woman();
- 优点
- 在组合继承方式优点上,解决了创建两次实例的问题
- 缺点
- 实现复杂