Javascript中的prototype以及__proto__

基本概念

先用构造函数创建一个对象

function Person() {}
var person = new Person();
person.name = 'Greg';
console.log(person.name) // Greg

在这个例子中,Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person

prototype

首先来说说prototype属性,不像每个对象都有__proto__[[Prototype]])属性来标识自己所继承的原型,只有函数才有prototype属性。

当你创建函数时,Javascript会为这个函数自动添加prototype属性,值是空对象。而一旦你把这个函数当作构造函数调用(即通过new关键字调用),那么Javascript就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向其构造函数的prototype来实现这种继承)。

function Person() {}
Person.prototype.name = 'Greg';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name); // Greg
console.log(person2.name); // Greg

函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1person2 的原型。

你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

让我们用一张图表示构造函数和实例原型之间的关系:

那么我们该怎么表示实例与实例原型,也就是 personPerson.prototype 之间的关系呢,这时候我们就要讲到__proto__

__proto__

这是每一个JavaScript对象(除了null)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。

绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)

function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

于是我们更新下关系图:


既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?

constructor

指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。

为了验证这一点,我们可以尝试:

function Person() {}
console.log(Person === Person.prototype.constructor); // true

当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性:

var person = new Person();
console.log(person.constructor === Person); // true
console.log(person.hasOwnProperty('constructor')); // false

所以再更新下关系图:


综上我们已经得出:

function Person() {}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
console.log(Object.getPrototypeOf(person) === Person.prototype) // true

深入探究prototype与__proto__

对象有[[prototype]]属性,函数对象有prototype属性,原型对象有constructor属性。
其中,[[Prototype]]作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome中提供了__proto__这个非标准(不是所有浏览器都支持)的访问器(ECMA引入了标准对象原型访问器Object.getPrototype(object))。

先创建一个构造函数

function Person() {}

函数的原型

console.log(Person.__proto__ === Function.prototype);   // true
console.log(Person.constructor === Function);  // true
console.log(typeof Person);  // function
console.log(Person);  // f Person() {}
console.log(Person.prototype.__proto__ === Object.prototype);  // true;
console.log(Person.__proto__);  // f () { [native code] }
console.log(Person.__proto__.__proto__ === Person.prototype.__proto__);  // true
console.log(Person.__proto__.__proto__ === Object.prototype);  // true
console.log(Person.prototype.constructor);  // f Person() {}
console.log('============================================');
console.log(Function.__proto__ === Function.prototype);  // true
console.log(Function.constructor === Function);  // true
console.log(typeof Function);  // function
console.log(Function);  // f Function() { [native code] }
console.log(Function.prototype);  // f () { [native code] }
console.log(Function.prototype.__proto__ === Object.prototype);  // true
console.log(Function.__proto__);   // f () { [native code] }
console.log(Function.__proto__.__proto__ === Function.prototype.__proto__);   // true
console.log(Function.__proto__.__proto__ === Object.prototype);   // true
console.log(Function.prototype.constructor);   // f Function() { [native code] }

结果分析:

  • JavaScript中有个Function对象(类似Object),这个对象本身是个函数;所有的函数(包括FunctionObject)的原型(__proto__)都是Function.prototype
  • Function对象作为一个函数,就会有prototype属性,该属性将对应function () {}对象。
  • Function对象作为一个对象,就有__proto__属性,该属性对应Function.prototype,也就是说,Function.__proto__ === Function.prototype
  • 对于Function的原型对象Function.prototype,该原型对象的__proto__属性将对应Object {}
  • 对于原型对象Person.prototypeconstructor,将对应Person函数本身。

Object的原型

console.log(typeof Object);  // function
console.log(Object);   // ƒ Object() { [native code] }
console.log(Object.prototype);  // Object {}
console.log(Object.prototype.__proto__);  // null
console.log(Object.prototype.constructor);  // ƒ Object() { [native code] }
console.log(Object.__proto__);  // ƒ () { [native code] }
console.log(Object.__proto__ === Function.prototype);  // true
console.log(Object.__proto__.prototype);  // undefined

结果分析:

  • Object对象本身是一个函数对象。
  • 既然是Object函数,就肯定会有prototype属性,所以可以看到Object.prototype的值就是Object {}这个原型对象。
  • 反过来,当访问Object.prototype对象的constructor这个属性的时候,就得到了Obejct函数。
  • 另外,当通过Object.prototype.__proto__获取Object原型的原型的时候,将会得到null,也就是说Object {}原型对象就是原型链的终点了。
  • Object对象作为一个对象,就有__proto__属性,该属性对应Function.prototype,也就是说,Object.__proto__ === Function.prototype

实例的原型

var will = new Person();
console.log(Person.prototype.__proto__ === Object.prototype);  // true
console.log(will.__proto__ === Person.prototype);  // true
console.log(will.__proto__.__proto__ === Object.prototype);  //true
console.log(will.constructor);  // ƒ Person() {}
console.log(will.prototype);  // undefined
console.log(Person.prototype.constructor === Person);  // true

结果分析:

  • will.__proto__ === Person.prototype,在JavaScript中,每个函数都有一个prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性),也就是说,所有实例的原型引用的是函数的prototype属性。
  • prototype属性是函数对象特有的,如果不是函数对象,将不会有这样一个属性。
  • 当通过Person.prototype.__proto__语句获取will对象原型的原型时候,将得到Object {}对象,所有对象的原型都将追溯到Object {}对象。
  • JavaScript的原型对象中,还包含一个constructor属性,这个属性对应创建所有指向该原型的实例的构造函数。will对象本身并没有constructor这个属性,但是通过原型链查找,找到了will原型(will.__proto__)的constructor属性,并得到了Person函数。

字面量对象的原型

var o = {};
console.log(o.constructor);  // ƒ Object() { [native code] }
console.log(o.__proto__ === Object.prototype);  // true
console.log(o.prototype);  // undefined

结果分析:

  • 字面量对象实质上就是Object构造函数的实例。

对比prototype和__proto__

对于prototype__proto__这两个属性有的时候可能会弄混,Person.prototypePerson.__proto__是完全不同的。

在这里对prototype__proto__进行简单的介绍:

  • 对于所有的对象,都有__proto__属性,这个属性对应该对象的原型
  • 对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)

图解实例

image.png

附上网上扣的一张图

网上抠的一张图

对上图的总结

  • 所有的对象都有__proto__属性,该属性对应该对象的原型
  • 所有的函数对象都有prototype属性,该属性的值会被赋值给该函数创建的对象的__proto__属性
  • 所有的原型对象都有constructor属性,该属性对应创建所有指向该原型的实例的构造函数
  • 函数对象和原型对象通过prototypeconstructor属性进行相互关联

重写原型

可以通过改变函数的prototype属性或对象的__proto__属性来重写原型,但会切断现有原型与任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型。

function Person() {}
var will = new Person();
Person.prototype = {
  constructor: Person,
  name: 'Greg',
  sayName: function () {
    console.log(this.name);
  }
};
will.sayName();  // error

报错的原因就是因为will指向的原型中不包含以该名字命名的属性。

原型对象的问题

原型模式的最大问题是由其共享的本性所导致的。

function Person() {}
Person.prototype = {
  constructor: Person,
  friends: ['a', 'b']
};
var person1 = new Person();
var person2 = new Person();

person1.friends.push('c');
console.log(person1.friends);  // ["a", "b", "c"]
console.log(person2.friends);  // ["a", "b", "c"]
console.log(person1.friends === person2.friends);  //true

在此,Person.prototype对象有一个名为friends的属性,该属性包含一个字符串数组。然后创建两个Person实例。接着修改person1.friends引用的数组,向数组中添加一个字符串。由于friends数组存在于Person.prototype而非person1中,所以此修改也会通过person2.friends(与person1.friends指向同一个数组)反映出来。

真的是继承吗?

最后是关于继承,前面我们讲到“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:

继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容