JavaScript之Object对象相关方法

434 阅读21分钟

Object对象中的相关方法

Object对象的原生方法分成两类:Object本身的方法与Object的实例方法。

所谓本身的方法就是直接定义在当前构造函数对象Object对象上的也称之为静态方法

//例子
Object.xxx()

Object实例方法就是定义在Object.prototype的方法。它可以被Object实例直接共享

Object.prototype.hello = function(){
    console.log('hello');
}
var obj = new Object();
obj.hello();//hello

Object的静态方法

所谓静态方法,是指定制在Object对象本身的方法

Object.keys()

获取属性名的,keys()用来遍历对象的属性。能传一个参数,参数是一个对象,返回是一个数组,该数组都是对象自身的所有属性名。

var arr=["1","2","3","4"]
console.log(Object.keys(arr))   //获取出数组的键

var obj={
    1:"w",
    4:"c",
    3:"d"
}
console.log(Object.keys(obj))  //获取出数组并从小到大排列

Obj.getOwnPropertyNames()

和上面keys方法是类似的,也是接收一个参数,也返回一个数组,包含了该对象的所有属性。 Object.getOwnPropertyNames方法与Object.keys类似,也是接受一个对象作为参数,返回一个数组,包含了该对象自身的所有属性名。

var obj={
    1:"w",
    4:"c",
    5:"d"
}
console.log(Object.getOwnPropertyNames(obj))

对于一般的对象来说,Object.keys()Object.getOwnPropertyNames()返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。Object.keys方法只返回可枚举的属性,Object.getOwnPropertyNames方法还返回不可枚举的属性名。

枚举:可以理解为一个属性能不能被遍历,能被遍历就称为可枚举的属性,不能被遍历就称为不能被枚举的属性

var arr=["a","b","c","d"]
console.log(Object.getOwnPropertyNames(arr))

上面代码中,数组的length属性是不可枚举的属性,所以只出现在Object.getOwnPropertyNames方法的返回结果中。

由于 JavaScript 没有提供计算对象属性个数的方法,所以可以用这两个方法代替。

var obj = {
    0:'a',
    1'b',
    2: 'c'
}
Object.keys(obj).length //3
Object.getOwnPropertyNames(obj).length //3

一般情况下,使用Object.keys方法遍历对象的属性应用最多。

Object.getPrototypeOf()

Object.getPrototypeOf方法,参数是该对象,返回参数是该对象的原型。这是获取原型对象的标准方法

function fn(){};
var f1=new fn();
console.log(Object.getPrototypeOf(f1))
console.log(Object.getPrototypeOf(f1)=== fn.prototype)

通过该方法知道实例f1这个对象的原型是fn的prototype。

在这个时候我们有一些特殊的原型:

// 空对象的原型是 Object.prototype
Object.getPrototypeOf({}) === Object.prototype // true
// Object.prototype 的原型是 null
Object.getPrototypeOf(Object.prototype) === null // true
// 函数的原型是 Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype // true

Object.setPrototypeOf()

这个方法接收两个参数,第一个参数是现有对象,第二个是原型对象。

var a = {};
var b = {x : 1};
Object.setPrototypeOf(a,b);
console.log(Object.getPrototypeOf(a));//{x:1}

new命令可以使用Object.setPrototypeOf方法模拟。

var F = function () {
  this.foo = 'bar';
};
var f = new F();
console.log(f)
// 等同于
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);
console.log(f)

Object.create()

生成实例对象的常用方法是,使用new命令让构造函数返回一个实例。但是很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢?

JavaScript 提供了Object.create方法,用来满足这种需求。该方法接收一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。

// 原型对象
var A = {
  hello: function () {
    console.log('hello');
  }
};
// 实例对象
var B = Object.create(A);
console.log(Object.getPrototypeOf(B) === A )// true
B.hello() // hello
console.log(B.hello === A.hello )// true

B 对象通过Object.create来生成并且B还继承A(var B = Object.create(A);)把A对象作为B的原型返回一个新的实例,通过 Object.getPrototypeOf(B)获取B的原型是否为ABA都有hello这个方法,B可以去调用hello这个方法。

以A对象为原型生成B对象,那么B就能继承A。 除了上面提到的两个方法,Object还有不少的其它静态方法,后文咱们会一一详细讲解

其它方法

1. 对象属性模型的相关方法

Object.getOwnPropertyDescriptor():获取某个属性的描述对象。 Object.defineProperty():通过描述对象,定义某个属性。 Object.defineProperties():通过描述对象,定义多个属性。

2. 控制对象状态的方法(了解部分)

Object.preventExtensions():防止对象扩展。 Object.isExtensible():判断对象是否可扩展。 Object.seal():禁止对象配置。 Object.isSealed():判断一个对象是否可配置。 Object.freeze():冻结一个对象。 Object.isFrozen():判断一个对象是否被冻结。

Object的实例方法

方法定义在Object.prototype对象上。我们称之为实例方法,所有Object的实例对象都继承了这些方法

Object实例对象的方法,主要有以下六个。

  • Object.prototype.valueOf():返回当前对象对应的值。
  • Object.prototype.toString():返回当前对象对应的字符串形式。
  • Object.prototype.toLocaleString():返回当前对象对应的本地字符串形式。
  • Object.prototype.hasOwnProperty():判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。
  • Object.prototype.isPrototypeOf():判断当前对象是否为另一个对象的原型。
  • Object.prototype.propertyIsEnumerable():判断某个属性是否可枚举。

Object.prototype.valueOf():

返回当前对象对应的值。默认情况下是返回对象本身。

var obj = new Object();
obj.valueOf() === obj;//true

用途

可以在自动类型转换的时候默认使用这个方法

var obj = new Object();
//JavaScript就会默认调用valueOf()方法,求出obj的值再与1相加
console.log(1+obj);//"1[object Object]"

所以,如果自定义valueOf方法,就可以得到想要的结果

比如说现在调用一下1+obj想要结果等于3。使用obj调用valueOfobj.valueOf),重写一下这个函数,返回出一个2,obj+1执行的时候就会隐式的去调用一下valueOf,最终得到想要的结果

var obj = new Object();
obj.valueOf = function(){   //这里重写函数
    return 2;
}
console.log(1 + obj);//3  obj隐式调用valueOf,此时valueOf为2

用定义的Object.valueOf覆盖Object.prototype.valueOf

实现原理: 给当前实例对象定制一个valueOf属性,给一个函数,找obj的时候先去找自己的valueOf,发现这里要返回2,那就直接返回2,就不再根据原型链向上查找了。

Object.prototype.toString()

返回当前对象对应的字符串形式。默认是返回字符串的类型。

var obj1 = new Object();
console.log(obj1.toString());//"[object Object]"
var obj2 = {a:1};
obj2.toString() // "[object Object]"

返回的这个结果说明了对象的类型。

字符串[object Object]本身没有太大的用处,但是通过自定义toString方法,可以让对象在自动类型转换时,得到想要的字符串形式。

var obj = new Object();
obj.toString = function(){
    return 'hello';
}
console.log(obj + '' + 'world');//"hello world"

像数组、字符串、函数、Date对象都分别定义了自定义的toString方法,覆盖了Object.prototype.toString()方法

console.log([1, 2, 3].toString() );// "1,2,3"
			
console.log('123'.toString()); // "123"

console.log((function(){
  return 123
}).toString());

console.log((new Date()).toString())

Object.prototype.toLocaleString()

返回当前对象对应的本地字符串形式。 方法跟toString方法用法一致。

目前,主要有三个对象自定义了toLocaleString方法

Array.prototype.toLocaleString() Number.prototype.toLocaleString() Date.prototype.toLocaleString() 举例来说,日期的实例对象的toStringtoLocaleString返回值就不一样,而且toLocaleString的返回值跟用户设定的所在地域相关

var date = new Date();
console.log(date.toString()) // "Tue Jan 01 2018 12:01:33 GMT+0800 (CST)"
console.log(date.toLocaleString()) // "1/01/2018, 12:01:33 PM"

toString是国际性的、统一的。toLocaleString是根据所在地返回我们对应的结果

Object.prototype.isPrototypeOf()

用来判断该对象是否是另一个对象的原型

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
console.log(o2.isPrototypeOf(o3));//true
console.log(o1.isPrototypeOf(o3));//true

上面代码中,o1o2都是o3的原型。这表明只要实例对象处在参数对象的原型链上,isPrototypeOf方法都返回true

//Object.prototype是否为对象的原型
console.log(Object.prototype.isPrototypeOf({})); // true 

//Object.prototype是否为数组的原型
console.log(Object.prototype.isPrototypeOf([])); // true

//Object.prototype是否为null的原型
console.log(Object.prototype.isPrototypeOf(Object.create(null))); // false

null作为一个原型,object的上一层就是null也就是最顶层。那它当然不是Object.prototype。所以返回false。通过这个例子也能看出来,Object.prototype是原型链的最顶端,所以各种实例,像数组、对象都会返回true。只有直接继承null这个对象除外,因为它不是object.prototype的原型

每一个实例对象里面都有一个__proto__,同样的情况下吧object.prototype作为一个实例对象,这里面也有一个__proto__,它也指向它的原型对象,

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

Object.prototype作为一个实例原型指向null

Object.prototype.hasOwnProperty()

判断一下当前这个实例对象是否有他自己的属性,如果有这个属性返回true,如果没有则返回false

var obj = {
    a: 123
}
console.log(obj.hasOwnProperty('b'));//false
console.log(obj.hasOwnProperty('a'));//true

我们知道obj实例对象继承了Object原型上的属性,比如通过Object调用toString()toString()作为一个被继承来的属性,返回false

var obj = {
    a: 123
}
console.log(obj.hasOwnProperty('toString'));//false  判断是否有toString属性

Object.prototype.hasOwnProperty方法接受一个字符串作为参数,返回一个布尔值,表示该实例对象自身是否具有该属性。只校验实例的自身的属性,继承来的属性返回为false

属性描述对象

JavaScript提供了一个内部数据结构,用来描述对象的属性,控制它的行为,比如该属性是否可写、可遍历等等。这个内部数据结构称之为”属性描述对象”。每个属性都有自己对应的属性描述对象,保存该属性的一些元信息

var obj={};
obj.name="小太阳";
obj.name="小胖子" ;   //name是可被修改的
console.log(obj.propertyIsEnumerable("name")) ;  //可枚举可以被遍历
console.log(obj.name);    //能打印   小胖子
console.log(delete obj.name);    //可被删除的

{
  value: 小胖子,
  writable: false,    //是否有可写性,是否能被修改
  enumerable: true,    //可枚举吗?内否遍历
  configurable: false,   //能否被删除
  get: undefined,        //取值的函数  默认是undefined
  set: undefined        //赋值的函数     默认是undefined
}

属性描述对象提供了6个元属性

| 属性 | 含义 | |-----------------| ----------------|----------------| | value | value是该属性的属性值,默认为undefined。| | writable | writable是一个布尔值,表示属性值(value)是否可改变(即是否可写),默认为true。| | enumerable | enumerable是一个布尔值,表示该属性是否可遍历,默认为true。如果设为false,会使得某些操作(比如for...in循环、Object.keys())跳过该属性。| | configurable | configurable是一个布尔值,表示可配置性,默认为true。如果设为false,将阻止某些操作改写该属性,比如无法删除该属性,也不得改变该属性的属性描述对象(value属性除外)。也就是说,configurable属性控制了属性描述对象的可写性。| | get | get是一个函数,表示该属性的取值函数(getter),默认为undefined。| | set | set是一个函数,表示该属性的存值函数(setter),默认为undefined。|

在一个描述对象中,它的内部控制属性的行为就有这些属性来控制。

可以通过 **Object.getOwnPropertyDescriptor()**方法来获取这些属性。

Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor()方法可以获取属性描述对象。它的第一个参数是目标对象,第二个参数是一个字符串,对应目标对象的某个属性名。

对象举例

var obj={};
obj.name="小太阳";
obj.name="小胖子" ;   //name是可被修改的
console.log(Object.getOwnPropertyDescriptor(obj,"name"))  //获取属性描述对象的配置

数组举例

var arr=["1","2","3"];
arr.length=5;    //length可写,这里给写成5 
console.log(arr.length)
console.log(Object.getOwnPropertyDescriptor(arr,"0"));
console.log(Object.getOwnPropertyDescriptor(arr,"length"));
console.log(Object.getOwnPropertyDescriptor(arr,"toString"))   //不能用于继承的属性

注意:Object.getOwnPropertyDescriptor()方法只能用于对象自身的属性,不能用于继承的属性

Object.prototype.propertyIsEnumerable()

判断某个属性是否可枚举(是否能被遍历的意思),可枚举性就是能被遍历的(实例自身的属性),不可枚举性就是不能被遍历的属性(继承来的,设置不可枚举的属性一律返回false)。

var obj={};
obj.a=123;
for(var key in obj){
    console.log(key)    //a  能被遍历出来的
    console.log(obj[key])   //打印值
}

var obj={};
obj.a=123;
console.log(Object.propertyIsEnumerable("a"))   //a可被枚举
console.log(Object.propertyIsEnumerable("toString"))   //toString是继承下来的,不能被枚举

Object.defineProperty()

Object.defineProperty方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象。

语法如下:

Object.defineProperty(object, propertyName, attributesObject)

//Object.defineProperty(属性所在的对象, 要修改的属性名, 属性描述对象)

Object.defineProperty方法接受三个参数,依次如下。

名称含义
object属性所在的对象
propertyName字符串,表示属性名
attributesObjec属性描述对象
var obj = Object.defineProperty({},'name',{
    value:'小太阳',
    writable:false,
    enumerable:true,
    configurable:false
})
console.log(obj.name);//小太阳
obj.name = '小胖子';
console.log(obj.name);//小太阳  没被修改
delete obj.name;
console.log(obj.name);//小太阳还是能被获取  没被删除

上面代码中,Object.defineProperty()方法定义了obj.name属性。由于属性描述对象的writable属性为false,所以obj.name属性不可写。注意,这里的Object.defineProperty方法的第一个参数是{}(一个新建的空对象)name属性直接定义在这个空对象上面,然后返回这个对象,这是Object.defineProperty()的常见用法。

如果属性已经存在,Object.defineProperty()方法相当于更新该属性的属性描述对象。

Object.defineProperties()

如果一次性定义或修改多个属性,可以使用Object.defineProperties()方法

var obj = Object.defineProperties({}, {
p1: { value: 123, enumerable: true },
p2: { value: 'abc', enumerable: true },
p3: { 
  get: function () { 
      return this.p1 + this.p2 
  },
  enumerable:true,
  configurable:true     //可配置性
}
});
console.log(obj.p1);//123
console.log(obj.p2);//"abc"
console.log(obj.p3);//"123abc"

上面代码中,Object.defineProperties()同时定义了obj对象的三个属性。调用obj.p3的时候,每个属性描述对象中都有一个getset取值函数,get是获取值的,在这里获取p1p2valuep3属性定义了取值函数get,即每次读取该属性,都会调用这个取值函数。

注意,一旦定义了取值函数get(或存值函数set),就不能将writable属性设为true,或者同时定义value属性,否则会报错

不能将writable定义为true,尽量不要写

var obj2=Object.defineProperty({},"name",{
    writable:true,
    get:function(){
        return 456
    }
})

定义了取值函数get,就不能同时定义value属性

var obj2=Object.defineProperty({},"name",{
    value:"123",
    get:function(){
        return 456
    }
})

Object.defineProperty()Object.defineProperties()参数里面的属性描述对象,writableconfigurableenumerable这三个属性的默认值都为false

var obj = {};
Object.defineProperty(obj, 'foo', {});
Object.getOwnPropertyDescriptor(obj, 'foo')
/*
{
configurable: false,
enumerable: false,
value: undefined,
writable: false,
}
*/

上面代码中,定义obj.foo时用了一个空的属性描述对象,就可以看到各个元属性的默认值。obj也遍历不出来。

元属性

value

value属性是目标属性的值

var obj = {};
obj.p = 123;
console.log(Object.getOwnPropertyDescriptor(obj, 'p').value); //123
console.log(Object.defineProperty(obj, 'p', { value: 246 }));
console.log(obj.p )// 246

上面代码是通过value属性,读取或改写obj.p的例子。

writable

writable属性是一个布尔值,决定了目标属性的值(value)是否可以被改变。

var obj = {};
Object.defineProperty(obj, 'a', {
  value: 37,
  writable: false   //不能被修改
});
console.log(obj.a )// 37
obj.a = 25;    //不会被改变
console.log(obj.a )// 37

上面代码中,obj.awritable属性是false。然后,改变obj.a的值,不会有任何效果。

注意,正常模式下,对writablefalse的属性赋值不会报错,只会默默失败。但是,严格模式下会报错,即使对a属性重新赋予一个同样的值。

'use strict';
var obj = {};
Object.defineProperty(obj, 'a', {
  value: 37,
  writable: false
});
obj.a = 37;

上面代码是严格模式,对obj.a任何赋值行为都会报错。

如果原型对象的某个属性的writablefalse,那么子对象将无法自定义这个属性。

var proto = Object.defineProperty({}, 'foo', {
  value: 'a',
  writable: false
});
var obj = Object.create(proto);
obj.foo = 'b';    //不会被修改
console.log(obj.foo) // 'a'

上面代码中,proto是原型对象,它的foo属性不可写。obj对象继承proto,也不可以再自定义这个属性了。如果是严格模式,这样做还会抛出一个错误。

但是,有一个规避方法,就是通过覆盖属性描述对象,绕过这个限制。原因是这种情况下,原型链会被完全忽视。

var proto = Object.defineProperty({}, 'foo', {
  value: 'a',
  writable: false
});
var obj = Object.create(proto);
Object.defineProperty(obj, 'foo', {
  value: 'b'
});
console.log(obj.foo) // "b"

用obj覆盖了原型对象上的描述对象,这样就能修改了

enumerable

enumerable(可遍历性)返回一个布尔值,表示目标属性是否可遍历。

如果一个属性的enumerablefalse,下面三个操作不会取到该属性

  • for...in 循环
  • Object.key方法
  • JSON.stringify 方法
var obj = {};
  Object.defineProperty(obj, 'x', {
    value: 123,
    enumerable: false
  });
  obj.x // 123
  for (var key in obj) {
    console.log(key);
  }            								//获取不到,啥也没有
  console.log(Object.keys(obj)) 			 // []
  console.log(JSON.stringify(obj))			// "{}"

上面代码中,obj.x属性的enumerablefalse,所以一般的遍历操作都无法获取该属性,使得它有点像“秘密”属性,但不是真正的私有属性,还是可以直接获取它的值。

注意,for...in循环包括继承的属性,Object.keys方法不包括继承的属性。如果需要获取对象自身的所有属性,不管是否可遍历,可以使用Object.getOwnPropertyNames方法。

function A(){
    this.name="小太阳";
}
function B(){
    A.call(this);
}
var b=new B();
Object.defineProperty(b,"age",{
    value:18,
    enumrable:false   //不可枚举的属性
})
console.log(b);
//用for...in遍历一下继承的属性能否被遍历出来
for(var key in b){
    console.log(key)
}
console.log(Object.getOwnPropertyNames(b))
console.log(Object.keys(b))

通过for..in循环不能遍历不可枚举属性,Object.keys(b)也不能获取不可枚举属性age,但是通过Object.getOwnPropertyNames可以获取继承和不可枚举的属性。

另外,JSON.stringify方法会排除enumerable为false的属性,有时可以利用这一点。如果对象的 JSON 格式输出要排除某些属性,就可以把这些属性的enumerable设为false

configurable

configurable(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说,configurablefalse时,valuewritableenumerableconfigurable都不能被修改了。

var obj = Object.defineProperty({}, 'p', {
  value: 1,
  writable: false,   
  enumerable: false,
  configurable: false    //不能被修改
});
Object.defineProperty(obj, 'p', {value: 2})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {writable: true})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {enumerable: true})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {configurable: true})
// TypeError: Cannot redefine property: p

上面代码中,obj.pconfigurablefalse。改动valuewritableenumerableconfigurable,结果都报错。

注意,writable只有在false改为true会报错,true改为false是允许

var obj = Object.defineProperty({}, 'p', {
    writable: true,
    configurable: false
});
console.log(Object.defineProperty(obj, 'p', {writable: false}));
// 修改成功

至于value,只要writableconfigurable有一个为true,就允许改动。

var o1 = Object.defineProperty({}, 'p', {
  value: 1,
  writable: true,
  configurable: false
});
Object.defineProperty(o1, 'p', {value: 2})
// 修改成功
console.log(o1);

var o2 = Object.defineProperty({}, 'p', {
  value: 1,
  writable: false,
  configurable: true
});
Object.defineProperty(o2, 'p', {value: 5})
console.log(o2);

可配置性决定了目标属性是否可以被删除(delete)。

var obj = Object.defineProperties({}, {
  p1: { value: 1, configurable: true },
  p2: { value: 2, configurable: false }
});
delete obj.p1 // true
delete obj.p2 // false
obj.p1 // undefined
obj.p2 // 2

上面代码中,obj.p1configurabletrue,所以可以被删除,obj.p2就无法删除

存储器

Object.defineProperty() 里面有个get和set来对我们当前的属性进行存储。很多高级应用中都用这个方法

  1. 除了直接定义以外,属性还可以用存取器定义。其中,存值函数称为setter,使用属性描述对象的set属性;取值函数称为getter,使用属性描述对象的get属性
  2. 一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多高级特性,比如某个属性禁止赋值
var obj = Object.defineProperty({},'p',{
    get:function(){
        return 'getter';
    },
    set:function(value){
        console.log('setter:'+value);//"setter:123"
    }
})
console.log(obj.p )//"getter"
obj.p = 123 ;

上面代码中,obj.p定义了getset属性。obj.p取值时,就会调用get,赋值时,就会调用set

用这种方法我们可以设置某个对象中的某个属性是否能赋值

var obj = Object.defineProperty({},'p',{
    get:function(){
        return 'getter';
    },
    set:function(value){
        // console.log('setter:'+value);//"setter:123"
        return;   
    }
})
console.log(obj.p )//"getter"
obj.p = 123 ;

上述代码中set的代码不执行,所以下面obj.p=123这个赋值不成功。

JavaScript中除了提供上述代码的写法,还提供了一种简便写法。 get是取值函数不能接收参数,set是存值函数,要接收一个参数仅能接收一个参数。

var obj={
    get p(){     //这是取值函数不能传入参数
        return "getter";
    },
    set p(value){   //只能接收一个参数
        console.log("setter:"+value)
    }
}
console.log(obj.p);
obj.p=123;

存储器getset往往用于属性值依赖,一个属性中有value属性值,在这里面会针对于当前对象中的某个属性值进行操作。 存取器往往用于,属性的值依赖对象内部数据的场合

var obj = {
    $n : 5,
    get next(){  
        return this.$n++;
    },
    set next(value){
        if(value >= this.$n){
            this.$n = value;
        }else{
            throw new Error('新的值必须大于当前值');
        }
    }
};
console.log(obj.next) //5
obj.next = 10;
console.log(obj.next) //10
obj.next = 5;
console.log(obj.next) //10
// Uncaught Error: 新的值必须大于当前值

上面代码中,next属性的存值函数和取值函数,都必须依赖内部属性$n,一开始值存储在$n中,next$n做比较,如果赋予的值大于$n代码将依次执行,如果小于则抛出错误。

拷贝

在JavaScript中拷贝分为深拷贝浅拷贝

次数回顾一下JavaScript中的数据类型,数据类型也分为两种数据类型,一种是基本数据类型,一种的引用的数据类型。

基本数据类型:按值来传递的

经典例子:

var a=1;
var b=a;
b=200;
console.log(a);
console.log(b);

上述,b的值修改为200,当修改了拷贝之后变量中的值,不会影响原来数据类型中的的数据。那么这种方案称为深拷贝。

引用数据类型:引用类型按引用传递 例子:

var arr=[1,2,3,4];
var newArr=arr;
newArr.push(10);
console.log(newArr);
console.log(arr)

当该变量拷贝了之后这个对象中的数据的时候,紧接着原始对象中的数据也会发生改变,这种方案叫浅拷贝。

拷贝完这个对象一旦发生改变,原始对象中数据也跟着发生改变的叫浅拷贝,相反则是深拷贝(有影响的叫浅拷贝,没影响的叫深拷贝)。

浅拷贝

var obj={
    name:"小太阳",
    age:18,
    hobby:"打球",
    friend:{
        name:"小胖子",
        age:15
    }
}
function shadowCopy(toObject,fromObj){
    for(var key in fromObj){
        toObject[key]=fromObj[key]    //这样就是直接赋值了
    }
    return toObject;
}
var newObj = shadowCopy({},obj);
console.log(newObj)   //此时原始的对象已经拷贝过来了
//改变新的对象中age
newObj.age=20;
newObj.friend.name="小明";
console.log(newObj);
console.log(obj)

nameage都是基本的数据类型。hobby爱好friend朋友对应一个对象,朋友有名字和年龄,现在把obj中的数据拷贝到新的对象中,把obj中的每一个属性直接复制到新的数据中,定制一个函数shadowCopy,传入两个参数,第一个参数toObject,第二个参数fromObj。从fromObj对象中拷贝到toObject对象中。调用ShadowCopy的时候传入一个空对象,把obj对象传递进去,然后return一个拷贝后的对象。var newObj是一个储存拷贝过后的新对象。ShadowCopy对象函数内部把拷贝之后的对象return toObj返回出来。friend是一个对象属于引用类型,如果想实现一个浅拷贝,一旦newObj该变了friend中的name。通过for..in循环在内部toObj[key]=fromObj[key]直接复制。

newObjage发生了改变,在原始对象objage却没有发生改变。因为这些都是基础的数据类型。但是friend中的name在新的对象和原始对象中都有发生变化,这是因为friend是一个引用类型。安引用地址进行传递,改变了新的数据地址把原始数据地址也改变了。

由此可见浅拷贝不是直接赋值,浅拷贝是新建一个对象,将原对象的属性一一复制,复制的是值,而不是引用,引用是按地址访问的,然后在给引用地址赋值。浅拷贝的复制只是复制了第一层的属性,并没有递归所有的值复制归来(仅仅复制了obj中的name一类的属性,但是friend把一个内存地址给复制过去)。一层一层进行遍历就会存在递归。

深拷贝

深拷贝就是对目标的完全拷贝,不像浅拷贝只复制了一层的引用。只要进行了深拷贝,原来的对象和现在的对象就是独立的再无关联,谁也不影响谁。

var obj={
    name:"小太阳",
    age:18,
    hobby:["打球","跑步"],
    friend:{
        name:"小胖子",
        age:15,
        friend:{
            name:"小彩",
            age:12
        }
    }
}
function deepCopy(to,fromA){
    // 遍历fromA对象所有的属性,拷贝到to中
    for(var key in fromA){
        // 不遍历原型链上的属性
        if(fromA.hasOwnProperty(key)){
            // 如果值是对象并且有值,接着遍历对象
            if(fromA[key] && typeof fromA[key] === "object"){   //是否是一个对象,并且有值
                //fromA[key]的构造器函数是否等于Array,如果是一个Array就返回一个数组,如果不是就返回一个对象
                // 然后给to[key]赋值
                to[key]=fromA[key].constructor===Array ? [] : {};// 区分一般对象还是数组区分好了就给to[key]赋值
                  //继续递归再调用deepCopy(to.[key],fromA[key]) 遇到对象再深入的遍历,最终赋值给to[key]
                  to[key] = deepCopy(to[key],fromA[key]);

            }else{
                // 如果不是直接赋值
                to[key]=fromA[key]
            }
        }

    }
    return to;
}
var newObj = deepCopy({},obj);
newObj.friend.name="小明";    //在新对象中把朋友的名字改掉
console.log(newObj)     //这里朋友的名字改变了
console.log(obj)      //这里朋友的名字 没有改变

原始数据没被改变,新的数据改变了。hasOwnProperty属性用来过滤掉继承的属性。