JavaScript基础--对象篇

对象和数组一样是我们开发中非常非常常用的类型,确切的说其实数组也是属于对象的。只是数组的特殊性一般会单独拿来说。

创建对象

创建对象有两种方式一种是字面量方式创建,也是实际开发中用的最多的一种方式,直接就声明一个变量然后使用双大括号来表示这个空对象。另一种就是构造函数式创建对象。

let obj = {} //字面量创建
let obj1 = new Object()//构造函数创建
function obj2(name,age){
	this.name=name;
	this.age=age
}
let obj3 =new obj2('Roronoa',19)
console.log(obj,obj1,obj3)//{},{},obj2 {name: 'Roronoa', age: 19}

删除对象属性

delete  操作符可以删除对象属性,比如delete.obj3.name就会将name属性从obj3内删除。

访问对象属性

可以通过.的方式获取到对象内部属性值 比如 obj3.name,对象属性值可以是任意类型,数组,对象,函数等等。对象内的属性是以,号结尾,是一定要加,号。不过这种方式只能用来访问单词结构的属性名多词的会报错,这时候就可以使用object[“key”]的方式来访问对象属性了

let obj = {
    name:'Roronoa',
    age:'19'
    "the career" :'导航'
}
console.log(obj.name)//Roronoa
console.log(obj.the career)//会报语法错误
console.log(obj[the career])//导航

object["key"]这种访问模式有一个好处,就是我们可以动态获取想要的值,在这里,变量 key 可以是程序运行时计算得到的,也可以是根据用户的输入得到的。然后我们可以用它来访问属性。这给了我们很大的灵活性。

let obj = {
    name:'Roronoa',
    age:19,
}
let objName = 1>0?'name':'age'
console.log(obj[objName])//Roronoa

属性简写

使用构造函数创建的对象我们可以让它更简单一些,如果属性和值是相同的可以只写属性

function obj1(name,age){
	return{
        name,
	    age
    }
}
let obj =new obj1('Roronoa',19)

console.log(obj)//{name: 'Roronoa', age: 19}

in操作符

in操作符用来判断对象里是否包含某属性,如果包含则会返回true,否则返回false

let obj = {
    name:'Roronoa'
}
console.log("name" in obj)//true

Object.hasOwn()

语法:Object.hsaOwn(instance,prop)

instance:必须,要检测的对象

prop:必须,要检测的属性名或者Symbol

如果指定的对象中直接定义了指定的属性true;否则返回false。该方法检测对象的直接属性,如果是原型链上的属性,即使有也会返回false。

for...in循环

语法:for(let item in Object){

        // 对此对象属性中的每个键执行的代码

}

item:自定义的对象键值(属性名)

object:要循环的对象

let user = {
  name: "John",
  age: 30,
  isAdmin: true
};

for (let key in user) {
  // keys
  console.log( key );  // name, age, isAdmin
  // 属性键的值
  console.log( user[key] ); // John, 30, true
}

this(划重点)

this是指向对象实例,怎么说呢。

let menu = {
  width: 200,
  height: 300,
  title: "My menu",
  fn:function(){
	  let sum =0
	 return  sum = this.width+this.height//500
	 // return  sum = menu.width+menu.height//500
  }
};

console.log(menu.fn())

看起来好像this没啥用,因为可以使用对象名称访问,但是如果我们将该对象值赋值给其他对象,然后将该对象清空的后再去访问fn就会有问题。

let user = {
  name: "John",
  age: 30,

  fn() {
    alert( user.name ); // 导致错误
  }

};


let admin = user;
user = null; // 重写让其更明显

admin.fn(); // TypeError: Cannot read property 'name' of 

如果使用this替换掉user就可以,因为不论怎样换,this指向的都是自身。

this这个东西的值取决于函数如何被调用,而不是函数在哪里被定义。

举个例子吧,this这个东西太抽象

let menu = {
  width: 200,
  height: 300,
  title: "My menu",
  fn(){
	  console.log(this)
  }
};
menu.fn()//{width: 200, height: 300, title: 'My menu', fn: ƒ}

这时候this指向的是menu这个对象。

let menu = {
  width: 200,
  height: 300,
  title: "My menu",
  fn:()=>{
	  // return  sum = this.width+this.height//500
	  // return  sum = menu.width+menu.height//500
	  console.log(this)
  }
};
menu.fn()//Window {window: Window, self: Window, document: document, name: '', location: Location, …}

这时候this却指向了window,这是因为用了剪头函数,箭头函数是普通函数的简写,他的一个特性就是没有自己的this。this是啥是调用它的时候赋予它的一个特性。当this查找不到的时候会向上一层借用。

肯定不止一个人像我一样认为这个剪头函数不管有没有this它的上一层都是menu这个对象。从结构上看确实是这样,但是决定this的是调用它的。我们这样想,最外层都是window,一个方法执行的时候都是从window上。就是window调用了menu,这时候this是window,然后呢menu又调用了fn,this指向就从window上到了menu这里。如果是箭头函数的话就是到了menu这里的时候使用this发现它没有,那就只能止步于window层面了。

也有大佬跟我解释说箭头函数fn和meun是同一空间层级的,所以this指向了meun的上层。只是这种解释不太理解,就试着用自己的理解来理解这个东西。如果有大佬有更好的解释欢迎,特别欢迎留言。

复制

了解复制之前要先知道复杂类型的复制和简单类型的复制有什么区别。复杂类型是保存在堆里的,而简单类型是保存在栈里的。然后简单类型复制就会在栈里多增加一个新的简单类型。但是复杂类型会在栈里多增加一个堆的地址信息。我们可以理解通讯录里存了两个名字的一个手机号。可是打通后联系到的人都是同一个,这个人就是对象。通讯录就是栈,而那个接电话的人的家,就是堆。(可曾有那么一个人你即使换了栈,可是记录的到的地址永远是那个人,可是始终没能让ta和你一个堆,大半夜谁看到这段emo死你)。上代码看看简单类型和复杂类型拷贝的效果

let str = 'Roronoa'
let menu = {
  width: 200,
  height: 300,
  title: "My menu",
  fn:()=>{
	  let sum =0
	  // return  sum = this.width+this.height//500
	  // return  sum = menu.width+menu.height//500
	  console.log(this)
  }
};
let obj = menu
let str1=str 
obj.width = 400
str1 ='Roronoa.Zoro'
console.log(obj.width,menu.width,str,str1)//400,400,RoronoaRoronoa.Zoro

可以看到str1改变了值str的值并没有变,说明str1和str是彼此相互独立的。而obj修改了width的值后menu的值也跟着变了。说明虽然他们的名字不一样,但是最终还是同一个对象。浅拷贝只是拷贝了复杂类型数据保存在栈里的地址。

浅拷贝
1、直接赋值

实现浅拷贝的方法最常用的就是=号。将右边的地址保存到左边。

2、Object.assign()

语法:Object.assign(target,obj1,obj2,obj3...objx)

target:目标对象

obj1....objx:需要被复制的对象们

assign方法可以实现浅拷贝也可以实现一维对象的深拷贝

let menu = {
  width: 200,
  height: 300,
  title: "My menu",
  info:{
	  name:'hehe'
  },
  fn:()=>{
	  let sum =0
	  // return  sum = this.width+this.height//500
	  // return  sum = menu.width+menu.height//500
	  console.log(this)
  }
};
let obj = {}
Object.assign(obj,menu)
obj.info.name = 'Zoro'
obj.width = 400
console.log(obj.width,menu.width,obj.info.name,menu.info.name)//400,200,Zoro,Zoro

可以看到一维层面是实现了独立,但是在info内的还是同样的浅拷贝。

3、展开符拷贝

展开符拷贝和assign方法一样,也是一维层面的深拷贝。把上面的代码中的Object.assign(obj,menu)去掉,然后再创建obj的时候改成let obj = {...menu}就是展开符拷贝

手写一个浅拷贝

function showClone(obj){
    for (let key in obj){
        if(obj.hasOwn(key)){
            let newObj[key]=obj[key]
        }
    }
    return newObj
}
深拷贝
1、递归
function deepClone(obj){
	let newObj = {}
	for(let key in obj){
		if(Object.hasOwn(obj,key)){
			if(typeof obj[key] === 'object'){
				newObj[key]=deepClone(obj[key])
			}else{
				newObj[key]=obj[key]
			}
		}
	}
	return newObj
}
2、Object.assign()

assign方法实现一维的深拷贝

3、展开符

展开符实现一维的深拷贝

4、structuredClone()

很简单的方法,但是structuredClone方法有个问题,不能拷贝方法,也就是对象中如果有方法的话会抛出错误。虽然可以实现深拷贝,但是多少还是有些不完美。

let menu = {
  width: 200,
  height: 300,
  title: "My menu",
  info:{
	  name:'hehe'
  },

};
let obj1 = structuredClone(menu)
obj1.info.name='Zoro'
console.log(obj1.info.name,menu.info.name)//Zoro,hehe
垃圾回收

我们声明的变量会占用内存,当然这个内存我们是看不到的。是有js分配管理的。js中内存管理的核心是可达性,所谓的可达性可以理解为可以通过引用访问到某属性的值。比如全局变量,或者正在运行的函数的局部变量。当函数执行完成,局部变量就会变成不可达的。也就是将访问不到它了。这时候的局部变量就会被回收。我们经常会做一些手动清空变量的操作。为的就是释放内存,方式就是使变量变得不可达。比如我们创建一个全局对象let user ={name:'Roronoa'} 创建完成后改对象是具有可达性的。我们可以通过user.name访问到name属性的值。然后我们将user =null。这就是改变了对象的可达性。通过user将访问不到该对象了,然后该对象将会被回收掉释放内存。

回收机制

回收机制就是从根查询引用,通过引用查找引用的对象,然后再通过对象查找该对象的引用如此重复直到查询完所有的。被查询到的就是可达的,可达的不会被删除,查询不到的会被删除。很像疫情,疫情感染到的是阳性,没有感染的是阴性。然后就会回收阴性。

可选链

在实际开发中我们经常会做一些判断,比如判断后台获取到的数据res里的user的类型type,如果为某值执行一些方法,可是经常会遇到有些list里没有user属性。这时候在执行到这一步的时浏览器会抛出一个错误,表示type undefined 。所以就经常会写一个if(res.user&&res.user.type)这样的判断逻辑。当然这种还不够明显,如果是需要更多的判断条件的话代码会有很多的重复性以及可读性就会很差。这时候我们可以使用可选链?. 一个问号一个点。if(res?.user?.type)这样就不会报错遇到不存在的就会终止而不是抛出错误。

可选链一定要使用在右边可能不存在的地方,如果每个地方都加的话会让调试更麻烦,你想想,你想要的效果没出现,浏览器还没有报错。头疼不。另外可选链还可以作用于函数上

let userAdmin = {
  admin() {
    alert("I am admin");
  }
};

let userGuest = {};

userAdmin.admin?.(); // I am admin

userGuest.admin?.(); // 啥都没发生(没有这样的方法)

可选链 ?. 语法有三种形式:

obj?.prop —— 如果 obj 存在则返回 obj.prop,否则返回 undefined。

obj?.[prop] —— 如果 obj 存在则返回 obj[prop],否则返回 undefined。

obj.method?.() —— 如果 obj.method 存在则调用 obj.method(),否则返回 undefined。

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值