对象--删除属性、检测属性、枚举属性

  • 删除属性

delete运算符,可以删除对象的属性。它的操作数应当是一个属性访问表达式。delete只是断开属性和宿主对象的联系,而不会去操作属性中的属性:
delete book.author;//book不再有属性author
delete book["main title"];//book也不再有属性"main title"
delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)。

当delete表达式删除成功或没有任何副作用(比如删除不存在的属性)时,它返回true。如果delete后不是一个属性访问表达式,delete同样返回true:
o={x:1};//o有一个属性x,并继承属性toString
delete o.x;//删除x,返回true
delete o.x;//什么都没做(x已经不存在了),返回true
delete o.toString;//什么也没做(toString是继承来的),返回true
delete 1;//无意义,返回true
delete不能删除那些可配置性为false的属性(尽管可以删除不可扩展对象的可配置属性)。某些内置对象的属性是不可配置的,比如通过变量声明和函数声明创建的全局对象的属性。
delete Object.prototype;//不能删除,属性是不可配置的
var x=1;//声明一个全局变量
delete this.x;//不能删除这个属性
function f(){}//声明一个全局函数
delete this.f;//也不能删除全局函数

当在非严格模式中删除全局对象的可配值属性时,可以省略对全局对象的引用,直接在delete操作符后跟随要删除的属性名即可:
this.x=1;//创建一个可配置的全局属性(没有用var)
delete x;//将它删除
然而在严格模式中,delete后跟随一个非法的操作数(比如x),则会报一个语法错误,因此必须显式指定对象及其属性:
delete x;//在严格模式下报语法错误
delete this.x;//正常工作
  • 检测属性

JavaScript对象可以看做属性的集合,我们经常会检测集合中成员的所属关系——判断某个属性是否存在于某个对象中。可以通过in运算符、hasOwnPreperty()和propertyIsEnumerable()方法来完成这个工作,甚至仅通过属性查询也可以做到这一点。

in运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回true:
var o={x:1}
"x"in o;//true:"x"是o的属性
"y"in o;//false:"y"不是o的属性
"toString"in o;//true:o继承toString属性
对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回false:
var o={x:1}
o.hasOwnProperty("x");//true:o有一个自有属性x
o.hasOwnProperty("y");//false:o中不存在属性y
o.hasOwnProperty("toString");//false:toString是继承属性
propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到是自有属性且这个属性的可枚举性(enumerable attribute)为true时它才返回true。某些内置属性是不可枚举的。通常由JavaScript代码创建的属性都是可枚举的:
var o=inherit({y:2});
o.x=1;
o.propertyIsEnumerable("x");//true:o有一个可枚举的自有属性x
o.propertyIsEnumerable("y");//false:y是继承来的
Object.prototype.propertyIsEnumerable("toString");//false:不可枚举
除了使用in运算符之外,另一种更简便的方法是使用“!==”判断一个属性是否是undefined:
var o={x:1}
o.x!==undefined;//true:o中有属性x
o.y!==undefined;//false:o中没有属性y
o.toString!==undefined;//true:o继承了toString属性
然而有一种场景只能使用in运算符而不能使用上述属性访问的方式。in可以区分不存在的属性和存在但值为undefined的属性。例如下面的代码:
var o={x:undefined}//属性被显式赋值为undefined
o.x!==undefined//false:属性存在,但值为undefined
o.y!==undefined//false:属性不存在
"x"in o//true:属性存在
"y"in o//false:属性不存在
delete o.x;//删除了属性x
"x"in o//false:属性不再存在
注意,上述代码中使用的是“!==”运算符,而不是“!=”。“!==”可以区分undefined和null。有时则不必作这种区分:
//如果o中含有属性x,且x的值不是null或undefined,o.x乘以2.
if(o.x!=null)o.x*=2;//如果o中含有属性x,且x的值不能转换为false,o.x乘以2.
//如果x是undefined、null、false、""、0或NaN,则它保持不变
if(o.x)o.x*=2;
  • 枚举属性

除了检测对象的属性是否存在,我们还会经常遍历对象的属性。通常使用for/in循环遍历,ECMAScript 5提供了两个更好用的替代方案。

for/in循环可以在循环体中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举的,但在代码中给对象添加的属性都是可枚举的(除非用下文中提到的一个方法将它们转换为不可枚举的)。例如:
var o={x:1,y:2,z:3};//三个可枚举的自有属性
o.propertyIsEnumerable("toString")//=>false,不可枚举
for(p in o)//遍历属性
    console.log(p);//输出x、y和z,不会输出toString
有许多实用工具库给Object.prototype添加了新的方法或属性,这些方法和属性可以被所有对象继承并使用。然而在ECMAScript 5标准之前,这些新添加的方法是不能定义为不可枚举的,因此它们都可以在for/in循环中枚举出来。为了避免这种情况,需要过滤for/in循环返回的属性,下面两种方式是最常见的:
for(p in o){
    if(!o.hasOwnProperty(p))continue;//跳过继承的属性
}
for(p in o){
    if(typeof o[p]==="function")continue;//跳过方法
}
下面定义了一些有用的工具函数来操控对象的属性,这些函数用到了for/in循环。实际上extend()函数经常出现在JavaScript实用工具库中。

用来枚举属性的对象工具函数
/*
*把p中的可枚举属性复制到o中,并返回o
*如果o和p中含有同名属性,则覆盖o中的属性
*这个函数并不处理getter和setter以及复制属性
*/
function extend(o,p){
    for(prop in p){//遍历p中的所有属性
        o[prop]=p[prop];//将属性添加至o中
    }
    return o;
}/*

*将p中的可枚举属性复制至o中,并返回o
*如果o和p中有同名的属性,o中的属性将不受影响
*这个函数并不处理getter和setter以及复制属性
*/

function merge(o,p){
    for(prop in p){//遍历p中的所有属性
        if(o.hasOwnProperty[prop])continue;//过滤掉已经在o中存在的属性
        o[prop]=p[prop];//将属性添加至o中
    }
    return o;
}/*

*如果o中的属性在p中没有同名属性,则从o中删除这个属性
*返回o
*/

function restrict(o,p){
    for(prop in o){//遍历o中的所有属性
        if(!(prop in p))delete o[prop];//如果在p中不存在,则删除之
    }
    return o;
}/*

*如果o中的属性在p中存在同名属性,则从o中删除这个属性
*返回o
*/

function subtract(o,p){
    for(prop in p){//遍历p中的所有属性
        delete o[prop];//从o中删除(删除一个不存在的属性不会报错)
    }
    return o;
}/*

*返回一个新对象,这个对象同时拥有o的属性和p的属性
*如果o和p中有重名属性,使用p中的属性值
*/

function union(o,p){return extend(extend({},o),p);}/*
*返回一个新对象,这个对象拥有同时在o和p中出现的属性
*很像求o和p的交集,但p中属性的值被忽略
*/

function intersection(o,p){return restrict(extend({},o),p);}/*
*返回一个数组,这个数组包含的是o中可枚举的自有属性的名字
*/

function keys(o){
    if(typeof o!=="object")throw TypeError();//参数必须是对象
    var result=[];//将要返回的数组
    for(var prop in o){//遍历所有可枚举的属性
        if(o.hasOwnProperty(prop))//判断是否是自有属性
            result.push(prop);//将属性名添加至数组中
    }
    return result;//返回这个数组
}
除了for/in循环之外,ECMAScript 5定义了两个用以枚举属性名称的函数。第一个是Object.keys(),它返回一个数组,这个数组由对象中可枚举的自有属性的名称组成,它的工作原理和上面中的工具函数keys()类似。

ECMAScript 5中第二个枚举属性的函数是Object.getOwnPropertyNames(),它和Ojbect.keys()类似,只是它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。在ECMAScript 3中是无法实现的类似的函数的,因为ECMAScript 3中没有提供任何方法来获取对象不可枚举的属性。
  • 属性getter和setter

我们知道,对象属性是由名字、值和一组特性(attribute)构成的。在ECMAScript 5[8]中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter。由getter和setter定义的属性称做“存取器属性”(accessor property),它不同于“数据属性”(data property),数据属性只有一个简单的值。

当程序查询存取器属性的值时,JavaScript调用getter方法(无参数)。这个方法的返回值就是属性存取表达式的值。当程序设置一个存取器属性的值时,JavaScript调用setter方法,将赋值表达式右侧的值当做参数传入setter。从某种意义上讲,这个方法负责“设置”属性值。可以忽略setter方法的返回值。

和数据属性不同,存取器属性不具有可写性(writable attribute)。如果属性同时具有getter和setter方法,那么它是一个读/写属性。如果它只有getter方法,那么它是一个只读属性。如果它只有setter方法,那么它是一个只写属性(数据属性中有一些例外),读取只写属性总是返回undefined。

定义存取器属性最简单的方法是使用对象直接量语法的一种扩展写法:
var o={//普通的数据属性
    data_prop:value,//存取器属性都是成对定义的函数
    get accessor_prop(){/*这里是函数体*/},
    set accessor_prop(value){/*这里是函数体*/}
};

存取器属性定义为一个或两个和属性同名的函数,这个函数定义没有使用function关键字,而是使用get和(或)set。注意,这里没有使用冒号将属性名和函数体分隔开,但在函数体的结束和下一个方法或数据属性之间有逗号分隔。例如,思考下面这个表示2D笛卡尔点坐标的对象。它有两个普通的属性x和y分别表示对应点的X坐标和Y坐标,它还有两个等价的存取器属性用来表示点的极坐标:
var p={//x和y是普通的可读写的数据属性
    x:1.0,
    y:1.0,//r是可读写的存取器属性,它有getter和setter.
//函数体结束后不要忘记带上逗号
    get r(){return Math.sqrt(this.x*this.x+this.y*this.y);},
    set r(newvalue){
        var oldvalue=Math.sqrt(this.x*this.x+this.y*this.y);
        var ratio=newvalue/oldvalue;
        this.x*=ratio;
        this.y*=ratio;
    },//theta是只读存取器属性,它只有getter方法
    get theta(){return Math.atan2(this.y,this.x);}
};
注意在这段代码中getter和setter里this关键字的用法。JavaScript把这些函数当做对象的方法来调用,也就是说,在函数体内的this指向表示这个点的对象,因此,r属性的getter方法可以通过this.x和this.y引用x和y属性。

和数据属性一样,存取器属性是可以继承的,因此可以将上述代码中的对象p当做另一个“点”的原型。可以给新对象定义它的x和y属性,但r和theta属性是继承来的:
var q=inherit(p);//创建一个继承getter和setter的新对象
q.x=1,q.y=1;//给q添加两个属性
console.log(q.r);//可以使用继承的存取器属性
console.log(q.theta);
这段代码使用存取器属性定义API,API提供了表示同一组数据的两种方法(笛卡尔坐标系表示法和极坐标系表示法)。还有很多场景可以用到存取器属性,比如智能检测属性的写入值以及在每次属性读取时返回不同值:
//这个对象产生严格自增的序列号
var serialnum={//这个数据属性包含下一个序列号
//$符号暗示这个属性是一个私有属性
    $n:0,//返回当前值,然后自增
    get next(){return this.$n++;},//给n设置新的值,但只有当它比当前值大时才设置成功
    set next(n){
        if(n>=this.$n)this.$n=n;
    else throw"序列号的值不能比当前值小";
    }
};
最后我们再来看一个例子,这个例子使用getter方法实现一种“神奇”的属性:
//这个对象有一个可以返回随机数的存取器属性
//例如,表达式"random.octet"产生一个随机数
//每次产生的随机数都在0~255之间
var random={
    get octet(){return Math.floor(Math.random()*256);},
    get uint16(){return Math.floor(Math.random()*65536);},
    get int16(){return Math.floor(Math.random()*65536)-32768;}
};
 

猜你喜欢

转载自blog.csdn.net/wuyufa1994/article/details/85418820
今日推荐