对象的扩展

基本扩展

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

推荐阅读更多精彩内容

  • 欢迎访问个人站点萧然自我 1. 属性的简洁表示法 ES6允许直接写入变量和函数作为对象的属性和方法。意思就是说允许...
    勿忘巛心安阅读 292评论 0 0
  • 1. 属性的简介表示法 ES6允许在对象之中,只写属性名,不写属性值。此时,属性值等于属性名所代表的变量。如果某方...
    ForeverYoung20阅读 1,130评论 0 0
  • 一. 属性的简洁表示法 ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。 这种写法用于函...
    markpapa阅读 345评论 0 0
  • 属性的简洁表示法 ES6允许在对象中直接写入函数和变量 属性名表达式 即使用变量作为属性名 也可以表示函数名 但不...
    秋枫残红阅读 151评论 0 0
  • 1. 属性的简介表示法 varfoo ='bar'; varbaz = {foo}; baz//{foo: "ba...
    ningluo阅读 287评论 0 0