前文
新年第一篇,先说句新年一个星期后快乐,然后再开始我的正文。上个一篇主要说了面向对象中,创建对象的三种模式,工厂模式、构造函数模式还有原型模式,其中比较重要的是原型模式。原型模式中起作用的是原型对象,这次就对原型进行一番深入的学习。
内容
一、in操作符
首先我们来了解一下in操作符。通过in操作符能够访问到一个对象中的属性。也就是说,我们可以用过in操作符来查看到底这个对象中是不是有这个属性,以及确定这个属性是来自实例还是原型。
讲一下in操作符的两种使用方法
1. 单独使用:通过对象能访问到属性是就会返回true,不管是在对象的实力上还是在原型上。
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操作符是无法知道我们要检测的属性到底是来自原型对象还是实例本身。
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就来自对象的实例。
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也显示出来了
二、原型的动态性
动态性就是说,我们在原型对象上的修改,都可以立即在实例中反映出来,即使是先创建了对象的实例再修改。
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]]已经不再是指向原来的原型对象了。
修改前:
修改后:
尽管重修原型对象切换了现有的原型与已经创建的实例之间的关系,但是引用的依旧是最初的原型。
三、原生对象的原型
原生对象有哪些:Object、String、Array等等都是原生对象,这些原生的引用类型都是采用原型模式,在其构造函数的原型上定义了方法,比如Array.prototype可以找到sort()方法。通过原生对象的原型,不仅仅可以获取默认方法的引用,也可以在其原型上定义方法。但是最好就不要这么做。
四、原型对象的问题
并不是说原型就是完美的,问题一:忽略参数传递初始化参数这一块,结果所有的实例在默认情况下取得的值是相同的。问题二:共享性带来的问题,也是最大的问题。
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。这当然是我们不愿意看到的,当然共享函数我们是愿意的,而共享属性的话那么就问题就大了去,就好比每个人都有自己隐私,但是因为共享的问题,我的隐私被侵犯了,不就像是被偷窥了一样严重。
夜深人静了,是时候要就寝了,今晚的内容就这样吧。晚安~