zoukankan      html  css  js  c++  java
  • 深入理解JS中的对象(一):原型、原型链和构造函数

    目录

    • 一切皆是对象吗?
    • 对象
      • 原型与原型链
      • 构造函数
    • 参考

    1.一切皆是对象吗?

    首先,“在 JavaScript 中,一切皆是对象”这种表述是不完全正确的。

    JavaScript 的数据类型分为两类:原始值类型和对象(Object类型)。

    原始值类型(ES5):

    • undefined
    • null - typeof null 的值为"object",是因为 ES5 规范规定:对于 null 值的 typeof 字符串值返回"object"
    • true/false - 布尔值
    • number
    • string
    var a = undefined
    var b = null
    var c = true
    var d = 1
    var e = "abc"
    

    这些值是在底层上直接实现的,它们不是object,所以没有原型(__proto__),没有构造函数(constructor)。

    但我们再实践过程中,会发现虽然字符串,布尔值和数字是原始值类型,但却表现得有点像对象。

    以字符串为例:

    字符串原始值类型

    在上图中,可以看到定义了一个值为"abc"的字符串变量 e,访问其 _proto_ 和 constructor 属性,发现其居然有值?不是说原始值类型没有原型和构造函数,这是怎么回事呢?

    原来原始值类型(布尔值、数字、字符串)有其对应的包装器对象:Boolean(布尔对象)、Number(数字对象)、String(字符串对象),在这些原始值类型上尝试调用属性或方法(比如 constructor 等)时,JS会自动进行 Auto-Boxing(临时包装)的过程,首先将其转换为临时包装器对象,再访问其上的属性或方法,而不会影响原始值类型的属性。

    这也能解释为什么我们直接对原始值类型变量(布尔值、数字、字符串)添加了一些属性,再次访问依旧为 undefined,因为我们访问属性操作的是临时包装器对象,不会影响基本原始值类型本身。如下图:

    临时包装器对象

    而原始值类型(null 与 undefined)没有对应的包装器对象,所以在其上尝试访问任何属性或方法都会报错。如下图:

    null 与 undefined 没有包装器对象


    2.对象

    在JS中,Object 是一个属性的集合,并且拥有一个单独的原型对象 [prototype object] (其可以是一个 object 或 null 值)。

    在浏览器或 Node.js 中,可以通过 _proto_ 属性访问这个原型对象, _proto_ 被称为该对象的原型,但为了和函数的原型属性(prototype)区分,一般称其为隐式原型。

    var position = {
      x: 10,
      y: 20,
      z: 30,
    }
    

    上面的代码中,对象与隐式原型的关系如下图:

    对象与隐式原型的关系


    (1)原型与原型链

    在JS中,对象的继承关系是通过隐式原型(__proto__)来实现的。对象的隐式原型在对象创建时由对象的构造函数自动关联,也可以通过修改隐式原型,更改对象的继承关系。

    由 Object 构造函数创建的对象,其隐式原型指向 Object.prototype。而 Object.prototype 对象的隐式原型的值默认为 nulll。

    代码示例:

    // x, y, z 的隐式原型 __proto__ 默认都指向 Object.prototype
    var x = {
        a: 10,
    }
    var y = {
        a: 20,
        b: 30,
    }
    var z = {
        a: 40,
        b: 50,
        c: 60,
    }
      
    // 设置 x 的隐式原型为 y
    // 设置 y 的隐式原型为 z
    x.__proto__ = y
    y.__proto__ = z
      
    console.log(x.a)  // 10 - 来自 x 自身的属性
    console.log(x.b)  // 30 - 来自 y 的属性
    console.log(x.c)  // 60 - 来自 z 的属性
    
    // 修改 y 的属性 b 的值
    y.b = 70
    
    console.log(x.b)  // 70 - 来自 y 的属性
    
    // 移除 z 的属性 c
    delete z.c
    
    console.log(x.c)  // undefined - 沿着隐式原型一级一级往上找,没有找到该属性
    

    从上述代码,我们可以看到,当访问一个对象的属性时,会优先在这个对象的属性中查找是否存在所要访问的属性,若存在,则获取成功,停止查找;若没有找到该属性,则会继续去查找该对象的隐式原型中是否存在,若存在,则获取成功,停止查找;若还是没有查找到,将继续再往上一级的隐式原型中查找,直到找到则返回找到的属性值 或 直到遇到隐式原型值为 null 则返回 undefined。

    这种由原型相互关联(指向)的关系就形成了所谓的原型链,而对象的属性或方法的查找就是沿着原型链顺序进行查找的。

    上述代码示例中的原型链关系如下图:

    代码示例中的原型链关系


    (2)构造函数

    首先要明白,函数也是一个特殊的对象,除了和其他对象一样有 _proto_ 属性外,还有自己特有的属性——显示原型(prototype),这个属性指向一个对象,其用途就是包含所有实例共享的属性和方法。显示原型对象也有一个 constructor 属性,这个属性指向原构造函数。

    而所谓构造函数,就是提供一个生成对象的模板,并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。而JS中所有函数(除了箭头函数)都可以当做构造函数。

    一个对象由构造函数创建时,其隐式原型(__proto__)指向构造该对象的构造函数(constructor)的显示原型(prototype),这保证了实例能够访问在构造函数原型中定义的属性和方法。

    代码示例:

    // 构造函数 C
    function C(x) {
        this.x = x
    }
    
    // 继承属性 y
    C.prototype.y = 30
    
    // new 两个对象实例a、b
    var a = new C(10)
    var b = new C(20)
    
    console.log(a.x) // 10
    console.log(a.y) // 30
    console.log(b.x) // 20
    console.log(b.y) // 30
    
    // 对象实例 a、b 的隐式原型(__proto__)指向构造该对象的构造函数 C 的显示原型(prototype)
    console.log(a.__proto__ === C.prototype) // true
    console.log(b.__proto__ === C.prototype) // true
    
    // 构造函数的显示原型(prototype)的 constructor 属性指向原构造函数
    console.log(C === C.prototype.constructor) // true
    
    // 构造函数 C、Function 与 Object 的隐式原型(__proto__)指向构造该对象的构造函数 Function 的显示原型(prototype)
    console.log(C.__proto__ === Function.prototype) // true
    console.log(Function.__proto__ === Function.prototype) // true
    console.log(Object.__proto__ === Function.prototype) // true
    
    // C.prototype 与 Function.prototype 的隐式原型(__proto__)指向构造该对象的构造函数 Object 的显示原型(prototype)
    console.log(C.prototype.__proto__ === Object.prototype) // true
    console.log(Function.prototype.__proto__ === Object.prototype) // true
    
    // Object.prototype 的隐式原型(__proto__)等于 null
    console.log(Object.prototype.__proto__ === null) // true
    

    上述代码示例中的完整原型链关系如下图:

    代码示例中的完整原型链关系

    从上图我们可以总结:

    1. 所有的(隐式)原型链的最末端最终都会指向 null(JS不允许有循环原型链,避免死循环)

    2. 所有(非继承的)函数默认都是由 Function 构造函数创建,即所有(非继承的)函数的隐式原型(__proto__)都指向 Function.prototype。

    3. 所有对象默认都继承自Object对象,即默认情况下,所有对象的(隐式)原型链的末端都指向 Object.prototype。

    注:所谓默认情况,即没有手动修改原型链关系。


    3.参考

    JS中一切都是对象吗?看这一篇就知道了

    js中__proto__和prototype的区别和关系?

    深入理解JavaScript系列(10):JavaScript核心(晋级高手必读篇)

    深入理解JavaScript系列(18):面向对象编程之ECMAScript实现(推荐)

  • 相关阅读:
    常用的网址
    Powerdesigner使用建议(完整版)
    非常实用的钩子程序(c++).
    SQLPlus中的复制和粘贴技巧 http://www.oradb.net/sql/sqlplus_007.htm
    【C#】输出的XML文件中空标签多换行符
    CMD创建当前日期文件夹
    【PostgreSQL】Select取得行号
    職業定義
    【SQLSERVER】CMD执行SQL语句
    【Oracle】PACKAGE输出LOG文件
  • 原文地址:https://www.cnblogs.com/forcheng/p/12866827.html
Copyright © 2011-2022 走看看