# 对象
对象是JavaScript的一个引用数据类型,是一种复合值,它将很多值(原始值或者其他对象)聚合在一起,可通过名字访问这些值。即属性的无序集合。
在JavaScript中,一个对象可以是一个单独的拥有属性和类型的实体。我们拿它和一个杯子做下类比。一个杯子是一个对象(物体),拥有属性。杯子有颜色,图案,重量,由什么材质构成等等。同样,javascript对象也有属性来定义它的特征。
# 对象的组成
对象由属性 和 方法 组成。一个属性存储的是原始值,也可以是引用值。若一个属性的值可以是函数,这种情况下属性也被称为 方法。
名称 | 描述 |
---|---|
属性 | 用数据值来描述他的状态 |
方法 | 用来改变对象行为的方法,一个方法是一个值为某个函数的对象属性。 |
# 对象的创建
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
- 隐式创建(json格式创建)
var obj = {}
- 实例化Object
var obj=new Object();
- 实例化自定义构造函数
function Obj(a){ }
var obj = new Obj();
- 实例化类 (实例化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);
- 创建了一个空对象obj
- 将这个空对象的__proto__成员指向了Base函数对象prototype成员对象。相当于obj拥有了Base原型上的属性方法
- 将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方法
# 对象的遍历
- 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"]
},
}
- 浅拷贝: 直接拷贝对象的内存地址,如果原地址中对象被改变了,那么浅拷贝出来的对象也会相应改变
- 深拷贝: 新开辟一块内存,将对象中所有属性全部复制,如果原地址中对象被改变了,那么深拷贝出来的对象不变
深拷贝实现方法:
- 递归遍历,逐层拷贝。 因为基础类型可以直接拷贝,所以通过递归遍历对象的每一层,全部得到基础类型后再拷贝。
- 通过
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的最终指向的是那个调用它的对象
- 在普通函数中this指
window
全局对象 - 作为对象方法调用,this 指代调用该方法对象
- 在构造函数中this指向构造函数的
实例
- 在事件中,this指向事件源
- 在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 }
}