对象继承
1. 理解原型链
所有引用类型(函数、对象、数组),都存在对象特性,即可以自由拓展属性。(除了null以外)
所有的引用类型(函数、对象、数组),都有一个
__proto__
(我们这里称他为隐形原型)属性,属性值是一个普通的对象。所有函数都有一个prototype属性,属性值也是一个普通的函数
所有的引用类型(函数、对象、数组),*proto属性值指向它的构造函数的 prototype(显性属性)属性值。
当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_*(即他的构造函数的prototype)中寻找。如果没有,则会接着往上找,一直上溯到Object.prototype,也就是说所有对象都继承Object.prototype的属性,Object.prototype的原型是null,null没有任何属性和方法。
构造函数、原型和实例的关系
- 每个构造函数(函数)都有一个原型对象
prototype
- 原型对象都包含一个指向构造函数的指针
construtor
- 而实例都包含一个指向原型对象的内部指针
__proto__
- 实例的
__proto__
指向构造函数的prototype
把一个对象的__proto__
指向另一个原型对象,而这个原型对象的__proto__
又会指向另一个原型对象,这些就会形成原型链
特殊的Function
- 函数都是由Function构造出来的,Function作为函数,是由其自身构建出来,故Function的原型指针指向其自身的原型对象。
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.prototype.constructor === Function);
function Person (name,age){
this.name = name ;
this.age= age;
this.class = ['en','math'];
this.sayName = function(){
alert(this.name);
}
}
Person.prototype.like = 'fruit';
继承 我们需要继承什么?
继承的最终目的 :用最少的代码 可以实现继承公有属性和方法的同时,拥有自己的属性和方法
2.原型链继承
原理
让新实例的原型等于父类的实例
优点
实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点
1.新实例无法向父类构造函数传参。
2.继承单一。只能继承一个父类
3.所有新实例都会共享父类原型的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
function Per (name) {
this.name = name;
}
Per.prototype = new Person();
var per = new Per("la");
var per2 = new Per("la");
per2.class.push('ss') //
per.class //
per.__proto__ --> (Per.prototype = new Person)
Per.prototype.__proto__-->Person.prototype
Person.prototype.__proto__ -->Object.prototype
Object.prototype.__proto__ --> null
如图 蓝色链为 原型链 红色为构造函数和原型的关系
3.借用构造函数继承 伪造对象 或经典继承
原理
用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
优点
传递参数
1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点
1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。(构造函数缺点 所有属性都绑定在对应的对象上)
function Con(name){
Person.call(this,"jer",10)// 调用了 父构造函数 可以传参 提高自由度
// Person2.call(this,"") 多个构造函数 多继承
this.name = name;
}
var con1 = new Con('rr');
console.log(con1 instanceoof Person)
4.组合继承(组合原型链继承和借用构造函数继承)(常用)
原理
结合了两种模式的优点,传参和复用
优点
1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点
调用了两次父类构造函数(耗内存),
// 子类的构造函数会代替原型上的那个父类构造函数(没有理解)。
function C(name){
Person.call(this,name);//构造函数继承属性
}
C.prototype = new Person();//原型继承方法 new的时候调用第二次
var per1=new C("aa");
per1.name; // 构造函数属性
per1.age; // 原型属性
5.原型式继承
原理
先创建了一个临时性的构造函数,然后将传入的对象作为个构造函数的原型,最后返回这个构造函数的实例 ,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理 。
优点
类似于复制一个对象,用函数来包装。
缺点
1、所有实例都会继承原型上的属性(共享问题)。
2、无法实现复用。(新实例属性都是后面添加的) (不能传参)
function D(obj){
function F(){}
F.prototype = obj;
return new F();
}
var per4 = new Person()
var per5 = D(per4);
console.log(per5.name)
per5 instanceof Person //
//
new 默认原型对象 create 指定原型对象
Object.create()是Object的内置方法,可以创建一个新对象,使用现有的对象来提供新创建的对象__proto__
Object.create ( proto, [ propertiesObject ] )
方法内部定义一个新的空对象obj
将obj.__proto__的对象指向传入的参数proto
将传入的对象属性复制到obj并且返回obj
6.寄生式继承
原理
就是给原型式继承外面套了个壳子。
优点
传参
缺点
没用到原型,无法复用
function D(obj){
function F(){}
F.prototype = obj;
return new F();
}
var per4 = new Person();
function E(obj,name){
var sub = D(obj);// 继承原型
sub.name = name; //在原来的基础上加上私有的东西
return sub;
}
var per6 = E(per4,'ee');
// 给原型式继承 加个处理函数传参
7.寄生组合式继承(最理想)
原理
寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。
2、在函数中用apply或者call引入另一个构造函数,可传参
优点
修复了组合继承的问题
缺点
过于繁琐,故不如组合继承
// 寄生
function G(obj){
function F(){};
F.prototype = obj;
return new F();
}
// G 是 F实例的另一种表示
var g1 = G(Person.prototype);
//组合
function Sub(){
Person.call(this);
}
//重点
Sub.prototype = g1; //继承 实例
g1.constructor = Sub; // 修复 实例
var sub1 = new Sub();
//sub1 就继承了继承 函数属性,父类实例,g1 的函数属性
sub1.age;
8.ES6继承 (class)(extends)
原理
Class之间通过使用extends关键字,这比通过修改原型链实现继承,要方便清晰很多
新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
相当于构造函数的另一种写法
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
console.log(this.x);
}
}
class Colorpoint extends Point {
//这个就是默认方法 使用new 生成实例时 会调用这个方法,
//如果未定义 会自动添加
constructor(x,y,color){
//子类必须在constructor方法中调用super方法,否则新建实例时会报错
//这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。
super(x,y); //调用父类构造函数(Point.prototype.constructor.call(this,x,y))
this.color = color
// 隐式返回 this
// 如果显示返回对象 就是该对象
}
toString(){
//通过 super调用父类的方法
return this.color + ' ' + super.toString();
}
}
class A extends B{}
A.__proto__ === B; //继承属性
A.prototype.__proto__ == B.prototype;//继承方法
typeOf(Colorpoint)
//类的数据类型就是函数,类本身就指向构造函数。
//类的所有方法都定义在类的prototype属性上面。
Colorpoint.prototype.constructor === Colorpoint // true
Object.assign(Colorpoint.prototype, {
toString(){},
toValue(){}
});
与 es5 不同之处
1. toString方法是Colorpoint类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。
2. 必须使用new 调用
3.不存在变量提升,必须先声明在使用
与es5 相同之处
1.prototype对象的constructor属性,直接指向“类”的本身
2. 与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
3.类的所有实例共享一个原型对象。
Colorpoint.hasOwnProperty('x') // true
Colorpoint.hasOwnProperty('color') // true
Colorpoint.hasOwnProperty('toString') // false
“extends” 语法会设置两个原型:
- 在构造函数的
"prototype"
之间设置原型(为了获取实例方法) - 在构造函数之间会设置原型(为了获取静态方法)
//继承对象
class A extends Object 和 class A 区别
继承自对象 继承自函数
构造函数中需要调用父类构造函数
super
作为函数使用
调用父类构造函数
作为对象使用
静态时指向父类本身可以调用父类本身的属性和方法
·指向父类的原型
//动态时由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();// 调用父类函数时 会绑定子类的this
console.log(super.p()); // 2
}
}
let b = new B();
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B();
b.m // undefined
// 用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1
var child = new Child();
child.myMethod(2); // instance 2
//在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
static m() {
super.print();
}
}
B.x = 3;
B.m() // 3
- 所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined A.prototype.x
console.log(this.x); // 3
}
}
let b = new B();
- 由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。
- 直接打印 super会报错 由于浏览器不知道是函数还是对象, 所以必须显式指定是作为函数、还是作为对象使用
-
箭头函数没有
super
-
[[HomeObject]]
super 的特殊特征
学习整理
JS高程学习-第六章(一)---认识对象
JS高程学习-第六章(二)---创建对象
JS高程学习-第六章(三)---对象继承