js的面向对象不像其他语言的面向对象,js中没有类的概念。ECMA-262将对象定义为:无需属性的集合,其属性可以包括基本值、对象或函数。严格来说对象就是一组没有特定顺序的值。
1、理解对象
var person=new Object();
person.name="wsh";
person.age=28;
person.job="software engineer";
person.sayName=function(){alert(this.name);}
早期常用的创建对象的模式。几年后自变量方式创建对象成为首选。
var person={
name:"wsh",
age:28,
job:"software engineer",
sayName:function(){
alert(this.name);
}
};
1.1属性类型
a.数据属性
b.访问器属性
1.2定义多个属性
1.3读取属性的特性
2 创建对象
2.1 工厂模式
function createPerson(name,age,job)
{
var o=new Object()l;
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){alert(this.name);}
return o;
}
var person1=new createPerson("wsh",28,"程序员");
var person2=new createPerson("Tom",30,"服务员");
工厂模式对然解决了创建多个相似对象的问题,但没有解决对象识别的问题(即怎样知道一个对象的类型)。随着js的发展有个模式出现了。
2.2 构造函数模式
ECMAScript中的构造函数可以用来创建特定类型的对象。
function Person(name,age,job)
{
this.name=name;
this.nage=age;
this.job=job;
this.sayName=function(){aleft(this.name);}
}
var person1=new createPerson("wsh",28,"程序员");
var person2=new createPerson("Tom",30,"服务员")
在这个例子中,Person函数取代了createPerson()函数。他们代码区别
A 没有显式的创建对象
B将属性和方法赋值给了this
C没有return 返回语句
此外还应注意到Person()中P大写,按照惯例,构造函数始终都因该第一个字母大写。而非构造函数应该以一个小写字母开头。这个做法借鉴其他编程语言,主要是为了区别ECMAScript的其他函数;因为构造函数本身也是函数,只不过可以用了创建对象而已。
要创建Person的实例,必须使用new操作符。以这种方式创建对象都有一个constructor属性,该属性指向Person,如下代码所示:
alert(person1.constructor==Person)//ture
alert(person2.constructor==Person)//ture
这个例子中我们创建的所有对象既是object的实例,同时也是Person的实例,可以通过instanceof操作符可以得到验证。
alert(person1 instanceof Object)//true
alert(person1 instanceof Person)//true
alert(person2 instanceof Object)//true
alert(person2 instanceof Person)//true
创建自定义的构造函数意味着将来可以将它的实例作为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。
1 将构造函数作为函数
构造函数和其他函数为i的区别就在于调用他们的方式不同。不过,构造函数也是函数,不存在定义构造函数的特殊语法。任何函数只要通过mew操作符来调用他就可以作为构造函数;而任何函数如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。
//当作构造函数来调用
var person=new Person("wsh",30,"程序员");
person.sayname();
//作为普通函数调用
Person("wsh",30,"程序员");//添加到window
window.syaName();//wsh
//在另一个作用域中调用
var o=new Object();
Person.call(o,"wsh",28,"程序员");
o.sayName();//wsh
2 构造函数的问题
构造函数模式的问题是,每个方法都要在每个实例上重新重建一遍。当前例子person1和person2中都定义了sayName()方法,但那两个方法不是同一个Function实例。
创建通样任务的Function实例确实没有必要,况且有this对象在,根本不用在执行代码前就把函数绑到到特定对象上面。通过把函数sayName()转移到构造函数内容可以解决这个问题。代码如下:
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName(){
alert(this.name);
}
这样做确实解决了相同函数重新创建的问题,但是新的问题出现了,函数定义为了全局,没有封装行可言。好在这些问题可以通过原型模式来解决。
2.3 原型模式
prototype:调用构造函数创建的实例对象的原型对象。使用原型对象的好处是所有实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义实例的信息,而是将这些信息直接添加到原型对象中。
function Person(){}
Person.prototye.name="wsh";
Person.prototype.age=28;
Person.prototype.job="程序员";
Person.prorotype.sayName=function(){alert(this.name);};
var person1=new Person();
person1.sayName();//wsh
var person2=new Person();
person1.sayName();//wsh
1 理解原型对象
无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。Person.prototype.constructor指向的是Person.
创建了自定义的构造函数后,其原型对象默认只会取得constructor属性;其他方法都继承Object。当调用构造函数创建一个新实例后,该实例内部将包含一个指针,指向构造函数的原型对象。要明确真正重要的一点就是,这个链接存在与实例和构造函数原型对象之间,而不是存在与实例和构造函数之间。
虽然你person1,person2不包含属性和方法,但却可以调用person1.sayName()这是通过对象查找属性的过程来实现的。
虽然在所有的实现中都无法访问到[[Prototype]],但可以通过isPrototype()方法来确定对象之间是否存在这种关系。
ECMAScript5定义了新的方法,Object.getPrototypOf()返回对象的原型。
alert(Object.getPrototypeOf(person1)==Person.prototype)//true
alert(Person.getPrototypeOf(person1).name)//wsh;
每当代码读取到某个对象的属性时,都会指向一次搜索,目标是具有给定名字的属性。首先搜索对象实例本身,如果实例中有次属性,则返回该属性,如果没找到则继续搜索原型对象。
当为实例添加一个新属性时,这个属性就会屏蔽原型对象中的同名属性。使用delete操作符则可以完全删除实例属性,恢复与原型对象的连接。
function Person(){}
Person.prototye.name="wsh";
Person.prototype.age=28;
Person.prototype.job="程序员";
Person.prorotype.sayName=function(){alert(this.name);};
var person1=new Person();
var person2=new Person();
person1.name='lc';
alert(person1.name)//lc
alert(person2.name);//wsh
delete person1.name
alert(person1.name);//wsh
person1.sayName();//wsh
person2.sayName();//wsh
使用hasOwnProperty()方法可以检测一个属性是存在于实例中还是原型中。给定属性在实例中时返回true.
2 原型与in操作符
有2中方式使用in操作符:单独使用 for-in循环使用。
alert("name" in person1);//true
havPrototypeProperty()属性存在原型中返回true.
要取的对象上所有可以枚举的实例属性,可使用ECMAScript5中的Object.keys(),此方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
var keys=Object.keys(Person.prototype);
alert(keys);//name,age,job,sayName
3 更简单的原型语法
前面例子中每添加一个属性或方法就要重新敲一遍Person.prototype,为了减少不必要的输入
,也为了从视觉上更好的封装原型,更常见的做法是用一个包含所有属性好方法的的对象自变量来重写挣个原型对象。代码:
function Person(){}
Person.prototye={
name:"wsh",
age:28,
job:"程序员",
sayName: function(){alert(this.name);}
}
此块简写代码和上面代码最终结果相同,但有个例外:constructor属性不再指向Person.本来没创建一个函数就同时会创建它的prototype对象,这个对象也会自动活动xonstructor属性,而这里使用的语法完全重写了默认的prototype对象,因此contrutctou对象也就变成了新对象的constructors属性(即object构造函数),不再指向Person.尽管instanceof操作符还能正常返回正确的结果,但是通过contructor已经无法确定对象的类型了。
var friend=new Person();
alert(friend instanceof Object)//true
alert(friend instanceof Person)//true
alert(friend.constructor == Person)//false
alert(friend.constructor==Object)//true
如果constructor的值真的很重要,可以设置回适当的值。代码:
function Person(){};
Person.prototye={
constructor:Person,
name:"wsh",
age:28,
job:"程序员",
sayName: function(){alert(this.name);}
}
注意,以这种方式重设constructor属性会导致它的[[Enumerable]]为true.默认情况下,元素的constructor属性是不可枚举的。
4 原型的动态性
function Person(){}
var friend = new Person();
Person.prototye={
constructor:Person,
name:"wsh",
age:28,
job:"程序员",
sayName: function(){alert(this.name);}
}
friend.sayName();//error
在这个例子中,我们先创建了Person的一个实例,然后又重写了其原型对象。然后在调用friend.sayName()时发生了错误,因为friend指向的原型中不包含以该名字命名的属性,friend引用的仍然是最初的原型。下图展示了这个过程的内幕。
5 原生对象的原型
6 原型对象的问题
原型模式最大问题是由其共享的本性所导致的。原型中包含引用类型值的时候尤为明显。