什么是原型链
原型链是利用原型,让一个引用类型继承另一个 引用类型的属性和方法。说明白原型链,需要从构造函数、原型与实例的关系讲起。
构造函数、原型与实例的关系:
每个构造函数都有一个原型对象,
原型对象都包含一个指向构造函数的指针(显式原型),
而实例都包含一个指向原型对象的内部指针(隐式原型)。
那么,假如让原型对象等于另一个类型的实例,结果会让此事的原型对象包含一个指向另一个原型的指针,相应地,另一个原型中也包含着指向另一个构造函数的指针。
假如另一个原型又是另一个类型的实例,上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是原型链的基本概念。
ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
上图所示:
红色箭头表示 __proto__ 属性指向;
绿色箭头表示 prototype 属性指向;
棕色箭头表示本身具有的 constructor 属性;
蓝色方块表示对象;
浅绿方块表示函数;
在 JavaScript 中,这三者之间依附在不同的引用对象类型上。
对象:__proto__ 和 constructor 是对象独有的。
函数:prototype 是函数独有的。但是函数也是对象,所以函数也有 __proto__ 和 constructor。
什么是原型?
每个实例对象有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层构成一个原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
function Person(){
}
Person.prototype.name = '王新焱'
let p1 = new Person()
let p2 = new Person()
console.log(p1.name) // 王新焱
console.log(p2.name) // 王新焱
console.log(p2.age) // undefined
Person.prototype 就是实例p1和p2的原型,实例对象会继承原型的属性和方法。所以你会看到即使两个实例p1和p2没有自有属性name,但还是有值。
显式原型和隐式原型
显式原型对象 prototype 由函数所独有,它是从一个函数指向另一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象。由此可知:foo.__proto__ === Foo.prototype,它们两个完全一样。
那 prototype 属性作用又是什么呢?它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数的实例化对象们都可以找到公用的属性和方法。
任何函数在创建的时候,其实会默认同时创建该函数的 prototype 对象。
在 JavaScript 中的对象中都有一个 __proto__ 属性,从上图可以看出一个对象指向另一个对象,即指向相对应的对象的原型对象。这个原型对象称为隐式原型对象。
隐式原型对象的作用在于,当访问一个对象的属性或方法时,如果该对象内部不存在这个属性,那么就会从它的 __proto__ 属性所指向的(原型)对象中寻找(原型也是对象,也有它自己的原型),如果原型对象中也找不到,就会继续在该原型对象的原型对象中找,以此类推,直到找到属性或方法为止,或者查找到顶层原型对象 null,就结束查找,返回 undefined。
整个查找过程中,从当前对象出发沿着原型对象(__proto__)构成的链条查找相关属性和方法直到结束,这些相互关联的对象组成的链条就是原型链。
显式原型对象 | 隐式原型对象 |
---|---|
属性 prototype | 属性 __proto__ |
函数独有 | 对象独有(函数也是对象,因此函数也有该属性) |
定义函数时被自动赋值,值默认为 {} | 在创建实例对象时被自动添加,并赋值为构造函数的 prototype 值 |
用于实现基于原型的继承与属性的共享 | 构成原型链,同样用于实现基于原型的继承 |
原型链的作用?
原型链的作用是实现继承,即每个对象拥有一个原型对象,通过__proto__ 指针指向上一个原型 ,并从中继承方法和属性到新对象中。
原型链继承的优缺点
优点:非常纯粹的继承关系,实例是子类的实例,也是父类的实例。子类可以访问父类新的原型方法和属性。
缺点:子类实例共享属性,造成实例间的属性会相互影响
构造函数
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数),否则也只能称之为普通函数。对于构造函数,通常我们会将函数名首字母大写。
function Person(){
this.name = '王新焱',
this.age = 30
}
// 添加自身属性 name、age
Person.prototype.address = '湖北省黄冈市'
let person = new Person() // 创建了一个Parent构造函数的实例 person
console.log(person) // Person { name: '王新焱', age: 30 }
console.log(person.address) // 湖北省黄冈市
在javascript中,每个构造函数都有一个属性叫prototype(原型)
首先我们来了解三个属性:__proto__、prototype、constructor
①.__proto__、 constructor 属性是对象所独有的;
②.prototype 属性是函数独有的;上面说过js中函数也是对象的一种,那么函数同样也有属性__proto__、 constructor;
③.构造函数Person通过prototype属性引用实例的原型, 返过来原型通过constructor属性引用构造函数,原型和构造函数通过属性互相引用对方。
可通过如下代码检验Person.prototype.constructor是不是指向构造函数
console.log(Person.prototype.constructor === Person) // true
总结
①.每个对象的__proto__都是指向它的构造函数的原型对象prototype的
person1.__proto__ === Person.prototype
②.构造函数是一个函数对象,是通过 Function构造器产生的
Person.__proto__ === Function.prototype
③.原型对象本身是一个普通对象,而普通对象的构造函数都是Object
Person.prototype.__proto__ === Object.prototype
④.所有的构造器都是函数对象,函数对象都是 Function构造产生的
Object.__proto__ === Function.prototype
⑤.Object的原型对象也有__proto__属性指向null,null是原型链的顶端
Object.prototype.__proto__ === null
汇总如下:
1.一切对象都是继承自Object对象,Object 对象直接继承根源对象null
2.一切的函数对象(包括 Object 对象),都是继承自 Function 对象
3.Object 对象直接继承自 Function 对象
4.Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象