引言
前面我用了两篇文章介绍完了this,本章将解锁新的技能点——对象。
对象是个很重要的知识点,我们平时项目开发过程中会遇到很多对象相关的点。
公主王子请往下看😚~
对象基础
对象的表现形式
对象字面量
let obj = {};
构造形式
let obj = new Object();
访问属性
访问对象属性的方式有两种:
-
obj.属性
-
obj[属性]
请记住,对象的key永远都是字符串。
就算你给人家命名为数字,那也会自动转为字符串,比如:
var obj = {};
obj[3] = 'bar'; // 这里的3也会自动转为字符串3
对于属性名,还有一点需要说明,就是,ES6增加了可计算属性名。
什么叫可计算属性名呢?(虽然目前我好像没有碰到过这种场景,这么写代码感觉很不规范😅)
就是在obj[属性]
这种情况下当中的属性允许是可计算的表达式。
比如:
var name = 'Dazzling';
var obj = {
[name + 'wen']: 'hello dazzlingwen!'
};
console.log(obj.Dazzlingwen); // hello dazzlingwen!
我就说很诡异的用法吧!
其实可计算属性真正的应用场景是Symbol。
// 创建一些 Symbol 属性
const symbol1 = Symbol('symbol1');
const symbol2 = Symbol('symbol2');
const obj = {
[symbol1]: 'value1',
[symbol2]: 'value2',
};
Symbol的用法传送门~ 不懂的自行查阅哦~
对象的类型
JavaScript中一共有六种主要类型:
-
string
-
number
-
boolean
-
null
-
undefined
-
object
上述前五个是基本数据类型,而JavaScript中还有一些内置对象;
-
String
-
Number
-
Boolean
-
Object
-
Function
-
Array
有一点值得注意的是,当我们以字面量的方式创建一个字符串,比如:
let s = 'hello,dazzlingwen!';
然后我们做出如下行为:
console.log(s.length); // 18
console.log(s.charAt(3)); // l
当我们访问字符串s的长度,调用charAt方法等行为,发现都能够正常打印。
这是为什么?明明它不是一个对象。
因为引擎会自动把字面量转换成String对象。
这个策略对于数组也是一样,比如:
var arr = ['foo', 20, 'bar'];
arr.myname = 'dazzlingwen';
console.log(arr.length); // 3
console.log(arr.myname); // dazzlingwen
我们在把数组arr当成一个对象,上面添加一个叫myname的属性,发现并不会报错,反而能够成功添加myname这个属性。
其实数组本身就是对象。你换个思路看。
arr[0]
代表着访问数组中索引为0,也就是数组中的第一个元素对吧。
然后我们回顾上面,对象访问属性的方式的其中一种obj[属性]
,类比一下去理解。
但是需要注意的是,虽然数组arr[0]
,但里面的0确确实实是一个数字0嗷。可不要搞混了!
复制对象
书中描述这小节太绕了,毕竟是引导性文章,简单说来,这里就是涉及到深拷贝和浅拷贝了。
首先,本身我们是想复制对象的,但我们不能按照赋值基本数据类型的方式来复制对象。
比如你用copyObj = obj
的方式就不能达到预期效果。
而且这种方式和Object.assign()
的效果是一样的。
对,也就是说,Object.assign()
是实现浅拷贝的一种优雅方式~
深拷贝的话,可以用JSON.parse(JSON.stringify(someObj))
,但其实这种方式并不是万能的。
因为它不能复制函数、undefined、循环引用的对象,也不能复制特殊的对象如Map、Set、Date、RegExp等。
想要较好的深拷贝效果,可以手写一个深拷贝。
function deepClone(source) {
if(typeof source !== 'object' || typeof source === 'null'){
return source
}
let target = Array.isArray(source) ? [] : {}
for (const key in source){
if(Object.prototype.hasOwnProperty.call(source,key)){ //直接到source上去找有没有key这个属性
if(typeof key === 'object' && typeof key !== 'null'){
deepClone(key)
}else{
target[key] = source[key]
}
}
}
return target;
}
拿出我的千年老代码,给诸位瞧瞧什么是手写deepClone。
2024.6.2更新
这里我想补充两个知识点,另起一篇文章不至于,少了又不行。
一个是对象中的 getter 和 setter,还有一个是对象的遍历。
getter和setter
getter 和 setter 都属于对象中的隐藏函数。
getter 会在获取属性值的时候调用,而 setter 是在设置属性值时使用的。
举个例子来理解 getter 和 setter 是干嘛用的:
var obj = {
get a() {
return this._a_;
},
set a(val) {
this._a_ = val * 2;
}
};
obj.a = 2
console.log(obj.a); // 4
可以看到 get 函数的作用是返回 a 的值,set 函数的作用是设置(改变)set 的值。
这里是给 a(对,就是挂在obj上的属性a)设置的 getter 和setter,也就是只属于a这个属性的 getter 和 setter。
// 了解了 getter 和 setter 之后,我请问呢?
// 这俩有什么意义?或者说,有啥用呢?
有没有人思考过一个问题,为什么obj.a
就能获取到a的属性值,怎么obj.a
就能获取到属性值了呢?
事实上,obj.a
执行的是内部的[[get]]。
对象默认的 [[Put]] 和 [[Get]] 操作分别可以控制属性值的设置和获取。
然后 getter 和 setter 就是能够更改对象里面默认配置属性值行为的东西。
在 ES5 中可以使用 getter 和 setter 部分改写默认操作,但是只能应用在单个属性上,无法应用在整个对象上。
因为ES5中 getter 和 setter 更底层的实现be like:
var myObject = {
// 给 a 定义一个 getter
get a() {
return 2;
}
};
Object.defineProperty(
myObject, // 目标对象
"b", // 属性名
// 给 b 设置一个 getter
get: function () {
return this.a * 2
},
// 确保 b 会出现在对象的属性列表中
enumerable: true
);
myObject.a; // 2
myObject.b; // 4
可以看到 Object.defineProperty 只能代理单个的属性。在Object.defineProperty
中只代理了 b,也就是说,只给 b 定义了 getter。
这里需要注意的一点是,getter和setter一般需要成对出现哦。
💡💡ADD💡💡
书里面还有一个经典的case,我想再记录一下。
var obj = {
a: undefined
};
console.log(obj.a); // undefined
console.log(obj.b); // undefined
obj.a
本身的值就是 undefined,而访问一个 obj 对象上本就没有的属性 b,也是 undefined。
那这时候该怎么区分呢?问题的核心是想判断对象本身上到底存不存在某个属性。
书中给出了几种方案:
-
in
-
hasOwnProperty
-
Object.keys(..)
-
Object.getOwnPropertyNames(..)
遍历
这里我们主要会讲解for...in
和for...of
,是不是特别眼熟,这也是面试的高频考点之一呀。
先说for...in
。
当我们拿for...in
去遍历数组,是怎样的呢?
let arr = [3,4,5];
for(let i in arr) {
console.log(i, arr[i]);
}
输出如下:
我们发现打印出来的i是数组的索引值,而不是数组中每一项的值。
然后我们再拿for...in去遍历对象呢,会发生什么?
let obj = {
a: 1,
b: 2,
c: 4
};
for(let i in obj) {
console.log(i, obj[i]);
}
显而易见:
但是,在书中提到一个关键点,需要注意一下。
Tips:
遍历数组下标时采用的是数字顺序(for 循环或者其他迭代器),但是遍历对 象属性时的顺序是不确定的,在不同的 JavaScript 引擎中可能不一样。
如果我们想直接就能遍历出对象的值,那么就可以用for...of
了。
let arr = [3,4,5];
for(let i of arr) {
console.log(i); // 3 4 5
}
(大家一定不要把“对象”的思维局限在obj上!!js里数组也是对象!!)
for...of
的原理在于,它循环的时候首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()
方法来遍历所有返回值。
但是如果:
let obj = {
a: 1,
b: 2,
c: 4
};
for(let i of obj) {
console.log(i);
}
结果如下:
因为普通的对象没有内置的@@iterator,所以无法自动完成 for..of 遍历。
小结
今天讲解了一些对象的基本知识,包括对象的表现形式、对象的属性值如何访问等等。
有一些值得关注的点,以前可能从未留意过,比如当我们定义一个字符串,访问它的长度的时候,发现这是一个对象才能够进行的行为,这就意味着这个字符串被引擎自动转换成String这个内置对象。
好啦,今天就到这里!
明天也要继续加油!!