上一篇文章把对象的概念讲解了一下,这篇文章要重点解释最让大家犯迷糊的一些概念,包括
- 构造函数
- 实例
- 继承
- 构造函数的属性与方法(私有属性与方法)
- 实例的属性与方法(共享属性与方法)
- prototype(原型)
- _proto_(原型)
构造函数
构造函数依然是个函数,用来生成对象。所有的对象都是由构造函数创建的
对象哪来的?构造函数生的。而普通函数不能生成对象(不孕不育),构造函数可以生成对象(有生育能力)。每个对象都会有一个自己对应的构造函数,但是不代表每个构造函数都会生(生成实例),有不会生的构造函数,例如Math对象(不孕不育)
console.dir(Array); //数组的构造函数
console.dir(Function); //函数的构造函数
console.dir(Object); //对象的构造函数
new Math(); //报错,不能生成实例
new Window(); //报错,不能生成实例
唐僧说过一句话,用来解释构造函数与实例再合适不过了“人是人他妈生的,妖是妖他妈生的”那这里的人他妈与妖他妈就是构造函数。人与妖其实是下面要说的概念“实例”
实例
实例就是对象,由构造函数生成
平时用的实际的东西都是实例(获取的DOM元素、声明的数组、声明的函数、声明的对象),有时候需要new关键字生成(不是绝对的)
实例身上有一个
constructor
属性,它指向对应构造函数(生母)判断一个对象是否是某个构造函数的实例,用
instanceof
运算符
//实例
[]; //不用new生成
new Array(); //用new生成
[].constructor===Array; //true
[] instanceof Array; //true
继承
生成的实例具有构造函数身上的属性与方法(就像老鼠的儿子会打洞)
一个对象身上有另一个对象身上的属性或方法,这种具有的方式就叫继承
说到继承首先想到的就是遗产。老人去世了,在二环里留了一套四合院,那这套房子归谁呀?不可能归我吧,归我的话我就不用在这吭哧吭哧写文章了。那也不能归你吧,归你了你也不能座这看我吭哧吭哧写的文章。得归人家儿子,这个就是继承。那从这个例子中得出几个重要信息,继承者与被继承者之间是有一定关系的
,最简单的就是父子或者母子关系。
那回到程序中,构造函数就是老人,实例就是儿子。那实例的属性或者方法哪来的?构造函数的,不过它可以继承过来,这是合理合法的。但是也会有特殊情况,假如老人膝下无子,那这房子怎么办?充公?不能吧,要你你干么?不过这个老人有一个唯一的亲人,就是弟弟。那弟弟能否拿到这个房子呢,应该是可以的。再回到程序中,刚才这种情况其实程序中也存在,就是一个对象可以有另一个对象身上的东西。
//arr身上是并没有push方法,这个方法来自于构造函数Array,是arr继承了构造函数身上的这个方法
const arr=[1,2];
arr.push(3);
console.log(arr); //[1, 2, 3]
//Array对象身上并没有valueOf方法,这个方法来自于Object对象,是Array对象继承了Object对象的这个方法
Array.valueOf===Object.valueOf; //true
构造函数的属性与方法(私有属性与私有方法)
构造函数身上的属性与方法,只有构造函数能用,实例不能用
再回到这1个亿的例子中来,这个老人为啥有个四合院呢,这位老人原来是个老兵,立过一等功,战功赫赫,获得了无数勋章。那我问你他的这些荣誉,儿子有么?或者弟弟有么?没有吧。这个就是构造函数特有的属性与方法,实例身上是没有的
//构造函数的私有属性与方法
console.log(Array.name); //Array
console.log(Array.of(5)); //[5]
//实例不能用
const arr=[]; //实例
console.log(arr.name); //undefined
arr.of(6); //报错
实例的属性与方法(共享属性与共享方法)
实例身上的属性与方法,只有实例能用,构造函数不能用(放在prototype里)
老人有两个孩子,这俩孩子跟我学会了js,又学会了ES6,现在在百度上班做开发,这你说扯不扯,我都快编不下去了。那这些技能老人会么,他不会。所以这个就叫实例的属性与方法,只能实例去用,构造函数用不了
//实例的方法
const arr=[1,2,3];
console.log(arr.concat(['a','b'])); //[1, 2, 3, "a", "b"]
Array.concat(['a','b'])); //报错。你的就是你的,我的就是我的,不能互用。就跟媳妇是一样
//但构造函数可以间接用
console.log(Array.prototype.concat(['a','b'])); //["a", "b"]
prototype(原型)
1、构造函数身上的一个属性,它的类型为对象,这个属性的值就是原型
2、这个对象里放的属性与方法,就是构造函数的共享属性与共享方法,所有实例都能用
还得回到那一个亿的例子里,我们说儿子能继承老子的遗产,为什么呢?因为他有个神器叫户口本,国家给发的,能证明他们是父子关系。你去办手续的时候肯定要拿着户口本。现在都得要证件,当然有的时候你可能需要到派出所开个证明,证明你就是你。虽然有点扯哈,但是有真实发生过,如果派出所不给你证明,那你就不是你。回到程序中,虽然你知道实例是构造函数生的,那实例就能有构造函数身上的方法,为什么呢?其实他们也有证,跟户口本一样,这个证就是prototype
//prototype 原型
console.log(Array.prototype); //数组的原型对象
//如果把原型上的方法删除了,那实例就不能用了。证明原型里放的属性与方法都是实例的属性与方法
const arr=[];
Array.prototype.push=null;
arr.push(6); //报错
_proto_(原型)
1、这个属性是浏览器自己部署的,到了ES6也没有正式写入标准里,建议大家不要用它,用
Object.getPrototypeOf()
方法替代
2、它也是指原型对象,与prototype
一样。但是有区别:归属不同,prototype
是函数身上的属性,__proto__
是对象身上的属性
这个属性与prototype
属性往往让大部分人都百思不得其解,看得是一头雾水,脑袋拧成了麻花,网上的资料是一堆一堆,但往往大家看得是一愣一愣。其实这俩东西很简单,是一道推算题,首先,你要明白原型的结果只有一个,就是构造函数的prototype
属性的值。那为什么浏览器看热闹不嫌事大,给我们找麻烦,又部署了一个__proto__
呢?其实浏览器也是好心,但没成想办了坏事。在没有这个属性以前,只能从函数身上找到原型。为了能从实例对象身上也能找到原型,浏览器就部署了这个属性。以字符串为例推算如下:
字符串构造函数的原型放在String.prototype
里。那现在我能否通过实例找到这个原型呢?也就是str ? ===String.prototype
const str=new String('kaivon');
console.dir(str); //打印出实例,点开后看到__proto__
要通过实例找到原型的话,首先要通过实例找到构造函数(因为原型在构造函数身上)。前面有说过,实例身上都有一个属性叫constructor
,这个就指向构造函数
console.log(str.constructor===String); //true 通过实例找到了构造函数
那找到了构造函数,原型不就放在构造函数身上么?所以变成了这样
console.log(str.constructor.prototype===String.prototype); //true
到这里就推出来如何通过实例找到原型,但是这么写是不是有点长呢?天空一声巨响,__proto__
闪亮登场,浏览器为了简化操作,就让__proto__
等于constructor.prototype
,也就是变成下面这样
console.log(__proto__===constructor.prototype); //true
所以整个语句其实就可以变成这样
console.log(str.__proto__===String.prototype); //true
到这里就明白了吧,再总结一下
prototype
属性是构造函数身上的,指向原型__proto__
属性是对象身上的,指向原型- 实例的.
__proto__
===构造函数.prototype
现在是不是恍然大悟、如梦初醒、豁然开朗,但是我不得不浇你一头冷水啊,还没有完,看下面
console.dir(String);//打印出构造函数,点开也看到了__proto__
刚才不是说这玩意是实例身上的么,现在怎么跑到构造函数身上了?童话里都是骗人的吗?别急,构造函数是函数么?函数是对象么?是吧,这就对了。其实上面的推理题都好说,都简单。大家弄不懂的是在这。网上的文章就在这里让你的麻花越拧越紧。构造函数是函数,是函数它就是对象,__proto__
既然是对象身上的,那这个构造函数身上就一定会有。点开__proto__
看一下
里面的内容不应该是String
原型的内容么?好像不是哎,这就傻逼了,刚搞清楚的东西现在全乱了。别急,记住那句话__proto__
永远指向实例对象对应的构造函数的prototype
,那就先看实例对象是谁。我们点开的这个String
它是什么?它是构造函数,它的类型是个函数。虽然它是一个构造函数,但在这里它就是一个实例对象,并且它的类型是函数,所以它是Function
构造函数的实例,那Function
的实例对象身上的__proto__
不应该指向Function
的prototype
么?所以这里面的内容为Function
对象的原型
![4.png](https://upload-images.jianshu.io/upload_images/10474628-6cb70c1c67bd7486.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
3.png](https://upload-images.jianshu.io/upload_images/10474628-0d93f31d31507e1d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
真是害怕什么来什么呀!这是`__proto__`来自于`Function`的`prototype`,它是个原型对象,原型对象的数据类型当然为对象了,所以它是`Object`构造函数的实例,那`Object`的实例对象身上的`__proto__`不应该指向`Object`的`prototype`么,所以点开这个`__proto__`里面的内容是`Object.prototype`的值
```javascript
console.log(String.__proto__.__proto__===Object.prototype); //true
因为Object
已经是顶层对象了,所以在它的prototype
里不会再出现__proto__
了,但是有人说Object
其实还有继承,继承于null
。但是我不太认同这种说法,__proto__
的值是个对象类型数据,而Object
已经是顶层对象了,它原型对象的__proto__
肯定没有值了,在ECMAScript
中null
的数据类型又为对象,所以就呼应上了,而不是继承于null
。
Object.prototype.__proto__===null; //true
到这里我把面向对象当中关键的一些概念算是说清楚了,下一篇文章来说一下真正的面向对象概念!