JavaScript的对象和原型、原型链

190 阅读3分钟

对象

在JavaScript中,万物皆对象Object。

Object是一个属性的集合,并且都拥有一个单独的原型对象[prototype object]. 这个原型对象[prototype object]可以是一个object或者null值。

一个Object的prototype是一个内部的[[prototype]]属性的引用

proto 与 prototype

  • [[prototype]]__proto__,对象__proto__属性的值就是它所对应的原型对象: 每个对象都有__proto__属性来标识自己所继承的原型,如
typeof function(){}.__proto__ === 'function' // true
typeof {}.__proto__ === 'object' // true
  • 只有函数才有prototype属性,当我们创建函数时,JS会自动创建该函数的prototype属性,它的值是一个有constructor属性的对象,指向该方法的原型对象。一旦你把这个函数当作构造函数调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。

原型链

JS正是通过__proto__prototype的合作实现了原型链,以及对象的继承。

官方注解:A prototype chain is a finite chain of objects which is used to implemented inheritance and shared properties.

构造函数,通过prototype来存储要共享的属性和方法,也可以设置prototype指向现存的对象来继承该对象。

对象的__proto__指向自己构造函数的prototypeobj.__proto__.__proto__...的原型链由此产生。

js的操作符instanceof正是通过探测obj.__proto__.__proto__... === Constructor.prototype来验证obj是否是Constructor的实例。

在支持Class类的语言中,我们可以通过Class A extends B {},实现A继承B。在Javascript语言,虽没有类的概念,但依然可以借由原型链实现继承,这种实现方式就是原型继承。

举例,a1对象通过设置自身__proto__,将自身的原型对象指向修改为a对象,实现了原型继承。

var a = {
  x: 10,
  calc: function(z) {
    return this.x + this.y + z
  }
}

var a1 = {
  y: 10,
  __proto__: a
}

console.log(`calc ${a1.calc(10)}`) // calc 30

在上述例子中,我们分析下,当我们调用a1.calc(10)时,先去找a1的calc方法,找不到继续在a1原型a上找到并调用该方法。在calc()方法内,有三个参数,分别是两个this上的属性x,y和一个传参z。因为是对象属性的调用方式,所以this指向的是a1,其中a1没有x属性,所以x属性用的是原型上的x的值,y属性用的是a1的属性,z用的是传进去的10;

两个注意点:

  • js查找都会从自身开始寻找,找不到的时候,沿着原型链在a1的原型上寻找,找到的话,调用该方法,找不到则继续往上查找,直至原型是null时,返回calc() undefined。
  • this的指向,在调用方法的时候决定,不会受到原型链查找的影响。

除了上述对象设置a1.__proto__ = a,或者函数设置function A(){}; A.prototype = AA这种方式,常见的创建对象方式是构造函数(Constructor);

构造函数

同样的上述例子,a1 原型继承 对象a,用构造函数的方式,改写如下:

function A(y) {
  this.x = 10;
  this.y = y;
  this.calc = function(z) {
    console.log(`x ${this.x}, y ${y}, z ${z}`);
    return this.x + this.y + z
  };
} 

A.prototype.name = 'A的原型属性name';
var a2 = new A(20)
console.log(`name: ${a2.name}, 运算值: ${a2.calc(30)}`);
// x 10, y 20, z 30
// name: A的原型属性name, 运算值: 60
console.log(
  a2.constructor === A,          // true
  a2.__proto__ === A.prototype,  // true
  a2.name === a2.__proto__.name, // true
  a2.name === A.prototype.name,  // true
  A.__proto__ === A.prototype,   // false
  `A的原型: ${A.__proto__ === Function.prototype}`, // A的原型: true
  `A的原型链: ${A.prototype.constructor === A}`,    // A的原型链: true
);

当我们创建函数A时,JS会自动创建函数A的prototype属性,它的值是一个有constructor属性的对象。 通过关键词new,我们创建了对象a2,实例a2继承了构造函数prototype的所有属性和方法(a2的构造器是A,a2的原型是A的原型,a2继承了A的属性方法)。

回顾总结

function Foo(name) {
  this.name = name;
}
var foo1 = new Foo('foo1');
var foo2 = new Foo('foo2');

var obj1 = new Object();
var obj2 = new Object();

console.log(foo1.__proto__ === Foo.prototype); // true
console.log(Foo.prototype.constructor === Foo); // true

console.log(obj1.__proto__ === Object.prototype); // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.constructor === Object); // true
console.log(Object.__proto__ === Function.prototype); // true

console.log(Function.prototype.constructor === Function); // true
console.log(Function.__proto__ === Function.prototype); // true

  • 对象有属性__proto__,指向该对象的构造函数的原型对象。
  • 方法除了有属性__proto__,还有属性prototypeprototype指向该方法的原型对象。