构造函数和原型对象
构造函数就是一个函数,构造函数和普通函数的区别在于使用 new 生成实例的函数就是构造函数,直接调用的就是普通函数。
当通过构造函数创建对象时,JS 中的所有函数都有 prototype 属性,这个属性引用了一个对象,这个对象就是调用该构造函数而创建的实例对象的原型。
当通过字面量方式创建对象时,就相当于调用 Object 构造函数创建对象,所以它的原型就是 Object 构造函数的 prototype 属性引用的对象。
所以构造函数和实例对象的原型之间的关系为:
既然构造函数中有一个属性 prototype 指向原型对象,在每个原型也都有一个 constructor 属性指向关联的构造函数。当然,使用普通函数创建的实例也会有 constructor 属性。对于一个引用类型的 constructor 是可以修改的,但是对于基本类型是只读的。
function Person() {}
Person === Person.prototype.constructor // true
所以更新下关系图为:
实例和构造函数
JS 中的每个对象都有一个属性 __proto__,这个属性会指向该对象的原型。
function Person() {}
const person1 = new Person()
person1.__proto__ === Person.prototype // true
综合构造函数和原型之间的关系,实例和构造函数之间的关系为:
原型链
因为原型对象也是一个对象,所以它也有一个 __proto__ 属性,这个属性指向它的构造函数的 prototype。
所以我们可以更新下关系图:
那哪里是尽头呢?
所有普通的原型链最终都会指向内置的 Object.prototype, 而 Object.prototype 作为一个对象,它的 __proto__ 是什么呢?是 null。
console.log(Object.prototype.__proto__ === null) // true
所以关系图画到 Object.prototype 就可以停止了。
那原型链就是由 __proto__ 连接起来的链状结构。
当然,构造函数是函数对象,是 Function 创建的实例,所以构造函数也有 __proto__ 属性,指向 Function.prototype。
实例与原型
原型对象中定义的任何属性都会被该构造函数创建的所有对象继承。
function Person() {}
Person.prototype.name = 'keer'
const person1 = new Person()
const person2 = new Person()
console.log(person1.name) // "keer"
console.log(person2.name) // "keer"
当读取实例的属性时,如果找不到,就会沿着上图画的原型链一直查找下去,一直查到原型链的最顶端,直到找到为止。
function Person() {}
Person.prototype.name = 'keer'
const person1 = new Person()
person1.name = 'kuo'
console.log(person1.name) // "kuo"
delete person1.name
console.log(person1.name) // "keer"
在写一个属性的值的时,JS 并不使用原型对象。
因为原型对象的属性被一个类的所有对象共享,所以通常只用它们来定义类中所有对象的相同的属性。