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

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

2017-04-07 14:22 417 查看

一、对象

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

//小实验  
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)工厂模式的优点是简单方便易用好理解,缺点是无法对对象进行识别,即不能判断对象实例的类型。

[javascript]
view plain
copy

//小实验  
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来使用构造函数。

[javascript]
view plain
copy

//小实验  
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方法,虽然代码是一样的,但这俩并不是一个函数。这种创建方式明显浪费内存。

[javascript]
view plain
copy

//小实验  
var car1=new Car("BMW",10000);  
var car2=new Car("BMW",10001);  
alert(car1.getValue==car2.getValue);//false  

(7)可以把方法卸载外面,来解决(6)遇到的问题。

[javascript]
view plain
copy

//小实验  
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掉发生负载的属性,就能重新读取原型中的属性值。

[javascript]
view plain
copy

//小实验  
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来判断某个构造函数的原型是不是某个实例对象的原型。这也是判断实例的类型的方法之一。

[javascript]
view plain
copy

//小实验  
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的枚举关闭。

[javascript]
view plain
copy

//小实验  
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)实例、构造函数、原型之间是松散连接,因此在创建实例后,再定义构造函数的原型是不起作用的。

[javascript]
view plain
copy

//小实验  
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)这样做基本上就挺好了,缺点是不利于类的继承(后面会说道)。

[javascript]
view plain
copy

//小实验  
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)其实这么做就是为了好看。

[javascript]
view plain
copy

//小实验  
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排判断实例是哪个构造函数创建的,跟工厂模式一样。原因就是通过这种方式得到的对象,和在函数外面直接生成对象没有区别。

[javascript]
view plain
copy

//小实验  
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。

[javascript]
view plain
copy

//小实验  
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 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: