Skip to content

js原型链指向

✍️ w 🕒 2024-06-17 14:53:00(5 days ago) 🔗 A.前端知识整理

要深入了解 JavaScript 对象,必须掌握三个关键属性:prototype__proto__constructor

prototype

  • 是函数的独有属性。
  • 每个函数都有一个 prototype 属性,该属性是一个对象,包含由该函数创建的所有实例所共享的属性和方法。
  • 该属性在构造函数调用时会作为新对象的原型。
js
function Person(name) {
  this.name = name;
}
console.log(Person === Person.prototype.constructor) // true
console.dir(Person)

proto

  • 是每个对象都有的属性,指向创建该对象的构造函数的 prototype 属性。
  • 它形成了对象的原型链,使得对象可以从其原型继承属性和方法。
  • 当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作,首先检查对象本身是否有对应的属,如果对象没有该属性,则访问对象的 [[Prototype]](即 __proto__)所指向的原型对象,继续查找,直到找到相应的属性或到达原型链的顶层(null)。
js
function Person(name) {
  this.name = name;
}
const p = new Person
console.log(p)

constructor

  • 是每个对象所具有的属性,指向创建该对象的构造函数。
  • 当创建一个对象时,这个属性被自动设置。
  • 对象具有 [[Prototype]](即 __proto__)属性。当访问一个对象的 constructor 属性时,如果对象本身没有 constructor 属性,那么会沿着原型链查找,直到找到 constructor 属性。
js
function Person() {}

var PersonPrototype = Person.prototype
console.log(PersonPrototype.constructor === Person) // true

var p = new Person()
console.log(p.__proto__.constructor === Person) // true
// true  因为当查找对象上的某个key 的时候本身没有就会顺着 __proto__ 往上查找因此可以直接其实本质p.__proto__.constructor
console.log(p.constructor === Person) 
console.log(p.constructor.name === Person.name) // true

三者关系

  • 每个函数都有一个 prototype 属性,它包含由该函数创建的所有实例对象共享的属性和方法。
  • 每个对象都有一个 __proto__ 属性,它指向创建该对象的构造函数的 prototype 属性,从而形成对象的原型链。
  • 每个对象都有一个 constructor 属性,指向创建该对象的构造函数。
  • 因为函数也是对象,所以函数也具有 __proto__constructor 属性,并且函数独有 prototype 属性(箭头函数没有自己的 prototype 属性)
  • 大部分 函数数据类型 的值都具备 prototype(原型/显式原型)属性,属性值本身是一个对象「浏览器会默认为其开辟一个堆内存,用来存储实例可调用的公共的属性和方法」,在浏览器默认开辟的这个堆内存中「原型对象」有一个默认的属性 constructor(构造函数/构造器),属性值是当前函数/类本身
js
function Person(name) {
  this.name = name;
}

// 函数的 prototype 属性
console.log(Person.prototype); // {constructor: Person}
console.log(Person.prototype.constructor); // [Function: Person]

// 实例的 __proto__ 属性
const alice = new Person("Alice");
console.log(alice.__proto__); // {constructor: Person}
console.log(alice.__proto__ === Person.prototype); // true

// 实例的 constructor 属性
console.log(alice.constructor); // [Function: Person]
console.log(alice.constructor === Person); // true
plaintext
   Person (函数)
     |
     ├── prototype(显式原型)

Person.prototype  
     |
     ├── constructor -> Points back to Person

实例 (alice)
     |
     ├── __proto__(隐式原型)

Person.prototype

不要去做的事 -- 重写原型对象

每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性,但重新赋值一个对象相当于丢失了指向自身constructor属性,而constructor属性还变成当前赋值对象的

js
function Person() {

}

console.log(Person.prototype)


// 直接赋值一个新的原型对象
Person.prototype = {
	message: "Hello Person",
	info: { name: "哈哈哈", age: 30 },
	running: function() {},
	eating: function() {},
	// constructor: Person
}

// 非要覆盖 可以这么写
Object.defineProperty(Person.prototype, "constructor", {
	enumerable: false,
	configurable: true,
	writable: true,
	value: Person
})

图解 function 和 Object

构造函数 Foo、实例 f1 以及它们与全局对象 Function 的关系。

js
// 定义构造函数 Foo
function Foo() {}

// 创建实例 f1
var f1 = new Foo();

图解说明

  1. 每一个构造函数(比如 Foo)都是 Function 的实例。
  2. 每一个实例对象(比如 f1)的 __proto__ 属性指向其构造函数的 prototype 属性。
  3. 每一个构造函数(比如 Foo)本身也是一个对象,所以它的 __proto__ 会指向 Function.prototype
  4. Function 本身也是一个函数,所以 Function__proto__Function.prototype
  5. Function.prototype__proto__Object.prototype,最终所有对象的 __proto__ 指向 null

图解

      (全局对象) Function
            |
            ├── prototype
            |
   Function.prototype
            |

      +-------------------+
      |                   |
  Foo (构造函数)          +----------------------+
      |                   |                      |
      ├── prototype       |                      |
      |                   |                      ⬇
Foo.prototype             f1 (实例)       Function (全局对象)
      |                   |
      └── constructor  ←-┘
            |
            |

          f1.__proto__
            |

      Foo.prototype
            |
            ⟶ Object.prototype
                   ⟶ null

代码示例

为了演示上述关系,以下是实际的代码示例及相关注释:

javascript
// 定义构造函数 Foo
function Foo() {}

// 创建实例 f1
var f1 = new Foo();

console.log(Foo.__proto__);       // Function.prototype
console.log(Foo.prototype);       // Foo.prototype 是一个对象,包含 constructor 属性
console.log(Foo.prototype.constructor); // Foo

console.log(f1.__proto__);        // Foo.prototype
console.log(f1.constructor);      // Foo

console.log(Function.__proto__);  // Function.prototype
console.log(Function.prototype.constructor); // Function

// 原型链的终点是 Object.prototype
console.log(Foo.__proto__.__proto__);        // Object.prototype
console.log(Function.prototype.__proto__);   // Object.prototype
console.log(Foo.prototype.__proto__);        // Object.prototype
console.log(Object.prototype.__proto__);     // null

详细解释

  1. FooFunction 的一个实例

    • Foo.__proto__ === Function.prototype (true)
    • 这是因为 Foo 作为一个构造函数,实际上是由 Function 构造出来的。
  2. f1 的原型链

    • f1.__proto__ === Foo.prototype (true)
    • 这是因为 f1 是通过 new Foo() 创建的实例,所以它的 __proto__ 属性指向 Foo.prototype
  3. 构造函数 constructor

    • Foo.prototype.constructor === Foo (true)
    • f1.constructor === Foo (true)
    • 这是因为在定义构造函数 Foo 时会自动创建 Foo.prototype 对象,其中包含 constructor 属性,指向 Foo 函数本身。
  4. 最终的原型链

    • Foo.__proto__ 指向 Function.prototype
    • f1.__proto__ 指向 Foo.prototype
    • Function.__proto__ 指向 Function.prototype
    • Function.prototype.__proto__ 最终指向 Object.prototype
    • Object.prototype.__proto__null,表示该原型链的终点。

其他案例

js
function DoSomething(){}
console.log( DoSomething.prototype );

DoSomething.prototype.name = 'wang'
// {
//     name: "wang",  ------------> name 属性是DoSomething自己的所以不是从他的copy原型来的,因此不再__proto__
//     constructor: ƒ DoSomething(),
//     __proto__: {    -----------------> 这里的原型链指向的是Object,这证明最开始说的'Object'就是所有对象的原型'
//         constructor: ƒ Object(),
//         hasOwnProperty: ƒ hasOwnProperty(),
//         isPrototypeOf: ƒ isPrototypeOf(),
//         propertyIsEnumerable: ƒ propertyIsEnumerable(),
//         toLocaleString: ƒ toLocaleString(),
//         toString: ƒ toString(),
//         valueOf: ƒ valueOf()
//     }
// }

const doSomething = new DoSomething()
doSomething.age = 17
console.log( doSomething) 
// {
//     age: 17, ---------》 age 属性是doSomething 的因此不再__proto__
//     __proto__: { ---------》doSomething 是从DoSomething克隆来的因此一层原型链指向是DoSomething
//         name: "wang",
//         constructor: ƒ DoSomething(),
//         __proto__: { -----------------》DoSomething 是从Object 来的因此第二层是在Object
//             constructor: ƒ Object(),
//             hasOwnProperty: ƒ hasOwnProperty(),
//             isPrototypeOf: ƒ isPrototypeOf(),
//             propertyIsEnumerable: ƒ propertyIsEnumerable(),
//             toLocaleString: ƒ toLocaleString(),
//             toString: ƒ toString(),
//             valueOf: ƒ valueOf()
//         }
//     }
// }

总结

1. Object 作为一个类(函数),是 Function 类的实例

  • Object instanceof Function => true
  • Object.__proto__ === Function.prototype

2. Function 作为一个类(函数),是 Function 类的实例

  • Function instanceof Function => true
  • Function.__proto__ === Function.prototype

解释:只有这样我们才可保证所有函数都可调用 callapplybind 等方法。

3. 函数也是对象,Function 作为一个普通对象,它是 Object 类的实例

  • Function instanceof Object => true
  • Function.__proto__.__proto__ === Object.prototype

4. Object 作为一个普通对象,它是 Object 类的实例

  • Object instanceof Object => true
  • Object.__proto__.__proto__ === Object.prototype

冷门知识

不具备prototype的函数

  • 箭头函数
  • 基于ES6给对象某个成员赋值函数值的快捷操作

Released under the MIT License.