原型链并不难
原型链
在理解原型链之前,首先要明白,当一个函数被创建时,Function构造器产生的函数对象会运行类似这样的一些代码
this.prototype={ constructor:this }
即,创建的函数会有一个prototype(原型对象)属性,这个原型对象也有个属性是”constructor===函数名”,而每个被构造函数创实例,都有一个proto,来连接到创建他的函数的原型对象
prototype
是什么:每个函数(方法)都会有个prototype属性(除了bind方法建的函数),指向其原型对象,我们可以叫做显式原型 这个对象伴随着函数而生
作用: 用实现基于原型的共享属性
_proto_
是什么: 每创建一个对象(所有的对象都是某个函数的实例),就会有个[[prototype]]属性,指向它的创建函数的原型对象(也称之为显式原型),我们称为_proto_为隐式原型,在ES5之前没有明确方法可以通过实例,来访问创建实例的函数的显式原型,在主流浏览器中可以通过_proto_来进行访问,所以也可以把_proto_看做对象的隐式原型,通过_proto_链接到原型上,可以访问原型上的属性和方法
作用: 构成原型链,实现基于原型链的继承
constuctor
是什么:每个原型对象都有的属性,在原型对象刚被创建时,值为产生原型对象的函数名,可修改
作用:除了知道该原型对象对应的函数外,没什么卵用
知道了上面的那些基础概念后,我们来看看js的数据结构。是时候祭出这张图了!
我们来理一理js数据类型的继承关系
js中创建了一个Function类型,由Function类型创建了Object类型
js说,要有原型对象,于是以Object为构造函数,创建了每个函数的原型对象,即,有了Function.prototype,Object.prototype
Object类型由Function创建,所以Object._proto_指向Function.prototype
而所有的prototype由Object创建,所以,Function.prototype._proto_指向Object.prototype,而Object.prototype._proto_不可能再指向自己,就被js设置为指向null
再由Function创建了js中其他的内置类型,和我们自己创建的函数,由于Function.prototype.proto指向Object.prototype,所以我们创建的任何函数(包括内置函数),都直接的继承了Function.prototype,而且通过原型链间接继承了Object.prototype,而我们创建的函数(包括内置函数)的原型对象都直接继承了Object.prototype,函数的实例都直接继承了fun.prototype(我们创建的函数或者内置函数的原型对象),间接的继承了Object.prototype
嗯,就这样了,简单吧,不懂就对照着文字看图,这么详细了,实在不懂我也没办法了
呱唧呱唧的说了半天的原型链概念,那怎么检验我说的对不对呢,instanceof可以检测是否为某类型,检测的实质就是通过检测原型链来判断是否是此类型的
console.log(String instanceof Function); //true
//每个构造函数,包括内置函数,都直接的继承了Function.prototype,所以返回true
console.log(Array instanceof Object);
//每个构造函数,包括内置函数,也间接的继承了Object.prototype,所以返回true
function fun(){}
console.log(fun instanceof Function); //true
console.log(fun instanceof Object); //true
var arr=new Array();
var str=new Array();
var i=new fun();
console.log(arr instanceof Function); //false
console.log(arr instanceof Object); //true
console.log(arr instanceof Array); //true
console.log(str instanceof Function); //false
console.log(str instanceof Object); //true
console.log(str instanceof String); //true
console.log(i instanceof Function); //false
console.log(i instanceof Object); //true
console.log(i instanceof fun); //true
//每个实例都直接继承了其构造函数的原型对象,间接继承了Object.prototype,而与Function无关系
继承
原型链语法糖
如果觉得原型链单词太长,懒得写,不如这里写一个语法糖,来简化为原型链添加属性或者方法的书写,并提供链式操作,减少代码量
Function.prototype.add=function(name,func){
if(!(this.prototype[name])){
this.prototype[name]=func;
}
return this;
}
不仅提供了链式,而且还能检测函数原型中是否已经存在该属性,避免覆盖(如果有意覆盖,可以去掉if语句)
function a(){};
a.add('name',function(){
console.log(1);
}).add('name',function(){
console.log(2);
});
var i=new a();
i.name(); // 1 避免了覆盖
new操作符
当我们理解了原型后,不如来看看new操作符背后的故事,人们都说,每个成功的基佬后面都有一个腐女,我们来试试用add来实现new操作符
Function.prototype.add = function(name, func) {
this.prototype[name] = func;
}
Function.add('new', function() {
var that = Object.create(this.prototype);
//创建一个新对象,并继承了构造函数的原型
var other = this.apply(that, arguments);
//在这个新对象上调用构造函数
return (typeof other === 'object' && other) || that;
//如果返回值不是一个对象,就返回一个新对象
});
使用new操作符,相当于是在每个函数上调用了这个new方法
继承的两种方式
在ES5中给了Object.create()方法(IE9+兼容)来帮助实现继承,那么我们就看看Object.create的底层和其他实现继承的方法
先看一个Object.create()的使用例子
var obj={
name:'kanggege',
showName:function(){
console.log(this.name);
}
}
function f(){};
f.prototype=Object.create(obj);
f.prototype.showName(); //kanggege
//f的原型对象继承了obj
console.log(f.prototype.hasOwnProperty('showName')); //false
//说明只是通过_proto_继承了,并没有复制
于是我们猜想,Object.create的实现应该是这样的
Object.add('create',function(obj){
function F(){}
F.prototype=obj;
return new F();
});
有一点不舒服的是,在使用Object.create时,必须先调用函数原型fun.prototype=Object.create(fun),我们可以自己写一个继承的函数,来减少代码量,并且让他支持链式书写
Function.add('inherit',function(fun){
this.prototype=new fun();
return this; //实现链式
});
function Father(){}
Father.add('getName',function(){
console.log('kanggege');
});
var Child=function(){}
.inherit(Father)
.add('getNewName',function(){
console.log(dykanggege);
});
var i=new Child(); // kanggege
console.log(i.hasOwnProperty('getName')); //false
对象说明符
有时候构造器要接受一大堆参数,这很令人恶心,因为要记住参数的顺序和个数非常困难,如果我们在编写构造器时让它接受一个简单的对象说明符,这样就简便许多了
function Person(per){
this.name=per.name || 'kanggege';
this.age=per.age || 19;
this.show=per.show || function(){
console.log(this.name);
}
}
var person1=new Person({age:20});
console.log(person1.name); //kanggege
person1.show(); //kanggege
我们甚至可以为没有传入的值提供默认值,这样在传入参数时只用考虑要传入的值,不用考虑传入的个数和顺序