本文详细解析JavaScript中的原型链机制,通过具体代码示例阐明对象原型(__proto__)与函数原型(prototype)的区别与联系。重点讲解如何利用Object.create()和构造函数调用实现类式继承,分析三种常见继承模式的原理、优缺点及潜在问题,帮助开发者掌握JS面向对象编程的核心概念。
JavaScript原型链基础概念
JavaScript中的原型链与继承是核心知识点,下面将通过三个具体示例进行详细解析。

首先定义一个对象obj,其内部原型引用为obj._proto_。可以使用ES5提供的getPrototypeOf方法查询obj的原型。通过判断obj的原型是否与Object.prototype相等,可以验证原型的存在性,结果返回true,证明原型存在。接着定义一个函数foo(),任何函数都拥有其prototype属性(即函数的原型)。我们可以在函数的原型上添加任意属性,随后通过new关键字实例化对象,该实例即可共享这些属性(后续两个例子将详细介绍这一过程)。
原型链查找机制与属性共享
function foo(){}
foo.prototype.z = 3;
var obj = new foo();
obj.x=1;
obj.y=2;
obj.x //1
obj.y //2
obj.z //3
typeof obj.toString; //function
obj.valueOf(); // foo {x: 1, y: 2, z: 3}
obj.hasOwnProperty('z'); //false

在此示例中,obj的原型(_proto_)指向foo函数的prototype属性,而foo.prototype的原型又指向Object.prototype,原型链的末端为null。使用hasOwnProperty方法检查z属性是否直接存在于obj上,结果显示false,说明obj本身没有z属性。但通过查找原型链,发现在foo.prototype上存在该属性,因此obj.z=3成立。此外,在第一个示例中,obj.valueOf()和toString方法均源自Object.prototype,因此任何对象都具备这两个属性,因为所有对象的原型最终都指向Object.prototype。当然,存在一个特例:
Object.create与空对象原型
var obj2 = Object.create(null);
obj2.valueOf(); //undefined
Object.create()用于创建一个空对象,且该对象的原型指向传入的参数。下面的综合实例展示了如何实现一个class来继承另一个class。
类式继承的实现与原型修复
//声明一个构造函数Person
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.hi = function (){
console.log('Hi,my name is ' + this.name +',my age is '+this.age);
};
Person.prototype.LEGS_NUM=2;
Person.prototype.ARMS_NUM=2;
Person.prototype.walk = function (){
console.log(this.name+' is walking !');
};
function Student(name,age,classNum){
Person.call(this,name,age);
this.classNum = classNum;
}
//创建一个空对象
Student.prototype = Object.create(Person.prototype);
//constructor指定创建一个对象的函数。
Student.prototype.constructor = Student;
Student.prototype.hi = function (){
console.log('Hi,my name is ' + this.name +',my age is '+this.age+' and my class is '+this.classNum);
};
Student.prototype.learns = function (sub){
console.log(this.name+' is learning '+sub);
};
//实例化一个对象Bosn
var Bosn = new Student('bosn',27,'Class 3');
Bosn.hi(); //Hi,my name is bosn,my age is 27 and my class is Class 3
Bosn.LEGS_NUM; //2
Bosn.walk(); //bosn is walking !
Bosn.learns('Math'); //bosn is learning Math

构造函数Person与Student中的this指向实例化的对象(Bosn),且该对象的原型指向构造器的prototype属性。
我们使用Object.create()方法创建一个空对象,该对象的原型指向Person.prototype。这样做的优势在于,我们可以在不影响Person.prototype属性的前提下,自由创建Student.prototype的任意属性,同时继承Person.prototype上原有的属性,因为子类Student继承自基类Person。如果直接执行Person.prototype = Student.prototype,两者将同时指向同一个对象,此时给Student.prototype添加属性,Person的原型链上也会增加相同的属性,导致污染。
对于构造函数Student内部的call方法,其中的this指向新创建的Student实例化对象,并通过call方法来实现继承逻辑。
Student.prototype.constructor = Student,这句话的含义是指定Student为创建Student.prototype这个对象的构造函数。如果不写这句话,该对象的constructor属性将默认指向Person。
对于继承,一共有三种实现方式:
三种继承模式的对比分析
function Person(name,age){
this.name = name;
this.age = age;
}
function Student(){
}
Student.prototype = Person.prototype; //1
Student.prototype = Object.create(Person.prototype); //2
Student.prototype = new Person(); //3
第一种,刚刚在上面已经说过了,直接这样写会让子类和基类同时指向bosn实例;
第二种,恰到好处的避开这一点,并很好地实现继承,让实例先查询子类,若没有相应属性,再查询基类;
第三种,虽然也实现了继承,但调用了Person这个构造函数,这个例子中此构造函数有两个参数name和age,但这第三种什么也没有传,并没有实例化。
以上所述就是本文的全部内容了,希望大家能够喜欢。