对象的属性操作

对于对象来说,属性操作是绕不开的话题。


基本操作

[1] 属性查询:可以使用点运算符或者方括号运算符来进行属性查询。

var o = {
  p: 'Hello World'
};
o.p // "Hello World"
o['p'] // "Hello World"

[注意] 变量中可存在中文,因为中文相当于字符,与英文字符同样对待,因此可写成person.白或person['白']。

var person = {
    白 : 1
}
person.白;//1
person['白'];//1
【点运算符】

点运算符是很多面向对象语句的通用写法,由于其比较简单,所以较方括号运算符相比,更常用。

由于JavaScript是弱类型语言,在任何对象中都可以创建任意数量的属性。但当通过点运算符(.)访问对象的
属性时,属性名用一个标识符来表示,标识符要符合变量命名规则。标识符必须直接出现在JavaScript程序
中,它们不是数据类型,因此程序无法修改它们。

var o = {
    a:1,
    1:2
};
console.log(o.a);//1
//由于变量不可以以数字开头,所以o.1报错
console.log(o.1);//Uncaught SyntaxError: missing ) after argument list

【方括号运算符】

当通过方括号运算符([])来访问对象的属性时,属性名通过字符串来表示。字符串是javascript的数据类型,
在程序运行中可以修改和创建它们。

使用方括号运算符有两个优点:

1. 可以通过变量来访问属性

var a = 1;
var o = {
    1: 10
}
o[a];//10

2. 属性名称可以为javascript无效标识符

var myObject = {
    123:'zero',
    class:'foo'
};
console.log(myObject['123'],myObject['class']);//'zero' 'foo'
console.log(myObject.123);//报错

方括号中的值若是非字符串类型会使用String()隐式转换成字符串再输出;如果是字符串类型,若有引号则
原值输出,否则会被识别为变量,若变量未定义,则报错。

var person = {};
person[0];  //[]中的数字不会报错,而是自动转换成字符串
person[a];  //[]中符合变量命名规则的元素会被当成变量,变量未被定义,而报错
person['']; //[]中的空字符串不会报错,是实际存在的且可以调用,但不会在控制台右侧的集合中显示

person[undefined];//不会报错,而是自动转换成字符串
person[null];//不会报错,而是自动转换成字符串
person[true];//不会报错,而是自动转换成字符串
person[false];//不会报错,而是自动转换成字符串

可计算属性名

	在方括号运算符内部可以使用表达式。

	var a = 1;
	var person = {
	    3: 'abc'
	};
	person[a + 2];//'abc'

	但如果要在对象字面量内部对属性名使用表达式,则需要使用ES6的可计算属性名。

	var a = 1;
	//Uncaught SyntaxError: Unexpected token +
	var person = {
	    a + 3: 'abc'
	};

	ES6增加了可计算属性名,可以在文字中使用[]包裹一个表达式来当作属性名。

	var a = 1;
	var person = {
	    [a + 3]: 'abc'
	};
	person[4];//'abc'

属性查询错误

1. 查询一个不存在的属性不会报错,而是返回undefined

var person = {};
console.log(person.a);//undefined

2. 如果对象不存在,试图查询这个不存在的对象的属性会报错

console.log(person.a);//Uncaught ReferenceError: person is not defined

	可以利用这一点,来检查一个全局变量是否被声明。

// 检查a变量是否被声明
if (a) {...} // 报错

//所有全局变量都是window对象的属性。window.a的含义就是读取window对象的a属性,如果该属性不存在,
就返回undefined,并不会报错
if (window.a) {...} // 不报错

[2] 属性设置,又称为属性赋值,与属性查询相同,具有点运算符和方括号运算符这两种方法。

o.p = 'abc';
o['p'] = 'abc';
在给对象设置属性之前,一般要先检测对象是否存在。

var len = undefined;
if(book){
    if(book.subtitle){
        len = book.subtitle.length;
    }
}

上面代码可以简化为:

var len = book && book.subtitle && book.subtitle.length;

null和undefined不是对象,给它们设置属性会报错。

null.a = 1;//Uncaught TypeError: Cannot set property 'a' of null
undefined.a = 1;//Uncaught TypeError: Cannot set property 'a' of undefined

由于string、number和boolean有对应的包装对象,所以给它们设置属性不会报错。

'abc'.a = 1;//1
(1).a = 1;//1
true.a = 1;//1

[3] 属性删除

使用delete运算符可以删除对象属性(包括数组元素)。

var o = {
    a : 1
};
console.log(o.a);//1
console.log('a' in o);//true
console.log(delete o.a);//true
console.log(o.a);//undefined
console.log('a' in o);//false

[注意]给对象属性置null或undefined,并没有删除该属性

var o = {
    a : 1
};
o.a = undefined;
console.log(o.a);//undefined
console.log('a' in o);//true
console.log(delete o.a);//true
console.log(o.a);//undefined
console.log('a' in o);//false

使用delete删除数组元素时,不会改变数组长度

var a = [1,2,3];
delete a[2];
2 in a;//false
a.length;//3

delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删
除它,而且这会影响到所有继承自这个原型的对象)。

var o  = {
    a:1
}
var obj = Object.create(o);
obj.a = 2;

console.log(obj.a);//2
console.log(delete obj.a);//true
console.log(obj.a);//1
console.log(delete obj.a);//true
console.log(obj.a);//1
返回值:delete操作符的返回值是个布尔值true或false。

1. 当使用delete操作符删除对象属性或数组元素删除成功时,返回true。

var o = {a:1};
var arr = [1];
console.log(delete o.a);//true
console.log(delete arr[0]);//true

2. 当使用delete操作符删除不存在的属性或非左值时,返回true

var o = {};
console.log(delete o.a);//true
console.log(delete 1);//true
console.log(delete {});//true

3. 当使用delete操作符删除变量时,返回false,严格模式下会抛出ReferenceError错误

var a = 1;
console.log(delete a);//false
console.log(a);//1

'use strict';
var a = 1;
//Uncaught SyntaxError: Delete of an unqualified identifier in strict mode
console.log(delete a);

4. 当使用delete操作符删除不可配置的属性时,返回false,严格模式下会抛出TypeError错误

var obj = {};
Object.defineProperty(obj,'a',{configurable:false});
console.log(delete obj.a);//false

'use strict';
var obj = {};
Object.defineProperty(obj,'a',{configurable:false});
//Uncaught TypeError: Cannot delete property 'a' of #<Object>
console.log(delete obj.a);

属性继承

每一个Javascript对象都和另一个对象相关联  —  原型对象

“另一个对象”就是我们熟知的原型,每一个对象都从原型继承属性。

所有通过对象直接量创建的对象都具有同一个原型对象,可以通过Object.prototype获得对原型对象的引用。

var obj = {};
console.log(obj.__proto__ === Object.prototype);//true

[注意]Object.prototype的原型对象是null,所以它不继承任何属性

console.log(Object.prototype.__proto__ === null);//true

对象本身具有的属性叫自有属性(own property),从原型对象继承而来的属性叫继承属性。

var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
//继承自原型对象o的属性a
console.log(obj.a);//1
//自有属性b
console.log(obj.b);//2
in

in操作符可以判断属性在不在该对象上,但无法区别自有还是继承属性。

var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
console.log('a' in obj);//true
console.log('b' in obj);//true
console.log('b' in o);//false

for-in

通过for-in循环可以遍历出该对象中所有可枚举属性

var o = {a:1};
var obj = Object.create(o);
obj.b = 2;
for(var i in obj){
    console.log(obj[i]);//2 1
}

hasOwnProperty()

与Object.keys()方法不同,Object.getOwnPropertyNames()方法返回所有自有属性(包括不可枚举的属性)

var o = {a:1};
var obj = Object.create(o,{
    c:{value:3,configurable: false}
});
obj.b = 2;
console.log(Object.getOwnPropertyNames(obj));//['c','b']

对象拷贝


对象拷贝分为浅拷贝(shallow)和深拷贝(deep)两种。

  • 浅拷贝只复制一层对象的属性,并不会进行递归复制,而JS存储对象都是存地址的。所以,浅拷贝会导致对象中的子对象指向同一块内存地址。
  • 而深拷贝则不同,它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深拷贝的方法递归复制到新对象上,拷贝了所有层级。

(1)浅拷贝

【方法一】简单拷贝
新建一个空对象,使用for-in循环,将对象的所有属性复制到新建的空对象中。

function simpleClone1(obj){
    if(typeof obj != 'object'){
        return false;
    }
    var cloneObj = {};
    for(var i in obj){
        cloneObj[i] = obj[i];
    }
    return cloneObj;
}

var obj1={a:1,b:2,c:[1,2,3]};
var obj2=simpleClone1(obj1);
console.log(obj1.c); //[1,2,3]
console.log(obj2.c); //[1,2,3]
obj2.c.push(4);
console.log(obj2.c); //[1,2,3,4]
console.log(obj1.c); //[1,2,3,4]

【方法二】使用属性描述符
通过对象的原型,建立一个空的实例对象。通过forEach语句,获取到对象的所有属性的属性描述符,将其
作为参数,设置到新建的空实例对象中。

function simpleClone2(orig){
    var copy = Object.create(Object.getPrototypeOf(orig));
    Object.getOwnPropertyNames(orig).forEach(function(propKey){
        var desc = Object.getOwnPropertyDescriptor(orig,propKey);
        Object.defineProperty(copy,propKey,desc);
    });
    return copy;
}

var obj1={a:1,b:2,c:[1,2,3]};
var obj2=simpleClone1(obj1);
console.log(obj1.c); //[1,2,3]
console.log(obj2.c); //[1,2,3]
obj2.c.push(4);
console.log(obj2.c); //[1,2,3,4]
console.log(obj1.c); //[1,2,3,4]

【方法三】使用jquery的extend()方法

var obj1={a:1,b:2,c:[1,2,3]};
var obj2=$.extend({},obj1);
console.log(obj1.c); //[1,2,3]
console.log(obj2.c); //[1,2,3]
obj2.c.push(4);
console.log(obj2.c); //[1,2,3,4]
console.log(obj1.c); //[1,2,3,4]

(2)深拷贝

【方法一】遍历复制
复制对象的属性时,对其进行判断,如果是数组或对象,则再次调用拷贝函数;否则,直接复制对象属性。

function deepClone1(obj,cloneObj){
    if(typeof obj != 'object'){
        return false;
    }
    var cloneObj = cloneObj || {};
    for(var i in obj){
        if(typeof obj[i] === 'object'){
            cloneObj[i] = (obj[i] instanceof Array) ? [] : {};
            arguments.callee(obj[i],cloneObj[i]);
        }else{
            cloneObj[i] = obj[i]; 
        }  
    }
    return cloneObj;
}

var obj1={a:1,b:2,c:[1,2,3]};
var obj2=deepClone1(obj1);
console.log(obj1.c); //[1,2,3]
console.log(obj2.c); //[1,2,3]
obj2.c.push(4);
console.log(obj2.c); //[1,2,3,4]
console.log(obj1.c); //[1,2,3]

【方法二】json
用JSON全局对象的parse和stringify方法来实现深复制算是一个简单讨巧的方法,它能正确处理的对象
只有Number、String、Boolean、Array、扁平对象,即那些能够被json直接表示的数据结构。

function jsonClone(obj){
    return JSON.parse(JSON.stringify(obj));
}

var obj1={a:1,b:2,c:[1,2,3]};
var obj2=jsonClone(obj1);
console.log(obj1.c); //[1,2,3]
console.log(obj2.c); //[1,2,3]
obj2.c.push(4);
console.log(obj2.c); //[1,2,3,4]
console.log(obj1.c); //[1,2,3]

【方法三】使用jquery的extend()方法

var obj1={a:1,b:2,c:[1,2,3]};
var obj2=$.extend(true,{},obj1);
console.log(obj1.c); //[1,2,3]
console.log(obj2.c); //[1,2,3]
obj2.c.push(4);
console.log(obj2.c); //[1,2,3,4]
console.log(obj1.c); //[1,2,3]