聊到面向对象编程就不得不说到面向对象的编程语言的三大基础特征(封装,继承,多态),js作为一门面向对象的编程语言,当然也是具备上面的几个特征,今天我们就一起认识下js如何实现其中的一个基本特征:继承,对,就是我们常说的prototype,原型及由原型组成的一个链,原型链
一直想分析为什么利用prototype就可以实现js的继承了!!!,接下来小伙伴们就和我一起来探讨这个比较深奥的问题哈
一.第一步还是先来看原型的概念:
1、Prototype:
每个函数就是一个对象(Function),函数对象都有一个子对象 prototype对象,类是以函数的形式来定义的。prototype表示该函数的原型,也表示一个类的成员的集合。(来自百度百科)
这个看的有的晕,把句子拆开一句一句分析:(1)明确了Prototype是一个引用。(2)这个引用指向的就是原型对象
2.proto
针对js对象提供的一个指向原型的指针,该属性并不标准,实际工作中并不会用到,所以称其为隐藏的原型
二、prototype的作用
针对上面的概念,我们用代码来演示一遍,来进行进一步的理解
function Demo(){
}
Demo.prototype.name = "xiaoming";
var demo = new Dem0();
demo.name //xiaoming
demo.__proto__;//{name: "xiaoming", constructor: ƒ}
如上我们定义了一个构造函数Demo(也就是js中的类,为了区分普通函数我们一般将构造函数首字母进行大写),每一个函数在定义的时候都会拥有自己的一个prototype属性(除Function.prototype.bind),而该属性指向的是自己的原型对象,即Demo.prototype;
我们首先定义了一个构造函数Demo,然后在其原型对象上定义了一个属性name并且赋值为xiaoming(注意:在一个原型对象中会默认有两个属性,一个是construct,一个是proto,后面会针对这两个属性来细讲)。
然后执行new操作进行对象的实例化(接下来也会对new这个过程进行拆分),结果demo.name可以访问到在构造函数原型上定义的属性,如何访问到的了?
我们输出demo.proto结果指向了一个对象,该对象和Demo.prototype对象一样,然后我们来证明一下,两者是否真的为同一个对象,执行demo.proto === Demo.prototype 结果返回 true,看来两者是一样的,即实列化的对象demo可以通过proto来访问Demo构造函数上的原型对象,而prototype和proto就是所谓的原型指针(原型),他们所指向的就是原型对象。
解释prototype后,我们来看看prototype在的作用,看如下代码:
function Car(color,size){
this.color = color;
this.size = size;
}
Car.prototype.action = function(){//共用的属性放到原型上
console.log('run....')
}
var bmw = new Car('blue',200);//定义了一辆车
var audi = new Car('white',230);//定义了另一辆车
bmw.action === audi.action //true
bmw.__proto__ === audi.__proto__ //true
audi.__proto__ === Car.prototype // true
为什么要把共用的东西放到prototype上面了?
1.节省内存
在进行实列化的时候都会将构造函数里的属性都执行一边,意思就是每进行一次new操作,就会对属性进行一次初始化,这样就会占用相应的内存空间,而将公共的属性放到prototype上面,就只在函数初始话的时候开辟了一个内存,在new的过程中不会重新开辟新的地址(题外话:那么这个构造函数在实例话后算不算是形成了一个闭包了?)
2.实列话的对象可以天然的继承(哈哈,不需要你去进行任何操作)
因为通过构造函数实列话后的对象,它的proto指向的就是该构造函数的原型对象,所以无论你实列化多少个 他们的proto都是指向该构造函数的原型对象,即它们都继承了该属性
为了加强理解,我们在看一个例子
function OldCar(){
this.color = "black";
}
function Car(color,size){
this.color = color;
this.size = size;
}
Car.prototype = new OldCar();
Car.prototype.action = function(){//共用的属性放到原型上
console.log('run....')
}
var car1 = new Car();
car1._proto_ === Car.prototype //true
//看下car1._proto指向的具体内容
好奇怪,怎么这个原型指向的构造函数变成了OldCar ,不应该是Car吗?
Car.prototype = new OldCar();这个操作后,就会Car原型指针指向了OldCar实列化后的对象上,所以访问car1._proto就相当于访问OldCar的实列,所以car1._proto.constructor 就是OldCar了
聊聊new、constructor .原型链
上面说的也许听起来不是很明白,接下来,我们将上面出现的几个名词进行解释,这样就能对原型有一个深入的理解,所以接下来将会解释 construct 、proto和prototype的关系 ,以及new的过程中内部具体发生了什么,然后看一个代码实列。
(1)constructor
w3c定义:constructor 属性返回对创建此对象的数组函数的引用。
还是一如既往的不好理解,不要担心,还是老办法拆开一句一句理解
首先constructor 是一个属性(即原型对象中存在的一个属性),然后这个属性指向的是创建该对象的函数。
function Person(){
}
Person.prototype.age = 38;
var person = new Person();
person._proto_.constructor ;//返回的是Person这个函数(因为person._proto_===Person.prototype所以两者的原理是一样的的)
//看看下面的构造函数指向谁
function Father(){
}
Father.prototype.name = "jhon";
function Son(){
}
Son.prototype = new Father();
var son = new Son();
son.__proto__.constructor// function Father(){}
上面的列子很好的说明了constructor指向的是创建该对象的函数,因为Son函数的原型指向的是 father这个对象实列,所以返回的constructor即为在father上查找constructor 所以返回值为Father函数
(2)new的过程中发生了啥
1、在每次我们定义对象的时候,我们会使用一个new操作符去实现一个对象的实列化,即使我们平时使用 var demo = {},这种使用字面量来声明一个对象时。其实它内部仍然使用了new操作符
var demo = {};
//相当于 var demo = new Object();
2、接下来我们来剖析剖析new的过程中到底发生了 什么,看如下代码
function Demo(){
this.name = "xiaoming";
}
//直接调用
var demo = Demo();
demo//undefined,这个很好理解,如果一个函数中没有返回值时默认返回的为undefined
//进行实例化
var demo = new Demo();//注意两者直接仅仅是多了一个new操作符
demo//Demo {name: "xiaoming"};返回的是一个对象
//将上面的函数进行更改,给他一个返回值看看有什么变化
function Demo(){
this.name = "xiaoming";
return age = 3
}
//直接调用
var demo = Demo();
demo// 3
//实例化
var demo = new Demo();
demo//Demo {name: "xiaoming"};返回的仍然是一个对象
//在把返回值变为一个对象 看看有木有变化
function Demo(){
this.name = "xiaoming";
return{
age:3
}
}
//直接调用
var demo = Demo();
demo// {age: 3}返回的是一个对象,木有问题
//实例化
var demo = new Demo();//这个会返回啥,是仍然不变还是已经变化了?
demo//{age: 3}返回的对象发生了变化
通过上面的几个例子我们可以得出如下结论:
(1)、如果构造函数没有返回值时,直接执行时,返回的为undefined,进行new操作符后返回的为一个对象,相当于在函数体内隐士执行了如下操作
function Demo(){
var this = {proto:Demo.prototype }
return this;
}
(2)、如果构造函数返回值为一个原始值时,进行new操作符后,返回值和(1)中返回的一样
(3)、如果构造函数返回值为一个引用值是(比如对象,数组)进行new操作符后,返回值和直接执行这个函数返回的值一样
3、看看new的过程中this的指向
function Demo(){
this.name = "jhon";
this.age =18;
console.log("当前的this指向的是"+this);
}
//直接执行函数
Demo();//当前的this指向的是[object Window],指向的为window
//new实例化
var demo = new Demo();
demo//指向的为当前的实例化后返回的对象 Demo {name: "jhon"}
也就是说在new的过程中,构造函数将this指向调用者。。。这个new就先聊这么多,嘎嘎,也只是提到了一些基本的。
(3)原型链
1、在上面我们已经知道了这两个都是原型的引用,只不过prototype是针对函数,而proto指的是对象(除null,undefined.和自定义创建的空对象,因为以上对象是没有proto属性的)
在js中,如果在访问一个对象属性的时候,在当前实列下没有找到该属性,那么他就会沿着proto原型去到上一层去查找
我们看到father这个实列是没有age属性的,但是我们却可以访问到age这个属性,这是因为当没有在当前实列下该属性,就会通过proto去向上进行查找。
说道这里就可以说说什么是原型链了,如上图所示,在father.proto对像中还存在一个proto,而这个proto指向的就是最原始的对象Object,而这个Object.proto指向的为null
这样就形成了一个原型查找的链,即所谓的原型链,这就是js对象在进行属性和方法进行查找时所采用的一个方法,沿着原型链从底层一层一层向上查找。
如何使用原型来实现继承
function Father(){
}
Father.prototype.firstName = 'gong';
function Son(){
this.lastName = 'cheng'
}
Son.prototype = Father.prototype;
var son = new Son();
son.firstName // gong
//如果改变son的原型对象会发生什么
Son.prototype.age = 18;
son.age // 18
//看看father对象会有什么变化
var father = new Father();
father.age// 18
//此时的不足就是如果给son的原型对象上增加属性会改变father的原型对象(两者属于同一引用)
//如何优化,优化重点即想办法让son的原型引用和father的原型引用不是指向同一个地址
//定义一个构造继承的函数
function inherit(Target,Orgin){
//定义一个中间函数
function F(){};
F.prototype = Orgin.prototype;
Target.prototype = new F();
}
//执行下上面的列子
Father.prototype.firstName = 'gong';
function Father(){
}
function Son(){
this.lastName = 'cheng'
}
inherit(Son,Father);
var son = new Son();
son.firstName//gong
Son.prototype.age = 18;
son.age//18
var father = new Father();
father.age//undefined
//如果把上述的inherit内部的代码 F.prototype = Orgin.prototype; Target.prototype = new F();)的顺序换下,结果会怎样呢
//把上面的代码在优化下
let inherit = (function(){
var F = function(){};
return function(Target,Orgin){
F.prototype = Orgin.prototype;
Target.prototype = new F();
Target.prototype.constructor= Target;//是否有疑问?为什么要重新构造函数
}
}(Target,Orgin))
//为何要重新构造函数
//构造函数:每创建一个原型对象,该原型对象就会根据一定的规则生成 一个construct,上面我们默认重写了Target的原型对象,所以construct属性也会变化(F),为了让construct保持原来的,我们可以重新他的construct属性
最后看一个题目
//考察原型相关的知识
function F(){
}
var person = new F();
F.prototype.name = 'jack';
F.prototype = {
name:'jack',
age:12,
say:function(){console.log(this)}
}
var person1 = new F();
//问题
person.name
person.say()
person1.name
person1.say()
这是我的理解,也许理解的不到位,说实话要将这个讲清楚,不是区区这点内容就可以的,这里相当于自己的一个总结,互相学习,互相促进~~~