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

原型和原型链的主要做用:javascript

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

因此下面的例子全是以构造函数为例。java

原型

函数是也是对象,是一个属性的集合,因此函数下也有属性,也能够自定义属性。当咱们建立一个函数时就默认会有一个prototype属性,这个属性是一个对象(属性的集合)。这个东西就是原型---经过调用构造函数而建立的那个对象实例的原型对象。prototype里也有个属性constructor,指向的是函数自己。git

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 均可以获取并使用。github

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

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

__proto__

每一个JavaScript对象都具备的一个属性 -- __proto__ 这个属性指向该对象的原型。不过它是一个隐式属性,并非全部浏览器都支持它,咱们能够把它看作一种实例与实例原型之间的联系桥梁。学习

function Person() {
}
var p1 = new Person();

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

上述p1.__proto__与原型对象时相等的,因而可知p1.__proto__指向的是原型对象。this

constructor

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

function Person() {
}
console.log(Person.prototype.constructor==Person) // true

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

实例与原型

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

相关文章
相关标签/搜索