对象原型


每一个对象都有一个内置的 proto 属性指向构造它的函数prototype属性. 而构造函数的prototype.constructor 则指向构造函数本生。一个对象的属性的寻找过程由以下几个部分组成:

  1. 寻找对象属性的数据描述符(writable, value)或存取描述符(getter, setter),如果查询到了,则返回 对应的值。如果查询不到,则进入第2步骤
  2. 寻找对象属性的值是否有被显示定义 (可以通过 Object.getOwnPropertyNames)检测,如果对象属性定义了,则返回定义的值。 如果没有,则进入第3步骤
  3. 寻找对象的隐藏原型__proto__对象的属性,规则同1,2步骤。如果还未找到,则重复第3步骤, 直到__proto__ 为null 为止。

  我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype。

1
2
3
4
5
6
7
8
9
10
function Person() { //创建构造函数

}

function MyClass() { //创建构造函数

}
console.log(Person.prototype); //返回一个对象[Object Obkect]
console.log(MyClass.prototype); //返回一个对象[Object Obkect]
console.log(MyClass.prototype == Person.prototype); //false

  每一个构造函数的prototype是惟一的,他们是不相等的。这个属性对应着一个对象,这个对象就是我们所谓的原形对象。
下面我们画图来分析一下:
在这里插入图片描述  这里prototype属性就是保存原型对象的地址,在MyClass函数里有一个属性指向原型对象。


  如果函数作为普通函数调用prototype没有任何作用
  当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的 原型对象,我们可以通过__proto__来访问该属性。

1
2
3
4
5
6
7
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
var mc1 = new MyClass(); //通过构造函数MyClass创建一个对象mc1
console.log(mc.__proto__); // 返回一个对象[Object Obkect]
console.log(mc.__proto__ == MyClass.prototype); //true

  通过__proto__来访问该属性,而且该属性和 MyClass.prototype指向的对象的同一个。不仅仅是mc,我再创建一个mc2,它也有__proto__,它指向的也是prototype。
  只要通过MyClass创建的对象,他们都有一个隐含属性指向原形对象。
画图来理解一下:
在这里插入图片描述

  原型对象就相当于一个公共区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中的公有的内容,统一设置到原型对象中。
看下列代码:

1
2
3
4
5
6
7
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
var mc1 = new MyClass(); //通过构造函数MyClass创建一个对象mc1
MyClass.prototype.a = 123; //向MyClass的原型对象中添加属性a
console.log(mc.a); //123

在这里插入图片描述
  我们向MyClass的原型对象中添加属性a,我们并没有向对象mc中添加a,此时mc中没有a,他就会向原型对象中寻找。
  当我们访问对象的一个属性时,会现在对象自身中寻找,如果有则直接使用,如果没有,则会去原型对象中寻找,如果找到则会直接使用。

再看,当我们想mc中添加a,如下:

1
2
3
4
5
6
7
8
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
var mc1 = new MyClass(); //通过构造函数MyClass创建一个对象mc1
MyClass.prototype.a = 123; //向MyClass的原型对象中添加属性a
mc.a = "我是mc中的a"; //向mc中添加属性a
console.log(mc.a); //我是mc中的a

在这里插入图片描述  因为mc中有a,则会优先使用自身中的a。所以会输出“我是mc中的a”
再看:

1
2
3
4
5
6
7
8
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
var mc1 = new MyClass(); //通过构造函数MyClass创建一个对象mc1
MyClass.prototype.a = 123; //向MyClass的原型对象中添加属性a
mc.a = "我是mc中的a"; //向mc中添加属性a
console.log(mc1.a); //123

在这里插入图片描述
  当我们查看mc2中的a时,返回的是123,这是因为此时mc2中没有a,自身没有,它会向它的原型对象中去寻找a。


  到现在你应该能感觉到了,原型对象就是一个公共区域,所有对象(这里的所有对象指的是同一个构造函数产生的对象)都能访问到原型对象里面的属性。
  既然属性能访问到,那我们来试试方法。
1
2
3
4
5
6
7
8
9
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
var mc1 = new MyClass(); //通过构造函数MyClass创建一个对象mc1
MyClass.prototype.sayhello = function() {
console.log("hello");
} //向MyClass原型中添加一个方法。
mc.sayhello(); //hello

  显而易见,方法也是能访问到的,进一步说明了原型对象就是一个公共区域。


  以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的 原型对象中,这不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法


补充说明:


  前面我们说到:我们向MyClass的原型对象中添加属性a,我们并没有向对象mc中添加a,那这样的话我们可以使用 in运算符 来检查一下。

什么是in运算符:

  • 通过该运算符可以检查一个对象中是否含有指定的属性
        如果有则返回true ,没有则返回false
    
  • 语法:
     “属性名” in 对象
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function MyClass() { //创建构造函数

    }
    var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
    var mc1 = new MyClass(); //通过构造函数MyClass创建一个对象mc1
    MyClass.prototype.a = 123; //向MyClass的原型对象中添加属性a
    console.log(mc.a); //123

    console.log("a" in mc); // true
      ???这也不对鸭,mc中不是没有a吗?怎么会返回true呢?。
    这是因为使用in检查对像中是否有某个属性时,如果对象中没有但是原型中有,也会返回true。
      可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性。使用该方法时,只有对象自身中含有属性时才会返回true。
1
2
3
4
5
6
7
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
MyClass.prototype.a = 123; //向MyClass的原型对象中添加属性a

console.log(mc.hasOwnProperty("a")); //flase
1
2
3
4
5
6
7
8
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
MyClass.prototype.a = 123; //向MyClass的原型对象中添加属性a

mc.b = 100; //向mc中添加属性b
console.log(mc.hasOwnProperty("b")); //true

  那又有疑问了,mc中有hasOwnProperty()吗?好像我们没有添加,那mc的原型对象里有hasOwnProperty()吗?好像我们也没有添加。测试一下:
1
2
3
4
5
6
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
console.log(mc.hasOwnProperty("hasOwnProperty")); //false
console.log(mc.__proto__.hasOwnProperty("hasOwnProperty")); //false

  那连mc原型对象里也没有,那hasOwnProperty()到底是谁的?
  我们想一下,原型对象也是对象,那原形对象也没有原型对象呢?我们试一下:

1
2
3
4
5
function MyClass() { //创建构造函数

}
var mc = new MyClass(); //通过构造函数MyClass创建一个对象mc
console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty")); //true

  看来这个hasOwnProperty()是属于原型对象的原型对象。
  说明一下:
在这里插入图片描述
原型对象也是对象,所以它也有原型,

  • 当我们使用一个对象的属性或者方法时,会先在自身中寻找,如果有直接使用。
  • 如果自身中没有则去原型对象中寻找,如果原型对象中有,则使用。
  • 如果没有则去原型的原型中寻找,直到找到Object对象的原型。
  • Object对象的原型没有原型,如果在Object中依然没有找到,则会返回Undefined。

一个对象的属性的寻找过程由以下几个部分组成:

  1. 寻找对象属性的数据描述符(writable, value)或存取描述符(getter, setter),如果查询到了,则返回 对应的值。如果查询不到,则进入第2步骤
  2. 寻找对象属性的值是否有被显示定义 (可以通过 Object.getOwnPropertyNames)检测,如果对象属性定义了,则返回定义的值。 如果没有,则进入第3步骤
  3. 寻找对象的隐藏原型__proto__对象的属性,规则同1,2步骤。如果还未找到,则重复第3步骤, 直到__proto__ 为null 为止。



愿你的坚持终有收获。