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

一、对象

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不能写。
[javascript] view plain copy
  1. //小实验  
  2. var book={_year:2014,edition:1};  
  3. Object.defineProperty(book,"year",{  
  4.     get:function(){return this._year;},  
  5.     set:function(value){  
  6.         if(value>2014){  
  7.             this._year=value;  
  8.             this.edition+=1;  
  9.         }  
  10.     }  
  11. });  
  12. book.year=2015;  
  13. alert(book.year);//2015  
  14. alert(book.edition);//2  
(3)使用Object.defineProperty可以同时定义多个属性,这是只能传入两个参数,第一个是对象实例,第二个是表示属性的json。
(4)使用Object.getOwnPropertyDescriptor(object,propertyName),可以返回对象实例某个属性的配置。

二、创建对象

1.工厂模式

(1)工厂模式的优点是简单方便易用好理解,缺点是无法对对象进行识别,即不能判断对象实例的类型。
[javascript] view plain copy
  1. //小实验  
  2. function createCar(name, value){  
  3.     var o = new Object();  
  4.     o.name=name;  
  5.     o._value=value;  
  6.     o.getValue=function(){return this._value;}  
  7.     o.setValue=function(v){this._value=v;}  
  8.     return o;  
  9. }  
  10. var mycar=createCar("BMW",10000);  

2.构造函数模式

(1)与工厂模式的不同在于:构造函数模式没有显示创建对象;直接将属性和方法加在了this上面;没有return。
(2)按照其他OO语言的习惯,构造函数首字母大写。
(3)使用构造函数模式,必须使用new操作符。创建后,构造函数的this指针指向了新对象(myCar)。
(4)可以用constructor判断一个实例的构造函数,也可以用instanceof判断。
(5)也可以使用函数的apply或者call来使用构造函数。
[javascript] view plain copy
  1. //小实验  
  2. function Car(name,value){  
  3.     this.name=name;  
  4.     this._value=value;  
  5.     this.getValue=function(){return this._value;}  
  6.     this.setValue=function(v){this._value=v;}  
  7. }  
  8. var myCar = new Car("BMW",10000);  
  9. var myCar2= new Object();  
  10. Car.apply(myCar2,["BMW2",10001]);  
  11. alert(myCar.constructor==Car);//true  
  12. alert(myCar instanceof Car);//true  
  13. alert(myCar instanceof Object);//true  
  14. alert(myCar2.getValue());//10001  
(6)构造函数模式的缺点是,每个对象实例的方法,都会独立创建。即myCar和myCar2的getValue方法,虽然代码是一样的,但这俩并不是一个函数。这种创建方式明显浪费内存。
[javascript] view plain copy
  1. //小实验  
  2. var car1=new Car("BMW",10000);  
  3. var car2=new Car("BMW",10001);  
  4. alert(car1.getValue==car2.getValue);//false  
(7)可以把方法卸载外面,来解决(6)遇到的问题。
[javascript] view plain copy
  1. //小实验  
  2. function CarBMW(owner,value){  
  3.     this.owner=owner;  
  4.     this._value=value;  
  5.     this.getValue=_CarBMW_getValue;  
  6.     this.setValue=_CarBMW_setValue;  
  7. }  
  8. function _CarBMW_getValue(){  
  9.     return this._value;  
  10. }  
  11. function _CarBMW_setValue(v){  
  12.     this._value=v;  
  13. }  
  14. var car3=new CarBMW("Brain",10000);  
  15. var car4=new CarBMW("Join",10001);  
  16. alert(car3.getValue==car4.getValue);//true  

3.原型模式

(1)原型模式利用了函数的prototype属性。这个属性指向一个对象,而这个对象包含了构造函数创建出的所有实例的公共属性和方法。通过语义可以这样理解,prototype指向的是一个原型,而构造函数根据这个原型来创建其他实例,这些实例会共享原型中早已定义好的属性和方法。
(2)原型模式解决了构造函数模式中创建方法副本的问题,毕竟2.7里面写的解决方案很丑陋。但原型模式也有自己的问题,就是除了方法共享外,属性也是共享的。
(3)我们可以通过实例读取原型中的属性,但不能通过实例写入原型的属性。如果有写入操作,其实是给实例创建了一个局部属性,这个局部属性屏蔽了原型的同名属性。如果出现了屏蔽现象,用delete掉发生负载的属性,就能重新读取原型中的属性值。
[javascript] view plain copy
  1. //小实验  
  2. function Car(){}  
  3. Car.prototype.name="DEMO";  
  4. Car.prototype._value=0;  
  5. Car.prototype.getValue=function(){return this._value;}  
  6. Car.prototype.setValue=function(v){this._value=v;}  
  7. var car1=new Car();  
  8. var car2=new Car();  
  9. car1.name="BMW";  
  10. car2.name="Ferrari";  
  11. alert(car1.name);//BMW;  
  12. alert(car2.name);//Ferrari;  
  13. delete car1.name;  
  14. alert(car1.name);//DEMO  
  15. car1.config.owner="Brain";  
  16. alert(car2.config.owner);//Brain;  
  17. car2.config.owner="Join";  
  18. alert(car1.config.owner);//Join;  
(4)通过isPrototypeOf和getPrototypeOf来判断某个构造函数的原型是不是某个实例对象的原型。这也是判断实例的类型的方法之一。
[javascript] view plain copy
  1. //小实验  
  2. alert(Car.prototype.isPrototypeOf(car1));//true  
  3. 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的枚举关闭。
[javascript] view plain copy
  1. //小实验  
  2. function Car(){}  
  3. Car.prototype={  
  4.     constructor:Car,  
  5.     name:"DEMO",  
  6.     _value:0,  
  7.     getValue:function(){return this._value;},  
  8.     setValue:function(v){this._value=v;}  
  9. }  
  10. Object.defineProperty(Car.prototype,"constructor",{enumerable:false,value:Car});  
(7)实例、构造函数、原型之间是松散连接,因此在创建实例后,再定义构造函数的原型是不起作用的。
[javascript] view plain copy
  1. //小实验  
  2. function Car(){}  
  3. var myCar=new Car();  
  4. Car.prototype={  
  5.     sayHello:function(){alert("hello");}      
  6. }  
  7. myCar.sayHello();//undefined is not a function  
(8)原型模式的缺点,就是属性不能是object,否则所有实例会共享这个属性。原因很简单,object属性,使用的是引用,所有实例的指针是相同的。例子在(3)的小实验部分已经给出了。

4.构造与原型混合模式

(1)把构造函数模式与原型模式混合起来,在构造函数内部创建属性成员,在原型中创建方法成员。
(2)这样做基本上就挺好了,缺点是不利于类的继承(后面会说道)。
[javascript] view plain copy
  1. //小实验  
  2. function Car(name,value){  
  3.     this.name=name;  
  4.     this._value=value;  
  5. }  
  6. Car.prototype={  
  7.     constructor:Car,  
  8.     getValue:function(){return this._value;},  
  9.     setValue:function(v){this._value=v;}  
  10. }  
  11. Object.defineProperty(Car.prototype,"constructor",{enumerable:false,value:Car});  

5.动态原型模式

(1)在js中,构造与原型混合模式已经很不错了,但对于常使用其他OO语言的程序员来说,这种构造函数和定义原型分开的做法会让他们感到亦或。他们更习惯与一个构造函数包含所有东西。包括属性定义、方法定义。
(2)可以在构造函数中判断是否已经重新定义了原型,如果没有,就定义原型,这样,起到了一个包装的作用。
(3)其实这么做就是为了好看。
[javascript] view plain copy
  1. //小实验  
  2. function Car(name,value){  
  3.     this.name=name;  
  4.     this._value=value;  
  5.     if(typeof this.getValue != "function"){  
  6.         Car.prototype.getValue=function(){return this._value;}  
  7.         Car.prototype.setValue=function(v){this._value=v;}  
  8.     }  
  9. }  

6.寄生构造函数模式

(1)寄生构造函数模式和工厂模式的写法完全相同,也是在构造函数内部创建对象,然后返回。寄生构造函数模式和工厂模式的区别是,要使用new关键字,而工厂模式不使用new。
(2)书中说“在构造函数不返回值的情况下,默认会返回新对象实例。”当使用new关键字时,构造函数是不返回值的,所以返回了它内部生成的实例。
(3)注意,正由于第三点,这种构造函数使用new和不使用new的效果是一样的。(PS:我在进行2015 A公司线试的时候遇到了这道题,题目是实现一个用A()和new A()效果相同的构造函数。)
(4)这种方法有着明显的缺点,就是不能用instanceof排判断实例是哪个构造函数创建的,跟工厂模式一样。原因就是通过这种方式得到的对象,和在函数外面直接生成对象没有区别。
[javascript] view plain copy
  1. //小实验  
  2. function Car(name,value){  
  3.     var obj=new Object();  
  4.     obj.name=name;  
  5.     obj.value=value;  
  6.     return obj;   
  7. }  
  8. var car1=new Car("BMW",10000);  
  9. var car2=Car("Ferrari",10000);  
  10. alert(car1.name);//BMW  
  11. alert(car2.name);//Ferrari  
  12. alert(car1 instanceof Car);//false  
  13. alert(car2 instanceof Car);//false  

7.稳妥构造函数模式

(1)这种模式其实就是闭包。调用一次函数后,把形参当作数据成员属性,函数内部定义object,专门保存接口,然后把object返回。
(2)这种模式的特点是不使用new,也不使用this,容易理解。缺点是如果不释放,闭包就一直在。
(3)使用这种模式,不能直接读取和修改数据成员属性,必须为这些成员配套getter和setter。
[javascript] view plain copy
  1. //小实验  
  2. function Car(name,value){  
  3.     var obj=new Object();  
  4.     obj.getValue=function(){return value;}  
  5.     return obj;  
  6. }  
  7. var car1=Car("BMW",10000);  
  8. var car2=Car("Ferrari",10001);  
  9. alert(car1.getValue());//10000  
  10. alert(car2.getValue());//10001 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值