JavaScript原型继承

javascript/jquery

浏览数:37

2020-5-23

AD:资源代下载服务

什么是原型继承:

设置某个对象(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,最终发现,没有这个属性。

作者:穗穗叨叨