对象的扩展

作者: faremax | 来源:发表于2017-10-03 17:24 被阅读8次

    基本扩展

    • 允许使用已有对象赋值定义对象字面量,并且只写变量名即可
    var name = "Bob";
    var getName = function(){console.log(this.name);};
    
    var person = {name, getName};
    //相当于
    //var person = {
    //name: "Bob",
    //getName: function(){console.log(this.name);}
    //}
    person.getName();   //"Bob"
    
    • 可以像定义存取器那样定义方法
    var o = {
      _age: 10,
      _score: 60,
      age(num){
        if(num > 0) {
          this._age = num;
          return this;
        }
        return this._age;
      },
      get score(){
        return this._score;
      }
    };
    
    console.log(o.age());    //10
    o.age(15);
    console.log(o.age());    //15
    console.log(o.score);    //60
    o.score = 100;           //TypeError
    

    注意,以下代码是等同的:

    var obj = {
      class () {}       //并不会因为 class 是关键字而解析错误
    };
    //等价于
    var obj = {
      'class': function() {}
    };
    

    如果一个方法是 Generator 函数,需要在前面加 *:

    var obj = {
      time: 1,
      *gen(){
        yield "hello " + time;
        time++;
      }
    }
    
    • 属性名表达式
      js 本来可以这样 obj['k'+'ey'] 访问一个对象属性,现在也可以这样定义属性了:
    var key1 = "name";
    var key2 = "age";
    
    var o = {
      [key1]: "Bob",
      [key2]: 18,
      ['first' + key1]: "Ellen"
    };
    o.name;    //"Bob"
    o.age;     //18
    o.firstname;   //"Ellen"
    

    注意:该方法不能和上一小节使用已有标识符定义对象字面量的方法混合使用,否则会报错;

    //错误用法
    var foo = 'bar';
    var bar = 'abc';
    var baz = {[foo]};  //报错
    
    • 方法的 name 属性
      函数有 name 属性,方法也就有 name 属性。一般方法 name 返回函数名(不包括对象名),对于存取器方法,没有 name 属性:
    var o = {
      _age: 10,
      _score: 60,
      _name: "Bob",
      _firstname: "Ellen",
      set age(num){
        if(num > 0) {
          this._age = num;
          return this;
        }
      },
      get age(){
        return this._age;
      },
      get score(){
        return this._score;
      },
      name(n){
        if(!n) return this._name + ' ' + this._firstname;
        this._name = n;
        return this;
      },
      set firstname(n){
        if(n) this._firstname = n;
        return this;
      }
    };
    console.log(o.name.name);      //"name"
    console.log(o.age.name);       //undefined
    console.log(o.score.name);     //undefined
    console.log(o.firstname);      //undefined,所以 set 函数更不会有 name 属性
    

    如果对象的方法是个 symbol,name 属性为空字符串 ""

    var sym1 = new Symbol("description of sym1");
    var sym2 = new Symbol();
    var o = {
      [sym1](){},
      [sym2](){},
    };
    o[sym1].name;    //""
    o[sym2].name;    //""
    
    • 静态方法
    1. Object.is(a,b): 比较a,b两个值是否严格相等,相当于 ===, 但有一点不一样:
    -0 === +0;     //true
    NaN === NaN;   //false
    
    Object.is(-0, +0);     //false
    Object.is(NaN, NaN);   //true
    
    1. Object.assign(target, source1,source2,...): 将每个 source 对象自身的可枚举属性复制到 target 对象上,不包括原型链上的属性和不可枚举属性。只有有一个参数不是对象,就会抛出 TypeError 错误。遇到同名属性,排在后面的会覆盖前面的:
    var target = {a:1,b:2};
    var source1 = {a:3,c:3};
    var source2 = {a:2,d:0};
    Object.assign(target, source1, source2);
    console.log(target);      //{a: 2, b: 2, c: 3, d: 0}
    

    对于属性名是 symbol 的可枚举属性也会被复制:

    Object.assign({a:'b'}, {[Symbol('c')]:'d'});    //{a: "b", Symbol(c): "d"}
    

    对于同名属性存在嵌套对象,外层会被直接替换:

    Object.assign({a:{b:'c',d:'e'}}, {a:{b:'hello'}});     //{a:{b:'hello'}}
    

    可以用 Object.assign处理数组,但会视其为对象:

    Object.assign([1,2,3], [4,5]);     //[4, 5, 3]
    

    技巧:为对象添加属性方法

    Object.assign(String.prototype, {
      newProperty: "value",
      newFunction: function(){}
    })
    

    技巧:克隆对象

    Object.assign({},origin);
    

    技巧:为对象添加属性方法

    Object.assign(target, ...source);
    

    技巧:为对象添加属性方法

    const DEFAULT_OPTION = {   //默认值
      a: 1,
      b: 2
    };
    function processContent(newOption){
      return Object.assign({}, DEFAULT_OPTION, newOption);
    }
    //设置属性应该是基本类型,否则会因为深拷贝出问题
    

    对象属性的可枚举性与遍历

    以下6个操作会忽略不可枚举的属性

    • for...in循环
    • Object.keys()
    • JSON.stringify()
    • Object.assign()
    • Reflect.enumerate()
    • 扩展运算符 ...

    以下4个方法不忽略不可枚举属性

    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Reflect.ownKeys()

    以上9个方法中,只有2个会操作包含继承到的属性

    • for...in循环
    • Reflect.enumerate()

    以上9个方法中,只有1个方法可以获得 Symbol 属性

    • Object.getOwnPropertySymbols()

    除此之外需要强调的是 ES6 中,所有 class 的原型方法都是不可枚举的:

    Object.getOwnPropertyDescriptor(class{foo(){}}.prototype, foo).enumerable;  //false
    

    ES6 起,有了7中遍历属性的方法:

    • for...in: 循环遍历对象自身和继承到的可枚举属性,不包括 Symbol 属性
    • Object.keys(obj): 返回包含自身可枚举属性的属性名数组,不包含 Symbol 属性
    • Object.getOwnPropertyNames(obj): 同上,但包括不可枚举属性
    • Object.getOwnPropertySymbols(obj): 返回自身所有 Symbol 属性名的数组,包括不可枚举属性
    • Reflect.ownKey(obj): 返回自身所有属性名数组,包括不可枚举属性和 Symbol 属性名
    • Reflect.enumerate(): 返回一个 Iterator, 用来遍历对象自身及继承到的可枚举属性,不包括 Symbol 属性;和 for...in 一样
    • for...of: 只能遍历具有 Iterator 接口的对象,具体作用范围由 iterator 决定,遍历没有 iterator 的对象会报错

    以上方法除了 for...of 以外,遍历顺序为:

    • 首先遍历所有属性名为数字的属性,按数字大小排序;
    • 其次遍历所有属性名为字符串的属性,按属性生成时间排序;
    • 最后遍历所有属性名为 Symbol 的属性,按属性生成时间排序;

    对象的proto属性

    这是个很老很老的属性,在大家想期待下,ES6终于把它写进去了,嗯?...是写进附录了。这个属性用来读写当前的对象的原型对象obj.constructor.prototype

    从本质上来讲,__proto__ 是定义在Object.prototype 上的一个存取器函数:

    function isObject(a){
      return Object(a) === a;
    }
    Object.defineProperty(Object.prototype, '__proto__', {
      get(){
        let _thisObj = Object(this);
        return Object.getPrototypeOf(_thisObj);
      },
      set(proto){
        if(this == null) throw new TypeError();
        if(!isObject(this) || !isObject(proto)) return undefined;
        let status = Object.setPrototypeOf(this, proto);
        if(! status) throw new TypeError();
      }
    });
    

    但是,还是不建议使用这个东西,毕竟看它这名字就是个内部属性,因为它有了不加,但不保证所以终端都能用,所以ES6推荐用下面这两个属性:

    Object.setPrototypeOf(obj, newProto);   //写
    Object.getPrototypeOf(obj);   //读
    

    简单举一个例子:

    function Rectangle(){}
    var rec = new Rectangle();
    Object.getPrototypeOf(rec) === Rectangle.prototype;    //true
    Object.setPrototypeOf(rec, Object.prototype);
    Object.getPrototypeOf(rec) === Rectangle.prototype;    //false
    

    当然如果你把一个对象的原型设置成了 null, 也是可以的,只是,它不具备任何你听说过的方法了:

    var o = Object.setPrototypeOf({}, null);
    //等价于 var o = {'__proto__': null};
    Object.getPrototypeOf(o);                //null
    o.toString();                    //TypeError: o.toString is not a function
    

    对象的扩展运算符

    这是 ES7 的一个提案, babel可以使用器部分功能使用。

    • rest参数
      用法和数组中很类似,这里不再过多赘述,直接看几个例子吧:
    var {x,y,...z}  = {x: 1, y: 2, a: 3, d: 4}; //x=1, y=2, z={a: 3, d: 4}
    

    值得强调的是, 对象的rest参数形式执行的是浅拷贝,赋值得到的是原对象的引用:

    let obj = {a: {b: 1}};
    let {...x} = obj;
    obj.a.b = 2;
    console.log(x.a.b);    //2
    

    此外 rest 不会复制不可枚举属性和继承自原型的属性:

    var p = {a: 0};
    var o = {b: 2};
    var o = Object.defineProperty(o, "foo", {
      value: 2,
      configurable: true,
      enumerable: false,
      writable: true
    });
    Object.setPrototypeOf(o, p);
    var u = { ...p };
    console.log(u);    //{b:2}
    
    • 扩展运算符
      复制参数对象所有可遍历属性到当前对象中:
    var o1 = {a: 1, b: 2};
    var n = { ...o1 };    //n={a: 1, b: 2};
    //相当于
    var n = Object.assign({}, o1);
    

    可以用扩展运算符合并多个对象, 排后的属性会覆盖之前的属性:

    var source0 = {a:1,b:2};
    var source1 = {a:3,c:3};
    var source2 = {a:2,d:0};
    Object.assign(target, source1, source2);
    var target = {...source0, ...source1, ...source2};
    console.log(target);      //{a: 2, b: 2, c: 3, d: 0}
    

    注意一点:如果扩展运算符的参数对象有 get 方法,该方法会被执行:

    var a = {o:1,p:2,m:4};
    var withoutError = {
      ...a,
      get x(){
        throw new Error();
      }
    };           //不报错
    var withError = {
      ...a,
      ...{get x(){
        throw new Error();
      }}
    };           //报错
    

    如果扩展对象是 null 或 undefined,会被忽略,不报错

    var o = { ...null,  ...undefined};    //不报错
    console.log(o);    //{}
    

    相关文章

      网友评论

        本文标题:对象的扩展

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