美文网首页
JavaScript对象的属性访问与复制

JavaScript对象的属性访问与复制

作者: yukipedia_yui | 来源:发表于2019-05-14 22:09 被阅读0次

    很多时候我们需要复制目标对象而非借助原型链访问,比如对象拷贝、各类继承方法,这里总结下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对象的属性访问与复制

          本文链接:https://www.haomeiwen.com/subject/rrbjaqtx.html