JavaScript基础:原型和原型链

什么是原型 原型是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个特殊情况。