# 对象

对象是JavaScript的一个引用数据类型,是一种复合值,它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。即属性的无序集合

在JavaScript中,一个对象可以是一个单独的拥有属性和类型的实体。我们拿它和一个杯子做下类比。一个杯子是一个对象(物体),拥有属性。杯子有颜色,图案,重量,由什么材质构成等等。同样,javascript对象也有属性来定义它的特征。

# 对象的组成

对象由属性方法 组成。一个属性存储的是原始值,也可以是引用值。若一个属性的值可以是函数,这种情况下属性也被称为 方法

名称 描述
属性 用数据值来描述他的状态
方法 用来改变对象行为的方法,一个方法是一个值为某个函数的对象属性。

# 对象的创建

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。

  1. 隐式创建(json格式创建)
var obj = {}
  1. 实例化Object
var obj=new Object();
  1. 实例化自定义构造函数
function Obj(a){ }
var obj = new Obj();
  1. 实例化类 (实例化Object的语法糖,详见 Class 一节)
class Animal{}  
var dog=new Animal();

# new操作符具体做了什么?

function Base(){
  this.name = "zhangsan";
  this.age = 20;
}
var obj = new Base();

// new操作符等价于
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
  1. 创建了一个空对象obj
  2. 将这个空对象的__proto__成员指向了Base函数对象prototype成员对象。相当于obj拥有了Base原型上的属性方法
  3. 将Base函数对象的this指针替换成obj,然后再调用Base函数,于是就给obj对象赋值了一个name和age成员变量。相当于obj拥有了Base构造函数上的属性方法
var this = Object.create(Peson.prototype);

# instanceof关键字

如果obj对象是构造函数Fun的一个实例,则 obj instanceof Fun 返回 true。

判断一个数据是否是数组:

var arr1 = [], arr2 = {}

arr1 instanceof Array   // true
arr2 instanceof Array   // false

# 对象的增、删、改、查

# 1. 增 (给对象增加一个新的属性或方法)

  • 声明后赋值
var obj = {}
obj.user = "张三";    // 属性
obj.eat = function(){    // 方法
  console.log('吃饭');
}
  • 声明的同时赋值
//json形式创建赋值
var obj={
  name:'张三',
  age:18,
  eat:function(){
    console.log('吃饭');
  }
}

//实例化构造函数
function Animal(){
  this.name='阿黄',
  this.age='2',
  this.sing=function(){
    console.log('汪汪');
  }
}

//实例化Object
var obj=new Object({a:'aa',b:'bb'})

# 2. 查(访问属性、方法)

  • 访问属性
对象.属性名;
对象["属性名"];
  • 访问方法
对象.方法名();
对象["方法名"]();

注:当属性名或方法名保存到变量中时,访问该属性或方法需要使用[]

for(let i in obj){
  obj[i]     // i是存储属性名的变量,所以访问该属性时必须通过[]
}

# 3. 改(修改属性和方法)

直接为属性或方法赋新值即可

对象.属性名=新的属性值;
对象.方法名=新的方法;

# 4. 删(删除对象或者对象上的属性方法)

  • 销毁对象

javascript中的垃圾回收机制在对象没有引用的时候会进行销毁,释放内存

对象=null;    
  • 删除对象上的属性和方法,使用delete运算符
var obj={
  a:'a',
  b:function(){console.log('这是一个方法')}
}
delete obj.a;   //删除obj对象上的a属性    
delete obj.b;   //删除obj对象上的b方法

# 对象的遍历

  1. for in 循环

循环遍历对象自身的和继承的可枚举属性(不含Symbol属性).

var obj={
  name:'小米',
  age:18,
  say:function(){
    console.log('你好');
  }
}
for (var i in obj) {
  console.log(obj[i]);
}

ES6其余遍历方式详见 Object对象一节

# 对象的拷贝

由于对象是引用类型,变量中存储的是数据的地址,所以对象直接通过=赋值只能将地址赋值而不是数据。

对象的拷贝分为浅拷贝、深拷贝:

var obj = {
  type: "animal",
  cat:{
    name:"Tom",
    weight: 16,
    food:["fish","meat"]
  },
}
  • 浅拷贝: 直接拷贝对象的内存地址,如果原地址中对象被改变了,那么浅拷贝出来的对象也会相应改变
  • 深拷贝: 新开辟一块内存,将对象中所有属性全部复制,如果原地址中对象被改变了,那么深拷贝出来的对象不变

深拷贝实现方法:

  1. 递归遍历,逐层拷贝。 因为基础类型可以直接拷贝,所以通过递归遍历对象的每一层,全部得到基础类型后再拷贝。
  2. 通过JSON.stringify()先将对象转化为字符串,字符串赋值后再通过JSON.parse()转化回对象。

方案1:

var obj = {
    id:1000,
    name:"张三",
    age:19,
    favs:['足球','电影','游戏','篮球']
}
var obj2 = JSON.parse(JSON.stringify(obj));

方案2:

/**
 * [deepCopy 深拷贝]
 * @param  {[Array|Object]}   [要拷贝对象或数组]
 * @return {[Array|Object]}   [返回拷贝对象或数组]
 */
function deepCopy(o){
	if(Array.isArray(o)){
		var result = [];
	}else if(typeof o == 'object'){
		var result = {};
	}else{
		return o;
	}
	for(var i in o){
		if(typeof o[i]== 'object'){
			result[i] = deepCopy(o[i])
		}else{
			result[i] = o[i];
		}
	}
	return result;
}

在拷贝日期正则等特殊对象是拷贝为空对象。如有需求加判断就可以解决。

# this

this是一个很特别的关键字,被自动定义在所有函数的作用域中。
this总是会指向一个对象。或者说,this就是属性或方法‘当前’所在的对象。

# this的指向

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象

  1. 在普通函数中this指window全局对象
  2. 作为对象方法调用,this 指代调用该方法对象
  3. 在构造函数中this指向构造函数的实例
  4. 在事件中,this指向事件源
  5. 在call和apply中,this指的是方法中传入的对象,如果apply中没有传对象,this指向window
function Fun(){
  run(){
    console.log(this)       // this指向实例化出的对象,因为是实例化出的对象调用了run方法

    setInterval(_=>{
      conosle.log(this)     // this指向 window,因为是window调用了setInterval方法
    })
  }
}

# 改变this的指向

call() apply() bind() 在对象的特性中会讲到

# ES6对象扩展

# 属性方法的简洁表示法

当属性名为变量名, 属性值为变量的值时,可直接将变量作为对象的属性:

let username = "张三"

let obj = {
  username,      // 属性名为变量名, 属性值为变量的值
  age: 20
}
  • 对象方法的简写:
let obj = {
  run:function(){
    alert(1)
  }
}
// 简写为:
let obj = {
  run(){
    alert(1)
  }
}

这种写法用于函数的返回值,将会非常方便。

function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y};
}

getPoint()

# 对象的解构赋值

解构不仅可以用于数组,还可以用于对象。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let { first: f, last: l } = { first: 'hello', last: 'world' };
f // 'hello'
l // 'world'

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo

如果变量名与属性名一致,可以写成下面这样:

let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
// 上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。

与数组一样,解构也可以用于嵌套结构的对象。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

注意,这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}

默认值 对象的解构也可以指定默认值。

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x: y = 3} = {};
y // 3

var {x: y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
  • 用途1:接收函数的参数
// 接收被除数x与除数y
function calc({dividend: x, dividend: y}){
  return x/y
}
let divide = calc({dividend:9,dividend:3})
  • 用途2:接收函数的返回值
function calc(a,b){
  return {
    add: a+b,
    subtract: a-b,
    multiply: a*b,
    divide: a/b
  }
}
// 相对于返回数组来讲,不需要知道返回值参数的顺序
let {add, subtract, multiply, divide} = calc(1,2)

# 对象的Rest属性

ES6中数组的rest和扩展运算符,用于数组于普通参数之间的转化

Math.max(...[1,2,3,4])

function fun(a,b,...c){ }

现在在ES9中,当对象结构复制时也可以使用rest运算符:

let obj = {a: 1, b: 2, c: 3};
let {a,...x}
//  a == 1
//  x == {b: 2, c: 3}

或在函数参数中也可以使用:

restParam({
  a: 1,
  b: 2,
  c: 3
});

function restParam({ a, ...x }) {
  // a = 1
  // x = { b: 2, c: 3 }
}