复制对象 | 属性描述符 | 不变性 | 存在性

一.对象

1.对象如何复制?

①方法一:巧妙地使用JSON

function test(){
    return 'yyc';
}
var obj1 = {
    age: 21
};
var arr = ['G','e','r','g'];
var anotherArray = [];
var obj = {
    a: 2,
    b: obj1,
    c: arr,
    d: test
};
JSON.parse(JSON.stringify(obj));
>>>{a: 2, b: {…}, c: Array(4)}

②方法二:ES6新定义了一种方法来实现浅复制,它的名字叫Object.assign()方法

var newObj = Object.assign({},obj);
newObj;
>>>{a: 2, b: {…}, c: Array(4), d: ƒ}

③浅复制是什么?

浅复制是对对象地址的复制。通过上面的Object.assign()方法的返回对象可知,复制得到的新对象中a的值会直接复制obj对象中相应的值,也就是2,但是新对象中bcd三个属性其实只是三个引用,它们和obj对象中bcd引用的对象是一样的。

④既然有浅复制,那应该也有深复制把?它是什么?

深复制是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

json100.png
age21.png
2.属性描述符

Object.getOwnPropertyDescriptor()

descriptor.png

var person = {
    age: 21
};
Object.defineProperty(person,'age',{
    configurable: false,
    writable: true,
    enumerable: true,
    value: 100
});
person.age;
>>>100

configurableWritable.png
var person = {
    age: 21
};
Object.defineProperty(person,'age',{
    configurable: false,
    writable: true,
    enumerable: true,
    value: 100
});
person.age = 66;
person.age;
>>>66

Object.defineProperty(person,'age',{
    configurable: true,
    writable: true,
    enumerable: true,
    value: 100
});
>>>Uncaught TypeError: Cannot redefine property: age
  • configurable特性修改成false是单向操作,无法撤销!

④一个细节:即使属性是configurable: false,我们还是可以把writable的状态由true改为false,但无法由false改为true

var person = {
    age: 21
};
Object.defineProperty(person,'age',{
    configurable: false,
    writable: true,
    enumerable: true,
    value: 100
});
person.age;
>>>100

Object.defineProperty(person,'age',{
    configurable: false,
    writable: false,
    enumerable: true,
    value: 100
});
person.age;
>>>100

person.age = 66;
person.age;
>>>100
var person = {
    age: 21
};
Object.defineProperty(person,'age',{
    configurable: false,
    writable: false,
    enumerable: true,
    value: 100
});
person.age;
>>>100

Object.defineProperty(person,'age',{
    configurable: false,
    writable: true,
    enumerable: true,
    value: 100
});
person.age;
>>>Uncaught TypeError: Cannot redefine property: age
writableTrueFalse.png

⑤除了无法更改配置,configurable: false还会禁止删除delete这个属性。

function run(){
    return 'l like running';
}
var person = {
    age: 21,
    sport: run
};
person.sport();
>>>"l like running"

Object.defineProperty(person,'sport',{
    configurable: false,
    enumerable: true,
    writable: true
});
delete person.sport;
person.sport();
>>>"l like running"
  • delete语句静默失败(silently failed)了,因为属性是不可配置的,连删都不能删

  • delete语句只用来删除对象的(可删除)属性。如果对象的某个属性是某个对象或函数的最后一个引用者,对这个属性进行delete操作后,这个未引用的对象或函数就可以被垃圾回收了。但是,delete只是一个删除对象属性的操作,仅此而已,并不是一个释放内存的工具。

3.不变性

①有时候你会希望属性或对象是不可改变的,在ES5中有很多方法可以做到这一点。但是这些方法创建的都是浅不变性

②什么是浅不变性?

  • 它们只会影响目标对象和它的直接属性。如果目标对象还引用了其他对象(数组、对象、函数等),其他对象的内容并不受到影响,仍是可变的。

③方法一:对象常量

var person = {};
Object.defineProperty(person,'favorite_color',{
    value: 'green',
    configurable: false,
    writable: false
});

④方法二:禁止扩展

如果你想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions()

function run(){
    return 'l like running';
}
var person = {
    sport: run,
    age: 21
};
Object.preventExtensions(person);
person.name = 'Gerg';
person.name;
>>>undefined
密封冻结.png

⑤密封:Object.seal()

  • 密封之后不能添加新属性,也不能重新配置或删除任何现有属性,不过可以修改现有属性的值。
//不能添加新属性
function run(){
    return 'l like running';
}
var person = {
    sport: run,
    age: 21
};
Object.seal(person);
person.name = 'Gerg';
person.name;
>>>undefined
//不能重新配置现有属性
function run(){
    return 'l like running';
}
var person = {
    sport: run,
    age: 21
};
Object.seal(person);
Object.defineProperty(person,'age',{
    configurable: true
});
Object.defineProperty(person,'age',{
    configurable: false
});
>>>Uncaught TypeError: Cannot redefine property: age
//不能删除任何现有属性
function run(){
    return 'l like running';
}
var person = {
    sport: run,
    age: 21
};
Object.seal(person);
delete person.age;
>>>false

person.age;
>>>21
//不过还能修改现有属性的值喔
function run(){
    return 'l like running';
}
var person = {
    sport: run,
    age: 21
};
Object.seal(person);
person.age = 100;
person.age;
>>>100

⑥冻结Object.freeze()

  • 完犊子,这回连修改现有属性都不行了!
function run(){
    return 'l like running';
}
var person = {
    sport: run,
    age: 21
};
Object.freeze(person);
person.age = 100;
person.age;
>>>21
4.[[Get]]
var person = {
    name: 'Gerg'
};
person.name;
>>>"Gerg"

person.name是一次属性访问,在person对象中实际是使用了[[Get]]操作。

②对象默认的内置[[Get]]操作首先在对象中查找是否拥有同名的属性,如果找到就返回该属性的值。

③如果没有找到同名的属性,按照[[Get]]算法的定义会执行另一种非常重要的行为:遍历可能存在的[[Prototype]]链,也就是原型链。

④最后如果在原型链上也没找到同名属性,那么[[Get]]]操作会返回undefined

⑤细节:访问属性和访问变量是不同滴!(前提:当前词法作用域中不存在的属性和变量)。

//访问属性
var person = {
    name: 'Gerg'
};
person.city;
>>>undefined
//访问变量
z;
>>>Uncaught ReferenceError: z is not defined
5.[[Put]]

①既然有获取属性值的[[Get]],那么也应该有对属性值的设置呀,也就是[[Put]]

②如果已经存在了这个属性,[[Put]]算法大致会检查以下内容:

  • 属性是否是访问描述符(请听后文分解)? 如果是并且存在setter就调用setter
  • 属性的属性(数据)描述符中writable是否为false?如果是,在非严格模式下静默失败(silently failed);在严格模式下抛出TypeError异常。
  • 如果都不是,将该值设置为属性的值。
6.GetterSetter

①对象默认的[[Put]][[Get]]操作分别可以控制属性值的设置和获取。

getter是什么?

  • getter是一个隐藏函数,会在获取属性值时调用。

③那setter又是什么?

  • setter也是一个隐藏函数,会在设置属性值时调用。

④那这两个玩意有什么用?

  • gettersetter可以部分改写默认操作,但只能应用在单个属性上,无法应用在整个对象上。

⑤前面提到的访问描述符是什么东西?

  • 当你为一个属性定义gettersetter或两者兼有时,这个属性就被定义为"访问描述符"。
属性描述符访问描述符.png

var person = {
    get name(){
        return 'Gerg';
    }
};
person.name;
>>>"Gerg"

Object.defineProperty(person,'sayHi',{
    get: function(){
        return 'Hi ' + this.name;
    }
});
person.sayHi;
>>>"Hi Gerg"
  • 不管是对面字面量中的get name() {...},还是defineProperty(...)中的显式定义,二者都会在对象中创建一个不包含值的属性,对于这个属性的访问会自动调用一个隐藏函数,它的返回值会被当做属性访问的返回值。

⑦若只定义了属性的getter,则会忽略对该属性的赋值操作。

var person = {
    get name(){
        return 'Gerg';
    }
};
person.name = 'yyc';
person.name;
>>>"Gerg"

⑧getter和setter一起使用

var obj = {
    get a(){
        return this._a_;
    },
    set a(val){
        this._a_ = val * 2;
    }
};
obj.a = 2;
obj.a;
>>>4
7.存在性

①如何区分属性值为undefined,还是属性压根就不存在?

var person = {
    name: undefined
};
person.name;
>>>undefined

person.age;
>>>undefined

②方法一:in操作符

'name' in person;
>>>true

'age' in person;
>>>false

③方法二:hasOwnProperty()方法

person.hasOwnProperty('name');
>>>true

person.hasOwnProperty('age');
>>>false

④方法三:Object.keys()

Object.keys(person);
>>>["name"]

⑤方法四:Object.getOwnPropertyNames()

Object.getOwnPropertyNames(person);
>>>["name"]

⑥细节又来了!

  • in操作符实际上检查的是某个属性名是否存在。对于数组来说这个区别非常重要。
var arr = [2,4,6];
4 in arr;
>>>false

for(var index in arr){
    console.log(index);
}
>>>
0
1
2

⑦枚举详解

  • 方法一:for...in循环
var person = {};
Object.defineProperty(person,'name',{
    enumerable: true,
    value: 'Gerg'
});
Object.defineProperty(person,'age',{
    enumerable: false,
    value: 21
});
person.age;
>>>21

'age' in person;
>>>true

person.hasOwnProperty('age');
>>>true

for(var i in person){
    console.log(i,person[i]);
}
>>>name Gerg
  • 可见"可枚举"就相当于"可以出现在对象属性的遍历中"。其他一切正常,该属性确实存在,并且有访问值。

  • 细节!

在数组上应用for...in循环有时会产生出人意料的结果,因为这种枚举不仅会包含所有数值索引,还会包含所有可枚举属性。因此,最好只在对象上引用for...in循环,使用传统的for循环遍历数组。

方法二:propertyIsEnumerable()方法

var person = {};
Object.defineProperty(person,'name',{
    enumerable: true,
    value: 'Gerg'
});
Object.defineProperty(person,'age',{
    enumerable: false,
    value: 21
});
person.propertyIsEnumerable('name');
>>>true

person.propertyIsEnumerable('age');
>>>false

Object.keys(person);
>>>["name"]

Object.getOwnPropertyNames(person);
>>>(2) ["name", "age"]
  • propertyIsEnumerable()方法会检查给定的属性名是否直接存在于对象中(而不是原型链上)。

  • Object.keys()Object.getOwnPropertyNames()区别?

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

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,975评论 6 13
  • 特别说明,为便于查阅,文章转自https://github.com/getify/You-Dont-Know-JS...
    杀破狼real阅读 671评论 0 1
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,211评论 9 118
  • 2016/1/7 考试的第一天,紧张的一天快要过去了,无论结果如何都是近两个月以来的交卷。 考完回来吃了想吃的寿司...
    新鲜wendy阅读 208评论 0 0
  • 不知该说是冥冥之中自有定数,还是说念念不忘,必有回响,在我印象里,每当别人问起我想要从事什么工作时,我的回答都是“...
    妍兮阅读 363评论 2 2