构造函数与原型
JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。
构造函数就是一个普通的函数,但是有自己的特征和用法。
var Vehicle = function () {
this.price = 1000;
};
上面代码中,Vehicle就是构造函数。为了与普通函数区别,构造函数名字的第一个字母通常大写。
构造函数的特点有两个:
函数体内部使用了this关键字,代表了所要生成的对象实例。
生成对象的时候,必须使用new命令。
function Dog(name, color) {
this.name = name
this.color = color
this.bark = function() {
console.log('wangwang~')
}
}
const dog1 = new Dog('dog1', 'black')
const dog2 = new Dog('dog2', 'white')
上述代码就是声明一个构造函数并通过构造函数创建实例的过程,有两个实例被创建,它们有自己的名字、颜色,但它们的bark
方法是一样的,而通过构造函数创建实例的时候,每创建一个实例,都需要重新创建这个方法,再把它添加到新的实例中。这无疑造成了很大的浪费,既然实例的方法都是一样的,为什么不把这个方法单独放到一个地方,并让所有的实例都可以访问到呢。
这里就需要用到原型(prototype):
- 每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype属性指向的原型对象就成为实例的原型对象。
- 原型对象默认拥有一个constructor属性,指向指向它的那个构造函数(也就是说构造函数和原型对象是互相指向的关系)。
- 每个对象都拥有一个隐藏的属性[[prototype]],指向它的原型对象,这个属性可以通过
Object.getPrototypeOf(obj)
或obj.__proto__
来访问。 - 实际上,构造函数的prototype属性与它创建的实例对象的[[prototype]]属性指向的是同一个对象,即
对象.__proto__ === 函数.prototype
。 - 如上文所述,原型对象就是用来存放实例中共有的那部分属性。
- 在JavaScript中,所有的对象都是由它的原型对象继承而来,反之,所有的对象都可以作为原型对象存在。
- 访问对象的属性时,JavaScript会首先在对象自身的属性内查找,若没有找到,则会跳转到该对象的原型对象中查找,如果还没有找到,就一直上溯到
Object.prototype
对象,最后,如果还没有找到,就只能返回undefined
。
将上述代码稍微做些修改,这里把bark方法放入Dog构造函数的原型中:
function Dog(name, color) {
this.name = name
this.color = color
}
Dog.prototype.bark = function() {
console.log('wangwang~')
}
const dog1 = new Dog('dog1', 'black')
dog1.bark() // 'wangwang~'
创建另一个实例并重写它的bark方法,然后再次分别调用两个实例的bark方法并观察结果:
const dog2 = new Dog('dog2', 'white')
Dog2.prototype.bark = function() {
console.log('haha~')
}
dog1.bark() //'wangwang~'
dog2.bark() //'haha~'
这里dog2重写bark方法并没有对dog1造成影响,因为它重写bark方法的操作实际上是为自己添加了一个新的方法使原型中的bark方法被覆盖了,而并非直接修改了原型中的方法。若想要修改原型中的方法,需要通过构造函数的prototype属性:
Dog.prototype.bark = function() {
console.log('miaomiao~')
}
dog1.bark() //'miaomiao~'
dog2.bark() //'miaomiao~'
这样看起来就没什么问题了,将实例中共有的属性放到原型对象中,让所有实例共享这部分属性。如果想要统一修改所有实例继承的属性,只需要直接修改原型对象中的属性即可。而且每个实例仍然可以重写原型中已经存在的属性来覆盖这个属性,并且不会影响到其他的实例。
原型链
JavaScript中所有的对象都是由它的原型对象继承而来。而原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)。
所有原型链的终点都是Object函数的prototype属性,因为在JavaScript中的对象都默认由Object()构造。Objec.prototype指向的原型对象同样拥有原型,不过它的原型是null,而null则没有原型。
上述代码中的原型链:
dog1 ----> Dog.prototype ----> Object.prototype ----> null
重要公式:
var 对象 = new 函数()
对象.__proto__ === 对象的构造函数.prototype
// 推论
var number = new Number()
number.__proto__ === Number.prototype
Number.__proto__ === Function.prototype // 因为 Number 是 Function 的实例
var object = new Object()
object.__proto__ === Object.prototype
Object.__proto__ === Function.prototype // 因为 Object 是 Function 的实例
var f = new Function()
f.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // 因为 Function 是 Function 的实例!
参考链接:
作者:clancysong
链接:https://juejin.im/post/5a94c0de5188257a8929d837
网友评论