JavaScript基础:原型和原型链

1,682 阅读5分钟

什么是原型

原型是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

image.png

image.png

构造函数

构造函数上可以附带 实例成员 和 静态成员

实例成员: 实例成员就是在构造函数内部,通过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一个新对象的过程发生了什么?

  1. 创建一个空对象son {}
  2. 为该对象准备原型链连接 son.proto = Father.prototype
  3. 重新绑定this,使构造函数的this指向新对象
  4. 为新对象属性赋值
  5. 返回this,此时新对象就拥有了构造函数的属性和方法

通过构造函数创建的每个实例的方法是共享的吗?

不一定。分两种情况讨论:

  1. 在构造函数上直接定义的方法不共享。
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为两个不同的实例,指向的不是同一块内存。每次生成实例都会新开辟一块内存存储属性和方法。

如果实例的属性是基本类型,不存在共享问题,是否相同要看具体值的内容是否相同。

  1. 通过原型添加的方法是共享的。
    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是一切对象的根源,根源之上在没有其他。

Functionprototype是一个内置函数,一切函数都派生自这个内置函数。该内置函数对象的prototype指向根源对象。

Function.prototype.__proto__ === Object.prototype

所以,ObjectFunction二者之间的联系为:所有函数的默认原型都是Object的实例,二者通过根源对象联系起来。

FunctionObject,既是函数,又是对象。因为都可以Function()或者Object()这样的方式执行,又可以Function.a = 'a',Object.a = 'a'这样赋值。
说它们是函数,是因为他们都是通过“内置函数工厂”派生出来的,因而具备函数的特性。
说他们是对象,是因为他们都是通过”根源“对象派生出来,因此具备对象的特征。
Function.prototype指向“内置函数”。而Object.prototype指向“根源对象”

现在可以更好地理解这张图: image.png

Parent既是函数,又是对象,有__proto__prototype属性
原生构造函数ObjectFunction一样有这两个属性

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.prototypeFunction.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.prototypeObject.prototype.__proto__ === null,是原型链的终点。也就是在原型链的终点处有2个特殊情况。