时间:2021-05-25
理解原型
我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。看如下例子:
function Person(){}Person.prototype.name = 'ccc'Person.prototype.age = 18Person.prototype.sayName = function (){ console.log(this.name);}var person1 = new Person()person1.sayName() // --> cccvar person2 = new Person()person2.sayName() // --> cccconsole.log(person1.sayName === person2.sayName) // --> true理解原型对象
根据上面代码,看下图:
需要理解三点:
注意:person1 和person2 实例与构造函数之间没有直接的关系。
在之前我们提到,所有实现中无法访问到[[prototype]],那我们如何知道实例和原型对象之间是否存在关系呢?这里可以通过两个方法来判断:
实例属性与原型属性的关系
前面我们提到过,原型最初只包含constructor属性,而该属性也是共享的,因此可以通过对象实例访问。虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性,而改属性与实例原型中的一个属性同名,那就会在实例上创建该属性并屏蔽原型中的那个属性。如下:
function Person() {}Person.prototype.name = "ccc";Person.prototype.age = 18;Person.prototype.sayName = function() { console.log(this.name);};var person1 = new Person();var person2 = new Person();person1.name = 'www' // 在person1中添加一个name属性person1.sayName() // --> 'www'————'来自实例'person2.sayName() // --> 'ccc'————'来自原型'console.log(person1.hasOwnProperty('name')) // --> trueconsole.log(person2.hasOwnProperty('name')) // --> falsedelete person1.name // --> 删除person1中新添加的name属性person1.sayName() // -->'ccc'————'来自原型'我们如何判断一个属性,到底是实例上的属性还是原型上的属性?这里可以通过hasOwnProperty()方法来检测一个属性是存在于实例中还是存在于原型中。(此方法继承于Object)
下图详细分析了上面例子在不同情况下的实现与原型的关系:(省略了Person构造函数的的关系)
更简单的原型语法
我们不可能总像之前的例子一样,没添加一个属性和方法就要敲一遍,Person.prototype。为了减少不必要的输入,更常见的方法是像下面这样:
function Person(){}Person.prototype ={ name: 'ccc', age: 18, sayName: function () { console.log(this.name) }}在上面代码中,我们将Person.prototype设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外,constructor属性不再指向Person了。前面我们介绍过,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。但是在我们使用的新语法中,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数了。此时,尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了。如下:
var person1 = new Person()console.log(person1 instanceof Object) // --> trueconsole.log(person1 instanceof Person) // --> trueconsole.log(person1.constructor === Person) // --> falseconsole.log(person1.constructor === Object) // --> true这里用instanceof操作符测试Object和Person仍然返回true,constructor属性则等于Object,不等于Person了,如果constructor真的很重要可以像下面这样写:
function Person(){}Person.prototype ={ constructor: Person, // --> 重设 name: 'ccc', age: 18, sayName: function () { console.log(this.name) }}但是这会引起一个新问题,用上述方式重置constructor属性会导致它的[[Enumerable]]特性被设置为true。而默认情况下,原生的constructor属性是不可枚举的。因此如果你要使用兼容ECMAscript5的JavaScript引擎,可以试一试Object.defineProperty()。
function Person(){}Person.constructor = { name: 'ccc', age: 18, sayName: function(){ console.log(this.name) }}// 重设构造函数,只适用于ECMAscript5兼容的浏览器Object.defineProperty(Person.constructor, "constructor", { enumerable: false, value: Person})原型的动态性
由于原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能立即从实例上反映出来。比如:
function Person(){}var person1 = new Person()Person.prototype.sayHi= function(){ console.log('hi')}person1.sayHi()上述代码我们先创建了一个Person实例,并将其保存在person1中,然后在Person.prototype中添加了sayHi()方法。即使person1是添加新方法之前创建的,但它仍然可以访问这个方法。原因是实例与原型之间的松散的连接关系。
尽管可以随时为原型添加属性和方法,并立即能够在实例中反映出来。但是如果重写整个原型对象,那么情况就不一样了。看如下代码:
看下图分析:
调用构造函数时为实例添加了一个指向最初原型的[[prototype]]指针,而把原型修改为另外一个对线更久等于切断了构造函数与最初原型之间的联系。请记住:实例中的指针仅指向原型,而不指向构造函数。
原型链
简单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
以上就是详解js中的原型,原型对象,原型链的详细内容,更多关于js中的原型,原型对象,原型链的资料请关注其它相关文章!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
一个例子让你彻底明白原型对象和原型链开篇之前对js中的原型链和原型对象有所了解,每当别人问我什么是原型链和原型对象时,我总是用很官方(其实自己不懂)的解释去描述
温馨提示:想要更好的理解JS继承方式,须了解构造函数、原型对象、实例化对象、原型链等概念第一种:原型链继承利用原型链的特点进行继承functionParent(
前言在之前的两篇博客中,我们详细探讨了JavaScriptOOP中的各种知识点(JSOOP基础与JS中This指向详解、成员属性、静态属性、原型属性与JS原型链
1.原型链继承:构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。确认原型
JavaScript中的继承比较奇葩,无法实现接口继承,只能依靠原型继承。原型链原型就是一个对象,通过构造函数创建出来的实例会有指针指向原型得到原型的属性和方法