JavaScript对象的属性访问与复制

很多时候我们需要复制目标对象而非借助原型链访问,比如对象拷贝、各类继承方法,这里总结下Js的属性访问方法以及注意事项

可以根据是否在原型链上与可枚举来区分:
获取对象直接包含的属性的方法:

Object.keys(obj)  //返回可枚举属性 字符串数组
Object.entries(obj)  //返回可以枚举属性 键值对数组
Object.getOwnPropertyNames(obj)
Object.getOwnPropertySymbols(obj)
Object.getOwnPropertyDescriptors(obj)
Object.getOwnPropertyDescriptor(obj,prop)

不仅返回自身属性,还能访问原型链上属性的只有一个方法(语句)

for..in  //遍历对象可枚举属性列表

需要注意这些方法的返回值:Object.entries(...)不仅返回属性还返回值,组成键值对,Object.getOwnPropertyDescriptor(obj,prop)需要对象以及具体的属性值,返回整个property descriptor对象,Object.getOwnPropertyDescriptors(...)返回一个property descriptor对象数组。

比较符合使用习惯的是Object.keys(...)Object.getOwnPropertyNames(...),通过返回的代表属性的字符串来进行某种操作。

涉及到具体的描述,比如访问器属性,就需要Object.getOwnPropertyDescriptors(...)这类方法

除去访问方法,另外还有对应的检测方法(运算符),检测存在性,均返回布尔值:

in
obj.hasOwnProperty(prop) //Object​.prototype​.has​OwnProperty(...)
obj.propertyIsEnumerable(prop) //Object​.prototype​.property​IsEnumerable(...)

我们可以对比这些方法来记忆:

for..inobj.propertyIsEnumerable(prop)Object.keys(obj): 针对可枚举属性,前者查找原型链
inobj.hasOwnProperty(prop):针对所有属性(包括Symbol),前者查找原型链
obj.hasOwnProperty(prop)Object.getOwnPropertyNames(obj):针对自身属性,前者可用于属性值为Symbol的情况,而后者需要同类方法Object.getOwnPropertySymbols(obj)

在用这些方法进行访问、取值之前,还有两个重要的方法需要介绍:

Object.assign(target, ...sources)
Object.create(proto, [propertiesObject])

两个方法都创建了对象:assign将可枚举属性的值复制到target,继承属性和不可枚举属性是不能拷贝的,source为多个对象时,相同属性会被后续对象合并;create创建指定原型链的对象,第二个参数指定可枚举属性。

可以开始操作了:

function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
     // do a thing
};

上面是MDN里关于混入的例子,实际上拷贝或者继承的用法核心便是如此,借用或者拷贝属性,具体一点可以是这样:

function copy(target, source, overlay) {
    for (var key in source) {
        if (source.hasOwnProperty(key)
            && (overlay ? source[key] != null : target[key] == null)
        ) {
            target[key] = source[key];
        }
    }
    return target;
}
function mixin(target, source) {
    for (var key in source) {
        if (!(var key in target)) {
            target[key] = source[key];
        }
    }
    return target;
}

通过for.. in语句 获得可枚举属性,并筛选出直接属性,当然透过target[key] = source[key];也清楚这和Object.assign()一样只能浅拷贝。
或者更具体的继承用法:

function inherits(clazz, baseClazz) {
    var clazzPrototype = clazz.prototype;
    function F() {}
    F.prototype = baseClazz.prototype;
    clazz.prototype = new F();
    // clazz.prototype = Object.create(baseClazz.prototype)
    for (var prop in clazzPrototype) {
        clazz.prototype[prop] = clazzPrototype[prop];
    }
    clazz.prototype.constructor = clazz;
    clazz.superClass = baseClazz;
}

是否把基类原型链上的方法拷贝过来、是否覆盖、是否只往上追溯一层原型链这些都视具体的应用场景而定。inherits会倾向于继承关系(保持原型链的联系),copy用于混入某些属性(组合)。

接下来总结一些注意事项
参考MDN上的分类,有这些容易忽略的情况:属性是否为访问描述符,原始类型包装,原生方法覆盖,以及异常处理是否中断执行。

1.Object.assign()使用了方法使用源对象的[[Get]]和目标对象的[[Set]],所以源对象的属性为访问器的话,只能获得[[Get]]的值,如果要完整拷贝需要结合Object.getOwnPropertyDescriptor()Object.defineProperty()

2.Object.assign()的source参数可以是基本值,基本值会封装为对象,null 和 undefined 会被忽略,并且只有字符串的包装对象才可能有自身可枚举属性。

  1. 数据描述符与访问描述符的enumerable属性默认为 false。如果使用直接赋值的方式创建对象的属性,则这个属性的enumerable为true,这是相对于Object.defineProperty(...)方法而言,比如
const obj = {
  foo: 1,
  get bar() {
    return 2;
  }
};//"foo"与"bar"均可枚举可配置
var o = {};
Object.defineProperty(o, "a", { value : 1 });
//"a"不可枚举不可写不可配置
  1. 原生方法可能被自定义的同名函数覆盖,这时候可以直接使用切换上下文的原生方法
var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};

({}).hasOwnProperty.call(foo, 'bar'); // true

// 也可以使用 Object 原型上的 hasOwnProperty 属性
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
  1. Object.assign 不会跳过那些nullundefined的源对象,在出现错误的情况下,例如,如果属性不可写,会引发TypeError,如果在引发错误之前添加了任何属性,则可以更改target对象。Object.create如果propertiesObject参数是 null 或非原始包装对象,同样抛出一个TypeError。

6.拷贝中常见等号赋值的操作如target[key] = source[key]clazz.prototype[prop] = clazzPrototype[prop],这个表达式同时有[[Get]]和[[Put]]的操作,需要注意属性设置[[Put]]可能发生屏蔽的状况:如果target本来就具有key属性,那么赋值语句只是修改;如果没有,就在其[[Prototype]]上寻找对应key,①找到并且key为可写的话,target会新增屏蔽属性,如果只读则会被忽略(严格模式下报错),②key为setter,那么target不会新增key属性,只是会调用setter。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容