在JS中,我们经常会遇到原型。字面上的意思会让我们认为,是某个对象的原型,可用来继承。但是其实这样的理解是片面的,下面通过本文来了解原型与原型链的细节,再顺便谈谈继承的几种方式。
原型在讲到原型之前,我们先来回顾一下JS中的对象。在JS中,万物皆对象,就像字符串、数值、布尔、数组等。ECMA-262把对象定义为:无序属性的集合,其属性可包含基本值、对象或函数。对象是拥有属性和方法的数据,为了描述这些事物,便有了原型的概念。
无论何时,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向该函数的原型对象。所有原型对象都会获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。
这段话摘自《JS高级程序设计》,很好理解,以创建实例的代码为例。
function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); }; } const person1 = new Person("gali", 18); const person2 = new Person("pig", 20);
上面例子中的person1跟person2都是构造函数Person()的实例,Person.prototype指向了Person函数的原型对象,而Person.prototype.constructor又指向Person。Person的每一个实例,都含有一个内部属性__proto__,指向Person.prototype,就像上图所示,因此就有下面的关系。
console.log(Person.prototype.constructor === Person); // true console.log(person1.__proto__ === Person.prototype); // true console.log(person2.__proto__ === Person.prototype); // true
继承JS是基于原型的语言,跟基于类的面向对象语言有所不同,JS中并没有类这个概念,有的是原型对象这个概念,原型对象作为一个模板,新对象可从原型对象中获得属性。那么JS具体是怎样继承的呢?
在讲到继承这个话题之前,我们先来理解原型链这个概念。
原型链构造函数,原型和实例的关系已经很清楚了。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例对象都包含一个指向与原型对象的指针。这样的关系非常好理解,但是如果我们想让原型对象等于另一个类型的实例对象呢?那么就会衍生出相同的关系,此时的原型对象就会含有一个指向另一个原型对象的指针,而另一个原型对象会含有一个指向另一个构造函数的指针。如果另一个原型对象又是另一个类型的实例对象呢?这样就构成了原型链。文字可能有点难理解,下面用代码举例。
function SuperType() { this.name = "张三"; } SuperType.prototype.getSuperName = function() { return this.name; }; function SubType() { this.subname = "李四"; } SubType.prototype = new SuperType(); SubType.prototype.getSubName = function() { return this.subname; }; const instance = new SubType(); console.log(instance.getSuperName()); // 张三
上述例子中,SubType的原型对象作为SuperType构造函数的实例对象,此时,SubType的原型对象就会有一个__proto__属性指向SuperType的原型对象,instance作为SubType的实例对象,必然能共享SubType的原型对象的属性,又因为SubType的原型对象又指向SuperType原型对象的属性,因此可得,instance继承了SuperType原型的所有属性。
我们都知道,所有函数的默认原型都是Object的实例,所以也能得出,SuperType的默认原型必然有一个__proto__指向Object.prototype。
图中由__proto__属性组成的链子,就是原型链,原型链的终点就是null。
上图可很清晰的看出原型链的结构,这不禁让我想到JS的一个运算符instanceof,instanceof可用来判断一个实例对象是否属于一个构造函数。
A instanceof B; // true
实现原理其实就是在A的原型链上寻找是否有原型等于B.prototype,如果一直找到A原型链的顶端null,仍然找不到原型等于B.prototype,那么就可返回false。下面手写一个instanceof,这个也是很多大厂常用的手写面试题。
function Instance(left, right) { left = left.__proto__; right = right.prototype; while (true) { if (left === null) return false; if (left === right) return true; // 继续在left的原型链向上找 left = left.__propo__; } }
原型链继承