由于接触项目不是很大,所以很少用到js一个流弊的技术栈:继承,对,就是我们常说的prototype,原型及由原型组成的一个链,原型链
一直想分析为什么利用prototype就可以实现js的继承了!!!,接下来小伙伴们就和我一起来探讨这个比较深奥的问题哈
一.第一步还是先来看原型的概念:
1、Prototype:
对该对象的对象原型的引用。对于所有的对象,它默认返回 Object 对象的一个实例。(来自w3c)
尼玛,这个看的有的晕,把句子拆开一句一句分析:(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指向的具体内容
1.png好奇怪,怎么这个原型指向的构造函数变成了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操作符后返回的为一个对象。
(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
3.png
这样就形成了一个原型查找的链,即所谓的原型链,这就是js对象在进行属性和方法进行查找时所采用的一个方法,沿着原型链从底层一层一层向上查找。
这是我的理解,也许理解的不到位,说实话要将这个讲清楚,不是区区这点内容就可以的,这里相当于自己的一个总结,互相学习,互相促进~~~
...
网友评论