JavaScript 原型链 | 青训营

86 阅读5分钟

JavaScript的原型链是一种特殊的对象连接机制,它用于实现继承和属性查找。理解原型链是理解JavaScript中继承和对象之间关系的关键

在JavaScript中,每个对象都有一个原型(prototype)属性,它指向另一个对象

当我们访问一个对象的属性时,如果该对象本身没有该属性,JavaScript引擎就会去查找原型对象是否有这个属性,如果原型对象也没有,它会继续沿着原型链向上查找,直到找到属性或者到达原型链的顶端,即Object.prototype,这样就形成了一个由对象组成的链条,这就是原型链

// 定义一个构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 在Person的原型上定义一个方法
Person.prototype.sayHello = function () {
    console.log("Hello, my name is " + this.name);
};

// 构造函数具有prototype属性
console.log(Person.prototype)       // 输出内容包括 sayHello() 方法

// 创建一个实例
const person1 = new Person("Alice", 30);
// 访问实例的属性和方法
console.log(person1.name); // 输出 "Alice"
console.log(person1.age);  // 输出 30
person1.sayHello();        // 输出 "Hello, my name is Alice"

自定义构造函数情况特殊

如果直接使用 Person() 调用构造函数而没有使用 new,则构造函数内部的 this 不会被绑定到新的对象实例上

在非严格模式下,this 将默认指向全局对象(在浏览器中为 window 对象)

let per1 = Person("Alice")
let per2 = new Person("Bob")
console.log("per1", per1)       // 输出 undefined
console.log(this.name)          // 将会输出 Alice(神奇)
console.log(name)               // 将会输出 Alice(神奇)
console.log(window.name)    // 将会输出 Alice(神奇)
console.log("per2", per2)

非函数对象(字面量方式创建)

const person = { name: "Alice", age: 30 };

//非函数对象不具有prototype属性
console.log("person", person.prototype);      // 输出 undefined

基本数据类型

let str = "123"

//基本数据类型不具有prototype属性
console.log("str", str.prototype)          // 输出 undefined

基本数据类型是不可变的,它们没有自己的属性和方法,然而,有时我们需要对基本数据类型进行一些对象的操作,比如调用方法或访问属性

这时,JavaScript 会对基本数据类型进行隐式的装箱操作,调用str.length存在隐式装箱操作

过程:

  1. 创建String类型的一个实例
  2. 在实例中调用指定方法或属性
  3. 销毁这个实例

·

console.log("str", str.length)      // 输出 3

// 以上过程等价于(显式装箱)
let strObj = new Object(str)
console.log("str", strObj.length)   // 输出 3
strObj = null

Function 构造函数动态创建函数

//参数列表:(arg1, arg2, ..., body)
const sum = new Function('a', 'b', 'return a + b;');
console.log(sum(2, 3));         // 输出 5

const greet = new Function('name', 'console.log("Hello, " + name + "!");');
greet('John'); // 输出 "Hello, John!"

在 JavaScript 中,几乎所有的构造函数都是由 Function 构造函数生成的,因为在 JavaScript 中,函数本身也是对象

function fn() { }
let f = new fn()

console.log(fn.constructor === Function)            // 输出 true
console.log(Object.constructor === Function)        // 输出 true
console.log(String.constructor === Function)        // 输出 true
console.log(Person.constructor === Function)        // 输出 true
console.log(Function.constructor === Function)        // 输出 true

// constructor 在实例对象中是一个属性,这个属性指向构造了这个实例的构造函数
console.log(person1.constructor === Person)     // 输出 true
// 而字面量方式创建的对象,它的 constructor 指向 Object
console.log(person.constructor === Function)        // 输出 false
console.log(person.constructor === Object)        // 输出 true

在JavaScript中,一个对象的__proto__属性指向这个对象的构造函数的 prototype 属性(这个对象的原型)

// fn 是函数对象
console.log(fn.__proto__ === fn.constructor.prototype)  // 输出 true
console.log(fn.__proto__ === Function.prototype)        // 输出 true
// f 是普通对象
console.log(f.__proto__ === fn.prototype)               // 输出 true
console.log(person1.__proto__ === Person.prototype) // 输出 true

// 此外
console.log(fn.prototype.constructor === fn)        // 输出 true
console.log(fn.prototype.constructor.prototype === fn.prototype)    // 输出 true

console.log(String.__proto__ === Array.__proto__)       // 输出 true(?)

// 构造函数的原型是个原型对象
// 既然 fn.prototype 是个对象,那么
console.log(fn.prototype instanceof Object)     // 输出 true

// 而一个对象的 __proto__ 为其构造函数的 prototype,如上述所言
console.log(fn.prototype.__proto__ === Object.prototype)    // 输出 true

// 函数有 prototype 和 __proto__
console.log(fn.prototype)
console.log(fn.__proto__)           // 输出 f () { [native code] }
console.log(Array.prototype)
console.log(Array.__proto__)    // 输出 f () { [native code] }
console.log(Function.prototype)   // 输出 f () { [native code] }
console.log(Function.__proto__)    // 输出 f () { [native code] }

// 对象只有 __proto__,没有 prototype
console.log(f.prototype)        // 输出 undefined
console.log(f.__proto__)

// fn 的 prototype 指向自身的原型对象
// 既然是原型对象,就没有 prototype 属性了
console.log(fn.prototype.prototype)     // 输出 undefined

// 由于一个对象的 __proto__ 属性指向这个对象的构造函数的 prototype 属性
// 那么
console.log(fn.prototype.__proto__ === Object.prototype)    // 输出 true

console.log(fn.prototype.constructor === fn)        // 输出 true
console.log(fn.constructor === Function)        // 输出 true

原型链的尽头是null

console.log(Object.prototype.__proto__)     // 输出 null
console.log(Object.prototype)

person1 的__proto__是 Person 的 prototype

Person.prototype 是个对象

所以说 Person.prototype 这一对象的__proto__,它指向 Object 的 prototype

Object.prototype 是个对象

Object.prototype 这一对象的__proto__,原本应该指向『其构造函数』的 prototype

但由于 Object.prototype 已经是原型链的顶端

所以 Object.prototype 这一对象的__proto__,它指向null,即原型链的尽头

也就是说 Object 不再有原型

console.log(person1.__proto__.__proto__.__proto__)      // 输出 null
console.log(Function.prototype.__proto__.__proto__)     // 输出 null

Function.prototype 是所有函数对象的原型,构造函数也是一个函数对象,函数对象都继承自 Function.prototype

console.log(fn instanceof Function)     // 输出 true
console.log(fn.__proto__ === Function.prototype)    // 输出 true

因此构造函数的原型也是 Function.prototype

.__proto__ 访问对象的构造函数的原型

console.log(fn.constructor.__proto__ === Function.prototype)
console.log(fn.constructor.__proto__ === Set.__proto__)

Array.prototype 是 Array 的原型对象,不是函数,因此.__proto__是 Object 的构造函数的 prototype 属性

console.log(Array.prototype.__proto__)
console.log(Array.prototype.__proto__ === Array.prototype.constructor.prototype)    // 输出 false
console.log(Array.prototype.__proto__ === Object.prototype)         // 输出 true

函数的 prototype.constructor 属性又指回这个函数本身

console.log(Array.prototype.constructor === Array)      // 环形引用,输出 true
console.log(Array.prototype.constructor.prototype === Array.prototype)      // 环形引用,输出 true

函数的原型是 Function.prototype

console.log(Array.prototype.constructor.__proto__ === Function.prototype)       // 输出 true
// String 本身就是构造函数,因此.__proto__直接获取到构造函数的原型
console.log(String.__proto__ === Function.prototype)        // 输出 true
console.log(Object.__proto__ === Object.constructor.prototype)      // 输出 true
console.log(Object.constructor.prototype === Function.prototype)    // 输出 true
console.log(Object.constructor.__proto__ === Function.prototype)    // 输出 true
console.log(Object.constructor.prototype === Object.constructor.__proto__)  // 输出 true
console.log(Function.__proto__ === Function.prototype)      // 输出 true
console.log(Object.__proto__ === Function.__proto__)        // 输出 true

总结

每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性 ,__proto__ 指向创建该对象的构造函数的 prototype

使用构造函数创建对象后,创建的对象不会拥有 prototype 属性,但是它会拥有创建它的构造函数的 prototype 属性内部的所有内容

console.log(Person.prototype)
console.log(person1.prototype)      // 输出 undefined
// person1 具有 Person 构造函数 prototype 中的 sayHello() 方法
console.log(person1.sayHello)

当试图访问一个对象的某个属性时,JavaScript 会首先在该对象自身的属性中查找

如果没有找到,它就会沿着原型链往上查找,直到找到为,如果整个原型链上都没有找到这个属性,那么返回 undefined