什么是原型
原型是JavaScript继承的基础,JavaScript的继承就是基于原型的继承。
我们创建的每一个函数都有一个prototype属性,它指向一个对象,而这个对象可以包含由特定类型创建的所有实例共享属性和方法。 那么prototype就是调用构造函数创建的对象实例的原型对象。
使用原型对象的好处:
可以让所有的对象实例共享它所包含的属性和方法。
构造函数、实例、原型对象的关系
每个构造函数都有一个原型对象
针对每一个函数,都有prototype属性,该属性是一个对象。
每个原型对象都有一个指向构造函数的指针
针对每一个对象,都有一个__proto__指针,指向构造函数的原型对象。
每个实例都由一个指向原型对象的指针
proto 通常称为隐式原型,prototype 通常称为显式原型,可以说一个对象的隐式原型指向了该对象的构造函数的显式原型。在显式原型上定义的属性方法,通过隐式原型传递给了构造函数的实例。这样一来实例就能很容易的访问到构造函数原型上的方法和属性了。
function Foo() {};
const a = new Foo();
a.__proto__ === Foo.prototype; // true
原型链
prototype机制就是存在于对象中的一个内部链接,它会引用其他对象。
如果在当前对象没有找到需要的属性或者方法引用,引擎就会沿着prototype关联的对象继续查找,以此类推。这一系列对象的链接被称为原型链。
Object.prototype是原型链的最顶端,它的__proto__仍然存在,值为null
构造函数
构造函数上可以附带 实例成员 和 静态成员
实例成员: 实例成员就是在构造函数内部,通过this添加的成员。实例成员只能通过实例化的对象来访问。
静态成员: 在构造函数本身上添加的成员,只能通过构造函数来访问。
function Star(name,age) {
//实例成员
this.name = name;
this.age = age;
}
//静态成员
Star.sex = '女';
let stars = new Star('小红',18);
console.log(stars); // Star {name: "小红", age: 18}
console.log(stars.sex); // undefined 实例无法访问sex属性
console.log(Star.name); //Star 通过构造函数无法直接访问实例成员
console.log(Star.sex); //女 通过构造函数可直接访问静态成员
复制代码
扩展
new一个新对象的过程发生了什么?
- 创建一个空对象son {}
- 为该对象准备原型链连接 son.proto = Father.prototype
- 重新绑定this,使构造函数的this指向新对象
- 为新对象属性赋值
- 返回this,此时新对象就拥有了构造函数的属性和方法
通过构造函数创建的每个实例的方法是共享的吗?
不一定。分两种情况讨论:
- 在构造函数上直接定义的方法不共享。
function Star() {
this.sing = function () {
console.log('我爱唱歌');
}
this.name = 'leemo';
}
let stu1 = new Star();
let stu2 = new Star();
stu1.sing();//我爱唱歌
stu2.sing();//我爱唱歌
console.log(stu1.sing === stu2.sing); // false
console.log(stu1.name === stu2.name); // true
复制代码
stu1和stu2为两个不同的实例,指向的不是同一块内存。每次生成实例都会新开辟一块内存存储属性和方法。
如果实例的属性是基本类型,不存在共享问题,是否相同要看具体值的内容是否相同。
- 通过原型添加的方法是共享的。
function Star(name) {
this.name = name;
}
Star.prototype.sing = function () {
console.log('我爱唱歌', this.name);
};
let stu1 = new Star('小红');
let stu2 = new Star('小蓝');
stu1.sing();//我爱唱歌 小红
stu2.sing();//我爱唱歌 小蓝
console.log(stu1.sing === stu2.sing); //true
复制代码
构造函数通过原型分配的函数,是所有对象共享的。
综上所述得出定义构造函数的规则: 公共属性定义到构造函数里面,公共方法放到原型对象上。
Object 和 Function 的关系
Object.prototype是一切对象的根源,根源之上在没有其他。
Function的prototype是一个内置函数,一切函数都派生自这个内置函数。该内置函数对象的prototype指向根源对象。
Function.prototype.__proto__ === Object.prototype
所以,Object跟Function二者之间的联系为:所有函数的默认原型都是Object的实例,二者通过根源对象联系起来。
Function和Object,既是函数,又是对象。因为都可以Function()或者Object()这样的方式执行,又可以Function.a = 'a',Object.a = 'a'这样赋值。
说它们是函数,是因为他们都是通过“内置函数工厂”派生出来的,因而具备函数的特性。
说他们是对象,是因为他们都是通过”根源“对象派生出来,因此具备对象的特征。
Function.prototype指向“内置函数”。而Object.prototype指向“根源对象”
现在可以更好地理解这张图:
Parent既是函数,又是对象,有__proto__和prototype属性
原生构造函数Object和Function一样有这两个属性
Parent、Object、Function作为对象,__proto__属性都指向Function.prototype
Person、Object、Function作为函数,都有prototype属性,它的值是一个对象,有constructor属性,有一些属性和方法。
Parent.__proto__ === Fucntion.prototype
Object.__proto__ === Fucntion.prototype
Fucntion.__proto__ === Fucntion.prototype
p1.__proto__ === Parent.prototype
以一个函数为例
function fn(){}
fn.__proto__ === Function.prototype //true
fn instanceof Function //true
fn instanceof Object //true
Function.__proto__ === Function.prototype //true
Object.__proto__ === Function.prototype //true
fn.prototype.__proto__ === Object.prototype //true
fn.prototype.constructor === fn //true
总结:
函数既是函数又是对象,含有__proto__和prototype属性。
__proto__指向Function.prototype,Function.prototype的__proto__属性又指向了 Object.prototype;
prototype最终指向Object.prototype。
原型链的终点问题
Q1: Object.prototype.__proto__ === null,这就是Javescript原型链的终点了,为什么是这个样子呢?
typeof Object.prototye === 'object'
说明他是一个object类型的对象,如果他是由Object函数生成的,那么Object.prototype.__proto__ === Object.prototype。那么Object.prototype.__proto__指向自身,那么以__proto__属性构成的原型链将没有终点了。
所以为了让原型链有终点,Javascript规定,Object.prototype.__proto__ === null。
Q2: Function属性的prototype是一个"function"类型的对象,而不像其他的对象是"object"对象,那么既然是对象,那也是有__proto__属性的,那么Function.prototype.__proto__是什么呢?
一般而言,一个"function"类型的对象,应该是由Function函数生成的,也就是Function.prototype.__proto__ === Function.prototype才对,如果是这样的话,也就出现了跟Object一样的问题,一直循环利用,没有尽头。
所以Javascript规定,Function.prototype.__proto__ === Object.prototype,
Object.prototype.__proto__ === null,是原型链的终点。也就是在原型链的终点处有2个特殊情况。