您的位置:首页 > Web前端 > JavaScript

《JavaScript高级程序设计 第三版》学习笔记 (四) 对象创建详解

2014-08-29 14:51 986 查看

一、对象

1.ECMAScript把对象定义为“一组没有特定顺序的值,这些值可以是基本值、对象或函数。”我们可以把js的对象想象成散列表,每个值对应这一个key。每个对象都是基于引用类型创建的,可以是前面提到的原声引用类型,也可以是自定义引用类型。

2.一个对象(实例),是它内部所有函数值(方法)的执行环境。

3.对象的属性,包括两种,数据属性和访问器属性。

(1)数据属性,包括一个数据值的位置。在这个位置可以读取和写入值。数据属性有四个特征:

  <1>configurable,默认为true,表示能否通过delete删除属性、能否修改属性的特征、能否把该属性改为访问器属性。一旦设置成false,就不能再设置成true了。

  <2>enumerable,默认为true,表示能否通过for-in循环返回属性。

  <3>writable,默认为true,表示能否修改属性值。

  <4>value,默认为undefined,包含这个属性的数据值。

  <5>想修改属性的特征,必须使用Object.defineProperty(object,propertyName,{writable:false});这种语句才能完成。在使用时,如果不指定configurable、enumerable、writable,这三个值默认都会被设成false,所以千万小心。

(2)访问器属性,不包含数据值,而是包含了一对getter和setter函数,但这俩函数不是必须的。访问期属性有四个特征:

  <1>configurable,默认为true,跟数据属性一样。

  <2>enumerable,默认为true,跟数据属性一样。

  <3>get,在读取属性时调用的函数。

  <4>set,在写入属性时调用的函数。

  <5>访问器属性不能自定义,必须用Object.defineProperty设置。不用同时指定getter和setter。不指定getter不能度,不指定setter不能写。

//小实验
var book={_year:2014,edition:1};
Object.defineProperty(book,"year",{
get:function(){return this._year;},
set:function(value){
if(value>2014){
this._year=value;
this.edition+=1;
}
}
});
book.year=2015;
alert(book.year);//2015
alert(book.edition);//2
(3)使用Object.defineProperty可以同时定义多个属性,这是只能传入两个参数,第一个是对象实例,第二个是表示属性的json。

(4)使用Object.getOwnPropertyDescriptor(object,propertyName),可以返回对象实例某个属性的配置。

二、创建对象

1.工厂模式

(1)工厂模式的优点是简单方便易用好理解,缺点是无法对对象进行识别,即不能判断对象实例的类型。

//小实验
function createCar(name, value){
var o = new Object();
o.name=name;
o._value=value;
o.getValue=function(){return this._value;}
o.setValue=function(v){this._value=v;}
return o;
}
var mycar=createCar("BMW",10000);

2.构造函数模式

(1)与工厂模式的不同在于:构造函数模式没有显示创建对象;直接将属性和方法加在了this上面;没有return。

(2)按照其他OO语言的习惯,构造函数首字母大写。

(3)使用构造函数模式,必须使用new操作符。创建后,构造函数的this指针指向了新对象(myCar)。

(4)可以用constructor判断一个实例的构造函数,也可以用instanceof判断。

(5)也可以使用函数的apply或者call来使用构造函数。

//小实验
function Car(name,value){
this.name=name;
this._value=value;
this.getValue=function(){return this._value;}
this.setValue=function(v){this._value=v;}
}
var myCar = new Car("BMW",10000);
var myCar2= new Object();
Car.apply(myCar2,["BMW2",10001]);
alert(myCar.constructor==Car);//true
alert(myCar instanceof Car);//true
alert(myCar instanceof Object);//true
alert(myCar2.getValue());//10001
(6)构造函数模式的缺点是,每个对象实例的方法,都会独立创建。即myCar和myCar2的getValue方法,虽然代码是一样的,但这俩并不是一个函数。这种创建方式明显浪费内存。

//小实验
var car1=new Car("BMW",10000);
var car2=new Car("BMW",10001);
alert(car1.getValue==car2.getValue);//false
(7)可以把方法卸载外面,来解决(6)遇到的问题。

//小实验
function CarBMW(owner,value){
this.owner=owner;
this._value=value;
this.getValue=_CarBMW_getValue;
this.setValue=_CarBMW_setValue;
}
function _CarBMW_getValue(){
return this._value;
}
function _CarBMW_setValue(v){
this._value=v;
}
var car3=new CarBMW("Brain",10000);
var car4=new CarBMW("Join",10001);
alert(car3.getValue==car4.getValue);//true

3.原型模式

(1)原型模式利用了函数的prototype属性。这个属性指向一个对象,而这个对象包含了构造函数创建出的所有实例的公共属性和方法。通过语义可以这样理解,prototype指向的是一个原型,而构造函数根据这个原型来创建其他实例,这些实例会共享原型中早已定义好的属性和方法。

(2)原型模式解决了构造函数模式中创建方法副本的问题,毕竟2.7里面写的解决方案很丑陋。但原型模式也有自己的问题,就是除了方法共享外,属性也是共享的。

(3)我们可以通过实例读取原型中的属性,但不能通过实例写入原型的属性。如果有写入操作,其实是给实例创建了一个局部属性,这个局部属性屏蔽了原型的同名属性。如果出现了屏蔽现象,用delete掉发生负载的属性,就能重新读取原型中的属性值。

//小实验
function Car(){}
Car.prototype.name="DEMO";
Car.prototype._value=0;
Car.prototype.getValue=function(){return this._value;}
Car.prototype.setValue=function(v){this._value=v;}
var car1=new Car();
var car2=new Car();
car1.name="BMW";
car2.name="Ferrari";
alert(car1.name);//BMW;
alert(car2.name);//Ferrari;
delete car1.name;
alert(car1.name);//DEMO
car1.config.owner="Brain";
alert(car2.config.owner);//Brain;
car2.config.owner="Join";
alert(car1.config.owner);//Join;
(4)通过isPrototypeOf和getPrototypeOf来判断某个构造函数的原型是不是某个实例对象的原型。这也是判断实例的类型的方法之一。

//小实验
alert(Car.prototype.isPrototypeOf(car1));//true
alert(Object.getPrototypeOf(car1)==Car.prototype);//true
(5)使用hasOwnProperty("propertyName")可以判断属性是不是实例里的。使用in操作可以判断对象是否包含某属性,不论这个属性是实例的还是原型的,alert("propertyName" in car1);。这两个结合,就能判断出属性是实例里的,还是原型里的。另外,Object.keys(object),可以获得一个实例所有可以用for-in访问的属性名字符数组。

(6)原型模式的简单语法,直接用字面量定义prototype。这样做有个问题,就是原型的constructor指针不再指向构造函数,所以不能用car1.constructor==Car来判断实例的类型,因此必须明确地指定constructor。但这又带来另一个问题,constructor本来是不能被for-in枚举的,显示定义会破坏这一原则,因此要把constructor的枚举关闭。

//小实验
function Car(){}
Car.prototype={
constructor:Car,
name:"DEMO",
_value:0,
getValue:function(){return this._value;},
setValue:function(v){this._value=v;}
}
Object.defineProperty(Car.prototype,"constructor",{enumerable:false,value:Car});
(7)实例、构造函数、原型之间是松散连接,因此在创建实例后,再定义构造函数的原型是不起作用的。

//小实验
function Car(){}
var myCar=new Car();
Car.prototype={
sayHello:function(){alert("hello");}
}
myCar.sayHello();//undefined is not a function
(8)原型模式的缺点,就是属性不能是object,否则所有实例会共享这个属性。原因很简单,object属性,使用的是引用,所有实例的指针是相同的。例子在(3)的小实验部分已经给出了。

4.构造与原型混合模式

(1)把构造函数模式与原型模式混合起来,在构造函数内部创建属性成员,在原型中创建方法成员。

(2)这样做基本上就挺好了,缺点是不利于类的继承(后面会说道)。

//小实验
function Car(name,value){
this.name=name;
this._value=value;
}
Car.prototype={
constructor:Car,
getValue:function(){return this._value;},
setValue:function(v){this._value=v;}
}
Object.defineProperty(Car.prototype,"constructor",{enumerable:false,value:Car});

5.动态原型模式

(1)在js中,构造与原型混合模式已经很不错了,但对于常使用其他OO语言的程序员来说,这种构造函数和定义原型分开的做法会让他们感到亦或。他们更习惯与一个构造函数包含所有东西。包括属性定义、方法定义。

(2)可以在构造函数中判断是否已经重新定义了原型,如果没有,就定义原型,这样,起到了一个包装的作用。

(3)其实这么做就是为了好看。

//小实验
function Car(name,value){
this.name=name;
this._value=value;
if(typeof this.getValue != "function"){
Car.prototype.getValue=function(){return this._value;}
Car.prototype.setValue=function(v){this._value=v;}
}
}

6.寄生构造函数模式

(1)寄生构造函数模式和工厂模式的写法完全相同,也是在构造函数内部创建对象,然后返回。寄生构造函数模式和工厂模式的区别是,要使用new关键字,而工厂模式不使用new。

(2)书中说“在构造函数不返回值的情况下,默认会返回新对象实例。”当使用new关键字时,构造函数是不返回值的,所以返回了它内部生成的实例。

(3)注意,正由于第三点,这种构造函数使用new和不使用new的效果是一样的。(PS:我在进行2015 A公司线试的时候遇到了这道题,题目是实现一个用A()和new A()效果相同的构造函数。)

(4)这种方法有着明显的缺点,就是不能用instanceof排判断实例是哪个构造函数创建的,跟工厂模式一样。原因就是通过这种方式得到的对象,和在函数外面直接生成对象没有区别。

//小实验
function Car(name,value){
var obj=new Object();
obj.name=name;
obj.value=value;
return obj;
}
var car1=new Car("BMW",10000);
var car2=Car("Ferrari",10000);
alert(car1.name);//BMW
alert(car2.name);//Ferrari
alert(car1 instanceof Car);//false
alert(car2 instanceof Car);//false

7.稳妥构造函数模式

(1)这种模式其实就是闭包。调用一次函数后,把形参当作数据成员属性,函数内部定义object,专门保存接口,然后把object返回。

(2)这种模式的特点是不使用new,也不使用this,容易理解。缺点是如果不释放,闭包就一直在。

(3)使用这种模式,不能直接读取和修改数据成员属性,必须为这些成员配套getter和setter。

//小实验
function Car(name,value){
var obj=new Object();
obj.getValue=function(){return value;}
return obj;
}
var car1=Car("BMW",10000);
var car2=Car("Ferrari",10001);
alert(car1.getValue());//10000
alert(car2.getValue());//10001
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐