JS 原型链

111 阅读4分钟

前言

学习JS一周了, 发现原型链是个常问的问题,今天想总结下,谈谈自己的理解,主要是方便自己日后复习。小菜鸡第一次写文章,不太懂排版,内容也可能理解得不太深刻。

原型

函数也是对象,可以有属性和方法。浏览器为我们定义的每一个函数都创建了一个prototype属性,该属性保存着原型对象的引用,而且原型对象会自动获得一个constructor的属性,指回那个函数。所以在我们自定义构造函数时,会在堆内存中生成一个原型,构造函数的prototype指向该原型。当我们每次调用构造函数生成新实例时,实例也会自动生成一个__proto__属性,指向构造函数的原型。其实实例与构造函数或者说类之间没有直接联系,但实例与构造函数的原型之间有直接联系,如图。

 function Person(){            this.name="盖伦"; }         let person1 = new Person(); 

这里顺便说下创建对象的过程:

1 new 之后,在堆内存中创建一个新对象

2 新对象的__proto__属性指向构造函数的原型

3 构造函数中this指向新对象

4 执行构造函数体内函数

5 给变量返回这个新对象 (这里传的时引用,JS中引用数据类型存储的都是引用,图中__proto__,prototype,constructor都是引用)

原型的作用

创建一个类的多个实例可以发现,这些实例指向同一个原型,那么实例就可以共享原型中的属性和方法。不用分别为每个实例分别再去添加属性和方法,最直观的好处就是节省了内存。

function Hero(name){            this.name=name;    }                Hero.prototype.sayName=function(){            console.log("i am "+this.name)        }        let hero1 = new Hero("盖伦"),            hero2 = new Hero("剑圣");        hero1.sayName();        hero2.sayName();        console.log(hero1.sayName===hero2.sayName);
       // 可以看到两个实例的sayName方法是同一个引用

需要注意的是通过对象访问属性和方法时,会先在对象本身中寻找,若没有再去他的原型中寻找,直到找到,或者都没有返回undefined。

原型链

终于到原型链了!原型链是JS主要继承方式。我们知道实例的__proto__指向构造函数的原型,原型也是对象,如果这个对象作为另一个类或者说构造函数的实例,那么原型的__proto__也会指向那个构造函数的原型。现在它此时既是一个原型也是另一个构造函数的实例,就会产生一条由实例与原型连接而成的原型链。

 function Hero(){            this.power=100;        }        Hero.prototype.sayPower=function(){            console.log("继承了力量点数"+this.power);        }        function Gailun(){            this.name="盖伦";        }        Gailun.prototype=new Hero(); //子类的原型成为父类的实例        let gailun = new Gailun();        console.log(gailun.name);        gailun.sayPower();

完成了子类Gailun对父类Hero的继承,产生了一条原型链,要注意的是子类的原型是没有constructor的,因为 Gailun.prototype=new Hero()中原型成为一个父类的新实例,实例本来就没有constructor属性的,并且将新实例的引用返回给子类的prototype, 相当于重写了子类的原型。换句话说,在这步发生之前,子类构造函数是有一个默认原型的,但重写后,没有指针再指向原来的原型了,由于JS的垃圾回收机制,那片内存会被回收。当然如果在子类原型成为父类的新实例之前,先创建一个子类的实例,那么这个实例的__proto__会指向默认的子类原型,默认子类原型中的constructor会指向子类。如图,很明显xiaogailun是不能继承父类的,因为没有一条原型链支持它指向父类的原型。图中子类看似有两个原型都叫Gailun Prototype,其实只有一个就是子类的prototype属性保存的那个引用,名字只是为了好区别,对于构造函数来说,它只认prototype保存的那个引用,按照引用来找到原型。

 function Hero(){            this.power=100;        }        Hero.prototype.sayPower=function(){            console.log("继承了力量点数"+this.power);        }        function Gailun(){            this.name="盖伦";        }        let xiaogailun=new Gailun(); //先创建一个子类的实例        Gailun.prototype=new Hero(); //子类的原型成为父类的实例        let gailun = new Gailun();        console.log(gailun.name);        gailun.sayPower();

最后,一般境况下,构造函数的原型都是object类的原型。所以很多对象可以继承很多object原型中的方法,如hasOwnProperty(),isProperty()... 。

原型链的缺点

当父类原型中存贮引用数据类型比如数组时,继承了它的属性的实例在操作数组时,比如添加,那么所有实例的属性都会变化,因为属性实际上存贮的是数组的引用。感觉类似浅拷贝。

结语

第一次写,感觉思路还不是很清晰,也不美观,不能完全把自己所想的表达出来。以后要多看看大佬们如何规划的。