理解JS原型对象与原型链(重要清晰)

2019-08-22 16:38:15

参考文章 理解JS原型对象与原型链

参考文章2 类的继承

目录:


什么是原型对象和原型链

为什么使用原型对象

原型对象的理解

“prototype"和”__proto__"区别

查找属性

原型对象操作

一、什么是原型对象和原型链

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象对应拥有一个原型,对象以其原型为模板、从原型继承方法和属性。而同时原型也是对象,它也拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。


注:因为原型同时也是对象,所以也可以称呼为原型对象。


二、为什么使用原型对象

1、Javascript 并没有类继承模型,而是使用原型对象进行原型式继承。


2、原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。


三、原型对象的理解

1、对象__proto__属性的值就是它所对应的原型对象,即“对象.__proto__ === 对象对应的原型对象”。


2、在Javascript中,每个函数都有一个属性为prototype指向函数自身的原型。即“函数.prototype === 自身对应的原型对象”。


案例分析:


code1


function Person(){ }  //构造函数创建对象

Person.prototype.name = "Jie"; //添加属性值到原型对象上

console.log(Person.prototype);

1

2

3


原型对象内包含我们之间添加到原型对象上的name属性,还包含一个"constructor"属性,这个属性对应创建所有指向该原型的实例的构造函数。


code2



function Person(){ }  //构造函数创建对象

Person.prototype.name = "Jie"; //添加属性值到原型对象上


var person = new Person(); //创建实例对象

person.age = 23;  //创建属性值到实例对象上

console.log(person);

1

2

3

4

5

6

7



通过上面的两段代码分析,我们可以得出实例对象person的__proto__属性就是Person的prototype属性。person.__proto__与Person.prototype均指向了原型对象。


并且通过上面的结果可以看出,实例对象person的原型(person.__proto__)的原型(person.__proto__.__proto__)是Object对象的原型。


可以将上面的分析进行图解,如下所示:



prototype: 在函数身上,指向原型对象

__proto__: 在对象身上(包括函数创建的对象, 函数本身和原型对象),指向自身的原型

constructor: 在原型对象上,指向构造函数, 在多级继承的时候,指明构造函数方便在对象上扩展原型属性

Object.__protp__为null: 原型的顶端

在Javascript中,每个函数都有一个原型属性prototype指向函数自身的原型,而由这个函数创建的对象也有一个__proto__属性指向这个原型。(即person.proto === Person.prototype; //true)


而函数的原型是一个对象,所以这个对象也会有一个__proto__指向自己的原型,这样逐层深入直到Object对象的原型(null),这样就形成了原型链。


函数的原型对象的constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针指向上一层的原型对象,而上一层的原型对象的结构依然类似,这样利用__proto__一直指向Object的原型对象上,而Object的原型对象用Object.prototype.__proto__ = null表示原型链的最顶端,如此变形成了javascript的原型链继承,同时也解释了为什么所有的javascript对象都具有Object的基本方法。


四、“prototype"和”__proto__"区别

对于所有的对象,都有__proto__属性,这个属性对应该对象的原型。

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


五、查找属性

当访问一个对象的属性时,Javascript 会从对象本身开始往上遍历整个原型链,直到找到对应属性为止。如果此时到达了原型链的顶部,也就是 Object.prototype,仍然未发现需要查找的属性,那么 Javascript 就会返回 undefined 值。


例如:当你访问 person 的一个属性, 浏览器首先查找 person 是否有这个属性. 如果 person 没有这个属性, 然后浏览器就会在 person 的 __proto__ 中查找这个属性(也就是 Person.prototype). 如果 person 的 __proto__ 有这个属性, 那么 person 的 __proto__ 上的这个属性就会被使用. 否则, 如果 person 的 __proto__ 没有这个属性, 浏览器就会去查找 person 的 __proto__ 的 __proto__ ,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 __proto__ 就是 Object.prototype. 所以 person 的 __proto__ 的 __proto__ (也就是 Person.prototype 的 __proto__ (也就是 Object.prototype)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 person 的 __proto__ 的 __proto__ 的 __proto__ 里面查找. 然而这有一个问题: person 的 __proto__ 的 __proto__ 的 __proto__ 不存在. 最后, 原型链上面的所有的 __proto__ 都被找完了, 浏览器所有已经声明了的 __proto__ 上都不存在这个属性,然后就得出结论,这个属性是 undefined.




六、原型对象操作

观察以下代码:


function Person(){

    this.name = "Jie";

}

var person = new Person();


Person.prototype.say = function(){

    console.log("success");

}

1

2

3

4

5

6

7

8

我们的代码中定义了构造器,然后用这个构造器创建了一个对象实例,此后向构造器的 prototype 添加了一个新的方法。

say() 方法仍然可用于 person 对象实例——旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象(父类)的方法不会复制到下游的对象(子类)实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。


因此,我们可以将一些通用的,公用的属性或方法定义在prototype中。


例如:

1、"getInfo"方法是构造函数Person的一个成员,当通过Person构造两个实例的时候,每个实例都会包含一个"getInfo"方法。



2、原型就是为了方便实现属性的继承,所以可以将"getInfo"方法当作Person原型(Person.__proto__)的一个属性,这样所有的实例都可以通过原型继承的方式来使用"getInfo"这个方法了。并且改变getInfo方法全部的实例都会同步。


function Person(name, age){

    this.name = name;

    this.age = age;

}


Person.prototype.getInfo = function(){

    console.log(this.name + " is " + this.age + " years old");

};

1

2

3

4

5

6

7

8



参考资料:

MDN对象原型

原型对象

了解JS原型对象

__proto__和prototype

 ———————————————— 

  • 2017-05-25 23:18:23

    webpack图片的路径与打包

    刚开始用webpack的同学很容易掉进图片打包这个坑里,比如打包出来的图片地址不对或者有的图片并不能打包进我们的目标文件夹里(bundle)。下面我们就来分析下在webpack项目中图片的应用场景。

  • 2017-06-17 18:33:17

    NodeJs使用asyncAwait两法

    async/await使用同步的方式来书写异步代码,将异步调用的难度降低到接近于0,未来必将大放异彩。然而在当下,由于标准化的缓存步伐,async/await尚在ES7的草案中。为了尝先,特试用了下面两种方式:

  • 2017-06-18 02:26:17

    Incorrect string value: '\xF0\x9F\x98\x84\xF0\x9F

    我们可以看到错误提示中的字符0xF0 0x9F 0x98 0x84 ,这对应UTF-8编码格式中的4字节编码(UTF-8编码规范)。正常的汉字一般不会超过3个字节,为什么为出现4个字节呢?实际上是它对应的是智能手机输入法中的表情。那为什么会报错呢?

  • 2017-06-18 02:34:22

    谈mysql中utf8和utf8mb4区别

    MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。好在utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。当然,为了节省空间,一般情况下使用utf8也就够了。

  • 2017-07-05 09:48:51

    CSS 元素垂直居中的 6种方法

    利用CSS进行元素的水平居中,比较简单,行级元素设置其父元素的text-align center,块级元素设置其本身的left 和 right margins为auto即可。本文收集了六种利用css进行元素的垂直居中的方法,每一种适用于不同的情况,在实际的使用过程中选择某一种方法即可。

  • 2017-07-06 10:02:18

    大白话讲解Promise(一)

    去年6月份, ES2015正式发布(也就是ES6,ES6是它的乳名),其中Promise被列为正式规范。作为ES6中最重要的特性之一,我们有必要掌握并理解透彻。本文将由浅到深,讲解Promise的基本概念与使用方法。