js 原型
js 所有复杂类型都是对象类型,但是 js 不是一门完全面向对象的语言 如何继承就是个问题。
前置知识
- 构造函数 因为在 es6 之前是没有 class 的概念的,所以 js 采用构造函数实现继承
- 构造函数创建一个实例的过程
- 创建一个新对象
- 将构造函数的作用域赋值给新对象(就是将 this 指向为新对象)
- 执行构造函数的代码(就是为新对象添加实例属性和方法)
- 返回新对象
原型对象
js 在每个函数创建的时候都会生成一个 prototype 属性,这个属性指向一个空对象,这个 prototype 就是该函数的原型对象,并且原型对象中有一个 constructor 属性,指向该函数,这样函数和原型之间就有了联系。
原型链
先看一段代码
function Animal(name, color) {
this.name = name;
this.color = color;
}
Animal.prototype.bar = function (voice) {
console.log(`${this.name}的叫声是${voice}`);
};
const dog = new Animal('dog', 'black');
dog.bar('汪汪汪'); //dog的叫声是汪汪汪
这里就要考虑个问题了,bar 方法是构造函数的原型对象上的方法,但是明明只有在构造函数内部通过 this 来赋值的属性或者方法才可以被实例所继承,那么为什么构造函数的原型对象上的属性和方法是怎么被实例所调用的呢,答案就是原型链 其实,每个通过构造函数创建出来的实例对象,本身都有一个__proto__属性,也叫作隐式原型,这个属性会指向该实例对象的构造函数和原型对象
注意
隐式原型__proto__并不是 js 语言本身的特性,这是浏览器在具体实现的时候添加的私有属性,但是在生产环境中并不建议直接使用该属性,可以使用
Object.getProtoTypeOf()获取实例对象的原型,并为原型添加属性和方法
访问对象属性的过程
当访问一个对象的某个属性时,会先在这个对象的本身查找,类似于dog.hasOwnProperty(),如果找不到就会通过隐式原型__proto__向上寻找它构造函数的原型对象,如果找不到,则会在构造函数的原型对象的__proto__查找,就这样一层一去找,这就形成了原型链
注意
这里有一个比较特殊的地方,当我们修改实例的隐式原型时,会改变实例对应的构造函数的原型对象,这样会被所有实例共享。