原型、对象原型、原型链

620 阅读5分钟

这部分内容是根据pink老师上课的内容总结,如果想要更生动地学习,可以看一下pink老师这部分的视频

进入正文

首先,先把这张图刻在心里

原型链.png

一、原型

原型提出的背景

最主要的原因是 构造函数的方法,存在浪费内存的问题:

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,创建出两个实例对象,在内存中的表现如下图所示

原型1.png

如图,当使用构造函数创建实例对象,每个实例对象都会在内存中开发一块内存来存放对象的方法,这样做就会浪费大量的空间来存放方法;这时,我们希望所有的对象都使用同一个函数,来节省内存空间;

构造函数原型 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个概念,现在再来看这张图:

原型链.png

原型链

只要是对象就会有__proto__属性,来指向原型对象prototype,那么原型对象prototype同样作为一个对象,因此它的__proto__原型指向的是 Object.prototype

同样,Object.prototype也作为一个对象,它里面的__proto__原型指向了 null

因此,形成了原型链。

js成员查找机制

  1. 当访问一个对象的属性或方法时,首先看这个对象自身有没有这个属性或方法
  2. 如果没有,就查找它的原型(也就是__proto__原型指向的prototype原型对象)
  3. 如果还没有,就查找原型对象的原型(Object原型对象)
  4. 以此类推一直找到Object为止

因此,所以这就是我们新建的对象为什么能够使用 toString() 等方法的原因

总结

因为无论是对象还是原型对象,它们都属于对象,因此都可以指向它们的原型对象。当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype 。

五、原型对象的应用

  1. 可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能

但是我们也需要注意的是,在Js的基本规范中,提到我们不要随意在内置对象的原型上添加方法,如 Array, Date。

  1. 在ES6之前,没有给我们提供extends继承,我们可以通过构造函数+原型对象的方式来模拟实现继承父类的方法
Son.prototype = new Father();Son.prototype.constructor = Son;  //如果利用对象的形式修改了原型对象,别忘了利用constructor指回

继承.png