felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 原型及原型对象 #54

Open felix-cao opened 6 years ago

felix-cao commented 6 years ago

《JavaScript 构造器》中我们聊过,通过 new 操作符调用构造器来创建一个对象,但是 new 操作符有个很严重的缺点:构造器内部的属性和方法无法被共享,本文就是来聊一聊原型对象上的属性和方法被实例对象所共享的问题。

一、原型和原型对象

为了解决构造器内部的属性和方法无法被实例对象所共享的问题,我们把需要共享的属性和方法放在构造器的原型(prototype)属性上。JavaScript 每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个对象。

我们认真分析上面的话,可以看出:

我们经常说“对象的原型”,就 JavaScript 的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。 👍

prototype 属性是所有通过 new 操作符调用构造器创建实例对象的原型对象。原型对象上的属性和方法可以被所有实例对象所共享。

对于构造器来说,prototype 作为构造器的属性,指向的对象我们通常叫做原型;对于实例对象来说, prototype 指向的对象是实例对象的原型对象。

function Person(name){
  this.name = name;
}
Person.prototype.group = 'China';
Person.prototype.getGroup = function() {
  return this.group + ' - method';
}
// 生成两个实例对象
var p1 = new Person('p1');
var p2 = new Person('p2');

console.log('property in p1: ', p1.group, p1.getGroup());
console.log('property in p2: ', p2.group, p2.getGroup());
// 修改原型对象上的属性和方法
Person.prototype.group = 'US';
Person.prototype.getGroup = function() {
  return this.group + ' - method changed';
}
console.log('property in p1: ', p1.group, p1.getGroup());
console.log('property in p2: ', p2.group, p2.getGroup());

现在 group 属性和 getGroup 方法放在 prototype 对象里,是 p1p2 实例对象共享的。只要修改了 prototype 对象中的属性和方法,就会同时影响 p1p2 两个实例对象。

由于所有的实例对象共享同一个 prototype 指向的对象,那么从外界看起来,prototype 对象就好像是实例对象的原型,而实例对象则好像"继承"了 prototype 指向的对象一样。这就是 Javascript 继承机制的设计思想。

需要注意的是,Object.prototype 是所有原型链的终点, 所有对象都继承于 Object 。这点以后另开一篇来聊。

二、对象的 __proto__ 属性

每个对象都会记住自己的原型 👍

《JavaScript 构造器》 中第四部分我们谈了 new 操作符到底干了啥? MDN 中有这样的代码: o.[[Prototype]] = Foo.prototype, 我们的理解是这样: obj.__proto__ = Person.prototype;

这里我们聊到了 [[Prototype]](注意首字母是大写) 和 __proto__ 两个属性,[[Prototype]] 是实例对象拥有的属性,它是一个指针,指向构造器的原型对象,他是“内部的”,这个特性是仅供 Javascript 引擎用的,因此在 Javascript 中不能直接访问它,同时为了表示特性是内部值,用两个方括号加以区分,像这样 [[Prototype]]。而 prototype 是构造函数中的属性,是可见的。[[Prototype]] 是对象的内部属性,仅供 Javascript 引擎用的,那么外部想访问它怎么办?

函数有一个默认的 prototype 对象,这个 prototype 对象用来指定函数的继承关系,prototype 对象默认有两个属性,一个是 constructor,另一个就是 __proto__ 属性,默认的 prototype 对象在被改变之前就像是这个函数用来表示自己的一个对象,其中 __proto__ 属性和普通对象的 __proto__ 属性一样用来对应继承关系,而 constructor 属性则代表了函数本身,

三、prototype 和 __proto__ 的区别

我们通过网络上流行的一张图来认识一下 prototype__proto__ 的区别

我们来验证一下

var a = {};
console.log(a.prototype);  // undefined
console.log(a.__proto__);  // Object {}

var b = function(){}
console.log(b.prototype);  // b {}
console.log(b.__proto__);  // ƒ () { [native code] }

四、对象的 __proto__ 属性指向谁

/*1、字面量方式*/
var a = {};
console.log(a.__proto__);  // Object {}

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

/*2、构造器方式*/
var A = function(){};
var a = new A();
console.log(a.__proto__); // A {}

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

/*3、Object.create()方式*/
var a1 = {a:1}
var a2 = Object.create(a1);
console.log(a2.__proto__); // Object {a: 1}

console.log(a.__proto__ === a.constructor.prototype); // false(此处即为图1中的例外情况)

Reference