每日读《你不知道的JavaScript(上)》| 对象

47 阅读7分钟

引言

前面我用了两篇文章介绍完了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的方式就不能达到预期效果。

image.png

而且这种方式和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。

那这时候该怎么区分呢?问题的核心是想判断对象本身上到底存不存在某个属性

书中给出了几种方案:

  1. in

  2. hasOwnProperty

  3. Object.keys(..)

  4. Object.getOwnPropertyNames(..)

遍历

这里我们主要会讲解for...infor...of,是不是特别眼熟,这也是面试的高频考点之一呀。

先说for...in

当我们拿for...in去遍历数组,是怎样的呢?

let arr = [3,4,5];
for(let i in arr) {
    console.log(i, arr[i]);
}

输出如下:

image.png

我们发现打印出来的i是数组的索引值,而不是数组中每一项的值。

然后我们再拿for...in去遍历对象呢,会发生什么?

let obj = {
    a: 1,
    b: 2,
    c: 4
};
for(let i in obj) {
    console.log(i, obj[i]);
}

显而易见:

image.png

但是,在书中提到一个关键点,需要注意一下。

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);
}

结果如下:

image.png

因为普通的对象没有内置的@@iterator,所以无法自动完成 for..of 遍历。

小结

今天讲解了一些对象的基本知识,包括对象的表现形式、对象的属性值如何访问等等。

有一些值得关注的点,以前可能从未留意过,比如当我们定义一个字符串,访问它的长度的时候,发现这是一个对象才能够进行的行为,这就意味着这个字符串被引擎自动转换成String这个内置对象。

好啦,今天就到这里!

明天也要继续加油!!