浅谈对象

1、复制对象
JavaScript初学者最常见的问题之一就是如何复制一个对象。
思考一下这个对象:

function anotherFunction(){
    /*..*/
}
var anotherObject ={
    c : true
};
var anotherArray = [];
var myObject ={
    a : 2,
    b : anotherObject, // 引用, 不是复本!
    c : anotherArray, // 另一个引用!
    d : anotherFunction
};
anotherArray.push(anotherObject, myObject);

如何准确的表示myObject的复制呢?
首先,应该判断它是浅复制还是深复制。对于浅复制来说,复制出的新对象中a的值会复制旧对象中a的值,但是新对象中b、c、d三个属性其实只是三个引用,它们和旧对象中b、c、d引用的对象是一样的。对于深复制来说,除了复制myObject以外还会复制anotherObject和anotherArray。这时问题就来了, anotherArray 引用了 anotherObject 和myObject, 所以又需要复制 myObject, 这样就会由于循环引用导致死循环。

对于JSON安全(也就是说可以被序列化为一个JSON字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法:
var newObj = JSON.parse(JSON.stringify(someObj));

相比深复制,浅复制非常易懂并且问题要少得多,ES6定义了Object.assign()方法来实现浅复制。Objext.assign()方法的第一个参数是目标对象,之后还可以跟一个或多个源对象。它会遍历一个或多个源对象的所有可枚举(enumerable)的自有键(owned key)并把它们复制到目标对象,最后返回目标对象,就像这样:

var newObj = Object.assign( {}, myObject );
newObj.a; // 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true

2、属性描述符
从ES5开始,所有的属性都具备了属性描述符

var myObject ={
    a : 2
};
Object.getOwnPropertyDescriptor(myObject, "a");
// {
// value: 2,
// writable: true, 可写
// enumerable: true, 可枚举
// confgurable: true,可配置
// }

在创建普通属性时属性描述符会使用默认值,也可以是哦那个Object.defineProperty()来添加一个新属性或者修改一个已有属性(如果它是configurable)并对特性进行设置。举例来说:

var myObject = {};
Object.defineProperty(myObject, "a",{
    value : 2,
    writable : true,
    configurable : true,
    enumerable : true
});
myObject.a; // 2

然而,除非你想修改属性描述符,一般不会用这种方式。

属性访问[[Get]]在实现时有一个微妙却非常重要的细节,思考下列代码

var myObject = {
      a: 2
};
myObject.a; //2

myObject.a是一次属性访问,在语言规范中,myObject.a在myObject上实际上是实现了[[Get]]操作。对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到就会返回这个属性的值。否则就会遍历可能存在的[[Prototype]]链,如果无论如何都没有找到名称相同的属性,那[[Get]]操作会返回undefined。

myObject.a的属性访问返回值可能是undefined。有两种可能,1是属性不存在所以返回undefined,2是属性中存储的undefined。

我们可以在不访问属性值的情况下判断对象中是否存在这个属性:

var myObject ={
    a : 2
};
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("b"); // false

[[Put]]操作:[[Put]]被触发时,实际的行为取决于许多因素,包括对象中是否已经存在这个属性(这是最重要的因素)。如果存在:

  1. 属性是否是访问描述符?如果是并且存在setter就调用setter。
  2. 属性的数据描述符中writable是否是false?如果是,在非严格模式下静默失败,在严格模式下抛出TypeError异常。
  3. 如果都不是,将该值设置为属性的值。

Getter和Setter
对象默认的[[Put]]和[[Get]]操作分别可以控制属性值的设置和获取。在ES5中可以使用getter和setter部分改写默认操作,但是只能应该在单个属性上,无法应用在整个对象上。getter是一个隐藏函数,会在获取属性值时调用。setter也是一个隐藏函数,会在设置属性值时调用。

当给一个属性定义getter、setter或者两个都有时,这个属性会被定义为“访问描述符”。对于访问描述符来说,JavaScript会忽略它们的value和writeable特性,取而代之的是关心set和get特性。思考下列代码:

var myObject = {
    // 给 a 定义一个 getter
    get a()
    {
        return 2;
    }
};
Object.defineProperty(
    myObject, // 目标对象
    "b", // 属性名
{ // 描述符
    // 给 b 设置一个 getter
    get : function (){
        return this.a * 2
    },
    // 确保 b 会出现在对象的属性列表中
    enumerable : true
});
myObject.a; // 2
myObject.b; // 4

不管是对象文字语法中的get a(){},还是defineProperty()中的显示定义,二者都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当作属性访问的返回值:

var myObject ={
    // 给 a 定义一个 gett
    get a(){
        return 2;
    }
};
myObject.a = 3;
myObject.a; // 2

由于之定义了a的getter,所以对a的值进行设置时set操作会忽略赋值操作,不会抛出错误。而且即使有合法的setter,由于自定义的getter只会返回2,多以set操作是没有意义的。

为了让属性更合理,还应当定义setter,setter会覆盖单个属性默认的[[Put]]操作。通常来说getter和setter是成对出现。

3、遍历
for...in循环可以用来遍历对象的可枚举属性列表(包括[[Prototype]]链。但如何遍历属性的值呢?

对于数值索引的数组来说,可以使用标准的for循环来遍历值,这实际上并不是在遍历值,而是在遍历下标来指向值。

使用for..in遍历对象是无法直接获取属性值的。需要手动获取属性值。ES6增加了一种用来遍历数组的for..of循环语法

var myArray = [ 1, 2, 3 ];
for (var v of myArray) {
      console.log( v );
} 
// 1
// 2
// 3

for..of循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有返回值。
数组有内置的@@iterator,因此for..of可以直接应用在数组上。

和数组不同,普通的对象没有内置的@@iterator,所以无法自动完成for..of遍历。
需要自行定义@@iterator:

var myObject = {
    a : 2,
    b : 3
};
Object.defineProperty(myObject, Symbol.iterator,{
    enumerable : false,
    writable : false,
    configurable : true,
    value : function () {
        var o = this;
        var idx = 0;
        var ks = Object.keys(o);
        return{
            next : function (){
                return{
                    value : o[ks[idx++]],
                    done : (idx > ks.length)
                };
            }
        };
    }
}
);
// 手动遍历 myObject
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefned, done:true }
// 用 for..of 遍历 myObject
for (var v of myObject){
    console.log(v);
}
//2
// 3
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容