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。

你可能感兴趣的:(JavaScript对象的属性访问与复制)