JavaScript必须掌握的基础 ---> 原型&原型链

原型和原型链的主要作用:

  • 实现属性和方法的公用
  • 继承

所以下面的例子全是以构造函数为例。

原型

函数是也是对象,是一个属性的集合,所以函数下也有属性,也可以自定义属性。当我们创建一个函数时就默认会有一个prototype属性,这个属性是一个对象(属性的集合)。这个东西就是原型---通过调用构造函数而创建的那个对象实例的原型对象。prototype里也有个属性constructor,指向的是函数本身。

prototype

function Person() {

}

Person.prototype.name='erdong';

var p1=new Person();

var p2=new Person();

console.log(p1.name); // erdong

console.log(p2.name); // erdong

函数Person有个prototype属性,给这个属性添加一个name的属性。p1 和 p2 为这个函数的实例,当访问 p1.name 和 p2.name 时其值都是 prototype下面 name的值。这个prototype对象就是 p1 和 p2的实例原型,它下面的所以属性和方法 p1 和 p2 都可以获取并使用。

看一下原型对象与构造函数的关系:

那么实例是怎样与原型对象做关联的呢?

__proto__

每个JavaScript对象都具有的一个属性 -- __proto__ 这个属性指向该对象的原型。不过它是一个隐式属性,并不是所有浏览器都支持它,我们可以把它看做一种实例与实例原型之间的联系桥梁。

function Person() {

}

var p1 = new Person();

console.log(p1.__proto__==Person.prototype); // true

上述p1.__proto__与原型对象时相等的,由此可见p1.__proto__指向的是原型对象。

constructor

每个函数都有一个prototype属性,而prototype下都有一个constructor属性,它指向prototype所在函数。

function Person() {

}

console.log(Person.prototype.constructor==Person) // true

以上就是关于原型几个重要的"属性"已经说完了,下面来讲讲原型连。

实例与原型

JavaScript规定,当读取对象的某个属性或方法时,先从自身查找,如果找不到就去其__proto__指向的原型对象上去找,如果找不到就去原型对象的原型对象上查找,如果再找不到就去原型对象的原型对象上去找... , 就这样直到找到最上层,至于哪里是最上层,下面会提到。

function Person() {

}

var p1 = new Person();

console.log(p1.name);// undefined

p1.show();// Uncaught TypeError: p1.show is not a function

上述例子,p1 为构造函数 Person的实例,当访问 p1 的 name 属性和 show 方法时,因为 p1 是刚 new 出来的实例,所以并没有找到。看下面的例子:

function Person() {

}

Person.prototype.name='erdong';

Person.prototype.show=function () {

console.log(this.name);

}

var p1=new Person();

console.log(p1.name);// erdong

p1.show();// erdong

当在 prototype上添加 name 属性和 show方法后,p1 就可以正确的访问,这就说明 p1 在查找属性(方法)时,在自身没有找到 就会去__proto__所指的原型对象上去查找。再来看一个例子:

function Person() {

}

Person.prototype.name='erdong';

Person.prototype.show=function () {

console.log(this.name);

}

var p1=new Person();

p1.name = 'chen'

console.log(p1.name);// chen

当我们在 p1(对象) 上添加一个属性 name 这个时候再去访问 p1.name 那么输出的就是 "chen" 而不是 "erdong"。 这就是一个对象查找属性(方法)时的一个规则。

原型的顶层

我们在上述例子查找 p1的name 时当查找到 Person.prototype 还未找到时,我们应该还往下查找,下一级是谁呢? 因为 Person.prototype是对象,那么他就有一个__proto__属性,指向的是其原型对象--也就是其对应构造函数的 prototype。那么Person.prototype 是谁呢?是Object,因为对象可以通过 new Object()创建:

var obj = new Object();

// 我们平时都是通过字面量的形式来写:var obj = { }; 其实就相当于 new Object(); 只不过是javascript在内部执行了。

obj.name = 'erdong';

console.log(obj.name); // erdong

看图:

为什么当查找到 Object.prototype 找不到就输出 undefined 了呢?
因为当在 Object.prototype 也找不到 name 属性,就会去 Object.prototype 指向的原型对象上查找,我们在上面提到,对象与其原型对象是通过 __proto__做关联的,但是javascript中规定,Object.prototype.__proto__是不存在的 也就是null

console.log(Object.prototype.__proto__ === null); // true

这一点要牢记。

原型链

原型链也是JavaScript中很重要的一个概念,之所以说是一个概念,是因为它是不存在的,不像一个对象的属性,或者是一个对象的方法一样实例存在。
我的理解就是--一个(实例)对象的属性或者方法的查找规则。这个规则可以很简单,也可以很复杂。

我们把上面所有的知识总结一下:
每个函数都有一个原型对象(prototype),原型对象又包含一个属性(constructor),指向的是函数本身,函数的实例都有一个隐式原型(__proto__),指向的是构造函数的原型对象(prototype)。

查找规则:当我们访问实例的一个属性时,先从实例自身查找,如果找不到就去其内部指向的原型对象上去查找,如果再找不到,就去其内部指向的原型对象内部指向的原型对象上去查找,就这样一直找到原型的最顶端。

看图:

蓝色的线就表示一条原型链。

改变prototype

function Person() {

}

Person.prototype={

name: 'erdong',

sex: '男',

doSoming: function() {

console.log(this.name);

}

}

var p1=new Person();

p1.doSoming();

console.log(p1.__proto__==Person.prototype); // true

console.log(Person.prototype.constructor==Person);// false

上面的例子,将构造函数的prototype属性重写了。虽然p1也能找的到name,但是prototype下的constructor属性不再指向Person了。实际上指向了Object

console.log(Person.prototype.constructor==Object); //true

是因为我们重写了Personprototype,此时Person.prototype只是一个普通的对象。即:

Person.prototype.constructor = Person.prototype.__proto__.constructor = Object.prototype.constructor = Object

constructor属性很重要时,我们可以这样做:

function Person() {

}

Person.prototype={

constructor: Person, // 主动加上constructor属性

name: 'erdong',

sex: '男',

doSoming: function() {

console.log(this.name);

}

}

console.log(Person.prototype.constructor==Person);// true

上述代码实例可以适用于当构造函数拥有很多方法或者属性时的写法。

继承

--

原型链是实现继承的一种方式。这里只是略提一下,下面的文章会详细理解。

当我们不去完全重写函数的prototype属性,而是让它等于另一个构造函数的实例时结果会怎样呢?

function SuperType() {

}

SuperType.prototype.name = 'erdong';

SuperType.prototype.getName=function() {

return this.name;

}

function SubType() {

}

SubType.prototype=new SuperType();

var instance=new SubType();

console.log(instance.name); // erdong

console.log(instance.getName()); // erdong

上述例子中有两个构造函数SuperTypeSubTypeSuperType原型上有个name属性和getName 方法。instance是另一个构造函数SubType的实例,原本instanceSuperType是没有关系的。但是现在instance可以获取到 name 属性和 getName 方法。原因就是SubType重写了prototype属性,让它的值等于SuperType的实例。所以存在SuperType.prototype中的属性和方法,现在也存在与SubType.prototype中。

看下面的图:

蓝色的线为SubType.prototype改变后的原型( __proto__ 和prototype)的指向(也是实例查找属性的路线),红色为原来的原型( __proto__和prototype)的指向。

JavaScript高级程序设计一书中解释上述的示例为原型链的基本概念--当我们让原型对象等于另一个构造函数的实例,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。加入另一个原型又是另一个构造函数的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。

关于原型的方法

isPrototypeOf、getPrototypeOf、instanceof、in、hasOwnProperty

isPrototypeOf

用于判断传入的对象内部是否有一个原型对象的指针。

function Person(){

}

var p = new Person();

console.log( Person.prototype.isPrototypeOf( p ) );// true

我们上面讲到实例与原型对象是通过__proto__做关联的,__proto__并不是Javascript规范,所以我们现实中不能使用它来判断实例与原型对象的关系,这个时候就用isPrototypeOf

getPrototypeOf

ES6 Object新增方法,返回的是传入对象的原型。

function Person(){

}

var p = new Person();

console.log( Object.getPrototypeOf(p)===Person.prototype );// true

上述代码输出的是 true 证明 Object.getPrototypeOf(p) 获取到的就是 p 的原型。

instanceof

判断前者是否是后者的一个实例。

function Person(){

}

var p = new Person();

console.log(p instanceof Person); // true

console.log(p instanceof Object); // true

由于 p 既是 Person的实例,同时它也是一个对象,所以也是Object的实例。

看下面的示例:

console.log(Function instanceof Object); // true

console.log(Object instanceof Function); // true

Function既是Object的实例,Object又是Function的实例。是有点绕了,下面会说明这一情况。

in

判断前者是否是后者原型链中的一个属性。

function Person() {

}

Person.prototype.name='erdong';

var p=new Person();

p.sex='男';

console.log('sex' in p); // true

console.log('name' in p); // true

console.log('address' in p); // false

hasOwnProperty

检测传入的字符串是否是调用者的自身属性,如果是自身的属性,返回true,如果是原型中的属性或者不存在,返回false。

function Person() {

}

Person.prototype.name='erdong';

var p=new Person();

p.sex='男';

console.log(p.hasOwnProperty('name'));// false

console.log(p.hasOwnProperty('sex')); // true

console.log(p.hasOwnProperty('address')); // false

与 in 不同的是如果该属性存在于实例上包括原型链上,就返回true,而hasOwnProperty只有是自身的属性,才会返回true。

思考

我们(构造)函数也是对象,上面说过对象下面都会有一个__proto__属性,那么函数的__proto__指向谁呢?

console.log(Person.__proto__===Function.prototype); // true 

函数都是通过 new Function()来创建的,虽然我们平时创建函数并不是通过 new

下面这个函数:

function sum (num1, num2) {

return num1 + num2;

}

其实在JavaScript内部应该是这样实现的:

var sum = new Function("num1", "num2", "return num1 + num2");

所以Person对应的构造函数应该是Function

那么新的问题又来了?

Function也一个函数,也是一个对象,那么他同样也有__proto__属性,也有prototype属性,它们分别指向什么呢?

console.log(typeof Function); // 'function'

console.log(Function.__proto__ === Function.prototype); // true

看到上面是不是会很奇怪?下面解释一下:

Function是一个函数,它也是通过new Function创建的,所以它是被自身创建的,它的__proto__指向的自身的prototype--也就是Function.prototype

那么Function.prototype.__proto__又指向谁呢?

console.log(Function.prototype.__proto__ === Object.prototype)

很显然,Function.prototype.__proto__是一个对象,所以它指向的是Object.prototype

还有一个问题,Object也是一个构造函数,也是一个对象,那么它应该也有prototype__proto__属性,我们在上面说到 Object.prototype. __proto__ null,那么Object.__proto__指向谁呢?

console.log(Object.__proto__ === Function.prototype); // true

上面Object.__proto__ 又指向了Function.prototype,是因为Object是函数,所以它的原型就是Function.prototype

最后总结一下:

看似关系很复杂,其实一条一条捋清楚就有一种恍然大悟的感觉。

写在最后

如果文中有错误,请务必留言指正,万分感谢。

点个赞哦,让我们共同学习,共同进步。

GitHub

以上是 JavaScript必须掌握的基础 ---> 原型&原型链 的全部内容, 来源链接: utcz.com/a/20939.html

回到顶部