这部分内容是根据pink老师上课的内容总结,如果想要更生动地学习,可以看一下pink老师这部分的视频
进入正文
首先,先把这张图刻在心里
一、原型
原型提出的背景
最主要的原因是 构造函数的方法,存在浪费内存的问题:
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){
console.log("lalalalalal")
}
}
var obj1 = new Star('xxx1',14);
var obj2 = new Star('xxx2',13);
通过构造函数Star,创建出两个实例对象,在内存中的表现如下图所示
如图,当使用构造函数创建实例对象,每个实例对象都会在内存中开发一块内存来存放对象的方法,这样做就会浪费大量的空间来存放方法;这时,我们希望所有的对象都使用同一个函数,来节省内存空间;
构造函数原型 prototype
JavaScript规定,每一个构造函数都会有一个prototype属性,指向另一个对象,这个对象的所有属性和方法都会被构造函数所拥有。
构造函数通过原型分配的函数是所有对象所共享的。
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有的对象实例就可以共享这些方法。
以上代码可以写为:
function Star(uname,age){
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function(){
console.log("lalalalalal");
}
总结
原型就是一个对象,我们也称prototype为原型对象,它是每一个构造函数都会有的一个属性,我们通过给原型对象定义方法,来实现实例对象之间的共享方法,节约了内存资源。
注:原型对象里面的this指向的是调用它的实例对象
所以,一般情况下,公共的属性定义在构造函数里面,公共的方法放在原型对象身上
二、对象原型
__proto__对象原型
对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有一个__proto__的存在。
注:__proto__对象原型和原型对象prototype是等价的,如下所示:
console.log(obj1.__proto__ === Star.prototype) //true
因此,对象方法的查找规则:首先看对象本身有没有要调用的方法,例如sing()方法,如果有,就执行对象上的这个方法;如果没有,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找sing()这个方法。
虽然,__proto__对象原型为我们对象的查找机制提供了一个方向,或者说一条路线,但是它是一个非标准属性,因此在实际开发的过程中,不可以使用这个属性。
总结
__proto__对象原型是每一个对象都有的一个属性,它是和原型对象是等价的,只不过一个是对象上的属性,一个是构造方法的属性,它为我们提供了对象方法查找的一个方向,但是是一个非标准属性,在实际开发中,我们并不会用到。
三、constructor 构造函数
constructor
对象原型__proto__和构造函数原型对象prototype里面都有一个constructor属性,constructor被我们称之为构造函数,因为它指回构造函数本身。
constructor主要记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
console.log(Star.prototype.constructor);
console.log(obj1.__proto__.constructoe);
//都返回Star这个构造函数
很多情况下,我们需要手动利用constructor 这个属性 指回原来的构造函数;
//当需要定义很多个方法时,我们使用对象的形式就改变了指向的构造函数
Star.prototype = {
sing: function(){
console.log('lalalallalalaa');
},
movie: function(){
console.log('dongcidaci');
}
}
console.log(Star.prototype.constructor);
console.log(obj1.__proto__.constructoe);
//这个时候,返回的不是Star这个构造函数
因此,需要将以上代码改为:
Star.prototype = {
constructor:Star, //手动利用constructor 这个属性 指回原来的构造函数
sing: function(){
console.log('lalalallalalaa');
},
movie: function(){
console.log('dongcidaci');
}
}
console.log(Star.prototype.constructor);
console.log(obj1.__proto__.constructoe);
//返回Star这个构造函数
总结
constructor是原型对象和对象原型的一个属性,它是用来指向引用的构造函数,但一般它的主要作用是,在我们给原型对象赋值了一个对象的情况下,我们需要手动利用constructor来指回原来的构造函数。
四、原型链
理解了以上3个概念,现在再来看这张图:
原型链
只要是对象就会有__proto__属性,来指向原型对象prototype,那么原型对象prototype同样作为一个对象,因此它的__proto__原型指向的是 Object.prototype
同样,Object.prototype也作为一个对象,它里面的__proto__原型指向了 null
因此,形成了原型链。
js成员查找机制
- 当访问一个对象的属性或方法时,首先看这个对象自身有没有这个属性或方法
- 如果没有,就查找它的原型(也就是
__proto__原型指向的prototype原型对象) - 如果还没有,就查找原型对象的原型(Object原型对象)
- 以此类推一直找到Object为止
因此,所以这就是我们新建的对象为什么能够使用 toString() 等方法的原因
总结
因为无论是对象还是原型对象,它们都属于对象,因此都可以指向它们的原型对象。当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype 。
五、原型对象的应用
- 可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能
但是我们也需要注意的是,在Js的基本规范中,提到我们不要随意在内置对象的原型上添加方法,如 Array, Date。
- 在ES6之前,没有给我们提供extends继承,我们可以通过构造函数+原型对象的方式来模拟实现继承父类的方法
Son.prototype = new Father();
Son.prototype.constructor = Son; //如果利用对象的形式修改了原型对象,别忘了利用constructor指回