类(ES5、ES6 、prototype、_proto_)关系解密

141 阅读5分钟

与类相关的名词

类属性/类方法:

  1. 类属性是类对象的直接属性,且该属性与基于该类对象生成的实例对象没有直接关系,无法直接调用。
  2. 可以直接通过 类名.属性名 调用该类属性。
  3. 如果想通过该类对象的实例对象调用类属性,那么可以使用对象实例.constructor属性;

备注: ‘类方法’同理

实例属性/方法

构造函数中的属性和方法我们称为成员,实例成员就是构造函数中通过this添加的成员,不可以通过构造函数访问实例成员,通过构造函数生成实例。生成在实例身上的属性,不必通过原型链去寻找。

prototype对象属性

  1. 实例对象没有prototype属性,只有构造函数才有prototype属性,也就是说构造函数本身保存了对prototype属性的引用;
  2. prototype属性对象有一个constructor属性,保存了引用他自己的构造函数的引用(看起来像是个循环:A指向B,B指向A…;原型链方便引用)
  3. prototype对象的属性变量和属性对象将会被该prototype对象引用的构造函数所创建的对象继承(实例Zhao继承了A的jump方法)

__proto__对象

  1. javascript可以通过构造函数创建多个实例,实例会通过__proto__属性继承原型对象的属性和方法。如果对实例对象的属性和方法进行读写操作,不会影响其原型对象的 属性和方法,也就是说,对于原型对象,javascript实例对象只能读,不能写。那当我们对实例对象的属性和方法进行修改的时候也可以改变其值这是为什么呢?其实当我们试图在实例对象中使用继承自原型对象的属性或方法的时候,javascript会在我们的实例对象中复制一个属性或方法的副本,这样,我们操作的时候,其实操作的就是实例对象自己的属性或方法了。
  2. 构造函数的prototype和其实例的__proto__是指向同一个地方的

实现继承与原型链的原因

__proto__属性保存了对创建该对象的构造函数引用prototype属性的引用,也就是说构造函数可以引用prototype,基于该构造函数生成的实例也可以引用,只不过引用的方式不一样。

原型链详解

继承与多态

继承的定义:

父对象的成员,子对象无需重复创建就可直接使用。既可重用代码, 又可节约内存

多态的定义:

多态指同一个函数,在不同情况下表现出不同的状态,包括重载和重写

重载overload同一个函数,输入不同的参数,执行不同的逻辑。
重写override在子对象中定义一个和父对象中的成员同名的自有成员。当从父对象继承来的个别成员不好用时,就可以在子对象中定义同名成员,来覆盖父对象中的同名成员。

ES5类

创建类及实例化

`

/**
* @类: 实例化的结果都是“对象”
* name 和 school: 实例属性
* jump:是A.prototype上的属性,实例对象通过_proto_原型链去原型A.prototype上寻找
* sex:原型A的constrcutor的静态成员,是‘类属性’
*/
function A(name, school) {
  this.name = name;
  this.school = school;
};
A.sex = 'men';  // 类属性
A.prototype.jump = function(){
  console.log("跳");
};
let Zhao = new A('赵', '崔寨');  // 实例化
console.log(Zhao.sex); // undefined: 静态变量 ,只在constructor下,无法被实例继承
console.log(Zhao.constructor.sex); // men: 通过这种方法可以获取类属性  Zhao.constructor == A的constructor
console.log(Zhao.prototype); // undefined: 对象实例身上没有prototype

`

继承(寄生组合式继承)

function B(name, school, action) {
  A.call(this, name, school, action) // 继承父类构造函数的属性
  this.action = action; 
};
B.prototype =  new A();  // 相当于复制了一个A, 继承父类的原型
B.prototype.constructor = B; //prototype第二条解释
B.prototype.song = function(){
  console.log('歌');
}
let Zhao1 = new B('赵', '崔寨', '学习');
console.log(Zhao1.__proto__.__proto__.__proto__); // 最终找到 原型对象的构造函数

ES6中的class类

创建类

example

class A {
	static  home = '赵王';
	constructor(name, school) {
		let a = '无效';
		this.name = name;
		this.school = school;
	}
	jump(action){
		console.log(`我正在${action}`);
	}
}
let Zhao = new A('赵', '崔寨');
  1. 要通过class关键字创建类,类名习惯大写;
  2. constructor构造函数中定义属性和方法必须加this(实例对象可以用点调用这里this添加的属性和方法),否则无效(let a = '无效';)
  3. 类中定义prototype方法不需要加function关键字,要用箭头函数或者函数名()简写的形式,各方法间也不能加逗号;

构造函数内外定义变量和函数的区别

  1. 在构造函数内定义的函数,会在类创建的实例的属性上,缺点是浪费内存,因为每次创建实例都会重复该函数。 构造函数外:
  2. 在构造函数外定义的函数,会在类的属性上,在类创建的实例的原型上,实例可以调用该方法,这样只会创建一个函数,在类上,这样节约内存,实例在调用方法的时候,先从自身属性上查找,如果找不到,就会向上找。(prototype)
  3. 在构造函数外使用普通函数定义的方法,他会在类的属性上。
  4. 在构造函数外使用箭头函数定义的方法,他会在类的实例的属性上。(相当于constructor中this创建方法)

继承类

example

class B extends A{
	constructor(name, school){
		super(name, school);   
	}
	study(subject){
		console.log(`我正在学习${subject}`);
	}
	jump(action){
		console.log(`我正在喘气`);
	}
}
let Zhao = new B('赵', '崔寨');
Zhao.jump('学习') // 我正在喘气,重写过后的方法
  1. 通过extends关键字实现B类对A类的继承。
  2. 同名覆盖:子类中声明的方法名和父类中的方法名相同时,子类中的方法将覆盖继承于父类的方法,采用自己的。eg:Zhao.jump('学习')。
  3. 当子类继承父类时,如果不需要通过constructor设置属性和继承父类constructor中的属性,那么可以不写constructor和super,否则,就必须写上constructor和super。
  4. super前面不能有this操作。

super关键字

使用super的时候,必须显示指定是作为函数使用还是作为对象使用(要么加括号,要么打点调属性方法),否则浏览器会报错。

super作为函数调用

代表父类的[构造方法],只能用在子类的构造函数中,用在其他地方就会报错;super虽然代表了父类的构造方法,但是内部的this指向调用这个函数的类的实例;eg:上面的example

super作为对象调用

  1. 在子类的构造方法或一般方法中,super代表的是原型对象A.prototype(父类的原型);但是this仍旧指向B的实例;
class A {
	static  home = '赵王';
	constructor(name, school) {
		let a = '无效';
		this.name = name;
		this.school = school;
	}
	jump(action){
		console.log(`我正在${action}`);
		console.log(this);  // B的实例
	}
}

class B extends A{
	constructor(name, school){
		super(name, school);   
	}
	study(subject){
		super.jump(subject);
	}
}

let Zhao = new B('赵', '崔寨');
Zhao.study('学习') 
  1. 在子类的静态方法中使用,super指向的是父类,而不是父类的原型对象,this指向子类;
class A {
	static  home = '赵王';
	constructor(name, school) {
		let a = '无效';
		this.name = name;
		this.school = school;
	}
	jump(action){
		console.log(`我正在${action}`);
		console.log(this);
	}
	static jump(action){
		console.log(`我正在${action}`);
		console.log(this); // 指向的是B类,非实例
	}
}

class B extends A{
	constructor(name, school){
		super(name, school);   
	}
	static study(subject){
		super.jump(subject);
		console.log(super.home); // 赵王
	}
}

B.study('嘿嘿')