在了解原型链以前,咱们先要弄清楚什么是JavaScript的对象,JavaScript对象又由哪些组成。有人说一个程序就是一个世界,那么咱们能够把对象称之为这个世界的组成类型,能够是生物,植物,生活用品等等。咱们在java中管这些类型叫作类,可是在JavaScript中没有类的说法,固然ES6新标准中开始出现了类。可是在此以前,咱们都管这些类型叫作对象。那么对象建立出来的实例就是就是组成该世界的各个元素,如一我的、一只小狗、一棵树等等。这些就称之为对象的实例。那么每种类型都有它不一样的属性和方法,一样的JavaScript对象也是由对象属性和对象方法组成。固然了每一个实例还能够存在与对象不同的方法与属性。java
var person = { name:"xiaoming", //对象属性 sayName:function(){ //对象方法 console.log(this.name); } }
在JavaScript对象中,每一个属性都有其各自的特性,好比你的性别具备不可修改的特性。那么下面简单粗略介绍一下这几个特性。这些特性在JavaScript中是不能直接访问的,特性是内部值。浏览器
若是要修改属性的默认特性,可使用Object.defineProperty()
方法,固然在这里就再也不继续展开了。接下来咱们开始介绍对象的建立函数
function createPerson(name,sex){ let obj = new Object(); obj.name = name; obj.sex = sex; obj.sayName = function(){ console.log(this.name); } return obj; } let p1 = new createPerson("小明","男");
这就是工厂模式,在函数内建立对象,而后在函数内封装好后返回该对象。可是该方法有个缺点就是看不出该对象的类型,因而乎构造函数模式应运而生。优化
function Cat(name,color){ this.name = name; this.color = color; this.sayName = { console.log("我是"+name+"猫"); } } let Tom = new Cat("Tom","灰白"); let HelloKity = new Cat("HelloKity","粉红");
构造函数模式和工厂模式的区别在于,构造函数模式没有用return
语句,直接把属性赋给了this
语句,而且没有显式的建立对象。固然,若是细心的朋友应该会发现函数名首字母大写了,这是约定在构造函数时将首字母大写。this
用构造函数建立新实例时,必需要用new
操做符。同时,每一个由构造函数建立的实例都会有一个constructor
指向该构造函数spa
Tom.constructor == Cat //true
这时候咱们就会想一个问题,咱们在建立不一样的Cat
实例时,咱们就会建立多个不一样sayName
函数,可是他们执行的功能都是同样的,这时候咱们就会想要一种更优化的方法。这时,咱们须要引入原型属性(prototype)的概念了prototype
咱们建立的每一个函数里面都会有个prototype
属性,这个就是原型属性,这个属性是个指针,指向一个该函数的原型对象。咱们能够捋一捋对象,对象原型,实例这三者的关系,简单来讲,咱们能够把对象想象成爸爸,那么对象原型就是爷爷,实例的话比如是儿子。爷爷有的东西(属性、方法),每一个儿子都会遗传到的,固然若是爸爸把爷爷的东西修改了一下,那么到儿子手上的就是爸爸修改过的东西了(方法重写)。固然,儿子也算是爷爷骨肉嘛,那么儿子就会有个指针[[prototype]]
指向爷爷,在Chrome、Firefox等浏览器上面能够用属性__proto__
能够访问到。指针
prototype
和__proto__
区别在哪?这么说,简单的说prototype
是指向各自的爸爸,__proto__
是指向各自的爷爷。固然这说法只是为了更好理解这二者是有区别的。接下来我给你们作一个图让你们更好的理解这二者的区别。code
这大概也是明白为何对象实例存在个constructor
指针指向对象了,由于对象原型上面存在这个属性指向该对象,并且原型最初只包含该constructor
属性。而实例寻找属性值的时候会向上找,先在实例中搜索该属性,没有的话向对象原型寻找。因此最后找到并返回该值。这样就能很清楚的分开prototype
和__proto__
的区别了。prototype
是对象的属性,而__proto__
是对象实例的属性。对象
那么咱们基本了解prototype
属性之后,咱们就能够给你们说说原型模式了。
function Cat(){ } Cat.prototype.name = "Tom"; Cat.prototype.color = "灰白"; Cat.prototype.sayName = function(){ console.log(this.name); } let cat1 = new Cat(); let cat2 = new Cat(); cat1.sayName(); //"Tom" cat2.sayName(); //"Tom" console.log(cat1.color); //"灰白" console.log(cat2.color); //"灰白" //由于对象原型是共享属性与方法,因此全部实例均可以访问到 //接下来玩点更复杂的 Cat.sayName = function(){ console.log("我是Cat"); } cat1.sayName = function(){ console.log("我是cat1"); } let cat3 = new Cat(); cat1.sayName(); //"我是cat1" cat2.sayName(); //"Tom" cat3.sayName(); //"Tom" Cat.sayName(); //"我是Cat"
这时候不少人就懵了,为何cat3
说的是"Tom"
,而不是输出"我是Cat"
。这是由于 Cat.sayName 这个函数是类方法,咱们要注意一点,Cat
也是一个函数,函数就是一个对象,能够为其添加方法和属性。因此咱们在实例中调用sayName并非调用该类方法。咱们还须要分清类方法与对象方法的区别。
function Person(){ //经过对象实例调用 this.say = function(){ console.log("我是Person对象方法"); } } Person.say = function(){ //只能经过Person调用 console.log("我是Person类方法"); } Person.prototype.say = function(){ //经过对象实例调用 console.log("我是Person对象原型方法"); }
到这里,也许仍是会有点懵,为何后面的cat1.sayName(); //"我是cat1"
,由于对象实例方法会屏蔽掉原型的方法。咱们以前说过,当代码读取对象的某个属性时,它会先从该对象实例开始搜索,若是找不到再往上搜索。因此当你定义了对象实例的方法时,若是跟对象原型中的同名,那么该对象实例的方法就会屏蔽掉对象原型中的方法。因此cat1
第二次输出的是我是cat1
。
prototype
属性能够找到对象原型,而对象实例的[[proto]]
能够找到对象原型 constructor
属性,该属性指向该对象 那么到这里咱们已经弄懂了对象原型,对象与对象实例之间的关系。下面我再介绍一种简单的原型语法。
function Cat(){ } Cat.prototype = { name:"Tom", color:"灰白", sayName:function(){ console.log(this.name); }, }
这样我就以字面量的形式建立了新对象,可是有个不同的地方就是constructor
属性不指向Cat
,由于咱们建立一个函数就会建立它的原型对象,原型对象里面自动得到constructor
属性,那么咱们再这样的状况下,重写了整个原型对象。因此此时的constructor
属性指向了Object
。那么咱们若是非要这个属性怎么办?很好办,咱们本身给它加上就好。
function Cat(){ } Cat.prototype = { constructor:"Cat", name:"Tom", color:"灰白", sayName:function(){ console.log(this.name); }, }
最后咱们讲一下原型模式的缺点,原型模式的缺点也很明显,就是它的共享性。成也共享败也共享。这让我忽然想起共享单车。废话很少说,直接撸码上来
function Cat(){ } Cat.prototype.name = "Tom"; Cat.prototype.color = "灰白"; Cat.prototype.catchMouse = ["Jerry"]; Cat.prototype.sayName = function(){ console.log(this.name); } let cat1 = new Cat(); let cat2 = new Cat(); cat1.catchMouse.push("Mickey"); console.log(cat1.catchMouse); //["Jerry", "Mickey"] console.log(cat2.catchMouse); //["Jerry", "Mickey"]
由于原型上面的属性是全部实例均可以访问的,那么当添加往catchMouse
属性添加一个值时,全部实例皆能够访问到该属性。这就让人们很头疼了,每一个实例的属性应该都是不同的才对,起码正常来讲,可是这样弄得你们都同样的话,就有问题了。这时候,聪明的人应该均可以想到,将构造函数模式和原型模式组合起来就能够了。
将其组合起来,结合他们两的优势,是广泛认同度最高的对象建立模式
function Cat(name,color){ this.name = name; this.color = color; this.catchMouse = []; } Cat.prototype.sayName = function(){ console.log(this.name); } let cat1 = new Cat("Tom","灰白"); let cat2 = new Cat("HellowKity","粉红"); cat1.catchMouse.push("Jerry"); cat1.sayName(); //"Tom" cat2.sayName(); //"HellowKity" console.log(cat1.catchMouse); //["Jerry"] console.log(cat2.catchMouse); //[]
本篇介绍了对象与对象的建立方法。同时引入并介绍了对象原型的概念。解析了对象原型,对象与对象实例间的关系。咱们在下一篇将会解析原型链的概念以及对象的继承与原型链的关系,带你们敲开原型链的奥秘。
原创文章,转载请注明出处