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实现(推荐)

  • 相关阅读:
    215. Kth Largest Element in an Array
    214. Shortest Palindrome
    213. House Robber II
    212. Word Search II
    210 Course ScheduleII
    209. Minimum Size Subarray Sum
    208. Implement Trie (Prefix Tree)
    207. Course Schedule
    206. Reverse Linked List
    sql 开发经验
  • 原文地址:https://www.cnblogs.com/forcheng/p/12866827.html
Copyright © 2011-2022 走看看