Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

理解JS中的原型和原型链 #2

Open
CharlesGC opened this issue Mar 9, 2019 · 0 comments
Open

理解JS中的原型和原型链 #2

CharlesGC opened this issue Mar 9, 2019 · 0 comments

Comments

@CharlesGC
Copy link
Owner

CharlesGC commented Mar 9, 2019

数据类型

JS中有5种简单数据类型(也称基本数据类型)和一种复杂数据类型(也称引用类型)即Object,typeof操作符就是用用来检测给定变量的数据类型的。如果要区分引用类型可以用instanceof操作符。

注意:null值是基本类型,但是用typeof检测时会返回Object,这是因为null值表示一个空对象指针。

对象

我们知道的对象是无序属性的集合,其属性可以包含基本、对象或者函数。创建对象的实例有两种方式,第一种是使用new 操作符后跟Object构造函数,例如:

var people = new Object();
peopel.name = 'Chaeles'
people.age = 10

第二种是使用对象字面量

var people = { name: 'Charles', age: 10 }

这两种方式都可以很方便的用来创建单个对象,但是也有一个很明显的缺点就是使用一个接口会创建很多重复代码。

工厂模式和构造函数模式

工厂模式
用函数来封装以特定接口创建对象的细节,例如下面

function createPeople(name,age){ var o = new Object(); o.name = name; o.age = age; return o; }

var people1 = createObject('Charles',10')
构造函数虽然解决了多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)于是构造函数模式出现了。

构造函数

function People (name,age) { this.name = name; this.age = age; }

var people1 = new People('Charles', 10)

构造函数模式与工厂模式相比,省去了在内部new Object即显式的创造对象,并且直接将属性和方法赋值给了this,并且省去了return语句。
所以要创建People的新shi实例,必须使用new 操作符。

但其实构造函数也有缺点,如果在构造函数内部定义一个方法,用这个构造函数定义的所有实例都会具有这个方法,这就造成巨大的内存浪费。于是原型模式出现了。

原型模式

什么是原型模式,我们创建的每个函数有一个prototype属性,(注意:也包括构造函数)这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。也就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

代码如下:

function People(){};
People.prototype.name = 'Charles';
People.prototype.age = 26;
var people1 = new People()
people1.name = 'Charles'
var people2 = new People()
people2.name = 'Charles'

从这里可以看出,在prototype上定义的属性会被所有构造函数的实例所共享,这个prototype对象就是函数的原型对象。在默认情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。

也就是说每当我们自定义一个构造函数之后,其原型对象上默认只会取得constructor属性,至于其他方法都是从Object上继承而来。如图:
image
我们可以看到People的构造函数上有一个prototype属性指向它的原型对象,并且它的原型对象上有一个constructor属性又指向了People构造函数。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(注意:这是一个内部属性)指向构造函数的原型对象,这个属性就是[[Prototype]],但是在脚本中是没有标准的方法去访问这个属性,但是浏览器在每一个对象上都支持一个属性__proto__,也就是隐式对象指针。如图:
image

继承

继承是OO语言中的一个概念,继承有两种方法,接口继承和实现继承。接口继承制继承方法签名,实现继承则继承实际的方法。在ECMAScript中,由于函数没有签名,没有办法实现接口继承。所以ECMAScript只支持实现继承,而实现继承主要是依靠原型链来实现的。也可以说成,原型链是javascript里用来解决继承的。

原型链

原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。我们回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假入我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型有事另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。大致代码如下:

function SuperType () { this.prototype = true; }
SuperType.prototype.getSuperValue = function () { return this.property; }
function SubType () { this.subproperty = false; }
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () { return this.subproperty; }
var instanse = new SubType();
alert(instanse.getSuperValue()) // true

在上面的代码中,我们没有使用SubType默认提供的原型,而是给它换了一个新原型,这个新原型就是SuperType 的实例。于是,新原型不仅具有作为一个SuperType的实例拥有的全部属性和方法,而且其内部还有一个指针,指向了SuperType 的原型。

用图来表示就是这样:
image

这样就实现了原型链。

我们来看下这张图:
image

我们看到所有函数都可以通过__proto__访问到Function,而Function.prototype可以通过__proto__访问到Object.prototype.

最后总结一下,所有的引用类型默认都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。

@CharlesGC CharlesGC changed the title 理解JS中的原型 理解JS中的原型和原型链 Mar 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant