原型和原型链应该是前端的面试中,必定会问到的东西吧。但是这一块很多小伙伴们都会绕不明白,今天我们就一起来深入了解一下原型和原型链吧。
咳咳,第一次写,大佬轻喷
要说原型的话,我们就要先来知道构造函数是一个什么东西。
定义:通过 new一个 函数名,来实例化对象的函数就叫做构造函数。
注意:构造函数在定义的时候,必须使用大驼峰命名方式,比如:CreateHuman
如果return的是五种简单的数据类型:Number、String、Boolean、Null、Undefined的话,构造函数还是返回this对象,而普通函数会返回return后面的值。 如果return的是引用类型的数据类型:Array、Date、Object、Function、RegExp、Error的话,构造函数和普通函数都会返回return后面的值。
构造函数可以分为两种,一种是系统自带的构造函数,另一种是自定义的构造函数,接下里我们就来看看这两种构造函数吧。
系统自带的构造函数
new Objectt() new Array() new Number() new Boolean() new Date()
系统自带的构造函数Object()可以批量的生产出对象,每一个对象都一样,但是彼此之间是相互独立的
在Object()前面加上一个new,变成new Object()的执行,通过return返回一个真正的对象,然后在拿一个变量来接收。如:var a = new Object()
注:var obj = {} 和 var obj = new Object()这样写区别不大
自定义的构造函数
Object.create(proto, [propertiesObject])创建一个新的对象,使用现有的对象来提供新创建的对象proto
参数proto:必须,表示新建对象的原型对象,也就是说该参数会被赋值到目标对象的原型上。该参数可以是null、对象、函数的prototype属性。 > **注:**创建空对象的时候,要填null,否则会报类型错误。
propertiesObject : 可选。 添加到新创建对象的可枚举属性,对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
注: 接下来的前提必须是new之后的,而且下面的三步都是隐式的。
在函数体的最前面隐式的添加上var this = {}空对象(但这个不是空对象,里面有什么我在后面揭晓)执行this.xxx = xxx隐式的返回return this说了这么久,终于讲到了原型,我们我们就来看看什么是原型吧。
定义:原型是function对象的一个属性,他定义了构造函数制造出的对象的公有祖先。通过该构造函数产生的对象,可以继承原型的属性和方法。并且原型也是对象噢~
利用原型可以做什么事呢?
利用原型的特点和概念, 可以提取共有属性
看下面的这个例子你就知道是什么意思了
//第一个应用,在工厂里面生产每个都一样的时候,我们可以把共有的属性给提供出来,放到原型里面 function Car (color, owner) { this.color = color; this.owner = owner; } //Car.prototype 刚出生的时候就已经定义好了 Car.prototype = { name: "BMW", height: 1400, lang : 4900 } var a = new Car('red', 'prof.Ji'); // Car {color: "red", owner: "prof.Ji"} var b = new Car('black', 'prof.Deng'); // Car {color: "black", owner: "prof.Deng"} //看上去a和b的属性是这样的,但是真正要用的时候: console.log(a.name); //BMW console.log(b.name); //BMW上面的这个例子,每个对象都会有一些一样的属性(我把这样的属性叫做工程化属性),我们把这些工程化属性放在原型里不是更好嘛。
我们就根据这上面的代码当作增删改查的例子
查 Person.prototype这样就会返回一个对象,里面有原型的所有属性
//返回结果 {LastName: "Qi", constructor: ƒ}改 要是想在原型链上修改的话,比如说我们把Person.prototype.LastName修改一下,需要这样Person.prototype.LastName = 'XiaoQi' 但要是a.LastName = 'XiaoQi'这样修改的话,那么这个就不是在原型链上修改了,而是在a自己的身上增加了一个LastName='XiaoQi'属性,原型链上的LastName还是Qi。
注:原型上想要修改自己的属性,除非Person.prototype.LastName = 'XiaoQi'这么来修改,要是想通过对象来改原型的东西,那么基本是不可能的。
增 和改的原理类似,除非是调用Person.prototype.xxx来增加属性,比如:
Person.prototype.age = 18 //那么我们再来看看原型上的属性 console.log(Person.Prototype);// {LastName: "Qi", age: 18, constructor: ƒ}删 delete Person.xxx 这样的话是可以删除原型上的属性的,但要是delete a.age的话,虽然返回的是一个true,但是你在访问a.age的话,还是会返回18。虽然他返回的是true,那是以为你想删除一个你没有的属性,那么电脑当然是同意的啦,所以返回的是true。
定义:prototype属性是函数独有的,它的含义是函数的原型对象,也就是这个函数所创建的实例的原型对象;这个属性是一个指针,指向一个对象。
用处:包含所有实例共享的属性和方法
定义:在这个原型的内部,系统自带了一个属性叫做constructor。
function Car () {} var a = new Car(); console.log(a.constructor) //function Car() {}在上面这个构造函数中,a的constructor就是Car。
作用:让构造函数构造出的所有对象,想找它的构造器可以找到。就好比,你的爸爸给你写了一个你家的家庭地址,当你出去玩想回家的时候,可以通过你爸爸写给你的地址回到家。这个constructor就可以看作是你爸爸给你写的家庭地址。
constructor是可以被人为的修改的 如:
function A() {} function B() {} A.prototype = { constructor: B } var a = new A() console.log(a.constructor); //B() {}我们先来看看这个例子
Person.prototype.name = 'abc'; function Person () {} var a = new Person(); console.log(a.__proto__); //打开这个对象▼Object // name: "abc" // ▶constructor: ƒ Person() // ▶__proto__: Object从上面这个例子就可以看出来,这个__proto__里面放的就是原型。
在这里,我们就要给构造函数的内部原理填上一个坑了。在构造函数内部原理new之后的第一步,是会在里面隐式的创建一个var this = {}的一个类似空对象的对象,其实这个对象它不是空的,里面本来就有这个__proto__属性,指向的是Person.prototype。
当你访问这个对象的属性的时候,这个对象如果没有这个属性的话,它就会通过__proto__指向的索引,去找这个哥们身上有没有你想要的那个属性,它相当于一个链接的关系,把原型和自己连接在一起,假如你现在访问a.name,他会现在自己的身上找有没有name这个属性,如果没有的话,他就会沿着__proto__指向的地方去找它身上有没有这个属性。
因为__proto__里面存放的就是这个对象的原型。
例一
Person.prototype.name = 'sunny'; function Perosn () {}; var a = new Person(); Person.prototype.name = 'cherry'; console.log(a.name); //cherry解释:现在访问的这个name属性,自己的身上没有,就沿着__proto__的指向找。它的__proto__指向的是Person.prototype,现在又把Person.prototype的值给修改了,那么访问的肯定是修改后的值啦。
例二
Person.prototype.name = 'sunny'; function Person() {}; var a = new Person(); Person.prototype = { name : 'cherry', }; console.log(a.name); // sunny为什么输出的是sunny呢? 首先我们来搞清楚Person.prototype.xxx和Person.prototype = {}有什么不一样!
Person.prototype.xxx是在原来的基础上把属性给修改了。Person.prototype = {}是把原型给修改了,换了一个新的对象。解释:上面的是在new之后,发生了一个过程:var this= {__proto__: Person.prototype}, 然后a的__proto__:Person.prototype,然后__proto__:Person.prototype和Person.prototype是一个人,也就是Person指向的空间,当a构建完成之后,Person.prototype把自己的空间转移了,但是__proto__所指向的没有改变,还是原来的,原来空间的 name是sunny,所以当访问a.name的时候,返回的还是sunny。
例三
Person.prototype.name = 'sunny'; function Person () {} Person.prototype = {name : 'cherry'}; var a = new Person(); console.log(a.name); //cherry为什么会这样呢?
解释:函数的提升,让后面的Person.prototype = {name: 'cherry'}把前面的Person.prototype.name = 'sunny'给覆盖了。因为要new之后才会有隐式的三步,才会在里面创建一个this的类似空对象的属性,里面的__proto__指向的是覆盖后的房间,所以访问a.name的返回结果是cherry。而上面的那个是先new了之后把那个对象给生成后,才修改的,那个时候已经玩了;现在这个是先修改,在创建的对象,所以是cherry。
定义: 在原型上面加一个原型再加一个原型,这样的一个方法,把原型形成链,访问顺序也是按照链的顺序,像作用域链一样的去访问东西,叫做原型链。原型链的连接点就是__proto__,访问的顺序都是先近后远的查。
当了解了构造函数和原型之后,我们理解原型链的话就更加的简单了。我们先看看下面这个代码
// 首先我们创建一个构造函数 function Foo () {} //这个Foo构造函数的原型是Foo.prototype //这个Foo构造函数的__proto__指向的是Function.prototype console.log(Foo.__proto__ === Function.prototype); // true console.log(Function.constructor); //Function () {} (函数对象) // 这个时候创建一个实例化对象 foo var foo = new Foo(); console.log(foo.__proto__); //Foo.prototype console.log(Foo.prototype.__proto__); //Object.prototype console.log(Object.prototype.__proto__); //unll console.log(Object.prototype.constructor); //Object (){} (函数对象)下面我们就用图来好好的捋一下思路吧
今天就先到这里吧,后面我会继续补充的,第一次写文章,请大佬轻喷~
