一、实质
ES5的继承实质:先创建子类,再实例化父类,并将父类的方法添加添加到子类this中。通过原型或构造函数机制来实现。
ES6实质: 先创建父类,实例化子类中通过调用super方法访问父级后,在通过修改this实现继承。如果不调用super方法,子类得不到this对象
es6如何实现继承:ES6实现继承是通过关键字extends、super来实现继承。
(通过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())
对于小白菜,只能记录学习,温习。。。