prototype
、__proto__
又是各类指向什么的,让人以为很头疼。若是你也有这种感受,或许这篇文章能够帮助到你一、先来一串代码javascript
var Person = function(msg){
this.msg = msg;
}
var person1 = new Person("wanger")
person1.constructor===Person; //true
Person === Person.prototype.constructor; //true
person1.__proto__ === Person.prototype; //true
person1.__proto__.constructor === person1.constructor //true复制代码
看晕了吧?是否是很胡里花哨?不用担忧,其实一张图就能了明白这其中的关系:java
- 蓝色的是构造函数
- 绿色的是构造函数实例出来的对象
- 橙色的是构造函数的prototype,也是构造函数实例出来的对象的原型(它其实也是一个对象)
二、这里特别要注意的是prototype
与__proto__
的区别,prototype
是函数才有的属性,而__proto__
是每一个对象都有的属性。(__proto__
不是一个规范属性,只是部分浏览器实现了此属性,对应的标准属性是[[Prototype]]
)。浏览器
一、咱们刚刚了解了原型,那原型链在哪儿呢?不要着急,再上一张图:app
经过这张图咱们能够了解到,person1的原型链是:函数
person1 ----> Person.prototype ----> Object.prototype ----> null网站
二、事实上,函数也是一个对象,因此,Person的原型链是:ui
Person ----> Function.prototype ----> Object.prototype ----> nullthis
因为Function.prototype定义了apply()等方法,所以,Person就能够调用apply()方法。spa
三、若是把原型链的关系都显示清楚,那会复杂一些,以下图:prototype
这里须要特别注意的是:全部函数的原型都是Function.prototype,包括
Function
构造函数和Object
构造函数(如图中的标红部分)
一、假设咱们要基于Person扩展出Student,Student的构造以下:
function Student(props) {
// 调用Person构造函数,绑定this变量:
Person.call(this, props);
this.grade = props.grade || 1;
}复制代码
可是,调用了Person
构造函数不等于继承了Person
,Student
建立的对象的原型是:
new Student() ----> Student.prototype ----> Object.prototype ----> null
示意图以下所示:
必须想办法把原型链修改成:
new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null
示意图以下所示:
那咱们应该怎么修改呢?仔细观察两张图的差别,咱们会发现,若是咱们将Student
的prototype
改为person1
对象不就大功告成了?因而有了下面的代码:
Student.prototype = person1 ;复制代码
可是这时候有个问题:
Student.prototype.constructor === Student; //false复制代码
原来Student.prototype
(即person1
)的constructor
指向的仍是Person
,这时候还须要咱们再改一下代码:
Student.prototype.constructor = Student;复制代码
这样就能把Student的原型链顺利的修改成: new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null 了;
完整的代码显示以下:
var Person = function(msg){
this.msg = msg;
}
var Student = function(props) {
// 调用Person构造函数,绑定this变量:
Person.call(this, props);
this.grade = props.grade || 1;
}
var person1 = new Person("wanger")
Student.prototype = person1 ;
Student.prototype.constructor = Student;复制代码
一、若是在控制台执行一遍上述的代码,咱们会发现一些问题,如图所示:
Student.prototype
上含有以前person1带有的属性,那么,这样的继承的方法就显得不那么完美了
二、这个时候,咱们能够借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向Person.prototype。为了实现这一点,参考道爷(就是发明JSON的那个道格拉斯)的代码,中间对象能够用一个空函数F来实现:
var Person = function(msg){
this.msg = msg;
}
var Student = function(props) {
// 调用Person构造函数,绑定this变量:
Person.call(this, props);
this.grade = props.grade || 1;
}
// 空函数F:
function F() {
}
// 把F的原型指向Person.prototype:
F.prototype = Person.prototype;
// 把Student的原型指向一个新的F对象,F对象的原型正好指向Person.prototype:
Student.prototype = new F();
// 把Student原型的构造函数修复为Student:
Student.prototype.constructor = Student;
// 继续在Student原型(就是new F()对象)上定义方法:
Student.prototype.getGrade = function () {
return this.grade;
};
// 建立wanger:
var wanger = new Student({
name: '王二',
grade: 9
});
wanger.msg; // '王二'
wanger.grade; // 9
// 验证原型:
wanger.__proto__ === Student.prototype; // true
wanger.__proto__.__proto__ === Person.prototype; // true
// 验证继承关系:
wanger instanceof Student; // true
wanger instanceof Person; // true复制代码
这其中主要用到了一个空函数F做为过桥函数。为何道爷会用过桥函数?用过桥函数F(){}主要是为了清空构造的属性。若是有些原Person的构造用不到,那么过桥函数将是一个好的解决方案
这样写的话,Student.prototype
上就没有任何自带的私有属性,这是理想的继承的方法
三、若是把继承这个动做用一个inherits()函数封装起来,还能够隐藏F的定义,并简化代码:
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}复制代码
封装后,写起来就像这样:
var Person = function(msg){
this.msg = msg;
}
var Student = function(props) {
// 调用Person构造函数,绑定this变量:
Person.call(this, props);
this.grade = props.grade || 1;
}
inherits(Student,Person) ;复制代码
这样再一封装的话,代码就很完美了。
事实上,咱们也能够在inherits
中使用Object.create()
来进行操做,代码以下:
function inherits(Child, Parent) {
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
}复制代码
若是有兴趣了解Object.create()
的其余用法,能够参考个人这篇博客JS中Object.create的使用方法;
在ES6中,新的关键字class,extends被正式被引入,它采用的相似java的继承写法,写起来就像这样:
class Student extends Person {
constructor(name, grade) {
super(msg); // 记得用super调用父类的构造方法!
this.grade = grade || 1;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}复制代码
这样写的话会更通俗易懂,继承也至关方便。读者能够进入廖雪峰的官方网站详细了解class的用法