0%
3245 字 --- 耗时5 分钟.

JS 进阶之路 : 原型链

刚学习 JavsScript 的时候,感觉它有点混乱,以前接触 C++或 Java,好歹有个 class 来实现继承,JS 作为动态语言,结构只有对象,偶尔还来个闭包,this 什么的。

JS 中是有继承的,通过原型来实现,即 prototype,每个函数对象都有 prototype ,而每个对象都有 __proto__,prototype 和 __proto__ 是实现原型链的根本。

prototype 实现继承

先看一个简单的例子,

function person(){};  
person.prototype.Say = function(){  
    console.log('I can say!')
};
var student = new person();  
student.Say(); //'I can say!'  

这段代码没问题,对于 new 初始化实际上之前的文章已经讨论过,有 3 个步骤

然后是下面的代码,

function person(){  
    this.Say = function(){
        console.log('Say!!!')
    }
};
person.prototype.Say = function(){  
    console.log('I can say!')
};
var student = new person();  
student.Say(); //'Say!!!'  

结果是 Say!!!,说明初始化的顺序:

  1. 通过原型给对象添加的属性
  2. 通过构造器给对象添加的属性

构造器内部的函数的优先级比原型的高,然后得出结论:

  1. 使用对象的一个属性时,首先检查该对象本身是否有该属性,有则返回,没有则继续;
  2. 查看对象的原型是否有该属性,有则返回,没有则继续查看对象原型的原型,知道为 null,则返回 undefined。

为了验证该结论的正确性,看下面的代码:

function person(){  
    this.Say = function(){
        console.log('Say!!!')
    }
};
var student = new person();  
person.prototype.Say = function(){  
    console.log('I can say!')
};
student.Say(); //'Say!!!'  

把初始化提前之后,同样是先访问对象本身。

constructor 实现初始化

JS 中每个对象,都有一个名为constructor 的隐式属性,该属性是引用创建该属性的构造器。

比如说 var student = new person(),student 就有一个 constructor 指向 person(),我们在上个代码的最后添上:

console.log(student.constructor)  
/*输出
function person(){  
    this.Say = function(){
        console.log('Say!!!')
    }
}
*/

原型链 __proto__

__proto__ 是每个对象内部的一个属性,任何一个对象,当我们访问对象的一个属性时,如果对象本身不存在这个属性,会去 __proto__ 里找,如果还不在,去__proto__的__proto__找......这就是原型链。

__proto__就像一个指针,指向父节点的 prototype。这下子前面的例子就很好理解了:

  1. person 是一个对象,student 继承 person,所以 student 的 __proto__指向person 的prototype;
  2. 访问 student.Say(),如果 student 有 Say() 属性,则返回;如果没有,则去__proto__里找,而__proto__指向父的 prototype,一直找下去。

之前在写原型链代码的时候碰到一个坑,实际上还是自己理解不深,比如:

var person = function(){};  
person.prototype.Say = function(){  
    console.log('I can say!')
}
var student = new person();  
person.prototype.doHomework = function(){  
    console.log('I can do Homework!')
};
var china_student = new student();  
china_student.Say();  
china_student.doHomework();  

这样子写很明显是有问题的,我们是通过原型链来实现继承,而正确的写法应该是:

var person = function(){};  
person.prototype.Say = function(){  
    console.log('I can say!')
}
var student = function(){};  
student.prototype = new person(); //这一步原型链  
person.prototype.doHomework = function(){  
    console.log('I can do Homework!')
};
var china_student = new student();  
china_student.Say();  
china_student.doHomework();  

下面附上 stackoverflow 上一幅图,

从上图可以看出,prototype 为继承而生,constructor 实现初始化,而真正形成原型链的是 __proto__它是一个指向父节点原型的指针,共勉。

参考

《JavaScript 忍者秘籍》

关于proto和prototype的一些理解