定义一个对象
声明形式
var obj = {
key: value,
key1: value1,
...
}
构造形式
var obj = new Object();
obj.key = value;
...
区别
构造形式需要逐个添加属性,声明形式可以一次性添加多个属性。
对象类型
tips:Javascript的7中基本语言类型是:string,number,boolean,null,undefined,symbol,object.
内置对象
JS中的一些对象子类型,实际上只是一些内置函数,可以当作构造函数来使用,从而可以构造一个对呀子类型的新对象。
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
对这段描述不太理解可以看一下这个例子:
var str = "I'm a string"
typeof str; // "string"
str instanceof String; // false
str.length;//13
str.charAt(3) //"m"
var strObj = new String("Im a string");
typeof strObj; // "object"
strObj instanceof String; // true
其实str这种创建方式,str只是一个字面量而不是对象,并且是一个不可变的值,那么为什么我们平时直接用这种方式也能获取到str的长度、访问其中某个字符呢?
自变量必须转换为对象才能进行一些操作,这里引擎会在必要时自动将字面量转换成String对象,因此可以访问String对象的属性和方法。
对于数值的字面量也是如此。
对于Object,Array,Function,RegExp,无论是文字形式还是构造形式,都是对象,不是字面量。
访问内容
存储在对象容齐内部的是属性的名称,就像一个指针,引用这些值真正的存储位置。
.key【属性访问】
var obj = {
a: 2,
b:7
}
obj.a; //2
[key]【键访问】
obj["b"]; // 7
可计算属性名
var pre = 'foo';
var obj = {
[pre+'x']: "x",
[pre+'y']: "y
}
obj["foox"];//x
区别
“.”操作符要求属性名满足标识符的命名规范。
注意:属性名永远是字符串,其他值若非字符串会被自动转换成字符串
var obj = {}
obj[3] = "3";
obj[obj] = "baz"
obj["3"] ; // "3"
obj["[object obj]"]; // "baz"
属性描述符
查看属性描述符:Object.getOwnPropertyDescriptor(obj, 属性)。
1.writable
writable决定是否可以修改属性的值。
var obj = {}
Object.defineProperty(obj, "a", {
value: 2,
writable: false,
configurable:true,
enumerable:true
})
obj.a = 3;
obj.a; //2,修改属性值失败,严格模式下还会抛出TypeError,表示无法修改一个不可写的属性。
2.configurable
只要属性是可配置的,就可以使用defineProperty()来修改属性描述符
var obj = {a:2}
obj.a = 3;
obj.a; //3
delete obj.a;
obj.a;//undefined
Object.defineProperty(obj, "a", {
value: 4,
writable: true,
configurable:false,
enumerable:true
})
obj.a;//4
obj.a = 5;//5
obj.a;//5
Object.defineProperty(obj, "a", {
value: 6,
writable: true,
configurable:true,
enumerable:true
}) // TypeError,把configurable改成false是单向操作,无法撤销
delete obj.a; //静默失败,因为属性是不可配置的
obj.a;//5
因此,把configurable改成false是单向操作,无法撤销。除了无法修改,还会禁止删除这个属性。
3.enumerable
是否可枚举,用户定义的普通属性默认都是true
[[Get]] 获取属性值
var obj = { a:2 }
obj.a;//2
以上代码是一次属性访问,我们看起来好像是在obj中查找名字为a的属性。
实际上,obj.a在obj上是实现了[[Get]]操作,像函数调用[[Get]]()。对象默认的内置[[Get]]操作首先在对象中查找是否有名称相同的属性,如果找到则返回这个属性的值。
否则,按照[[Get]]算法的定义会执行遍历可能存在的[[Prototype]]原型链,如果都未能找到,则返回undefined。
[[Put]]
设置属性值,实际行为如下,
如果对象中已经存在这个属性,[[Put]]会检查如下内容:
- 属性是否访问描述符?是且存在setter就调用setter
- 属性的数据描述符中writable是否为false?是,非严格模式下静默失败,严格模式下TypeError
- 如果都不是,将该值设为属性的值
Getter和Setter
当给一个属性定义setter或者getter时,这个属性会被定义为“访问描述符”(与“数据描述符”相对)。对于访问描述符,JS会忽略他们的value和writable特性,关心的是set和get(还有configurable,enumerable)特性。
var obj = {
get a(){
return 2;
}
}
Object.defineProperty(
obj,
"b",
{
get: function(){ return this.a *3 },
enumerable: true
}
)
obj.a;//2
obj.b;//6
obj.a =3; //会被忽略
obj.a;//2
var obj = {
get a(){
return this._a_;
},
set a(val){
this._a_ = val * 2;
}
}
obj.a =2;
obj.a; //4
不变性
常量对象
writable:false, configurable:false
禁止扩展 Object.preventExtensions()
var obj = { a:2 }
Object.preventExtensions(obj);
obj.b=3; //严格模式抛出错误
obj.b;//undefined,
密封 Object.seal()
实际上会调用Object.preventExtensions()同时把所有现有属性标记为configurable:false。因此,密封后不能添加新属性,不能重新配置或者删除现有属性(可以修改属性的值)
冻结 Object.freeze()
实际上会调用Object.seal()并且把所有“数据访问”属性标记为writable:false,无法修改他们的值。(只影响本身以及直接属性,不影响引用的其他对象)
深度冻结
现在这个对象上调用Object.freeze(),然后遍历它引用的对象并在这些对象上调用Object.freeze()。注意无意中冻结其他共享对象。
存在性
枚举
- 属性 in obj:操作符会检查对象及其原型链,无论是否可枚举
- obj.hasOwnProperty(属性):无论是否可枚举【只查找直接包含属性】
- 不可枚举属性不会出现在for ...in...循环中
- obj.propertyIsEnumerable(属性) //true/false :可枚举【只查找直接包含属性】
- Object.keys(obj):返回数组,包含所有可枚举属性【只查找直接包含属性】
- Object.getOwnPropertyNames():返回数组,包含所有属性,无论是否可枚举【只查找直接包含属性】
遍历
for...in...:遍历对象可枚举属性名(包括原型链)
for ... of..: 遍历对象属性值【对象本身定义了迭代器】
var arr = [1,2,3]
for (var v of arr){
console.log(v)
}
//1
//2
//3
在数组上for...in...不太好,因为这种枚举不仅会包含所有的数值索引,还会包含所有可枚举属性。最好只在对象上使用for/in循环。数组用传统的for循环。
复制对象
浅拷贝与深拷贝
浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。对于基本数据类型的拷贝,并没有深浅拷贝的区别。
function func(){}
var obj = {
c: true
}
var array = [];
var myObj = {
a:2,
b:obj,//引用而非复本
c:array, //引用而非复本
d:func
}
array.push(obj, myObj)
浅拷贝
只复制引用,而未复制真正的值。复制的新对象中会复制a的值,但是新对象中bcd三个属性只是三个引用,和旧对象中bcd引用的对象是一样的。
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
Object.assign(目标对象,源对象1,源对象2……): 拷贝可枚举(enumerable)的自有键。
var newObj = Object.assign({}, myObj);
newObj.a//2
newObj.b === obj; //true
newObj.c === array; //true
newObj.d === func; //true
深拷贝
对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
除了复制myObj以外,还会复制array、obj。那么这里array又引用了obj和myobj,所以又要复制myObj,就产生了循环引用导致死循环。
目前实现深拷贝的方法不多,主要是两种:
1.利用 JSON 对象中的 parse 和 stringify(对象中含有一个函数时(很常见),就不能用这个方法进行深拷贝)
const cloneArray = JSON.parse(JSON.stringify(originArray));
2.利用递归来实现每一层都重新创建对象并赋值。
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”
concat
连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。只是对数组的第一层进行深拷贝。
slice
只是对数组的第一层进行深拷贝。
...
实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。