原型prototype与__proto__
先简单理解以下几句话:
- 每一个对象都有一个__proto__属性,并且指向它的prototype原型对象
- 每一个构造函数都会有一个prototype原型对象,prototype原型对象里的constructor指向构造函数本身
直接看下面代码:
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 先打印看看Person是什么
// 打印window,从window上找到Person
console.log(window)
以下是Person的打印结果:
可以看到Person为一个构造函数,而这个构造函数身上有一个prototype属性,该属性的constructor又指向该构造函数本身。接下来让该构造函数创建一个实例,看卡这个实例身上有哪些内容。
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
var p1 = new Person('qwe', 12, male)
console.log(p1)
console.log(p1.prototype)
console.log(p1.__proto__)
)
__proto__是[[Prototype]]的getter和setter
,这里可以简单的将[[prototype]]理解为__proto__。从上图可以看出p1身上有一个__proto__
属性,该属性指向自己的原型对象,而p1自身是没有prototype
的。总的来说就是,对象自身的__proto__属性会指向自己的构造函数的prototype,即p1.__proto__ === Person.prototype
。
原型链
原型链简单的理解就是,多个不同的prototype之间通过__proto__链接成一条链(抽象的一种说明方式)。
如何体现出原型链有什么用,直接看下面代码:
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
// 在构造函数Person的prototype上增加一个sayName方法
Person.prototype.sayName = function() {
console.log(this.name)
}
上面说到过p1.__proto__ === Person.prototype
,那可以想到p1.__proto__身上也有sayName方法,通过以下几种调用方法来看一下分别是什么结果:
先看一下p1的结构:
p1.sayName() // abc
p1.__proto__.sayName() // undefined
// 这里之所以是undefined是因为this指向的是p1.__proto__,p1.__proto__身上并没有name属性
意思就是说我在构造函数的原型上添加一个方法后,在它的实例身上也会存在该方法。那如果将该实例的__proto__指向别的构造函数的原型呢?看以下代码:
function Person(name, age, sex) {
this.name = name
this.age = age
this.sex = sex
}
function Dog(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name)
}
Dog.prototype.sayName = function() {
console.log(this.name + ' dog')
}
var p1 = new Person('abc', 123, 'male')
// 改变p1的__proto__指向
p1.__proto__ = Dog.prototype
// p1的sayName方法为Dog原型上的sayName方法
p1.sayName() // abc dog
从上面可以看出实例的__proto__是可以更改的,__proto__指向谁的原型,就能够获取该原型上的方法。
再看一个例子:
var arr = [1, 2, 3]
arr.valueOf() // [1, 2, 3]
console.log(arr.__proto__ === Array.prototype) // true
console.log(arr.__proto__.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
这里就体现出原型链的结构,arr身上是没有valueOf方法的,但是它的__proto__指向Array.prototype,所以会去Array.prototype上找valueOf方法,发现Array.prototype身上也没有valueOf方法,但是Array.prototype.__proto__指向Object.prototype,所以又会去Object.prototype上找到valueOf方法。
继承
继承就是说一个对象可以得到另外一个对象身上的属性和方法,怎么得到,直接看下面事例:
假设我想写一个造车的方法,车的大小和模型的制作过程一样,只有车的名字需要定制。
// 制造车的基本信息
function CarBaseInfo(size, model) {
this.size = size + 1
this.model = '超大号:' + model
}
// 制造车的名字,基本信息的制作过程都一致
function CarName(name, size, modle) {
// 使得this指向CarName
// 如果不使用call,this指向window
CarBaseInfo.call(this, size, modle)
this.name = name + 'a'
}
var car1 = new CarName('兰博基尼', 123, '1')
console.log(car1)
如何继承方法,上面原型链中已经提到过,在构造函数的原型上增加方法,让对象的__proto__指向它即可。
function CarBaseInfo(size, model) {
this.size = size + 1
this.model = '超大号:' + model
}
CarBaseInfo.prototype.speed = function() {
console.log('加速')
}
function CarName(name, size, modle) {
CarBaseInfo.call(this, size, modle)
this.name = name + 'a'
}
// 按照思路来说,将CarBaseInfo的原型赋给CarName的原型,car1上就会有speed方法
CarName.prototype = CarBaseInfo.prototype
var car1 = new CarName('兰博基尼', 123, '1')
console.log(car1.speed()) // 加速
这里会存在一个问题,对象是引用类型,意思就是说CarName.prototype = CarBaseInfo.prototype
这样赋值后,CarName去更改自己原型上的speed方法时,CarBaseInfo原型上的speed方法也会跟着更改,说好只让你使用,可没让你更改,这样肯定是不允许的。所以这里需要完全将CarBaseInfo.prototype拷贝过来,并且将CarBaseInfo.prototype的constructor指向CarName自己。
// 这里使用Object.create方法,该方法为新建一个对象
CarName.prototype = Object.create(CarBaseInfo.prototype)
// 然后再将CarName.prototype的constructor指向构造函数本身
CarName.prototype.constructor = CarName
完整实现:
// 制造车的基本信息
function CarBaseInfo(size, model) {
this.size = size + 1
this.model = '超大号:' + model
}
CarBaseInfo.prototype.speed = function() {
console.log('加速')
}
// 制造车的名字,基本信息的制作过程都一致
function CarName(name, size, modle) {
CarBaseInfo.call(this, size, modle)
this.name = name + 'a'
}
CarName.prototype = Object.create(CarBaseInfo.prototype)
CarName.prototype.speed = function() {
console.log('减速')
}
var car1 = new CarName('兰博基尼', 123, '1')
var car2 = new CarBaseInfo(12, '222')
console.log(car1.speed()) // 减速
console.log(car2.speed()) // 加速
相关API
1. hasOwnProperty
用来检测该对象身上是否拥有某个属性,只会检测到自身属性,并不会检测继承来的属性,
注意:hasOwnProperty是可以作为属性名在对象身上的,所以在检测的时候可以采用Object.prototype.hasOwnProperty.call()的方式来检测。
2. instanceof
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
function Person(name) {
this.name = name
}
function Student(name) {
Person.call(this, name)
}
var p1 = new Student('123')
console.log(p1 instanceof Person) // false
p1.__proto__ = Person.prototype
console.log(p1 instanceof Person) // true
function C() {}
function D() {}
D.prototype = new C() // D.prototype.__proto__ === C.prototype
var o3 = new D() // o3.__proto__ === D.prototype
o3 instanceof D // true
o3 instanceof C // true C.prototype在o3的原型链上
console.log(o3.__proto__ === D.prototype)
console.log(o3.__proto__.__proto__ === D.prototype.__proto__)
console.log(o3.__proto__.__proto__ === C.prototype)
A instanceof B 即判断B的prototype是否在A的原型链上