JavaScript Prototype
原型与原型链
我觉得,直接来一张图就好了
构造函数
在上图中,Person是一个构造函数,一般情况下,它长这个样子。
function Person() {
}
什么是构造函数呢?构造函数,是一种特殊的方法,主要用来在创建函数的时候初始化对象。
原型
原型,prototye,是一个对象,这个对象能够给实例共享属性与方法。如图中的Person.prototype,就是Person的原型对象。
实例
通过new关键字生产的新的对象实例,它有一个指针_proto_,通常我们称为隐式原型,从实例指向Person.prototype。
因此,就有了person._prototype_ === Person.prototype
最初的js是不支持__proto__的,但因为绝大部分浏览器都支持这个属性,所以它才被加入到了ES6里。不得不低头。
构造器
Person.prototype,即原型对象里面,有一个constructor属性,这个constructor属性指向了原型所关联的构造函数,即Person,我们可以通过以下例子来验证这一点。
Person.prototype.constructor === Person // true
原型的原型的原型的原型....原型...Object(原型链)
原型是一个对象,既然是对象,我们肯定可以通过new Object来创建。所有原型对象都是通过Object创建来的,然而:
console.log(Object.prototype.__proto__ === null) // true
说明Object是原型的原型的原型...的尽头。
- 我们在查找一个实例的属性的时候,它会在当前实例的属性中找;
- 倘若找不到,便会到它的原型对象里面去找;
- 倘若还是找不到,然后会到它的原型对象的原型对象去找,知道找到顶层的Object为止。
这样相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
注意:当获取person.constructor的时候,person的constructor不存在于person中,于是便会去person.prototype中查找constructor,这时候 person.constructor === Person.prototype.constructor
如何用原型实现继承
-
类式继承
function SuperClass() { this.name = 'father'; } SuperClass.prototype.getSuperName = function() { console.log(this.name); } function SubClass() { this.name = 'son'; } SubClass.prototype = new SuperClass(); SubClass.prototype.getSubName = function() { console.log(this.name); } var instance = new SubClass(); console.log(instance instanceof SubClass); console.log(instance instanceof SuperClass); console.log(SubClass instanceof SuperClass);
类式继承虽然实现起来很简单,但是这种继承方式有两个缺点:
-
由于子类通过其原型对父类实例化,继承了父类,所以说父类中如果共有属性是引用类型,那么就会在子类中被所有的实例所共享。一个自类对其修改,就会影响到其他的子类。
- 由于子类实现的继承是靠其原型prototype对父类进行实例化实现的,因此在创造父类的时候,是无法向父类传递参数的。因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化
- 这种继承没有涉及到prototype,因此,父类prototype原型链的方法也不会被继承。
-
-
构造函数继承
function SuperClass(name) { this.skills = ['javascript','css', 'html']; this.name = name; } SuperClass.prototype.getName = function() { console.log(this.skills); } function SubClass(name) { //继承父类 SuperClass.call(this, name); } //创建第一个子类实例 var instance1 = new SubClass('jack'); //创建第二个子类实例 var instance2 = new SubClass('bob'); instance1.skills.push('java'); console.log(instance1) console.log(instance2) instance1.getName();//TypeError
SuperClass.call(this,id)
当然就是构造函数继承的核心语句了.由于父类中给this绑定属性,因此子类自然也就继承父类的共有属性。由于这种类型的继承没有涉及到原型prototype
,所以父类的原型方法自然不会被子类继承,而如果想被子类继承,就必须放到构造函数中,这样创建出来的每一个实例都会单独的拥有一份而不能共用,这样就违背了代码复用的原则,所以综合上述两种,我们提出了组合式继承方法 -
组合式继承
说白了就是再指一下原型
function SuperClass(name) { this.skills = ['javascript','css', 'html']; this.name = name; } SuperClass.prototype.getName = function() { console.log(this.skills); } function SubClass(name) { //继承父类 SuperClass.call(this, name); } // new 出一个SuperClass的实例,然后指定SubClass的原型 SubClass.prototype = new SuperClass(); //创建第一个子类实例 var instance1 = new SubClass('jack'); //创建第二个子类实例 var instance2 = new SubClass('bob'); instance1.skills.push('java'); console.log(instance1) console.log(instance2) instance1.getName();
这样功能正常了,就是有点丑,两个subClass,代码量偏多
-
原型式继承
function inheritObject(o) { function F(){}; F.prototype = o; return new F(); } var person = { name: 'name', age: '18' } var bob = inheritObject(person); bob.name = 'bob'; bob.age = 20; var jack = inheritObject(person); console.log(bob); console.log(jack);
如上代码我们可以看出,原型式继承和类式继承一个样子,对于引用类型的变量,还是存在子类实例共享的情况。
-
寄生式继承
var person = { name: 'name', age: 18 } function inheritObject(o) { function F(){}; F.prototype = o; return new F(); } function createPerson(obj) { // 通过原型方式创建新的对象 var o = new inheritObject(obj); // 拓展新对象 o.getName = function(name) { console.log(name); } // 返回拓展后的新对象 return o; }
多套了一层,可以在createPerson中新增方法属性等。
-
寄生组合式继承
function inheritObject(o) { //声明一个过渡对象 function F() { } //过渡对象的原型继承父对象 F.prototype = o; //返回过渡对象的实例,该对象的原型继承了父对象 return new F(); } function inheritPrototype(subClass,superClass) { // 复制一份父类的原型副本到变量中 var p = inheritObject(superClass.prototype); // 修正因为重写子类的原型导致子类的constructor属性被修改 p.constructor = subClass; // 设置子类原型 subClass.prototype = p; } ////////////////////////////////////////////////////////////////////////////////////// function SuperClass(name) { this.name = name; this.books=['js book','css book']; } SuperClass.prototype.getName = function() { console.log(this.name); } function SubClass(name,time) { SuperClass.call(this,name); this.time = time; } inheritPrototype(SubClass,SuperClass); SubClass.prototype.getTime = function() { console.log(this.time); } var instance1 = new SubClass('React','2017/11/11') var instance2 = new SubClass('Js','2018/22/33'); instance1.books.push('test book'); console.log(instance1.books,instance2.books); instance2.getName(); instance2.getTime();