资讯专栏INFORMATION COLUMN

JavaScript 中的原型原来是这样的

yuanxin / 2597人阅读

摘要:而在原型对象中默认有一个属性存储了构造函数的地址引用也就是指向了构造函数。原型链的顶端是是所有对象的祖宗,的值为。但是上面的代码把构造函数和原型分开写了。

什么是原型
原型其实就是一个特殊的对象,在声明函数的时候自动创建的。

比如,我们现在声明一个构造函数 A ,除了会申请保存函数的内存空间,还会额外申请一个内存空间,用于存储构造函数 A 的原型对象。所有函数中(Function.prototype.bind 除外)默认都有一个 prototype 的属性,它保存了函数的原型对象的地址(引用)(也就是它指向了原型对象)。
而在原型对象中默认有一个 constructor 属性存储了构造函数的地址(引用)(也就是 constructor 指向了构造函数)。如果不理解上面所说的,那我们看下面的图:

浏览器控制台中:

_ _proto_ _prototype
刚开始接触原型的时候这两个东西很容易就搞混了。

先记住以下两点,就很容易就区分了:

prototype 是函数中才有的属性

__proto__ 是所有对象都有的属性

我们已经知道了函数中的 prototype 属性指向的是它的原型对象,那么对象中的 __proto__ 代表什么?

一般情况下,对象中的 __proto__ 属性是指向它的构造函数的原型对象的,即和构造函数中的 prototype 属性所指向的对象是同一个对象。

用一段简单的代码:

function A() {}
var a = new A()

上图看着不够简便,我们简化一下:

还有一点,__proto__ 不是一个规范属性,ie(除了 ie10) 不支持。对应的标准属性是 [[Prototype]] ,但是这个属性我们没法直接访问到。开发者尽量不要用这种方式去访问,因为操作不慎会改变这个对象的继承原型链。

在使用 Object.create(参数) 方式创建对象时,对象的 __proto__ 属性指向的是传入的参数。

原型链
由于 __proto__ 是所有对象都具有的属性,而 __proto__ 本身指向的原型(函数.prototype)也是一个对象,它也有 __proto__ 属性。所以这样会形成由 __proto__对象原型连起来的链条。这就是原型链。原型链的顶端是 Object.prototype(Object 是所有对象的祖宗) ,Object.prototype.__proto__的值为 null

还是看之前的代码:

function A() {}
var a = new A()

它的原型链如下:

构造函数 A 其实也是一个对象。所有函数都是由 Function 函数构造的。(声明函数 function A() {} 等价于 var A = new Function()) 。所以所有函数的 __proto__ 指向的都是 Function.prototype 。更新上图:

Function 也是一个函数,它的 __proto__ 指向的也是 Functon.prototypeFuntion.__proto__ === Function.prototype。继更新上图:

Object 同样是一个函数,所以 Object.__proto__ === Function.prototype

到了这里,我们应该可以看懂下面这张图了:

原型的作用
当 JS 引擎查找对象属性时,先查找对象本身是否存在该属性,如果不存在,会在对象的 __proto__ 里找,还找不到就会沿着原型链一直找到原型链顶端(Object.prototype) 直到找到属性为止,最后在原型链顶端都没找到就返回 undefined

由于上面的机制,原型的作用就很明显了——共享属性,节省内存空间。

function Animal() {
    this.name = "动物"
    this.eat = function() {
        console.log("在吃···")
    }
}
var a1 = new Animal()
var a2 = new Animal()

console.log(a1.eat === a2.eat)  // false
// 每个对象的 eat 方法不是同一个,但方法类容一样,浪费内存

使用 原型解决:

function Animal(name) {
    this.name = "动物"
}
Animal.prototype.eat = function() {
    console.log("吃")
}

var a1 = new Animal()
var a2 = new Animal()

console.log(a1.eat === a2.eat)  //true
// a1.eat 和 a2.eat 都同一个方法(Animal.prototype.eat)

原型非常适合封装共享的方法。但是上面的代码把构造函数和原型分开写了。封装不到位。使用动态类型模式解决。

function Animal() {
    this.name = "动物"
    
    /*
      判断 this.eat 是不是 函数类型,
      如果不是,则表示是第一次创建对象或者调用 Animal 函数,
      会将 eat 添加到原型中去。
      如果是,则表示原型中存在了 eat 方法,不需要再添加。
    */
    if(typeof this.eat !== "function") {
        Animal.prototype.eat = function() {
            console.log("吃")
        }
    }
}

var a = new Animal()
a.eat()

原型基于之前的共享属性和方法,是实现 JS 中继承的基础。

与原型有关的方法 hasOwnProperty()

通过之前的学习,我们知道了去访问一个对象的属性时,会在原型链上查找。所以我们并不知道这个属性来自哪里。

hasOwnProperty() 方法返回一个布尔值,可以判断一个属性是否来自对象本身。

function Animal() {}
Animal.prototype.name = "动物"
var a = new Animal()
a.age = 3

console.log(a.hasOwnProperty("name"))  // false
console.log(a.hasOwnProperty("age")  // true
in 操作符
in 操作符用返回一个布尔值,用来判断一个属性能否在对象上找到。在对象的原型链上找到也返回 true
function Animal() {}
Animal.prototype.name = "动物"
var a = new Animal()
a.age = 3

console.log("name" in a)  // true
console.log("age" in  a)  // true
console.log("sex" in  a)  // false
总结

原型就是一个对象,声明函数就会创建原型对象

prototype 只存在于函数中

所有对象都有一个 __proto__ 属性,它指向对象的构造函数的原型

原型 也是对象,也有 __proto__ 属性,__proto__ 将对象和原型连接起来,形成原型链

Object.prototype 是原型链的顶端

访问对象的属性会沿着对象的原型链找下去

原型可以共享属性和方法,是继承的基础

阅读原文

参考资料:

https://juejin.im/post/5835853f570c35005e413b19

https://blog.csdn.net/u012468376/article/details/53121081

https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5bed40d951882545f73004f6

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109097.html

相关文章

  • JS面向对象二:this/原型链/new原理

    摘要:情况没有明确作用对象的情况下,通常为全局对象例如函数的回调函数,它的就是全局对象。正因如此,机器可以作为这类对象的标志,即面向对象语言中类的概念。所以机器又被称为构造函数。原型链也就是继承链。 JS面向对象二:this/原型链/new原理 阮一峰JavaScript教程:面向对象编程 阮一峰JavaScript教程:实例对象与 new 命令 阮一峰JavaScript教程:this 关...

    anRui 评论0 收藏0
  • 我来重新学习 javascript 面向对象(part 2)

    摘要:先来说其实构造函数也有,原型对象有,实例有也有,或者更加笼统的说,所有对象都是有的。构造函数的原型对象上的会指向构造函数。由于属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。 续上一集内容,通过构造函数的方式,成功地更新了生产技术,老板笑呵呵,工人少奔波,只是问题总比办法多,又遇到一个新问题,就是会造成一些资源的重复和浪费,那么经过工程师们的智慧交流,他们产生了一个新技...

    silvertheo 评论0 收藏0
  • 彻底理解Javascript原型链与继承

    摘要:在节中,我们学习到了通过构造函数创建对象的三个重要步骤,其中的一步是把构造函数的对象设置为创建对象的原型。利用而不是直接用创建一个实例对象的目的是,减少一次调用父构造函数的执行。 JavaScript语言不像面向对象的编程语言中有类的概念,所以也就没有类之间直接的继承,JavaScript中只有对象,使用函数模拟类,基于对象之间的原型链来实现继承关系,ES6的语法中新增了class关键...

    ziwenxie 评论0 收藏0
  • javascript高级程序设计》第六章 读书笔记 之 javascript对象几种创建方式

    摘要:三种使用构造函数创建对象的方法和的作用都是在某个特殊对象的作用域中调用函数。这种方式还支持向构造函数传递参数。叫法上把函数叫做构造函数,其他无区别适用情境可以在特殊的情况下用来为对象创建构造函数。 一、工厂模式 工厂模式:使用字面量和object构造函数会有很多重复代码,在此基础上改进showImg(https://segmentfault.com/img/bVbmKxb?w=456&...

    xiaotianyi 评论0 收藏0
  • 读《javaScript高级程序设计-第6章》之封装类

    摘要:创建构造函数后,其原型对象默认只会取得属性至于其他的方法都是从继承来的。上图展示了构造函数的原型对象和现有的两个实例之间的关系。所有原生的引用类型都在其构造函数的原型上定义了方法。 第6章我一共写了3篇总结,下面是相关链接:读《javaScript高级程序设计-第6章》之理解对象读《javaScript高级程序设计-第6章》之继承 工厂模式 所谓的工厂模式就是,把创建具体对象的过程抽象...

    seal_de 评论0 收藏0

发表评论

0条评论

yuanxin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<