JavaScript对象

ECMAScript中没有类的概念,因此它的对象也基于类的对象有所不同。

ECMA-262定义对象为:“无序属性的集合,属性可以包含基本值、对象或者函数”。

可以把它想象成一个散列表 无非就是一组名值对,值可以是属性或者方法。

面向对象程序设计中常用的概念对象,方法、属性、类、分装、聚合、继承、多态.

创建对象

//创建一个Object实例
var person = new Object();
person.name = "Alice";
person.age = 23;

person.sayname = function(){
     alert(this.name);
};

//用字面量创建

var person = {
     name: "Alice",
     age : 23,
     sayName:function(){
     alert(this.name);
}
};
与前面的person对象一样,都具有相同的属性和方法,这种创建对象成为开发人员的首选。
但是这样的创建方法有个明显的缺陷,就是创建很多对象会产生大量重复的代码,所以会出现以下几种创建对象的方法。

工厂模式

工厂模式抽象了具体创建对象的过程,ECMAScript没有类的接口,开发人员发明了一种函数,用函数类分装特定接口创建对象的细节。

function createPerson(name, age, job){
     var o = new Object();
     o.name = name;
     o.age = age;
     o.job = job;
     o.sayName = function(){
     alert(this.name);
};
     return o;
}
这个函数能根据接受的参数来构建一个包含所有表信息的person对象,可以无数次的调用这个函数,而且每次都会返回一个包含三个属性一个方法的对象。工厂模式解决了相似创建的问题但没有解决对象识别的问题。所以出现了另一种模式

构造函数模式
ECMAScript的构造函数可以用来创建特定的对象。Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。
function Person(name, age, job){ //按照惯例构造函数始终是以大写字母开头
     this.name = name;
     this.age = age;
     this.sayName = fuction(){
     alert(this.name);
};
}
var person1 = new Person("alice", 34, "doctor");
var person2 = new Person("bob", 24, "actor");

new操作符其实会经历四个步骤
  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象
  3. 执行构造函数中的代码
  4. 返回新对象

person1和person2都保存一个 constructor( 构造函数)属性,该属性指向Person,constructor最初是用来标识对象类型的。

创建自定义的构造函数意味着可以将他的实例标识为一种特定的类型;
所以接下来就可以用instanceof操作符来验证对象的类型,Person成为了一种类型:
alert(person1 instanceof Object);//true
alert(person2 instanceof Person);//true

构造函数与其他函数不同之处只有调用方式上的不同,不过构造函数也是函数,不存在构造函数的特殊语法。 任何函数只要通过new操作符来调用,就可以当做是构造函数。
构造函数的问题,就是每个方法都要在每个实例上重新创建一遍,前面的实例中的方法sayName实际上是不同的function的实例,其实从逻辑的角度来讲构造函数是这样定义的:

function Person(name, age, job){
     this.name = name;
     this.age = age;
     this.job = job;
     this.sayName = new function("alert(this.name)");
}

这样更容易明白每个person实例都包含不同的function实例,不同的实例上的同名函数不相等。
但是可以这样,将构造函数定义转移到构造函数外部来解决这个问题:
function Person(name, age, job){
     this.name = name;
     this.age = age;
     this.job = job;
     this.sayName = sayName;
}

function sayName(){
     alert(this.name);
}
这样做解决了两个实例中不同的函数做同样的事情,但是问题又来了; 在全局作用域中的某个函数实际上只能被某个对象调用,更让人无法接受的是如果需要定义很多方法,那么就要定义很多全局函数,然而自定义的引用类型就没有什么封装性可言了,好在这些问题可以使用原型模式来解决。

原型模式

我们创建的每一个函数有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途包含特定类型所有实例共享的属性和方法;使用原型对象的好处是可以让所有的实例对象共享它所包含的所有属性和方法。可以直接将这些信息添加在原型对象中。

function Person(){
}
Person.prototype.name = "alice";
Person.prototype.age = "alice";
Person.prototype.sayName = function(){
     alert(this.name);
};
var person1 = new Person();
Person1.sayName();
var person2 = new Person();
Person2.lsayName();

alert(person1.sayName == person2.sayName); //true
但是使用原型这些属性和方法是共享的,Person1和Person2访问的都是同一个sayName()函数;

可以通过isPrototype()函数来确认是否存在这中对象之间的关系比如:
alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Person.prototype.isPrototypeOf(person2));  //true
因为person1和person2内部都有一个指向Person.prototype的指针,所以这个方法就返回true。
使用Object.prototypeOf()就可以获取到一个对象的原型。

虽然可以通过对象实例访问保存在原型中的值,但是不能通过对象重写原型中的值。 如果为实例添加了和原型一样的一个同名属性,则实例中的属性会屏蔽掉原型中的属性,添加属性能够阻止我们访问原型中的属性。 但是使用Delete操作符可以完全删除实例中的属性,使我们可以访问原型中的属性。
delete person1.name;
hasOwnProperty(),用这个方法可以检测一个属性是否存在于实例中。
person1.hasOwnProperty("name");
原型与in操作符
有两种方式使用in操作符,单独使用和在for in循环中使用;单独使用时in会在对象能够访问到的属性时给返回true,无论属性是在实例中还是在原型中。
function Person(){
}
Person.prototype.name = "alice";
Person.prototype.age = "alice";
Person.prototype.sayName = function(){
     alert(this.name);
};
var person1 = new Person();
person1.hasOwnProperty("name");//false
alert("name" in person1);  //true

//通过hasOwnProperty和in操作符就能够确定属性在实例中还是在原型中
function hasPrototypeProperty(Object, name){
     return !Object.hasOwnProperty(name) && (name in Object);
}

更简单的原型语法
原始中每一次为原型添加一个属性或者方法都要敲一遍Person.prototype,为了减少输入,更好的封装,常见的做法是用包含所有属性和方法的对象字面量来重写整个原型对象:
function Person(){
};
Person.prototype = {
     name: "Aclie",
     age : 23,
     sayName:function(){
     alert(this.name);     
}
};
这样用字面量重写了对象的prototype对象,这样constructor属性不再指向Person对象,因此通过constructor属性无法确认对象的类型。//上面介绍过,创建一个函数的阿同时就会创建他的prototype对象,这个对象自动获得constructor属性,但是在这里完全覆盖了默认的prototype对象。

但constructor属性等于Object而不等于Person了,所以特意设定一次值。
function Person(){
};
Person.prototype = {
     conatructor : Person,
     name: "Aclie",
     age : 23,
     sayName:function(){
     alert(this.name);     
}
};
原型的动态性:由于在原型中查找值是一次搜索,因此对于原型对象所做的任何修改都能够从实例中立即反映出来,即使是先创建了实例之后再修改原型也是如此。
     但是注意:用这种方法重写原型会切断了现有原型与任何之前已经存在的对象实例之间的联系。在 调用构造函数的时候会为实例添加一个指向最初原型的指针,而 重写原型对象等于切断了构造函数和最初原型之间的关系
很多原生引用类型(Object,Array,String……)都是在器构造函数的原型上定义了方法。
原型对象的问题:
     原型对象的缺点,忽略了构造函数传入初始化参数,结果所有的实例取得相同的属性值,虽然在某种程度上不方便,但不是最大问题, 最大问题是共享的属性导致的。

对于包含引用类型值的属性来说,问题就比较突出了。
function Person(){
};
Person.prototype = {
     conatructor : Person,
     name: "Aclie",
     age : 23,
     firends :["alex", "penny"],
     sayName:function(){
     alert(this.name);     
     }
};
var person1 = new Person();
var person2 = new Person();

person1.firents.push("vam");

alert(person1.firends); //"alex", "penny",""vam
alert(person2.firends); //"alex", "penny",""vam
可是实例一般要有自己的全部属性的,所以这就是很少有单独使用原型模式的原因。

组合使用构造函数模式和原型模式
用构造函数定义实体属性,用原型模式定义共享的属性和方法。结果每个实例都有自己的一份实例属性的副本,但同时共享着对方法的引用。 另外这种混成的模式还支持构造函数传递参数。
function Person(name, age, job){
     this.name = name;
     this.age = age;
     this.job = job;
     this.firends = ["tiffniy", "cooper"];
}
Person.prototype = {
     constructor : Person,
     sayName : function(){
     alert(this.name);
     }
}

var person1 = new Person("Alcie", 23, "font-end engineer");
var person2 = new Person("Bob", 34, "Doctor");

person1.firends.push("van");
alert(person1.firends);
alert(person2.firends);
alert(person1.firends === person2.firends ); //false
alert(person1.sayName=== person2.sayName ); //true
这种构造函数和原型的混合模式是目前ECMAScript中使用最广泛,认同度最高的一种创建自定义类型的方法。

动态原型模式

跟上边的不同之处仅仅在于将原型的初始化工作放在构造函数中,这样的有点是将所有的信息都封装在构造函数中,保持了同时使用构造函数和原型的优点, 换句话说可以检查某个方法是否有效来决定是不是初始化原型。
function Person(name, age, job){
     this.name = name;
     this.age = age;
     this.job = job;
     this.firends = ["tiffniy", "cooper"];

     if(typeof this.sayName != "function"){ //检查一个即可
          //里面可以定义若干个
          this.prototype.sayName = function(){
          alert(this.name);
          }
     }
}

稳妥构造函数模式
Douglas Crockford发明了JavaScript中中的稳妥对象,他指的是没有公共属性,而且其方法也不引用this对象,稳妥对象适合在一些安全环境中(禁止this和new),或者防止其数据被其他应用程序使用或改动。 有两点不同的是创建新势力的方法不引用this;二不适用new操作符调用构造函数。
function Person(name, age, job){
     var o = new Object();
     //添加私有变量和函数

     //添加方法
     o.sayName = function(){
          alert(name);
     }
     //返回对象
     return o;
}

这样实例除了用sayName方法之外没有办法访问其数据成员,即使还可以添加方法或者数据成员,也没有其他办法访问传入到构造函数中的原始数据,稳妥的构造函数模式其提供这种安全性,使它得非常适用在某些安全的执行环境中使用。

稳妥的构造函数模式创建的 对象和构造函数之间也没有什么关系,因此使用instanceof操作符对这种对象也没有什么意义。

总结:
ECMAScript支持面向对象(OO)编程,但不使用类或者接口。 对象可以在代码执行过程中创建和增强,因此具有动态性而非严格定义的实体,在没有类的情况下,可以采用工厂模式,构造函数模式, 原型模式,组合原型和构造函数模式等创建对象.



你可能感兴趣的:(JavaScript,继承,函数,对象,工厂模式)