for-in循环可以循环所有实例可以拥有的属性,包括实例自己的属性和原型对象的属性,也包括Enumerable标记为false的属性。但是在ie中浏览器会跳过被标记为不可枚举的属性,但不是所有浏览器都是这样。
要获取对象上所有可以枚举的属性可以使用ecma5提供的Object.keys()方法,该方法返回对象所有可以枚举的属性,如果传入的参数是原型对象则返回原型对象的属性,属性的顺序与for-in循环出现的顺序一致,如果传入的是实例对象,则返回对象的实例属性,例如:
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var person1=new Person();
person1.name="zhangsan";
person1.job="worker";
var keys2=Object.keys(person1);
alert(keys2)//name,job
注意,如果想要得到所有的实例属性,而不论他是否可以枚举,则需要使用Object.getownpropertynames()方法,例如:
var keys3=Object.getOwnPropertyNames(Person.prototype);
alert(keys3);//constructor,name,age,job,sayName
实际上这个方法包括上一个方法都看传入的是什么,如果传入的是实例那么获取的就是实例属性,如果传入的是原型对象,则获取的就是原型属性。
4.更简单的原型语法
利用原型模式每当要添加一个属性就要敲一次Person.prototype,这样很麻烦,所以有更简单的原型语法,如下:
function Person1(){}
Person1.prototype={
name:"nicholas",
age:23,
job:"engineer",
sayName:function(){
alert(this.name);
}
};
var friend=new Person1;
alert(friend instanceof Person1);//true
alert(friend instanceof Object);//true
alert(friend.consturctor==Person1);//false
alert(friend.constructor==Object);//true
但是这样一来我们等于重写了原来默认的原型对象,使其失去了constructor属性,因此原型对象不在具有constructor属性,也就不再指向Person1了,所以Person1的实例也无法指向Person1,但是实例的constructor属性会指向Objec。
如果非要constructor重新指向Person1我们可以这么做:
Person1.prototype={
constructor:Person1,
name:"nicholas",
age:23,
job:"engineer",
sayName:function(){
alert(this.name);
}
};
但是这种重设constructor属性会导致本来不可以枚举的constructor属性变得可以枚举,我们可以使用Object.defineProperty()函数重新定义属性
例如:
function Person1(){}
Person1.prototype={
constructor:Person1,
name:"nicholas",
age:23,
job:"engineer",
sayName:function(){
alert(this.name);
}
};
Object.defineProperty(Person1.prototype,"constructor",{
enumerable:false,
value:Person1
});
var friend=new Person1;
alert(friend instanceof Person1);//true
alert(friend instanceof Object);//true
alert(friend.consturctor==Person1);//false
alert(friend.constructor);//function Person1(){}
5.原型的动态性
因为实例总是指向原型对象的所以无论我们在创建实例之前还是之后对原型对象做出修改,该修改都会立即反应在实例上,例如:
function Person(){}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi"
但是如果我们是完全重写原型,那么调用属性和方法就会出现错误,例如:
function Person(){}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
在这里我们先创建了实例,然后重写了原型对象,那么创建的实例仍然指向原来的原型对象,因而他找不到sayName方法,但是如果我们把重写的原型对象放在创建实例之前则该代码正常工作,这种关系表示为如图:
6. 原型模式的重要性不仅体现在自定义类型上,就连原生的引用类型都采用这种模式创建的。因此我们可以利用这一点扩展原生引用类型的属性和方法,例如给String原生引用类型增加startsWith函数:
如下所示:
String.prototype.starWith=function(text){
return this.indexOf(text)==0;
}
var msg="hello word";
alert(msg.starWith("hello"));//true
尽管我们可以通过这种方式修改原生对象的原型,但是并不推荐这样做,因为这样做会导致另外一个支持新添加方法的对象产生冲突,也会意外的重写原生对象的方法。
7.原型对象的问题
但原型对象也不是没有缺点的,因为新建的实例都指向原型对象,因此,一旦在某个实例上操作属性,则该变化会直接反映到其他实例上,例如:
function Person(){};
Person.prototype={
constructor:Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends:["zhangsan","lisi"],
sayName : function () {
alert(this.name);
}
};
var person1=new Person();
var person2=new Person();
person1.friends.push("wangmazi");
alert(person1.friends);//zhangsan,lisi,wangmazi
alert(person2.friends);//zhangsan,lisi,wangmazi
因此这就是我们日常很少看到有人单独使用原型对象模式的原因。
8.组合使用构造函数模式和原型模式
因为利用构造函数的模式可以定义实例属性,每当创建一个对象时该属性就会因实例不同而不同,换句话说每个实例都拥有自己的一套实例属性的副本,所以当定义实例属性时我们用构造函数模式,当需要共享方法时我们可以重写原型对象,将共享的方法写在原型对象中,如下例:
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["zhangsan","lisi"]
};
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
};
var person1=new Person("tom",34,"worker");
var person2=new Person("jey",24,"engineer");
person1.friends.push("wangmazi");
alert(person1.friends);//zhangsan,lisi,wangmazi
alert(person2.friends);//zhangsan,lisi
alert(person2.friends==person1.friends);//false friends来自实例属性
alert(person2.sayName==person1.sayName);//true来自原型属性
注意利用构造函数创建的对象实例彼此是独立的,因此我修改person1的friends属性不会影响到person2的friends属性。
构造函数模式和原型模式组合使用也是当前用来定义引用类型的一种默认模式。
9.动态原型模式
我们把固定的实例属性封装在构造函数中,同时又将可有可无的方法封装在原型中,,这样可以通过检查某个属性是否存在来决定是否初始化原型。
例如:
function Person(name, age, job){
//properties
this.name = name;
this.age = age;
this.job = job;
//methods
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();//nicholas
10.寄生构造函数模式
除了使用new操作符创建实例和把包装方法叫做构造函数外,它与工厂模式没有任何差别,默认情况下构造函数没有返回值的情况下返回新的对象实例,而通过在结尾添加return语句可以重写调用构造函数时返回的值。例如:
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
我们还可以利用寄生构造函数模式给数组或其他引用类型添加额外的方法,例如:
function specialArray()
{
var values=new Array();
values.push.apply(values,arguments);
values.toPipeString=function()
{
return this.join("|");
}
return values;
}
var colors=new specialArray("red","blue","yellow");
alert(colors.toPipeString());//red|blue|yellow
11.稳妥构造函数模式
此种模式比较适合安全性要求比较高的环境,这些环境会禁止使用this和new,所谓稳妥对象指的是没有公共属性,其方法也不引用this的对象,例如:
function person(name,age,job)
{
var o=new Object();
o.sayName=function()
{
alert(name);
}
return o;
}
var person1=person("zhangsan",29,"worker");
alert(person1.sayName());//zhangsan
注意构造函数里面没有this对象,创建实例时不允许用new。
以上插图及部分文字描述来自《JavaScript高级程序设计》,由本人加工整理再创作而成。如有欠妥之处还望批评指正。
来源:https://blog.csdn.net/weixin_38268037/article/details/100982743