什么是原型继承:
设置某个对象(A)为另一个对象(B)的原型(塞进该对象的隐式引用位置)。
有两种方式:
显式继承、隐式继承。
先说说显示继承——
有两种方法。
方法一:
const obj_a = { a : 1},obj_b = { b : 2 };
Object.setPrototypeOf(obj_b, obj_a);
dir(obj_b)
输出
{
b: 2,
__proto__:{
a: 1,
__proto__:object.prototype
}
}
*此时obj_b的原型是obj_a,且有一个属性b,值为2。
方法二:
const obj_a = { a: 1},
obj_b = Object.create(obj_a);
dir(obj_b)
输出
{
__proto__:{
a: 1,
__proto__:object.prototype
}
}
*此时obj_b的原型是obj_a,并且没有其他属性,是个空对象。
这两种方法的区别:
Object.setPrototypeOf——先给定两个对象,把其中一个设置为另一个的原型。(已经有两个对象存在,要构建原型关联)
Object.create——先给定一个对象,它将作为即将创建的新对象的原型,新对象是个空对象。(当只有一个对象,想以它为原型创建新对象)
再说说隐式继承——
前提:
想要得到一个包含数据、方法、关联原型三个组成部分的丰满对象。
步骤:
1)创建空对象
2)设置其原型(设置为另一个对象或者null)
3)填充该对象(增加属性或方法)
具体:
(假设无隐式继承的话该怎么做,有上面提到的两种方法:Object.setPrototypeOf和Object.create)
方法一
const obj = {}//创建空对象
Object.setPrototypeOf(obj, Object.prototype)//设置其原型
obj.first = 'name1'//填充该对象
obj.second = 'name2'
简化
方法二
//将某些函数成为constructor,专门用来做属性初始化,比如下面的User函数
function User(first, second){
this.first = first;
this.second = second;
}
//约定constructor函数有一个特殊属性prototype
User.prototype = Object.create(Object.prototype);//创建新对象&设置新对象为原型对象
//这种方式创建的原型对象没有constructor属性的
//注:create能创建一个新对象,
//让用户用new关键字创建新对象,并传参。这步相当于二合一。不用创建新对象了,方法一里还得利用const obj = {}创建一个新对象。所以跟方法一相比,算是简化了一步。
const user = new User('name1', 'name2');//创建&填充
再简化省略User.prototype = Object.create(Object.prototype);这步。约定User默认有prototype属性,属性值为“以Object.prototye为原型的空对象”。
方法三:(这个是隐式继承)
所有函数都有prototype属性,该属性的值是一个空对象,且以Object.prototype为原型,该空对象还有一个constructor属性,指向构造函数。
注:“以Object.prototype为原型”意为该对象的__proto__属性是Object.prototype。
最终简化为(隐式继承方式)
function User(first, second){
this.first = first;
this.second = second;
}
const user = new User('name1', 'name2');
因为有了默认约定,所以不需要为新的实例对象指定原型。
构造函数User的原型对象在控制台的console打印出来是这样的
构造函数的原型对象,江湖称其为User.prototype。它是一个对象。是一个啥样的对象?是一个空对象,有一个constructor属性,指向构造函数User,有一个__proto__属性,指向Object.prototype。
实例user的__proto__属性值是User.prototype,换一种说法是:实例对象user的__proto__指向构造函数User的原型对象,这个对象人称User.prototype。
其实开始大家被原型搞得很晕(包括我),就是卡在了不同的说法上,一会原型啊一会原型对象,一会prototype一会又__proto__了。其实统一一下说法,并且了解下什么说法和什么说法其实说的是一回事,就好懂了。
原型,就是原型对象的简称,一般都称作XXX.prototype,这里的XXX要么是构造函数,要么是Object,要么是Function。如果把XXX.prototype在控制台输出的话,就会发现,它本质是个空对象,有个constructor属性,有个__proto__属性。
constructor属性值是构造函数(同时这种说法等同于“有一个constructor属性指向构造函数”、“有个指向构造函数的属性叫constructor”等)。
XXX的__proto__属性值是谁,就是说这个“谁”是XXX的原型对象,另一种说法就是“__proto__指向其(代指XXX)原型对象”。
很奇怪吧,实例是构造函数new出来的,但是它爹却是一个叫“原型对象”的对象。其实也很好理解,构造函数是函数,实例是对象,函数当不了对象的爹(函数本质也是一个对象,此处先不深究)。
实例对象这个普通对象,有个__proto__属性指向创建该对象的构造函数的原型对象(说谁是谁的原型,其实就是说谁是谁的原型对象的简称,一样的)。
构造函数有个prototype属性指向它的原型对象。
原型、原型链:
1、首先,__proto__是对象才有的属性。
2、原型链:当我们访问对象的一个属性的时候,如果该对象内部不存在这个属性,那么就会去该对象的原型对象(父类)上去查找(该对象.__proto__=该对象的原型对象),如果依旧不存在,那么就会去这个原型对象的__proto__属性所指的对象上去查找(父类的父类上)。以此类推,直到找到null(它是Object.prototype的__proto__属性所指的对象)
3、
// 定义构造函数
function C(){}
function D(){}
D.prototype = new C();//继承
var d = new D();
d instanceof C ;//true
d是D的实例---constructor--->D----prototype--->C的实例----constructor-->C---prototype-->C.prototype
因为D.prototype = C的实例,所以d.\proto__ = D.prototype = C的实例,
而C的实例.__proto__ = C.prototype,所以
d.__proto__ .__proto__ = C.prototype
又因为“instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上。”
所以C.prototype存在于d的原型链上,换句话说,即d instanceof C 为true。
这样的话,如果访问d的某属性比如d.x的时候,如果d上没有x属性,就会去(d的构造函数D的原型对象上,也就是C的实例上,去找,如果还没有,就去C.prototype上去找。如果没有,再去Object.prototype上去找,如果没有就接着找,找到了null,最终发现,没有这个属性。