• 售前

  • 售后

热门帖子
入门百科

全面分析js中的原型,原型对象,原型链

[复制链接]
永远丶并不远 显示全部楼层 发表于 2021-10-25 20:06:01 |阅读模式 打印 上一主题 下一主题
目录


  • 理解原型
  • 理解原型对象

    • 实例属性与原型属性的关系
    • 更简单的原型语法
    • 原型的动态性

  • 理解原型链

    • 别忘记默认的原型
    • 确定原型和实例的关系
    • 审慎地界说方法
    • 原型链的题目


理解原型


我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定范例的所有实例共享的属性和方法。看如下例子:
  1. function Person(){
  2. }
  3. Person.prototype.name = 'ccc'
  4. Person.prototype.age = 18
  5. Person.prototype.sayName = function (){
  6. console.log(this.name);
  7. }
  8. var person1 = new Person()
  9. person1.sayName() // --> ccc
  10. var person2 = new Person()
  11. person2.sayName() // --> ccc
  12. console.log(person1.sayName === person2.sayName) // --> true
复制代码
理解原型对象


根据上面代码,看下图:

须要理解三点:
      
  • 我们只要创建了一个新的函数,就会根据一组特定的规则为该函数创建一个prototype属性,指向函数的原型对象。即Person(构造函数)有一个prototype指针,指向Person.prototype  
  • 默认情况下,每个原型对象上都会创建一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针  
  • 每个实例的内部都有一个指针(内部属性) ,指向构造函数的原型对象。即 person1 和person2 身上都有一个内部属性__proto__(在ECMAscript中管这个指针叫[[prototype]],虽然在脚本中没有尺度的方式访问[[prototype]],但是firefox,ie,chrome都支持一个属性叫__proto__) 指向Person.prototype
留意:person1 和person2 实例与构造函数之间没有直接的关系。
在之前我们提到,所有实现中无法访问到[[prototype]],那我们怎样知道实例和原型对象之间是否存在关系呢?这里可以通过两个方法来判定:
      
  • 原型对线上的方法:isPrototypeOf(),如:
    1. console.log(Person.prototype.isPrototypeOf(person1)) // --> true
    复制代码

  • ECMAscript5中新增的一个方法:Object.getPrototypeOf(),这个方法返回[[prototype]]的值。如:console.log
    1. (Object.getPrototypeOf(person1) === Person.prototype) // --> true
    复制代码

实例属性与原型属性的关系


前面我们提到过,原型最初只包含constructor属性,而该属性也是共享的,因此可以通过对象实例访问。虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。假如我们在实例中添加了一个属性,而改属性与实例原型中的一个属性同名,那就会在实例上创建该属性并屏蔽原型中的那个属性。如下:
  1. function Person() {}
  2. Person.prototype.name = "ccc";
  3. Person.prototype.age = 18;
  4. Person.prototype.sayName = function() {
  5. console.log(this.name);
  6. };
  7. var person1 = new Person();
  8. var person2 = new Person();
  9. person1.name = 'www' // 在person1中添加一个name属性
  10. person1.sayName() // --> 'www'————'来自实例'
  11. person2.sayName() // --> 'ccc'————'来自原型'
  12. console.log(person1.hasOwnProperty('name')) // --> true
  13. console.log(person2.hasOwnProperty('name')) // --> false
  14. delete person1.name // --> 删除person1中新添加的name属性
  15. person1.sayName() // -->'ccc'————'来自原型'
复制代码
我们怎样判定一个属性,到底是实例上的属性照旧原型上的属性?这里可以通过hasOwnProperty()方法来检测一个属性是存在于实例中照旧存在于原型中。(此方法继承于Object)
下图具体分析了上面例子在不怜悯况下的实现与原型的关系:(省略了Person构造函数的的关系)


更简单的原型语法


我们不大概总像之前的例子一样,没添加一个属性和方法就要敲一遍,Person.prototype。为了减少不须要的输入,更常见的方法是像下面如许:
  1. function Person(){}
  2. Person.prototype ={
  3. name: 'ccc',
  4. age: 18,
  5. sayName: function () {
  6. console.log(this.name)
  7. }
  8. }
复制代码
在上面代码中,我们将Person.prototype设置为等于一个以对象字面量情势创建的新对象。终极结果相同,但有一个例外,constructor属性不再指向Person了。前面我们先容过,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动得到constructor属性。但是在我们使用的新语法中,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数了。此时,只管instanceof操纵符还能返回正确的结果,但通过constructor已经无法确定对象的范例了。如下:
  1. var person1 = new Person()
  2. console.log(person1 instanceof Object) // --> true
  3. console.log(person1 instanceof Person) // --> true
  4. console.log(person1.constructor === Person) // --> false
  5. console.log(person1.constructor === Object) // --> true
复制代码
这里用instanceof操纵符测试Object和Person仍旧返回true,constructor属性则等于Object,不等于Person了,假如constructor真的很紧张可以像下面如许写:
  1. function Person(){}
  2. Person.prototype ={
  3. constructor: Person, // --> 重设
  4. name: 'ccc',
  5. age: 18,
  6. sayName: function () {
  7. console.log(this.name)
  8. }
  9. }
复制代码
但是这会引起一个新题目,用上述方式重置constructor属性会导致它的[[Enumerable]]特性被设置为true。而默认情况下,原生的constructor属性是不可枚举的。因此假如你要使用兼容ECMAscript5的JavaScript引擎,可以试一试Object.defineProperty()。
  1. function Person(){}
  2. Person.constructor = {
  3. name: 'ccc',
  4. age: 18,
  5. sayName: function(){
  6. console.log(this.name)
  7. }
  8. }
  9. // 重设构造函数,只适用于ECMAscript5兼容的浏览器
  10. Object.defineProperty(Person.constructor, "constructor", {
  11. enumerable: false,
  12. value: Person
  13. })
复制代码
原型的动态性


由于原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能立即从实例上反映出来。比如:
  1. function Person(){}
  2. var person1 = new Person()
  3. Person.prototype.sayHi= function(){
  4. console.log('hi')
  5. }
  6. person1.sayHi()
复制代码
上述代码我们先创建了一个Person实例,并将其保存在person1中,然后在Person.prototype中添加了sayHi()方法。即使person1是添加新方法之前创建的,但它仍旧可以访问这个方法。缘故原由是实例与原型之间的松散的连接关系。
只管可以随时为原型添加属性和方法,并立即可以或许在实例中反映出来。但是假如重写整个原型对象,那么情况就不一样了。看如下代码:
  1. function Person(){}
  2. var person1 = new Person()
  3. Person.prototype = {
  4. name: 'ccc',
  5. age: 18,
  6. sayName: function(){
  7. console.log(this.name)
  8. }
  9. }
  10. person1.sayName() // --> error
复制代码
看下图分析:

调用构造函数时为实例添加了一个指向最初原型的[[prototype]]指针,而把原型修改为别的一个对象就等于堵截了构造函数与最初原型之间的联系。请记住:实例中的指针仅指向原型,而不指向构造函数。

理解原型链


原型链是实现继承的重要方法。其根本头脑是让一个引用范例继承另一个引用范例的属性和方法。在理解原型链之前,我们起首得捋一下,原型,原型对象,实例之间的关系:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。假如我们让原型对象等于另一个范例的实例会怎么样?显然,这个原型对象将会包含一个指向另一个原型的指针。先看代码在看图:
  1. function SuperType(){
  2. this.property = true
  3. }
  4. SuperType.prototype.getSuperValue = function(){
  5. return this.property
  6. }
  7. function SubType(){
  8. this.subProperty = false
  9. }
  10. // 继承了SuperType
  11. SubType.prototype = new SuperType()
  12. SubType.prototype.getSubValue = function (){
  13. return this.subProperty
  14. }
  15. var instance = new SubType()
  16. console.log(instance.getSuperValue()) // --> true
复制代码
上述代码界说了两个范例:SuperType和SubType。每个范例分别有一个属性和一个方法。

分析上图:instance 指向SubType原型,SubType的原型又指向SuperType的原型。getSuperValue()方法仍旧还在SuperType.prototype中,但property则位于SubType.prototype中。这是因为property是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype现在是SuperType的实例,那么property当然就位于该实例中。别的要留意,instance.constructor现在指向的是SuperType,这是因为原来的SubType.prototype中的constructor被重写了的缘故。
为什么会返回true?
分析:调用instance.getSuperValue()方法会履历三个搜索步调:
搜索实例
搜索SubType.prototype
搜索SuperType.prototype,直到这里才找到方法。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末了才会停下来。

别忘记默认的原型


要知道,所有的引用范例默认都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype,这也正是所有自界说范例都会有toString(),valueOf()方法的缘故原由。所以完备的原型链应该如下:
看下图,subType的内部:

具体图解:

总之一句话,SubType继承了SuperType,而SuperType继承了Object。当调用instanct.toString()的时候,实际上调用的是保存在Object.prototype中的那个方法。

确定原型和实例的关系


当一个原型链很长的时候,想要确定原型和实例的关系,统共有两种方法:
使用instanceof 操纵符,只要用这个操纵符来测试实例与原型链中出现过的构造函数,结果就会返回true。
  1. console.log(instance instanceof Object) // --> true
  2. console.log(instance instanceof SuperType) // --> true
  3. console.log(instance instanceof SubType) // --> true
复制代码
使用isPrototypeOf()方法,跟instanctof判别方法类似,只要原型链中出现过的原型,都会返回true。
  1. console.log(Object.prototype.isPrototypeOf(instance)) // --> true
  2. console.log(SuperType.prototype.isPrototypeOf(instance)) // --> true
  3. console.log(SubType.prototype.isPrototypeOf(instance)) // --> true
复制代码
审慎地界说方法


子范例有时候须要覆盖超范例中的某个方法,大概须要添加超范例中不存在的某个方法。但不管怎样,给原型添加方法的代码肯定要放在更换原型的语句之后。如下:
  1. function SuperType(){
  2. this.property = true;
  3. }
  4. SuperType.prototype.getSuperValue = function(){
  5. return this.property
  6. }
  7. function SubType(){
  8. this.subProperty = false;
  9. }
  10. // 继承了 SuperType
  11. SubType.prototype = new SuperType()
  12. // 添加新方法
  13. SubType.prototype.getSubValue = function(){
  14. return this.subProperty
  15. }
  16. // 重写超类型中的方法
  17. SubType.prototype.getSuperValue = function(){
  18. return false
  19. }
  20. var instance = new SubType()
  21. console.log(instance.getSuperValue()) // --> false
  22. var instanceSuper = new SuperType()
  23. console.log(instanceSuper.getSuperValue()) // -> true
复制代码
上述代码中,第一个方法getSubValue()被添加到了SubType中。第二个方法getSuperValue()是原型链中已经存在的一个方法,但重写这个方法将会屏蔽原来的那个方法。即当通过SubType的实例调用getSuperValue()时,调用的就是这个重新界说的方法,但通过SuperType的实例调用getSuperValue()时,还会继续调用原来的那个方法。还有一点,在通过原型链实现继承的时候,不能使用对象自变量创建原型方法,因为如许会重写原型链,导致原型链被堵截。

原型链的题目


通过原型来实现继承时,原型实际上会变成另一个范例的实例,于是,原先的实例属性就变成了现在的原型属性了,这就会导致属性被共享。看如下代码:
  1. function SuperType(){
  2. this.colors = ['white', 'blue']
  3. }
  4. function SubType(){
  5. }
  6. // 继承了SuperType
  7. SubType.prototype = new SuperType()
  8. var instance1 = new SubType()
  9. instance1.colors.push('red')
  10. var instance2 = new SubType()
  11. console.log(instance1.colors) // -->["white", "blue", "red"]
  12. console.log(instance2.colors) // -->["white", "blue", "red"]
复制代码
在创建子范例的实例时,不能向超范例的构造函数中通报参数。实际上,应该是没有办法在不影响所有对象实例的情况下,给超范例的构造函数通报参数。因此,在实践中很少会单独使用原型链。
以上就是图解js中的原型,原型对象,原型链的具体内容,更多关于js中的原型,原型对象,原型链的资料请关注脚本之家别的相干文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作