新年第一篇,先说句新年一个星期后快乐,而后再开始个人正文。上个一篇主要说了面向对象中,建立对象的三种模式,工厂模式、构造函数模式还有原型模式,其中比较重要的是原型模式。原型模式中起做用的是原型对象,此次就对原型进行一番深刻的学习。数组
内容 函数
1、in操做符学习
首先咱们来了解一下in操做符。经过in操做符可以访问到一个对象中的属性。也就是说,咱们能够用过in操做符来查看到底这个对象中是否是有这个属性,以及肯定这个属性是来自实例仍是原型。this
讲一下in操做符的两种使用方法spa
1. 单独使用:经过对象能访问到属性是就会返回true,不论是在对象的实力上仍是在原型上。prototype
function Person(){} Person.prototype.name = 'jiang'; Person.prototype.age = 19; Person.prototype.job = 'sf'; Person.prototype.sayName = function(){ console.log(this.name); } var p1 = new Person(); 'name' in p1;//true
经过in操做符检测到的name属性,返回值为true,这说明在p1的实例中能够检测到name属性,可是咱们初始化时并无给实例一个name属性,说明这个属性是来自与原型对象的。指针
p1.name='z'; 'name' in p1;//true
当咱们给实例添加一个name属性的时候,此时依旧仍是true。可是这个很明显就知道这是来自实例中的属性。这两个例子说明,单单只是经过in操做符是没法知道咱们要检测的属性究竟是来自原型对象仍是实例自己。code
p1.hasOwnProperty('name');//true delete p1.name; p1.hasOwnProperty('name');//false
经过调用hasOwnProperty()方法能够知道这个属性有没有存在在实例自己,可是没法知道这个属性有没有存在在原型中。所以能够经过将这两种方法封装一块儿就能够知道究竟是来自原型仍是实例。对象
function hasPrototypeProperty(obj, name){ return !obj.hasOwnProperty(name) && name in obj; } var p2 = new Person(); hasPrototypeProperty(p2, 'name');//false p2.name = 'z'; hasPrototypeProperty(p2, 'name');//true
只要当hasOwnProperty()返回false,那么就能够肯定这个属性是来自原型,若是返回true就来自对象的实例。blog
2.for-in:返回的是可经过对象访问到的、可枚举的属性,包括原型和实例中的属性。屏蔽了原型中不可枚举属性的实例属性也能够在for-in循环中返回,全部开发人员自定义的属性都是可枚举的。
var o = { toString : function(){ return "myObject"; } } for(var prop in o){ if(prop == 'toString'){ console.log(prop);//在ie中不会显示 } }
注意:ie早期版本存在一个bug,即屏蔽不可枚举属性的实例属性不会在for-in循环中
3.Object.keys()方法。经过调用这个方法,能够返回对象中包含的全部可枚举属性的字符串数组
返回了全部原型对象中可枚举属性字符串
var keys = Object.keys(Person.prototype); console.log(key);//name, age, job, sayName 返回了实例中的可枚举属性 var key = Object.keys(p1); console.log(key);//name 这里了解一下getOwnPropertyNames()方法 var keys1 = Object.getOwnPropertyNames(Person.prototype); console.log(key);//constructor,name, age, job, sayName //这个方法连不可枚举属性constructor也显示出来了
2、原型的动态性
动态性就是说,咱们在原型对象上的修改,均可以当即在实例中反映出来,即便是先建立了对象的实例再修改。
function Person(){} Person.prototype.name = 'jiang'; Person.prototype.age = 15; Person.prototype.job = 'sf'; Person.prototype.sayName = function(){ console.log(this.name); }; var person = new Person(); Person.prototype.sayHi = function(){ console.log("hi"); };//向原型中添加一个函数 person.sayHi();
在上面的例子中,咱们先是在个Person对象的原型上添加了属性和方法,并进行了实例化,而后咱们对Person,prototype进行了修改,添加了一个名为sayHi的方法。咱们在后面经过实例始终仍是可以访问到这个方法,这是因为原型和实例之间的松散关系。可是若是咱们用另一种更简单的原型语法来重构这个对象,那效果就会不同了。
var friend = new Person(); Person.prototype = { name: 'jiang', age: 14, job: 'sf', sayName: function(){ console.log(this.name); } }
这种简单的原型语法也就是经过字面量的语法来重构,这样子至关于从新定义了一个新对象,经过重构后,会切断原型与对象之间的联系,原型链断开,原型中的constructor指针不会再指向Person,而是指向了Object。在以前也有说过,当咱们声明一个函数时,prototype对象会同时被建立,而这个对象也会自动得到constructor属性,并指向原来的函数,经过上面的方式无异于切断了二者之间的联系。可是咱们能够经过在对象中添加constructor:Person,来从新得到链接。可是若是咱们调用了friend.sayName(),就会报错,这是由于friend中最初的[[prototype]]已经再也不是指向原来的原型对象了。
修改前:
修改后:
尽管重修原型对象切换了现有的原型与已经建立的实例之间的关系,可是引用的依旧是最初的原型。
3、原生对象的原型
原生对象有哪些:Object、String、Array等等都是原生对象,这些原生的引用类型都是采用原型模式,在其构造函数的原型上定义了方法,好比Array.prototype能够找到sort()方法。经过原生对象的原型,不只仅能够获取默认方法的引用,也能够在其原型上定义方法。可是最好就不要这么作。
4、原型对象的问题
并非说原型就是完美的,问题一:忽略参数传递初始化参数这一块,结果全部的实例在默认状况下取得的值是相同的。问题二:共享性带来的问题,也是最大的问题。
function Person(){} Person.prototype ={ name : 'jiang', aga : 14, job : 'sf', friend : ['s', 'f'], sayName : function(){ console.log(this.name); } } var p1 = new Person(); var p2 = new Person(); p1.friend.push('v'); console.log(p1.friend);//"s","f","v" console.log(p2.friend);//"s","f","v"
由实例p1中没有friend这个属性,因此搜索到原型中有同名属性,因此在p1中添加的值实质上是添加到了原型的属性中,而实例p2一样没有这个属性,因此p1的修改影响到了p2。这固然是咱们不肯意看到的,固然共享函数咱们是愿意的,而共享属性的话那么就问题就大了去,就比如每一个人都有本身隐私,可是由于共享的问题,个人隐私被侵犯了,不就像是被偷窥了同样严重。
夜深人静了,是时候要就寝了,今晚的内容就这样吧。晚安~