写博客也有不短的时间了,经常做一些自己的总结和技术的分享。但是之前的由于之前的备用号码丢失,也正好准备换个备用卡,所以直接注销了,没有找回。所以就开了个新的博客。以后,我会经常在这里分享一下心得,和和大家做一些技术探讨。今天就原型链做一个总结。
在刚接触到js原型和原型链的时候,可能很多人都会有很多的困惑和疑问。这是很正常,因为当你有了这种状态的时候,说明你已经到了王国维先生说的学习的三重境界中的第一重了,昨夜西风凋碧树,独上高楼,望断天涯路。就是说你已经有了想要学的更透彻,追寻运行原理的想法了。是所谓独上高楼,望断天涯路。一定是有着穷其理的好奇心与干劲。
一,基本释义
所有类型(包括基本类型与函数,数组,对象)都拥有__proto__属性(隐式原型)
所有函数拥有prototype属性(显式原型)最底层的Object也有prototype属性。这也验证了万物皆对象,所有的类型都是从它中来。
函数的prototype是个指针,它指向自己的原型对象,
function Ball(words){
this.words = words;
}
Ball.prototype = {
a:10,
play:function(){
console.log( this.a);
}
}
var ball=new Ball();
这个 Ball.prototype就是构造函数function Ball的原型对象;
通过这个构造函数new 出来的新的对象ball中就有__proto__这个属性,并且__proto__指向这个构造函数function Ball的原型对象 Ball.prototype
二,构造函数实例解析
//创建实例
function Ball(words){
this.words = words;//当前this 指向window,new出新的ball对象后,指向ball(在面向对象中就用这里为原型对象传参数)
}
Ball.prototype = {
a:10,
play:function(){
console.log( this.a);//这里this指向原型对象 Ball.prototype,在new 出新的实例后, this指向实例即 ball;
}
}
var ball=new Ball();
ball.play();// a=10;
play()方法是ball实例本身具有的方法,所以ball.play()打印a=10;ball.play()不属于ball实例的方法,属于构造函数的方法,因为实例继承构造函数的方法。
实例w的隐式原型指向它构造函数的显式原型,指向的意思是恒等于
ball.__proto__ === Ball.prototype
当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找。所以很好理解实例继承构造函数的方法和属性:
ball本身没有play()方法,所以会去Ball()的显式原型中调用play(),即实例继承构造函数的方法。
三,原型和原型链
Object.prototype.play= “play”;
function Ball(){}
console.log(Ball); //function Ball()
let ball= new Ball();
console.log(ball); //Ball{} 对象
console.log(ball.play); //play
解析:
ball是Ball()的实例,是一个Ball对象,它拥有一个属性值__proto__,并且__proto__是一个对象,包含两个属性值constructor和__proto__
console.log(ball.__proto__.constructor); //function Ball(){}
console.log(ball.__proto__.__proto__); //对象{},拥有很多属性值
我们会发现ball.__proto__.constructor返回的结果为构造函数本身,ball.__proto__.__proto__有很多参数
原型链总结:
1.查找属性,如果本身没有,则会去__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined
2.ball.__proto__.constructor == function Ball(){}
3.ball.___proto__.__proto__== Object.prototype
4.ball.___proto__.__proto__.__proto__== Object.prototype.__proto__ == null
5.通过__proto__形成原型链而非protrotype
console.log(ball.__proto__.__proto__.__proto__); //null
console.log(Object.prototype.__proto__); //null
通过上述我们发现prototype下面是有constructor和__proto__的,也就是说构造函数也是有__proto__指向的,它就指向Object.prototype,而且Object.prototype下也有__proto__指向null;
所以利用这个原理,我们就能完成继承。在继承的类中,例如下列;
//原来的的类
function Ball(words){
this.words = words;//当前this 指向window,new出新的ball对象后,指向ball(在面向对象中就用这里为原型对象传参数)
}
Ball.prototype = {
a:10,
play:function(){
this.a===10;//这里this指向原型对象 Ball.prototype,在new 出新的实例后, this指向实例即 ball;
}
};
//写一个继承函数
//subClass想要继承的新类,supClass是原来的类
function extend(subClass,supClass) {
// 创建一个临时类
function F() {}
// 设置这个临时的类的原型是父类的原型
F.prototype=supClass.prototype;
// 子类的原型是实例化的这个临时类
subClass.prototype=new F();
// 设置子类原型中该子类的指针指向自己这个构造函数
subClass.prototype.constructor=subClass;
// 设置子类的属性superClass是父类的原型
subClass.superClass=supClass.prototype;
// 如果父类原型中指针没有指向父类的构造函数,仍然指向Object的指针时,
// 重设父类原型中指针指向父类的构造函数
if(supClass.prototype.constructor===Object.prototype.constructor){
supClass.prototype.constructor=supClass;
}
}
//继承得到的新类
function Box() {
// 使用冒充将父类的对象属性加入到子类的对象属性中
Ball.call(this);
}
extend(Box,Ball);
// console.log(Box);
Box.prototype.play=function () {
// Box.superClass.play.call(this);//类似es6中super作用的语句
console.log(this.a);
};
var box=new Box();
box.play(); //打印的结果为1;(如果上面的super语句不注解的话那么结果就为10;)
这就是es5当中我们常用的继承方法,是基于原型和原型链基础之上来的。可以看到Box.prototype=new new F(); 而 F.prototype=supClass.prototype;其中是通过new改变了Box中的__proto__的指向,让其指向supClass.prototype;所以我们在新类中可以用到原来类的属性和方法,也可以对Box.prototype.play进行原来类下某个方法和属性进行覆盖。(原理是对象属性和方法的调用顺序先找自身,再去原型链上找)但是注意不能对Box.prototype.__proto__进行修改,因为Box.prototype.__proto__指向Ball.prototype。