javascript深入理解系列(二)——__proto__,prototype原型以及原型链

javascript深入理解系列文章网址
https://www.jianshu.com/p/451eed9094f5

今天这节可能有点长,希望大家认真看,收获会很多
(2020年1月2日更新,添加es6写法)

1._proto_由来

我们都知道javascript是一种面向对象的语言,面向对象语言的一大特点就是一切皆对象,我们常见的一些数字,数组,字符串等都是对象。但是虽然都是对象,但因为表现形式不一样又分为普通对象和函数对象。

普通对象

  var obj={
    name:"xiaoming"
  };

输出以后

image.png

我们可以看到"_proto_"里面有很多的方法,这些方法都是可以直接使用的

函数对象

function Person(){
    alert("你好");
}

输出以后


image.png

从上面的例子我们可以看出普通对象和函数对象的定义和输出后的样子

我们再看一下typeof后普通对象和函数对象的区别

  console.log(typeof obj);
  console.log(typeof(Person));
image.png

不管是普通对象还是函数对象,它们都有一个隐含属性_proto_ ,而这属性就是我们通常说的原型(属性),这就是_proto_的由来

重点每一个对象都有_proto_属性,指向对应的构造函数的prototype属性

怎么理解好一点呢?
我们先来创建一个数组,输出他的\__proto__属性
let arr1=Array.of(1,2,3);
 console.log(arr1);

image.png
接着我们输出数组构造函数的prototype
console.log(Array.prototype);

image.png
结论就是发现他们是相等的

2.prototype的由来

对于函数对象,它们还会多一个prototype的属性,它和以它为构造函数创建的普通对象的”_proto_ “属性等同,即实例对象的_proto_指向它的构造函数prototype(划重点)(这也就是为什么实例对象能够使用原型对象上面方法的原因)

es5写法  
function Person(name,age){
    this.name=name;
    this.age=age;
}
var xiaoming=new Person("小明",10);
console.log(xiaoming.__proto__==Person.prototype);
es6写法
class Person {
    constructor(name,age) {
        this.name = name;
        this.age = age;
    }

    toString(){
        return this.name+'今年'+this.age+'岁!'
    }
}

const xiaoming=new Person('小明',10);
console.log(xiaoming.toString());
console.log(typeof Person);
// 函数对象的是prototype,普通对象的是__proto__
// 每一个对象都有_proto_属性,指向对应的构造函数的prototype属性,也可以说实例的__proto__指向对应的类对象的prototype

console.log(xiaoming.__proto__===Person.prototype);
// 很多人会问,为什么prototype这么重要呢,接下来我们会讲原型链,告诉你,那些最原始的方法都是怎么来的

下面讲一下prototype的作用

javascript是通过构造函数来实现实例对象的,实例对象的属性和方法都是在构造函数内部来定义的

<script>
        function Person(name,age){
            this.name=name;
            this.age=age;
        }
        var xiaoming=new Person("xiaoming",10);
        console.log(xiaoming.name);
        console.log(xiaoming.age);
    </script>

这样写的缺点是但是同一个构造函数之间无法共享方法和属性,造成了系统资源的浪费,比如说你创建两个Person实例,他们都有一个共同的方法比如说睡觉,可是每生成一个实例就会重新生成一个睡觉的方法。所以这就有了prototype.

JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。这里所说的原型也就是prototype了,这样子就能节省内存了

这句话的理解就是我们把私有的属性和方法定义在构造函数里面,把公有的属性定义在prototype上

3._proto_和prototype的关系

一.所有构造器/函数的_proto_都指向Function.prototype,它是一个空函数(Empty function)

Number.__proto__ === Function.prototype  // true
Boolean.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype  // true
Object.__proto__ === Function.prototype  // true
Function.__proto__ === Function.prototype // true 
Array.__proto__ === Function.prototype   // true
RegExp.__proto__ === Function.prototype  // true
Error.__proto__ === Function.prototype   // true
Date.__proto__ === Function.prototype    // true

这里所说的所有当然也包括自定义的函数

var Dog = function(){};
console.log(Dog.__proto__==Function.prototype);

我们知道prototype是放置公有属性和方法的地方,那就是说我们自定义的Dog函数原型继承了Function函数的原型,比如说bind,call,apply,length方法

Function.prototype也是唯一一个typeof XXX.prototype为 “function”的prototype。其它的构造器的prototype都是一个对象。如下

console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype)   // object
console.log(typeof Number.prototype)   // object
console.log(typeof Boolean.prototype)  // object
console.log(typeof String.prototype)   // object
console.log(typeof Array.prototype)    // object
console.log(typeof RegExp.prototype)   // object
console.log(typeof Error.prototype)    // object
console.log(typeof Date.prototype)     // object
console.log(typeof Object.prototype)   // object

上面我们提到Function.prototype是一个空函数,
输出后为


image.png
console.log(Function.prototype.__proto__ === Object.prototype) // true

那么也就是说构造器和函数都会继承Object.prototype上的方法和属性


image.png

最后Object.prototype的proto是谁?

Object.prototype.__proto__ === null  // true

也就是说通过_proto_的最顶端是null。

二、所有对象的_proto_都指向其构造器的prototype(看一下new源码就明白了)
先看看javascript内置构造器

var obj = {name: 'jack'}
var arr = [1,2,3]
var reg = /hello/g
var date = new Date
var err = new Error('exception')
 
console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype)  // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype)  // true
console.log(err.__proto__ === Error.prototype)  // true

再看看自定义的构造器

function Person(name) {
    this.name = name
}

var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true

每个对象都有一个constructor属性,可以获取它的构造器

function Person(name) {
    this.name = name
}

var p = new Person('jack')
console.log(p.__proto__ === p.constructor.prototype) // true

需要注意的是

function Person(name) {
    this.name = name
}

// 重写原型
Person.prototype = {
    getName: function() {}
}

var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false

这种情况是因为给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器 Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等

var p = {}
console.log(Object.prototype) // 为一个空的对象{}
console.log(p.constructor === Object) // 对象直接量方式定义的对象其constructor为Object
console.log(p.constructor.prototype === Object.prototype) // 为true,不解释 

4.原型链

从上面的知识我们可以知道对象的_proto_指向其构造函数的prototype,而构造函数的prototype指向Function.prototype,Function.prototype又指向Object.prototype,从而继承Object上面的方法,就这样对象到原型再到原型的原型,这种链式结构我们称之为原型链

从所有构造器/函数的proto都指向Function.prototype那一章我们可以知道,原型链的顶层是null

那么对象是如何获取属性和方法的呢,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。

function Person(name,age){
    this.name=name;
    this.age=age;
}
Person.prototype.color='white';
var xiaoming=new Person("小明",10);
xiaoming.color='yello';
var xiaoli=new Person("小李",12);
console.log(xiaoming.color);
console.log(xiaoli.color);

从上面我们还可以得出结论,如果原型指向数组,那么我们就可以拥有数组上方法

var MyArray = function () {};

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true

参考:https://wangdoc.com/javascript/oop/prototype.html#prototype-%E5%B1%9E%E6%80%A7%E7%9A%84%E4%BD%9C%E7%94%A8

https://blog.csdn.net/hkh_1012/article/details/53215133

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

推荐阅读更多精彩内容