欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS: 原型与原型链

程序员文章站 2022-03-18 15:36:23
原型与原型链 javascript 创建对象 类与构造函数是大多数编程语言所拥有的,而借鉴了 C 与 JAVA 的 javascript 也是有类和构造函数的,不过 javascript 的实现不太一样。 上面的例子中, 就是构造函数, 就是它的实例,但是它对共享性不太好,所以有了原型模式。 原型模 ......

原型与原型链

javascript 创建对象


类与构造函数是大多数编程语言所拥有的,而借鉴了 c 与 java 的 javascript 也是有类和构造函数的,不过 javascript 的实现不太一样。

// before es6
// 构造函数模式
function person(name){
  this.name = name;
    this.sayname = function() {
      console.log(this.name);
    }
}

var p = new person();
console.log(p instanceof person); // true

上面的例子中,person就是构造函数,p就是它的实例,但是它对共享性不太好,所以有了原型模式。

// 原型模式
function person(){}
person.prototype.name = 'kobe';
person.prototype.sayname = function() {
  console.log(this.name);
}

var p1 = new person();
var p2 = new person();

p1.sayname(); // kobe
p2.sayname(); // kobe

原型模式创建的实例都可以共享person.prototype的属性和方法,而prototype是 javascript 实现类的一个关键所在。

prototype


在 javascript 中,每个函数都有一个prototype属性,这个属性指向函数的原型对象,上面的例子,可以用下图解释,图中用person.prototype表示 person 的原型对象。

JS: 原型与原型链

constructor


person 是构造函数,有prototype指针指向原型对象,而person.prototype也有一个constructor属性指向构造函数。

JS: 原型与原型链

简单来说,就是 javascript 会在函数创建的时候为函数添加一个prototype属性,这个属性是指向函数的原型对象的指针,而原型对象也会获得一个constructor属性,这个属性是一个指向函数本身的指针,即指向构造函数。

function person(){}
person.prototype.name = 'kobe';
person.prototype.sayname = function() {
  console.log(this.name);
}

person.prototype.constructor == person; // true

__proto__


函数有prototype,那对象呢?其实对象内部也有一个[[prototype]]指针,这个指针指向创建实例对象的构造函数的原型对象。

在 es5 之前的语言标准里,是不允许访问这个属性的,但浏览器都实现了一个__proto__[[prototype]]进行访问操作,所以 es5 增加了一个object.getprototypeof()方法来访问[[prototype]]

object.getprototypeof(object)方法返回指定对象的原型(内部[[prototype]]属性的值)。

在 es5 中,如果参数不是一个对象类型,将抛出一个 typeerror 异常。在 es6 中,参数会被强制转换为一个 object。

建议在代码中使用object.getprototypeof()来访问内部[[prototype]]属性,不建议使用非标准的__proto__,为了方便区别,文中使用__proto__来表示[[prototype]]

回到上面的例子,当调用 person 来创建实例的时候,该实例会获得一个__proto__指针指向构造函数的原型对象。

JS: 原型与原型链

正是因为创建的 p1、p2 实例都拥有__proto__指向构造函数(person)的原型对象(person.prototype),它们才能共享person.prototype的属性和方法。

function person(){}
person.prototype.name = 'kobe';
person.prototype.sayname = function() {
  console.log(this.name);
}

let p1 = new person();
let p2 = new person();

object.getprototypeof(p1) == object.getprototypeof(p2); // true

简单来说,就是在 javascript 中,每一个对象(除null)在创建的时候都会将其与另一个对象进行关联,而关联的这个对象就是所谓的原型。

使用isprototypeof()方法就能确定这种关联:

person.prototype.isprototypeof(p1); // true
person.prototype.isprototypeof(p2); // true

屏蔽性


还是这个例子:

function person(){}
person.prototype.name = 'kobe';
person.prototype.sayname = function() {
  console.log(this.name);
}

let p1 = new person();
// 访问 p1 的 name
p1.name; // kobe

当我们通过实例 p1 访问name属性的时候,javascript 引擎会先在实例 p1 中查找name,当发现 p1 没有这个属性,就会通过__proto__向它指向的原型对象进行查找,找到就返回这个属性的值,也就是说访问name执行了两次查找。

如果 p1 自己添加了name属性,就会在第一次查找时返回值:

// 在 p1 中添加 name
p1.name = 'jordan';

// 只会执行一次查询
p1.name; // jordan

// 原型中的值并未改变
object.getprototypeof(p1).name; // kobe

也就是说,在实例上添加name属性后,该属性就会屏蔽原型中的那个属性,但并不会改变原型中的该属性的值。

原型链


既然在 javascript 中,所有对象(除null)都有__proto__,而实例 p1 指向person.prototype,那person.prototype对象的__proto__又指向哪里呢?

object.getprototypeof(person.prototype) == object.prototype; // true

答案是object.prototype,因为原型对象是通过构造函数 object 来创建的,所以它指向object.prototype

JS: 原型与原型链

那 object.prototype 的__proto__又指向哪里呢?它指向这条链的终点:null,代表无。

object.getprototypeof(object.prototype); // null

JS: 原型与原型链

回到上面所说的查找name属性,如果在第二次都没有查找到name属性,就会沿着这条链继续向上查找,直到终点都没有找到,就会返回 undefined。

function person(){}

let p1 = new person();
p1.name; // undefined

上图中虚线部分由原型相互关联组成的链状结构就是所谓的原型链。

备注


不得不说,《javascript高级程序设计》这本书把原型讲得很透彻,今天再看一遍依然感觉通透。