JavaScript|对象原型的理解

认识对象的原型

JavaScript当中每个对象都有一个特殊的内置属性 [[prototype]],这个特殊的对象可以指向另外一个对象。

那么这个对象有什么用呢?
当我们通过引用对象的属性key来获取一个value时,它会触发 [[Get]]的操作;
这个操作会首先检查该属性是否有对应的属性,如果有的话就使用它;
如果对象中没有该属性,那么会访问对象[[prototype]]内置属性指向的对象上的属性;

获取对象的原型

obj = {
    name: 'John',
    age: 30,
}

console.log(obj.__proto__); // [Object: null prototype] {}
console.log(Object.getPrototypeOf(obj)); // [Object: null prototype] {}

原型作用

当我们从一个对象中获取某一个属性时, 它会触发 [[get]] 操作

  1. 在当前对象中去查找对应的属性, 如果找到就直接使用
  2. 如果没有找到, 那么会沿着它的原型去查找 [[prototype]]

函数的原型 prototype

所有的函数都有一个prototype的属性:

function Person() {
    this.name = 'John';
    this.age = 30;
}

console.log(Person.prototype); // [Object: null prototype] {}
// 因为它是一个函数,才有了这个特殊的属性,而不是因为它是一个对象

new 操作符

  1. 在内存中创建一个新对象
  2. 将新对象的原型指向构造函数的prototype属性
function Person() {

}

var p1 = new Person()
var p2 = new Person()

console.log(p1.__proto__ === p2.__proto__); // true
console.log(p1.__proto__ === Person.prototype); // true
62e7cb8bd9d5e

constructor 属性

事实上原型对象上面是有一个属性的:constructor,默认情况下原型上都会添加一个属性叫做constructor,这个constructor指向当前的函数对象;

function Person() {

}

p1 = new Person();

console.log(Person.prototype); // [Object: null prototype] {}
console.log(p1.__proto__.constructor); // [Function: Person]
console.log(p1.__proto__.constructor.name); // Person

利用getOwnPropertyDescriptors方法可以获取到所有的属性和属性描述符
通过这种方法我们可以发现constructor属性是一个属性描述符,它有一个value属性,指向当前的函数对象

console.log(Object.getOwnPropertyDescriptors(foo.prototype)) // {constructor: {value: [Function: Person], writable: true, enumerable: false, configurable: true}}

Object.defineProperty(foo.prototype, "constructor", {
  enumerable: true,
  configurable: true,
  writable: true,
  value: "哈哈哈哈"
})
console.log(foo.prototype) // {constructor: "哈哈哈哈"}

原型对象的constructor

如果我们需要在原型上添加多种属性,那么可以重新定义整个原型对象,这样就可以添加多个属性了;

function Person() {

}

Person.prototype = {
    name: 'John',
    age: 30,
    sayHello: function() {
        console.log('Hello');
    }
};

var p1 = new Person();

console.log(p1.name, p1.age); // John 30
p1.sayHello(); // Hello

每创建一个函数, 就会同时创建它的prototype对象, 这个对象也会自动获取constructor属性;而我们这里相当于给prototype重新赋值了一个对象, 那么这个新对象的constructor属性, 会指向Object构造函数, 而不是Person构造函数了

console.log(f1.constructor) // [Function: Object]

// 真实开发中我们可以通过Object.defineProperty方式添加constructor
Object.defineProperty(Person.prototype, "constructor", {
  enumerable: false,
  configurable: true,
  writable: true,
  value: Person
})

console.log(f1.constructor) ; // [Function: Person]

利用原型和构造函数创建对象

上一篇中我讲到通过构造函数创建对象时,会创造出重复的函数,这里我们可以通过原型对象来创建对象,这样就不会创造出重复的函数了;

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.sayHello = function() {
    console.log(this.name + 'Hello');
}

Person.prototype.sayGoodbye = function() {
    console.log(this.name + 'Goodbye');
}

var p1 = new Person('John', 30);
var p2 = new Person('Mary', 25);

p1.sayHello(); // JohnHello
p2.sayGoodbye(); //MaryGoodbye