继承的核心机制
要理解继承必须理解原型对象 和原型链的关系和指向问题
一个构造函数由构造函数本身和构造函数的原型对象两个部分组成 ,也就是构造函数有一个隐藏的属性__proto__,指向构造函数的原型对象。当构造函数被实例化时,把构造函数中的所有属性和方法拷贝到实例当中,同时,这个__proto__属性也会被拷贝,当访问实例对象当中的属性或方法时,会先从实例本身找,如果找到了,就会使用实例中的这个属性或方法;如果没有找到,就会通过__proto__属性中保存的地址找到原型对象,再从原型对象里找这个属性或方法。
以上就是原型链的核心机制,属性搜索机制。
构造函数和、实例和原型对象三者之间的关系可以用下图表示
function Star(uname, age) {
this.uname = uname;
this.age = age;
// this.sing = function() {
// console.log("我想唱歌");
// }
}
Star.prototype.sing = function() {
console.log("我想唱歌");
}
//一般情况下,我们的公共属性定义在构造函数里面,公共的方法放在原型对象身上
var ldh = new Star("刘德华", 19);
ldh.sing();
console.log(ldh); //对象身上系统的自己添加一个 __proto__指向我们构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype); //true
// 方法的查找规则:首先看ldh对象身上是否有sing方法,如果有就执行对象上的sing
//如果没有sing这个方法,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找sing这个方法
当给一个实例对象设置属性值时,会在实例对象中找有没有这个属性,如果找到,就直接把值赋给这个属性;如果没有的话,不会再去原型对象中找该属性,因为javascript语言是一种动态语言,这时会直接给这个实例对象添加一个新的属性,即使原型对象中有这个属性,也不会改变原型对象中的这个值。
当实例对象中和原型对象中有一个同名的属性和方法时,此时只能访问到实例对象中的属性或方法(,如果一定要访问,可以delete掉实例中的属性或方法,或都直接加上__proto__属性访问。
同样上述方法种的原型链表示如下图
六种继承方式
js里面的六种继承方式分别为 原型练继承、构造函数继承,实例继承,拷贝继承,组合继承,寄生组合继承
1、原型链继承
子类的实例继承自身实例属性 也继承父类里面的构造函数和方法 父类的原型属性和方法
子类的实例可以通过proto__访问到 Child.prototype 也就是Person的实例,这样就可以访问到父类的私有方法,然后再通过__proto指向父类的prototype就可以获得到父类原型上的方法。
缺点
不能继承子类的原型属性和方法
只能进行单继承,不能多继承
继承之后 原型对象上的属性全是共享
子类不能直接向父类传递参数
原型链继承实例
Animal.prototype = {
constructor: Animal,
sleep: function () {
console.log(this);
},
height: 200
}
var a = new Animal();
console.log(a.sleep());
function fun1(){
}
fun1.prototype=new Child();
var sss=new fun1();
console.log(sss);
console.log(child.__proto__);// Person
console.log(child.__proto__.__proto__); //Person 的原型对象
console.log(child.__proto__.__proto__.__proto__); //Object
console.log(child.__proto__.__proto__.__proto__.__proto__);
2、构造继承
通过使用call apply方法来实现继承
用.call()和.apply()将父类构造函数引入子类函数
构造函数的优点:1、可以实现多继承
2、可以向父类传递参数
缺点:1、只能继承父类的实例属性和方法,不能继承原型属性和方法
2、子类的实例是本身而不是父类
构造函数继承
核心使用call apply来实现继承
function Star(n) {
this.name = n
this.sleep = function() {
return "睡觉";
}
}
Star.prototype.sing = "唱歌";
function Type() {
this.Type = arguments[0];
}
function Person(n, s, t) {
this.sex = s;
this.eat = function() {
return "吃饭";
}
//构造继承
Star.call(this, n);
Type.apply(this, [t])
}
Person.prototype = {
constructor: Person,
sleep: function() {
}
}
var person = new Person("刘德华", "男", "歌手");
console.log(person);
console.log(person instanceof Person); //true
console.log(person instanceof Star); //false 子类的实例对象是本身而不是父类
3、实例继承
在子类内部直接构造父类的实例 直接new父类
优点:不限制调用方式 可以向父类传递参数
缺点:不能实现多继承
不能拿到子类构造函数属性和方法
子类的实例不是本身而是父类
//js 里面的实例继承
function Person(name, sex) {
this.name = name;
this.sex = sex;
this.sleep = function() {
return "睡觉"
}
}
function Child(name, sex) {
this.sex = sex;
this.eat = function() { //那不到子类的属性和方法
}
return new Person(name, sex);
}
var child = new Child("刘德华", "男");
console.log(child);
//子类的实例对象
console.log(child instanceof Child); //false
console.log(child instanceof Person); //true 子类的实例对象是父类
4、拷贝继承
将父类里面的方法属性拷贝给子类
优点:
支持多继承 子类的对象实例是本身而不是父类
可以向父类传递参数
缺点:在继承的事或 不断new 占内存
// 拷贝继承
function Star(n) {
this.name = n;
this.sleep = function() {
return "睡觉"
}
}
function Type(t) {
this.type = t;
}
function Person(n, s, t) {
this.sex = s;
this.eat = function() {
return "吃饭";
}
//拷贝父类里面的属性和方法 拷贝给原型对象prototype
//先实例化父类对象
var star = new Star(n);
for (var key in star) {
Person.prototype[key] = star[key];
}
var type = new Type(t);
for (var key in type) {
Person.prototype[key] = type[key];
}
}
var ldh = new Person("刘德华", "男", "歌手");
console.log(ldh);
//子类对象的实例是本身 不是父类
console.log(ldh instanceof Person); //true
console.log(ldh instanceof Type); // false
console.log(ldh instanceof Star); // false
5、组合继承
1、组合继承就是 原型链继承+构造继承
2、子类的实例即是本身也是父类
3、可以实现多继承
4、可以像父类传递参数
5、没有实现原型对象属性的共享
缺点:调用了两次父类的构造函数
//组合继承
function Father(n) {
this.name = n;
this.eat = function() {
return this.name + "吃饭"
}
}
Father.prototype = {
constructor: Father,
color: null
}
function Child(a, s, n) {
this.age = a;
this.sex = s;
this.sleep = function() {
return "睡觉";
}
Father.call(this, n); // 构造继承 只能获取到父类的属性和方法 拿不到原型属性和人方法
}
Child.prototype = new Father("huahua");
var child = new Child(12, "男", "小明");
console.log(child);
console.log(child instanceof Child); // true
console.log(child instanceof Father); // true 子类的实例对象是父类也是本身
6、寄生组合继承
寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参
原理:将父类的原型对象给一个空对象的prototype 再把空对象和子类的原型对象关联
function Animal() {
this.name = null;
this.sleep = function() {
return this.name + "睡觉";
}
}
Animal.prototype = {
constructor: Animal,
color: null
}
function Dog() {
this.age = null;
this.eat = function() {
return this.name + "吃饭";
}
Animal.call(this);
}
//开始寄生
(function() {
var fn = function() {
}
fn.prototype = Animal.prototype;
Dog.prototype = new fn;
})()
var dog = new Dog();
console.log(dog);
console.log(child instanceof Child); //true
console.log(child instanceof Person); //true