es5和es6继承的区别

1,670 阅读5分钟

一、实质

ES5的继承实质:先创建子类,再实例化父类,并将父类的方法添加添加到子类this中。通过原型或构造函数机制来实现。

ES6实质: 先创建父类,实例化子类中通过调用super方法访问父级后,在通过修改this实现继承。如果不调用super方法,子类得不到this对象

es6如何实现继承:ES6实现继承是通过关键字extendssuper来实现继承。

(通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其调用。如果不调用super方法,子类得不到this对象)

注意:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键。

二、es6关键字 class\extends\super 源码分析   链接

三、原型链、prototype 、 __proto__

每个对象都具有一个__proto__(内置的属性)、通过__proto__建立一个链,原型链的搜索机制,先在实例中查找,找不到就在原型中找,找不到再往上一级原型查找...继续逐级向上查找直到:原型链终点。Object.prototype.__proto__ === null

(1)原型链面试题

function Foo(){
	Foo.a = function (){console.log(1)} 
	this.a = function(){console.log(2)}
	Foo.prototype.b = function(){console.log(5)}
}
Foo.prototype.b = function(){console.log(3)} 
Foo.a = function(){console.log(4)} 
Foo.a() 
let obj = new Foo() 
obj.a()
Foo.a()
obj.b()

(2)prototype\__proto__

只有函数对象才有 prototype属性 ,重要的事情说三遍!

// 系统内置的函数对象
Function,Object,Array,String,Number

除开函数对象之外的对象都是普通对象!

function具有prototype属性,function是一种特殊的对象。

对象具有__proto__(内置的原型),一般没有prototype属性(Object除外),其中Object是一种特殊的函数,同时Object 是所有对象最根部的原型。Object.prototype.__proto__ 返回null。

var a = {};
function b(){};
var c = new b();
b.prototype.frpm="sad";
console.log(Object); // ƒ Object() { [native code] } (Object是一种特殊的函数)
console.log(Object.prototype); // Object(返回一个Object内置属性所组成对象)
console.log(Object.prototype.__proto__); // null 证明Object是所有对象最底层的原型
console.log(a.prototype); // undefined  (a属于对象,一般对象无prototype)
console.log(a.__proto__);// Object
console.log(a.__proto__ === Object.prototype);  // true
console.log(a.__proto__.__proto__ === Object.prototype.__proto__) // true
console.log(b.prototype); // {frpm: "sad", constructor: ƒ}
console.log(b.prototype.__proto === Object.prototype);  // true
console.log(c.prototype);// undefined (对象一般没有prototype)

对象实例、原型、原型对象、构造函数、通过原型对象找到构造函数、原型的唯一性、原型链(原型链的终止是null)、继承、原型链继承、对象属性可以自定义(会覆盖继承的属性)、实例不能改变原型的基本值属性、原型中引用类型属性的共享、原型的动态性、原型的整体重写。参考链接

(1)实例不能改变原型的基本值属性/原型中引用类型属性的共享

     function Person(name){
        this.name = name;
     }
 
     function Mother(){ }
     Mother.prototype = {   
         age: 18,
         home: ['Beijing']
     }
Person.prototype = new Mother();   
var p1 = new Person('p1');  
var p2 = new Person('p2'); 
p1.home[0] = 'hello'  //原型中引用类型属性的共享
p1.home = 'world' // p1 自身對象添加屬性和原型无关
console.log(p1)
console.log(p2)

p1.home[0] 和 p1.home 的区别?

p1.home[0] = ‘xxx’  等同于   Mother.prototype.home[0] = ’xxx’

譬如 var p1 = {}, p1.home = 'xxx' /  p1.home[0] 直接报错

譬如 var p1 = {}, p1.home = [] , p1.home[0] = 'xxx' 不会报错

这里p1.home[0]不会报错,是由于原型链的搜索机制,先在实例中查找,找不到就在原型中找,找不到再往上一级原型查找...继续逐级向上查找直到:原型链终点Object.prototype.__proto__  === null

(2)重写原型、改写原型的原型、重写原型的原型

原型的动态性:改写原型,动态反应到实例中。

重写原型,需要应用改动的话,那就要重新再次綁定。

function Person(name){
        this.name = name;
 }
 function Mother(){ }
  Mother.prototype = { 
    age: 18,
    home: ['Beijing']
  }
 
Person.prototype = new Mother(); 
var p1 = new Person('p1');  
var p2 = new Person('p2');
Person.prototype = { // 重写原型:新的对象(后妈)
    age: 28,
    address: { country: 'USA', city: 'Washington' }
};
Mother.prototype.no = 9527 // 改写原型的原型(亲妈的妈)
var p3 = new Person('p3');
console.log(p1)
console.log(p2)
console.log(p3) // 属于后妈。和亲妈无关

重写原型的原型

function Person(name){
        this.name = name;
 }
 function Mother(){ }
  Mother.prototype = { 
    age: 18,
    home: ['Beijing']
  }
 
Person.prototype = new Mother(); 
var p1 = new Person('p1');  
var p2 = new Person('p2');
Person.prototype = { // 重写原型(后妈)
    age: 28,
    address: { country: 'USA', city: 'Washington' }
};
Mother.prototype.no = 9527 // 改写原型的原型(亲妈的妈)
var p3 = new Person('p3');
Mother.prototype = { // 重写原型的原型
    car: 2,
    hobby: ['run','walk']
};
Person.prototype = new Mother(); //如果想应用这些改动的话、需要再次绑定。
var p4 = new Person('p4')
console.log(p1)
console.log(p2)
console.log(p3)
console.log(p4)
p1、p2 属于关联亲妈和亲妈的亲妈
p3、p4 属于关联后妈...

原型链继承的主要问题在于属性的共享,很多时候我们只想共享方法而并不想要共享属性,理想中每个实例应该有独立的属性。

四、继承几种方式

就这几种就很牛了。

(1)原型链继承:

优点:实现原型共享,减少内存占用。

缺点:来自父类原型对象的引用属性是所有子类共享的(父类原型和子类原型指向同一个地址)。创建子类实例时,无法向父类构造函数传参。无法实现多继承。

(2)构造函数继承:

优点:可以多继承,可以进行参数的传递

缺点:不能实现公用(占用内存),不能继承原型上的方法和属性。

(3)组合继承:钻石

function Parent (name,age) {
	this.name = name
	this.age = age
}
Parent.prototype.say = function(){
	console.log('say==>', this.age)
}
function Child(name,age) {
	this.age = age
	this.name = name
	Parent.apply(this.arguments)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
var f1 = new Child('f1',20)
var f2 = new Child('f2',30) 
console.log(f1)
console.log(f2)
console.log(f1.say())
console.log(f2.say())

优点:合并了原型继承和构造函数的继承的优点。

缺点:父类的构造函数被调用了两次。设置原型的时候进行了一次。call/apply的时候进行了一次。

(4)空对象作为中介(寄生组合式继承)星耀

function Parent (name,age) {
	this.name = name
	this.age = age
}
Parent.prototype.say = function(){
	console.log(this.age)
}
function Child(name,age) {
	this.age = age
	this.name = name
}
function extend(child,parent){
	var F = function(){}
	F.prototype = new parent()
	child.prototype = new F()
	child.prototype.constructor = child
}
extend(Child, Parent)

var f1 = new Child('f1',20)
var f2 = new Child('f2',30) 
console.log(f1)
console.log(f2)
console.log(f1.say())
conosle.log(f2.say())

(5)class继承(王者)

class PP {
    constructor(name,age){
        this.name = name
        this.age = age
    }
    showName(){
        console.log(this.name);
    }
}
 
class CC extends PP{
    constructor(name,age,sex){
        super(name,age)
        this.sex = sex
    }
    showSex(){
        console.log(this.sex);
    }
}
let pp = new PP('Jack',30)
console.log(pp.name,pp.age);
let cc1 = new CC('cc1',12,'男')
let cc2 = new CC('cc2', 20, '女')
console.log(cc1.name,cc1.age);
console.log(cc1.showName()) 
console.log(cc1.showSex()) 
console.log(cc2.name,cc2.age)
console.log(cc2.showName()) 
console.log(cc2.showSex())

继承参考链接

对于小白菜,只能记录学习,温习。。。