JS中的原型、类和继承

1. 原型与原型链

1.1 什么是原型?原型是干嘛的?

原型是为其他对象提供共享属性(和方法)的对象。JS通过原型实现类的实例化和类的继承。除了undefined和null外,所有对象都有一个指向其原型的隐式指针属性"[[Prototype]]"(Chrome、IE11+、Safari等将此属性实现为 _ _ proto _ _;字面量数字、字符串等通过自动类型转换获得指向转换后对象原型的指针属性),而原型对象又可以有指向自己的原型的指针属性,这就形成了原型链。一个对象可以按照一定规则,顺着原型链寻找属性和方法,并将找到的属性和方法挪为己用。

1.2 原型与对象的构造

JS不以定义方式区分构造函数与普通函数,而是以函数调用方式区分。在一个函数调用前加一个new关键字,就构成构造函数调用。构造函数调用会产生一个新对象,这个新对象使用新开辟的内存。

JS函数对象除了拥有隐式指针属性[[Prototype]]外,还有一个显式属性prototype。protoype属性初始值具有两个属性:[[Prototype]]和constructor。[[Prototype]]就是prototype属性的隐式原型引用,而constructor是指向拥有prototype属性的函数的引用值(即函数对象本身)。

通过构造函数调用构建的对象的[[Prototype]]等于构造函数的prototype属性。在多次调用同一个构造函数创建多个对象的情形下,所有被创建的对象共享函数的prototype。其结构如图所示:
截图自ES2015

1.3 原型链与属性访问

一个对象的原型链以对象本身作为第一个节点,以null作为最后一个节点,以Object.prototype为倒数第二个节点。一个典型的原型链如下所示:

当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。在这里插入图片描述
下面是一个属性访问和对象构造的例子

let f = function(){
	this.a = 1;
	this.b = 2;
}

let o1 = new f(); //o1/2由b创建,o1/2的[[Prototype]]指向f.prototype
let o2 = new f(); //两个对象共享f.prototype
o1.a = 11;//构造调用下,函数的this指向新构造对象。因此,a和b都是新对象自有的属性, 因此改变o1(2)的a属性不影响o2(1)的a属性
o2.a = 111;
//由于共享原型,为f.prototype添加属性相当于为每个oi都添加了属性
f.prototype.b = 3; 
f.prototype.c = 4;
console.log(o1.a, o2.a, o1.b, o2.c); // 11, 111, 2, 4
//由于在对象本身就找到了b属性,就不再继续沿原型链找寻b了
f.prototype = {
	"b":33,
	"c":44
}
//虽然构造函数的prototype属性引用的对象变了,但是之前创造的对象的[[Prototype]]仍指向原来的原型对象,而原来的原型对象因为仍被引用,所以不会被销毁回收
let o3 = new f();
console.log(o1.b, o2.b, o1.c, o2.c, o3.c); //2, 2, 4, 4, 44

2. 类与继承的实现

2.1 类的实现

JS中类的实现方法有很多,最主流的方法是通过共享构造函数的原型方法、独享属性实现的。共享方法如下所示:

let f = function(a, b){
	this.a = a;
	this.b = b;
}
//所有实例共享方法,避免重复构造方法引起浪费
f.prototype.m = function(){
	console.log(this.a + this.b);
}
// new关键字使f中的this指向新创建对象,属性a、b为自有
let o1 = new f(2,3);
let o2 = new f(5,6);
//方法被作为对象的方法调用,this指向对象,能够正确引用属性
o1.m(); // 5
o2.m(); // 11

函数,包括被用作构造函数的函数,也是另一个函数构造出来的。这个函数就是Function。Function本身也有prototype属性,也有隐性的[[Prototype]]属性(指向Object.prototype)。所以,函数(包括构造函数)的原型都为Function.prototype。而Function.prototype的原型是Object.prototype。这就形成下面的结构
在这里插入图片描述

2.2 继承的实现

JS的继承是通过原型链实现的。构造原型链也有很多种实现的方法。最被认可的一种方法如下所示:

function class1(a1){
	//属性初始化;
	this.a1 = a1 || undefined;
}
class1.prototype.m1 = function(){
	//class1作为基类时,m1中this指向子类实例
	console.log(this.a1);
}

function class2(a1, a2) {
	//子类构造函数中调用基类构造函数,初始化基类属性。如果是多层继承,则链式调用。
	class1.call(this, a1)
	this.a2 = a2 || undefined;
}
class2.prototype = new class1 //class2.prototype指向无名class1实例,而此实例的__proto__指向class1.prototype
class2.prototype.constructor = class2 //相当于为无名class1实例添加属性
class2.prototype.m2 = function(){
	console.log(this.a2);
}

let obj = new class2(1,2);
obj.m1(); //1
obj.m2(); //2
//这种方法将子类、基类属性都作为实例直接拥有的属性,将子类、基类方法放到原型链上;基类
console.log(obj.hasOwnProperty("a1")); // true
console.log(obj.hasOwnProperty("a2")); // true
console.log(obj.hasOwnProperty("m1")); // false
console.log(obj.hasOwnProperty("m2")); // false

obj的原型链如下:
在这里插入图片描述
可见原型链上有一些可能永远用不到的、被初始化为undefined是参数。为了避免这种场景,可以通过Object.create实现类的继承,方法如下:

var Person = function(name) {
  this.name = name;
  this.canTalk = true;
};

Person.prototype.greet = function() {
  if (this.canTalk) {
    console.log('Hi, I am ' + this.name);
  }
};

var Employee = function(name, title) {
  Person.call(this, name);
  this.title = title;
};

Employee.prototype = Object.create(Person.prototype)
Employee.prototype.constructor = Employee
//必须先替换Employee.prototype,再添加属性。否则属性丢失,实例无法获得属性。
Employee.prototype.greet_1 = function () {
    if (this.canTalk) {
        console.log('Hi, I am ' + this.name + ', the ' + this.title);
    }
};

function SalesMan(name,job,title) {
    Employee.call(this, name, title);
    this.job = job
};

SalesMan.prototype = Object.create(Employee.prototype);
SalesMan.prototype.constructor = SalesMan;

SalesMan.prototype.greet_2 = function () {
    if (this.canTalk) {
        console.log('Hi, I am ' + this.name + ', the ' + this.title + ', I ' + this.job);
    }
};

var joe = new SalesMan('Joe','Sell things','Sales-Man');
console.log(joe);
joe.greet_2(); // Hi, I am Joe, the Sales-Man, I Sell things
joe.greet_1(); // Hi, I am Joe, the Sales-Man
joe.greet();   // Hi, I am Joe

对象直接拥有子类和基类的所有属性,对象的一切方法都在原型链上。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值