原型,原型链,继承
最近在看js高级程序设计,以前对prototype不了解,重新看完书后得到了很多东西不止是原型这块知识点,还有一些细节点,写法上的性能优化、一些方法的处理机制,这里就不说明了。就来把自己所知道的原型来梳理一下吧
一丶原型是什么
在js里,万物皆对象,Function也是对象,在使用字面量创建基本类型的时候也会实例化成一个对象。 我们在创建对象的时候可以看到,对象里会有一个属性__proto__,可称为隐式原型。
在函数(Function)这个对象中,还有一个特有的属性,prototype(显式原型)。函数的prototype是一个指针,指向一个对象。
function a(){};
a.prototype={};
new a().__proto__ === a.prototype; //true
这里就能知道一个对象的隐式原型指向和构造该对象的构造函数的原型的指向是一样的,这样就保证了能够访问到构造函数原型时定义的方法了。在对象__proto__属性里能看到里面有许多的方法还有一个属性constructor,指向我们的构造函数。
二丶继承
js没有现有的继承的机制,但是可以通过其他的方式来实现继承,比如常见的类式继承,利用原型的特性实现继承
1.类式继承
function SuperClass(){
this.val=true
};
SuperClass.prototype.getVal=function(){
console.log(this.val)
}
SuperClass.prototype.o={
a:1
}
function SubClass(){
this.val=false
}
SubClass.prototype=new SuperClass();
SubClass.prototype.constructor=SubClass;
var sub1=new SubClass();
sub1.getVal(); //false;
sub1.o.a=2;
console.log(new SubClass().o.a)//2
类的原型对象的作用就是为类的原型添加共有方法。当子类的原型对象指向实例化一个父类的时候,再去实例化这个子类的时候,实例化后的对象里的__proto__就是我们父类的prototype对象,所以就能够访问到父类原型对象的方法和构造属性了。
但是有个缺点,因为prototype是指向对象,对象和基本值不一样,是引用,所以我们更改其中的属性也就是相当于更改父类的属性,就会相互影响。
当然也可以通过父类的原型对象for-in循环复制到子类里,只要加上hasownproperty的判断就好啦。
2.构造函数继承
js是灵活的,我们可以使用其他继承方式来解决引用问题,比如常见的构造函数继承
function SuperClass(){
this.val=true;
this.o={
a:1
}
};
function SubClass(){
SuperClass.call(this)
}
var sub1=new SubClass();
sub1.val//true
sub1.o.a=2
var sub2=new SubClass();
console.log(sub2.o.a)//1
当我们实例化子类的时候,执行一遍父类的函数,利用call函数更改为子类的作用域,那么在this添加属性的时候就会对子类的实例化对象添加属性了,这样创建的实例就会单独拥有一份属性了,而不能共用,
这样就违背了代码复用的原则。
3.组合继承
function SuperClass(type){
this.val=type;
this.o={
a:1
}
};
SuperClass.prototype.getVal=function(){
console.log(this.val)
}
function SubClass(type){
SuperClass.call(this,type)
}
SubClass.prototype=new SuperClass();
这样就能共用方法和享有自己的副本了,但是还是有点瑕疵,为什么呢?因为构造函数被调用了两次,这样会多出来一个副本,这样不清真,也不完美。
4.原形式继承
后来,有个dalao提出一句话,书上有翻译,但是没看懂,就不打了,直接上代码
function inheritObject(o){
function F(){}
F.prototype=o;
return new F();
}
SubClass.prototype=inheritObject(SuperClass.prototype);
SubClass.prototype.constructor=subClass
因为F类构造函数没有内容,所以更纯净,开销更小。所以人们会借用这种方式来实现对prototype的继承
二丶原型链
原型链我并没有过多的了解,也就只是一个概念吧,对象进行搜索属性的概念。
对象在查找属性的时候会依次往下搜寻,从构造函数的属性开始往下找,所以为什么当我们在构造函数和prototype的key相同时,输出的是构造函数的值。如果没有,就往__proto__找,如果有继承了父类,继续往实例的__proto__.__proto__(父类的实例)寻找我们需要的属性,直到Object.prototype.__proto__(null)。
tips
- 我们在通过点(.)寻找对象属性时能也能看到一些实例化对象后Object共有的方法toString,valueOf啊,这些就是原型链的概念了也就是Object.prototype的方法。他们能访问到,但是不能被枚举出来,所以和普通的对象使用点(.)添加属性不一样,应该是比较底层的方法定义出来的。es5有个方法Object.defineProperty可以对对象的属性定义更多的操作,列如可否枚举,是否能够被修改,还有强大的getter,setter方法能够被现流行的框架用于实现数据绑定。
对于上面的原型式继承,为了规范,出现了一个新的方法Object.create,这和原型式继承的实现是一样的,可以看看MDN官方文档对于低版本浏览器给出的polyfill
if (typeof Object.create !== "function") { Object.create = function (proto, propertiesObject) { if (!(proto === null || typeof proto === "object" || typeof proto === "function")) { throw TypeError('Argument must be an object, or null'); } var temp = new Object(); temp.__proto__ = proto; if(typeof propertiesObject ==="object") Object.defineProperties(temp,propertiesObject); return temp; }; }
对象拷贝,Object.assgin能实现多继承的概念,并且也是深拷贝,但是只能也只能达到一级的深拷贝。JSON.parse(JSON.stringify(obj))能实现到深拷贝。但是两者都不会把__proto__复制到新对象里