js高级----回顾prototype、__proto__、constructor与JavaScript的原型链继承
JavaScript没有传统面向对象语言的类继承机制,而是基于原型链继承实现的,其本质是使用函数模拟类的特征。我们可以通过prototype
将属性写到原型链上,调用new
操作符创建对象(实例化)时,对象实例会把类原型链上的属性关联到自身的__proto__
属性上;而子类继承父类时,是将子类的prototype
属性指向父类的prototype
属性,并在子类prototype
属性添加自己的方法和属性实现对父类的扩展。
1. 类与实例
1.1 类定义
在JavaScript中我们会像下面这样模拟一个类:
function Person(name,sex) {
this.name = name;
this.sex = sex;
}
Person.prototype.sayName = function() {
console.log(this.name)
}
var p = new Person('muzi','男');
p.sayName();
该类包含两个属性和一个方法。
1.2 实例化
类定义后,就可以通过new
关键创建类实例:
var person = new Person('muzidigbig','男');
console.log(person.__proto__ === Person.prototype);//true
JavaScript中实例化不同与传统面向对象语言(如:Java、C++等),其实例化基于对象原型。
__proto__
是一个对象内部属性,继承自Object.prototype.__proto__
。当类被实例化时,对象的(类实例)的__proto__
属性会指向类的原型,即:类的prototype
属性。
这就是基于原型的类的实现方式,也就是原型链的实现方式。实例化后当调用对象的属性或方法时,会有如下过程:
- 查找对象是否有该属性或方法,如果则在则返回或调用
- 如果不存在,则通过
__proto__
属性,在原型链上查找有没有属性或方法
2. 类的继承(4步曲)
继承、封装、多态是面向对象语言三大特征,JavaScript基于原型同样可以模拟出这三个特性。单就继承来说又分为单继承和多继承,但JavaScript只能实现单继承。
我可以像下面这样模拟一类继承:
//第一步:创建父类
function Person(name,sex) {
this.name = name;
this.sex = sex;
}
//在父类原型对象上创建方法
Person.prototype.sayName = function() {
console.log(this.name+',我是一个人')
}
// 等同于Object.create的方法
function object(prop) {
var F = function() {};
F.prototype = prop;
return new F();
}
//第二步:创建子类
function Student(name,sex) {
Person.call(this,name,sex);
}
//第三步:创建继承的关系
//等价于Student.prototype = Object.create(Person.prototype)
Student.prototype = object(Person.prototype);
//第四步:改变子类的构造器
Student.prototype.constructor = Student;
Student.prototype.work = function() {
console.log('我的工作是学习');
}
在这个继承过程中,我们做了以下几件事:
- 在子类的构造函数中调用父类的构造函数
- 将父类的原型属性
prototype
复制到子类的原型prototype
中 - 将子类的构造器
constructor
指向子类的构造函数
JavaScript中继承的本质是原型链的复制,创建子类的实例后,其__proto__
属性会指定子类的prototype
,但它同时是一个父类的实例。
console.log(student.__proto__ === Person.prototype);//false
console.log(student.__proto__ === Student.prototype);//true
console.log(student instanceof Student);//true
console.log(student instanceof Person);//true
仍然可以通过__proto__.__proto__
访问父类中的方法:
student.__proto__.__proto__.sayName(); // undefined,我是一个人
除上述的自定义object
方法外,还可以使用Object.create()实现类原型的复制。无论使用哪种方式,这只对面向对象继承的一种模拟,其与传统的类的实例化与继承相比有以下几个特点:
- 在传统的类中,“类名”同进是一个构造函数,而JavaScript使用函数模拟了构造函数
- 在传统的类中有可以被继承到子类中的属性和方法,而而JavaScript使用
prototype
实现了继承 - 传统的类通过
new
关键字来实例化一个类,而JavaScript的实例化过程是对prototype
属性的复制
注意:在ECMAScript 2015(ES6)中新增了class
类定义的方式,但其本质仍然是基于函数的类模拟。
//Object.getPrototypeOf()方法读取实例的原型对象
console.log(Object.getPrototypeOf(student));
//isPrototypeOf()函数用于指示对象是否存在于另一个对象的原型链中。如果存在,返回true,否则返回false。
补充:
- constructor属性
每个函数都有constructor这个属性指向构造函数,例如:{}和new Object()的contructor指向同一个构造函数
prototype属性又指向了一个prototype对象,注意prototype属性与prototype对象是两个不同的东西,要注意区别。在prototype对象中又有一个constructor属性,这个constructor属性同样指向一个constructor对象,而这个constructor对象恰恰就是这个function函数本身。
我们接着看代码:
function Person(name) {
this.name=name;
this.showMe=function(){
alert(this.name);
}
};
Person.prototype.from=function() {
alert('I come from prototype.');
}
var one=new Person('js');
one.showMe();//js,这个结果没有什么好奇怪的
one.from();//I come from prototype.,这个结果有一点奇怪吧
要解释这个结果就要仔细研究一下new这个操作符了.var one=new Person(‘js’);这个语句执行的过程可以分成下面的语句:
var one={};
Person.call(one,'js');
按照《悟透javascript》书中说的,new形式创建对象的过程实际上可以分为三步:
第一步是建立一个新对象(叫A吧);
第二步将该对象(A)内置的原型对象设置为构造函数(就是Person)prototype 属性引用的那个原型对象;
第三步就是将该对象(A)作为this 参数调用构造函数(就是Person),完成成员设置等初始化工作。
其中第二步中出现了一个新名词就是内置的原型对象,注意这个新名词跟prototype对象不是一回事,为了区别我叫它inobj,inobj就指向了函数Person的prototype对象。在person的prototype对象中出现的任何属性或者函数都可以在one对象中直接使用,这个就是javascript中的原型继承了。
又头晕了,上图吧!
这样one对象通过内置的原型对象inobj就可以直接访问Person的prototype对象中的任何属性与方法了。这也就解释了上面的代码中为什么one可以访问form函数了。因为prototype对象中有一个constructor属性,那么one也可以直接访问constructor属性。
代码:
one.showMe();//js,这个结果没有什么好奇怪的
one.from();//I come from prototype.,这个结果有一点奇怪吧
alert(one.constructor);//function Person(name) {...}
alert(Person.prototype.constructor);//function Person(name) {...}
console.log(one.constructor === Person.prototype.constructor);//true
再看看继承是如何实现的:
function Person(name)
{
this.name=name;
this.showMe=function()
{
alert(this.name);
}
};
Person.prototype.from=function()
{
alert('I come from prototype.');
}
function SubPerson()
{
}
SubPerson.prototype=new Person();
var subOne=new SubPerson();
subOne.from();//I come from prototype.
alert(subOne.constructor);//function Person(name) {...};
alert(SubPerson.prototype.constructor);//function Person(name) {...};
继承的实现很简单,只需要把子类的prototype设置为父类的一个对象即可。注意这里说的可是对象哦!
那么通过prototype属性实现继承的原理是什么呢?还是先看图形说明,然后编写代码进行验证。
注意:红色的方框就是把子类与父类链接起来的地方。这个就应该是传说中的prototype链了吧。下面有代码进行验证。
js代码:
function Person(name)
{
this.name=name;
this.showMe=function()
{
alert(this.name);
}
};
Person.prototype.from=function()
{
alert('I come from prototype.');
}
var father=new Person('js');//为了下面演示使用showMe方法,采用了js参数,实际多采用无参数
alert(father.constructor);//查看构造函数,结果是:function Person(name) {...};
function SubPer()
{
}
SubPer.prototype=father;//注意这里
SubPer.prototype.constructor=SubPer;
var son=new SubPer();
son.showMe();//js
son.from();//I come from prototype.
alert(father.constructor);//function SubPer(){...}
alert(son.constructor);//function SubPer(){...}
alert(SubPer.prototype.constructor);//function SubPer(){...}
参考文献:https://itbilu.com/javascript/js/NkERWtG.html
contructor参考文献:https://www.cnblogs.com/zjunet/p/4559895.html