一.对象
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
,但是新对象中b
、c
、d
三个属性其实只是三个引用,它们和obj
对象中b
、c
、d
引用的对象是一样的。
④既然有浅复制,那应该也有深复制把?它是什么?
深复制是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
2.属性描述符
①Object.getOwnPropertyDescriptor()
②
var person = {
age: 21
};
Object.defineProperty(person,'age',{
configurable: false,
writable: true,
enumerable: true,
value: 100
});
person.age;
>>>100
③
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
⑤除了无法更改配置,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
⑤密封: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.Getter
和Setter
①对象默认的[[Put]]
和[[Get]]
操作分别可以控制属性值的设置和获取。
②getter
是什么?
-
getter
是一个隐藏函数,会在获取属性值时调用。
③那setter
又是什么?
-
setter
也是一个隐藏函数,会在设置属性值时调用。
④那这两个玩意有什么用?
-
getter
和setter
可以部分改写默认操作,但只能应用在单个属性上,无法应用在整个对象上。
⑤前面提到的访问描述符是什么东西?
- 当你为一个属性定义
getter
、setter
或两者兼有时,这个属性就被定义为"访问描述符"。
⑥
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()
区别?
-
in
操作符和hasOwnProperty()
方法的区别在于是否查找[[Prototype]]
链。