js面向对象编程之constructor、prototype、__proto__

760 阅读4分钟

在Javascript中没有类的概念,也就没有父类和实例的区分,在Javascript中实现继承主要是基于constructor,也就是构造函数,至于Javascript的继承的设计思想,阮一峰老师的一片文章说的很清楚,从Javascript的诞生讲了Javascript的设计思想————Javascript继承机制的设计思想

constructor

constructor,也就是构造函数,本质上就是一个函数,其内部使用this变量。对构造函数使用 new 运算符就能得到实例,并且this指向会绑定到实例对象上。

function Person(name) {
    this.name = name;
}
const p = new Person('小明')
p // {name: '小明'}

例子中,p是通过new Person得到的实例,所以,p的构造函数就是Personp.constructor === Person)。

所以,一个对象的constructor属性,指向的就是使其诞生的那个函数,在java等语言中,也就是父类,父类中是有一个构造函数的——constructor,而Javascript中没有类的概念,就直接使用constructor来得到实例对象,仅此而已。

new关键字实例化得到的对象,其constructor就指向new关键字之后的那个函数;那如果是使用Object.create方法生成的实例呢?此时的对象的构造函数又是谁呢?

const obj = {
    name: 'xiaohong'
}
const a = Object.create(obj)

console.log(obj.constructor)  // ƒ Object() { [native code] }
a.constructor === Object //true

从上面的例子可以看出,使用 Object.create 方法得到的实例对象,其 constructor 指向的是 Object

constructor的缺点

我们把对象的属性写进构造函数,需要的时候就通过 new 运算符得到实例,很好用,但是存在一个问题,就是内存浪费很严重。

在所有实例中都有的属性和方法,如果写在构造函数中,那么,每生成一个实例,就会创建一遍这些属性和方法,但是,我们的属性和方法可能是所有实例都一样的

function Person(name){
    this.name = name;
    this.age = 12;
    this.eat = function (){
        console.log(1)
    }
}

此例子中的 age 属性和 eat 方法每个实例都会有,但是他们是不会发生变化的,所以就存在了内存浪费,那可不可以将这些属性和方法放在一个地方,然后所有的实例公用呢?当然可以,那就是 prototype。

prototype

前面已经讲了将所有属性与方法都放在 constructor 中缺点,那如何解决这个缺点呢?就是Prototype 模式

在 javaScript 中,每一个构造函数都有一个 prototype属性,这个属性指向另一个对象。这个对象的所有属性和方法都会被构造函数的实例继承。

所以我们可以把那些不变的属性和方法放在 prototype 上

function Person(name) {
    this.name = name;
}
Person.prototype.age = 12;
Person.prototype.eat = function() {console.log(1)};

const person1 = new Person('小明');
const person2 = new Person('小王');

console.log(person1.age); // 12
console.log(person2.age); // 12
// eat方法也是一样的。。。

那如何判定一个对象是否是存在另一个对象的原型链上呢?

  1. isPrototypeOf 这个方法用来判断一个对象是否在另一个对象的原型链上。

使用方式:

Person.prototype.isPrototypeOf(person1) // true
  1. instanceof instanceof 同 isPrototypeOf作用类似,也是用来判断一个对象是否在另一个对象的原型链上的,其区别在于 instanceof 对 person1 的原型链是针对Person.prototype 去检查的,而不是 Person 本身。

使用方式:

person1 instanceof Person  // true。
  1. hasOwnProperty 每个实例对象都有一个 hasOwnProperty 方法,用来判断属性是本地属性,还是原型属性。

使用方法:

person1.hasOwnProperty('name') // false 表示是本地属性
  1. in in 方法用来判断指定的属性是否在指定的对象或其原型链中

使用方法:

'name' in person1 // true

__proto__属性

前面已经讲过每个构造函数都有一个 prototype 属性,而其实例对象都能访问到 prototype 中的属性跟方法,那么,我们如何直接通过实例对象取到构造函数的 prototype对象呢?那就是通过 __proto__属性

每个对象都有一个__proto__属性,其指向的就是构造函数(父类)的prototype(原型对象)

function Person(name) {
    this.name = name;
}
Person.prototype.age = 12;
Person.prototype.eat = function() {console.log(1)};

const person1 = new Person('小明');
const person2 = new Person('小王');

person1.__proto__ === Person.prototype // true
person1.__proto__ === Person.prototype // true

Person.__proto__ === Function.prototype // true
Person.constructor === Function // true

从例子中可以看出,实例对象的__proto__属性指向的就是其构造函数的 prototype,而构造函数的__proto__指向的是 Function.prototype,这样所有对象的prototype通过__proto__链接起来形成的一条链,就是原型链了。

总结

  • constructor 就是构造函数,与普通函数的区别就是,构造函数首字母一般大写
  • 每个构造函数都有一个 prototype 对象,称为原型对象,原型对象上的属性与方法在所有实例中共享
  • 每个对象都有一个__proto__属性,其指向的就是实例化改对象的构造函数的prototype对象

至此,关于js中constructor、prototype、__proto__三者之间的关系就已经讲完了。