一遍就懂:JavaScript中的原型和原型链

222 阅读4分钟

一遍就懂:JavaScript中的原型和原型链

前言

每个对象都拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层,一次类推。这种关系常被称为原型链,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。准确地说,这些属性和方法定义在Object的构造函数之上的prototype属性上,而非对象实例本身。下面通过写例子来认识原型原型链

原型和原型链

首先,JavaScript中一切引用类型都是对象,对象就是方法和属性的集合。就以数组而言,我们对于一个数组可以直接使用push,unshift,pop,splice等这些方法,可一个数组上怎么会多出这么多这些方法呢?这就引出了原型.

let arr = [1,2,3,4,5]
arr.push(6)
console.log(arr); // [ 1, 2, 3, 4, 5, 6 ]

每一个对象从被创建开始就和另一个对象关联,从另一个对象上继承其方法和属性,而这个另一个对象就是原型。 所以说每一个对象都有它自己的原型,当访问一个对象的属性时,先从该对象的本身找,找不到再去对象的原型上找,如果还是找不到,就去对象的原型的原型上去找,一层一层,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就返回undefined这种由对象及其原型组成的一层一层的关系就是原型链

  • 下面通过在Chrome中运行的例子来看下结果。

运行结果(原型).png

从上面可以看到,people_1这个实例对象上,有height和weight两个属性,既然是自己的属性那么people_1肯定能直接取到,但是当people_1访问local时,它本身并没有这个属性,这个时候就通过[[Prototype]]去查找,这个属性等同于_proto_ ,通常把它叫做隐式原型,指向该实例对象的构造函数的prototype(显示原型),因此就能拿到local的值。

构造函数、原型与实例的关系:

  • 每个构造函数都有一个原型对象
  • 原型对象都包含一个指向构造函数的指针(显示原型)
  • 实例都包含一个指向原型对象的内部指针(隐式原型)

[[Prototype]]和__proto__意义相同,均表示对象的内部属性,其值指向对象原型。前者在一些规范中表示一个对象的原型属性,后者则是在浏览器中指向对象原型。

该原型链图片描述

原型链图片描述.png

从一个对象上查找某个属性,从本身开始找,然后通过_proto_ 一层一层的去查找,那么尽头是什么呢? 所有普通的_proto_ 链最终都会指向内置的Object.prototype,所以它也包含了JavaScript的许多功能。当访问到这个尽头还没查找要访问的属性,就会返回undefined。目前,所有的浏览器都部署了__proto__这个属性,但是该属性没有写入ES6的正文,而是写入了附录,所以建议是用别的方法去访问对象的原型,可以使用Object.getPrototype()。这里就不上浏览器运行的图了,继上面例子,在Chrome中输入以下代码会得到:

people_1.__proto__.__proto__.__proto__   //  null
people_1.foo  //  undefined
people_1.__proto__ === People.prototype //  true
Object.getPrototypeOf(people_1) === People.prototype  //  true

constructor属性

每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数

现在继上面例子去检测下people_1.constructor === People是否成立?

constructor.png

浏览器给出判断是false,难道儿子的父亲不是他的亲生父亲(隔壁老王)?其实不然,在重写People.prototype的时候,相当于:

People.prototype = new Object({
    local: 'China'
})

所以这个时候People.prototype对象的构造函数就是Object。而people_1对象的constructor属性继承自原型链中,原型链中的constructor发生了改变,所以是儿子的DNA突变,所以才检测到不是亲生的。

修正方法:People.prototype.local = 'China'。

通过实例对象去创建另一个实例对象

var people_2 = new people_1.constructor(182,76)

正常情况下,是不会用这种情况去创建新的实例,除非刚好因为某些原因没有原始构造器的引用,那这种方法就能派上用场了。constructor其实也能获取到某个对象实例的构造器的名字:

people_1.constructor.name  //  People

如果有疑问或者错误的地方,希望大家提问或纠正,一起进步!!!