JavaScript原型,原型链

112 阅读4分钟

原型和构造函数密切相关,了解原型之前,我们先回顾一下构造函数。

一、构造函数

像 Object 和 Array 这样的原生构造函数,运行时可以直接在执行环境中使用。当然也可以自定义构造函数,以函数的形式为自己的对象类型定义属性和方法。——JavaScript高级程序设计

function Person(name, age){
    this.name = name
    this.age = age
    this.hobby= function(){
        console.log('sport')
    }
}
const person1= new Person('Lisa', 18)
const person2 = new Person('Jony', 20)

person1.hobby()
person2.hobby()

需要注意的是,构造函数名称的首字母都是要大写的。函数体内使用this关键字,代表所要生成的对象实例。任何函数只要使用 new 操作符调用就是构造函数。

构造函数虽然有用,但是也存在着浪费内存的问题:每创建一个对象,都会在堆内存空间中新开辟一个空间来存储方法。这个问题可以通过引入原型对象来解决。

二、原型

原型就是一个对象,也叫原型对象。

1. 理解原型

  • 每个函数都会创建一个 prototype 属性,属性值是一个普通对象。
function Person(){}
console.dir(Person)

1.png

  • 原型上默认有一个叫做constructor的属性,指向这个构造函数本身。
console.log(Person.prototype.constructor === Person) // true
  • 使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型。
function Person(){}
Person.prototype.name = "Mike";
Person.prototype.age = 7;
// 公共的方法写到原型对象上  节约了内存
Person.prototype.hobby= function(){
    console.log('sport')
}

let person1 = new Person()
const res = person1.name 
console.log(res)  // "Mike"

let person2 = new Person()
const res = person2.name 
console.log(res)  // "Mike"

console.log(person1.hobby== person2.hobby)  // true

2. __proto__隐式原型

  • 所有对象,都有一个__proto__属性,指向原型对象。
  • 实例通过__proto__访问原型对象。
  • 对象的隐式原型指向构造函数的显示原型
console.log(person1.__proto__ === Person.prototype)   //true

proto 表示了实例和原型之间的一个关系,相当于就是一个桥梁,实例通过它访问原型。

三、构造函数、原型、实例的关系

  1. 构造函数.prototype ——> 原型对象
  2. 原型对象.constructor ——> 构造函数
  3. 实例对象.__ proto__ ——> 原型对象
  4. 构造函数 new , 创建出实例对象

2.png

实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有直接联系。

四、原型的五条规则

  1. 所有的引用类型(对象 数组 函数),都具有对象的特征,可以自由扩展属性。
  2. 每一个对象都有一个__proto__属性,这个属性是一个对象。
  3. 所有的函数都有一个 prototype 属性,这个属性也是一个对象。
  4. 所有对象的__proto__属性值,都指向它的构造函数 prototype 属性值。
  5. 实例化对象想要获取的属性值如果构造函数没有,它会向构造函数的原型中寻找,如果原型也没有,则会向原型的__proto__中寻找,直到拿到或没有该属性值。

五、原型链

原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

function Person(name){
    this.name = name
}
const person = new Person()
  1. 对象的隐式原型指向构造函数的显示原型。
console.log(person.__proto__ === Person.prototype)  // true
  1. 所有的对象都有一个__proto__,属性值也是一个对象,Person.prototype 也是一个对象,所以,它也有__proto__属性,属性值也是对象。原型上默认有一个 constructor 属性,指回的构造函数,所以 Person.prototype 的构造函数是 Object。
console.log(Person.prototype.__proto__)  // __proto__ 访问到原型
// Person.prototype 这个原型对象 是Object这个构造函数创建的
console.log(Person.prototype.__proto__ === Object.prototype)  // true
  1. Object 是一个构造函数,Object.prototype 得到的也是一个原型,所以 Object.prototype 这个原型上默认有一个constructor属性,指向构造函数本身。
Object.prototype.constructor === Object  // Object.prototype指向构造函数本身
console.log(Person.prototype.__proto__ === Object.prototype)    // true
  1. Object.prototype 也是一个原型对象,所以它也有一个__proto__属性。
// 正常的原型链终止到Object.prototype, 也就是 Object.prototype.__proto__ === null
console.log(Object.prototype.__proto__ === null)  // true

根据以上结论,我们用一张图来更加清晰的理解原型链:

3.png

简单概况一下:

每个对象通过__proto__属性都能访问到它的原型对象,原型对象也有它的原型对象,当访问一个对象属性或方法的时候,先在自身中寻找,如果没有,就会沿着__proto__这条链向上查找,直到最顶层 Object.prototype 为止,最后 Object.prototype.proto === null。