从字面量到动态管理,对象:你也太细节了吧--JS基础篇(四)

410 阅读8分钟

写在前面

有关闭包的那些事,一文搞定!--JS基础篇(二) - 掘金 (juejin.cn)

前端修行,从var、let和const关键字开始+面试题--JS基础篇(三) - 掘金 (juejin.cn)

各位小伙伴大家好!欢迎来到JS的基础篇(四),本期我们来聊聊JavaScript中的对象。先问大家一句,你了解你的对象吗?

对象的概念

  • 含义:对象(Object)是一个抽象的集合,由属性(properties)和方法(methods)组成,是能够操作和存储相关数据和方法。

  • 数据结构: 对象是无序的键值对的集合,内容格式为键(key):值(value)

  • 分类:

    • JS内置对象 :Object、Array、String、Function、Math、Boolean、Number、Date等。
    • 宿主对象 : Window、Document、Navigator等。
    • 其他对象 :Global和通过字面量方式自定义的对象等。

大部分情况下,我们认为除了原始值,其他的都是对象类型,那么函数体也可以看作是一个对象。

一、对象的创建与使用

我们都知道JS是一门具有面向对象(OOP)特性的语言,对象在复杂功能实现以及代码模块化等方面有重要意义。

1.对象字面量:

const obj = {
    name : 'novalic',
    age : 18,
    dream : 'BAT',
    hobby : function(){
        console.log('running');
    }
}

上面是创建对象最质朴的方式,直接使用一对花括号{}封装对象的属性(properties)和方法(methods)。我们在浏览器中运行这段代码看看效果:

image.png

obj拥有的属性都被识别,并且我们可以注意到[[Prototype]]:Object,prototype属性是每个一个对象(null除外)天生的一个属性,该属性默认指向对象的原型对象(Prototype Object),这里我们只需要先知道:obj这个字面量的类型是对象(Object)。

2.访问对象成员

  • 点表示法
const obj = {
    name : 'novalic',
    age : 18,
    dream : {
        forWork : 'BAT',
        forLive : 'To reach financial independence'
        },
    //上述代码也可写成dream : ['xxx','xxx']
    hobby : function(){
        console.log('running');
    }
}
console.log(obj.name);
console.log(obj.age);
console.log(obj.dream.forWork);
obj.hobby();

使用对象.属性/方法()的方式即可调用对象的成员,需要注意的是对象可以有一个子命名空间,同样以花括号{ }为边界。

  • 方括号表示法
const obj = {
    name : 'novalic',
    age : 18,
    dream : ['BAT','To reach financial independence'],
    hobby : function(){
        console.log('running');
    }
}
console.log(obj['name']);
console.log(obj['age']);
console.log(obj['dream'][0]);
obj['hobby']();

方括号表示法需要在[]中写明需要访问的属性,且该值可以是一个变量。 结果如图:

image.png

从编写和阅读角度来看,点表示法确实优于方括号表示法。不过,当我们需要灵活访问对象的一个属性时,括号法绝对是点表示取代不了的:
let property = 'name';
console.log(obj[property]);

上述我们定义了一个变量来存放属性名,并使用方括号表示法访问该属性,而如果使用点表示obj.property则无法访问到。为了方便我们也可以使用函数传参的方式来访问属性。

function findProperty(param){
    return obj[param];
}
console.log(findProperty('name'));

3.设置对象的成员

我们已经知道如何访问成员,接下来看看如何给对象添加一个成员:

const obj = {
    name : 'novalic',
    age : 18,
    dream : {
        forWork : 'BAT',
        forLive : 'To reach financial independence'
        },
    //上述代码也可写成dream : ['xxx','xxx']
    hobby : function(){
        console.log('running');
    }
}
obj.height = '180cm';//动态添加
console.log(obj.height);

声明的方式更新一个对象的成员是常见的方式,若对象内不存在该成员则会添加到这个对象中,存在则更新成员的值。为对象添加方法一个方法也是同理。

4.删除对象的成员

JS提供了一个关键字delete用于删除对象的成员,其本质是一个运算符。

const obj = {
    name : 'novalic',
    age : 18,
    dream : {
        forWork : 'BAT',
        forLive : 'To reach financial independence'
        },
    //上述代码也可写成dream : ['xxx','xxx']
    hobby : function(){
        console.log('running');
    }
}
delete obj.name;
delete obj['age'];

delete删除对象中给的一个成员后会返回Boolean类型的值,成功返回true。当我们使用Object.defineProperty()或者Object.freeze()设置一个对象后,删除该对象的成员则会返回false。如果删除不存在的成员,也会返回true

使用delete删除对象中的一个成员后,其原型链上的其他同名成员依旧能够被访问。

以上就是如何使用字面量的方式创建一个对象并且如何动态管理对象的基本操作。

拓展:初识new关键字

我们再来看另一个常用且更加完备的对象创建方式:new关键字。

const array = new Array();

如文章开头所言,,Array是一个内置对象,Array()是它的一个构造函数(Constructor),即是一个对象(Object)。

new关键字能够以共享的方式将Aarry()函数中的固有属性(定义在函数作用域的变量)传递给array,此时我们就说创建了一个Array()的实例化(Instance)对象。这里也可以理解为是一种继承(Extends)行为,这也是传统面向对象的一个经典思想。

array此时是一个数组对象,我们可以使用array.xxx的方式去调用JS中内置数组对象的属性和方法,例如:

console.log(array.length); //数组长度为0
array.push(8); //添加一个元素8
console.log(array.length); //数组长度为1

可以看到,这里我们并没有自行定义对象array的属性length和方法push()。能够使用是因为array在被实例化的时候继承了Array.prototype中的成员。

二、对象的简单复制与比较

  • 复制

通过将对象以不同方式复制、比较前后对象的方式我们能够更深入地了解对象。我们考虑一下这个语句:

const obj = {
    name : 'novalic',
    age : 18,
    dream : {
        forWork : 'BAT',
        forLive : 'To reach financial independence'
        },
    //上述代码也可写成dream : ['xxx','xxx']
    hobby : function(){
        console.log('running');
    }
}
const newObj = obj;
newObj.age = 20;
console.log(newObj.age);//20
console.log(obj.age);//20

这里我们将一个对象obj赋给新对象newObj,这是一种浅层的复制。我们根据15、16行不难发现,浅层复制后修改新对象newObj的属性值,原对象obj的属性值也被修改了。

换句话说,我们将原对象的内存地址复制了一份给了新对象,这使得修改任意一个对象的值,另一个的值也被改变了,因为它们引用的都是同一个内存地址的值,这也叫浅拷贝。

浅拷贝是新手常用的默认复制,所以需要我们了解。对应的深拷贝则是创建一份原对象的全新副本,将拷贝的对象和原对象的数据隔离开,互不影响。具体内容我将在后面专门写篇文章聊聊。

  • 比较

我们常常使用==或者===来比较两个原始值(原始数据类型)是否相等,这比较的是它们的具体内容。

==非严格相等,通常比较两个变量或者常量的值,即使它们的基本数据类型不一致。

var num = 123;
var str = '123';
console.log(num==123); //true
console.log(str=='123'); //true
console.log(num==str); //true

我们可以发现只要是内容相等,字符串格式的123与整数123被==视为相等。也就是说JS内部会自动将不同类型的原始值转换成相同类型再进行比较。

那么思考一下以下内容:

var num = 123;
var str = new String('123');
console.log(num==str); //true

这里我们实例化了一个String类型对象,此时str作为一个对象,其默认内容为字符串'123'。通过结果我们不难看出,一个原始值与对象比较,JS会将对象的默认内容与取出与原始值进行比较,结果就是true

=== : 严格相等,通常比较两个变量、常量或者对象,严格比较类型与值。

var num = 123;
var str = '123';
console.log(num===123); //true
console.log(str==='123'); //true
console.log(num===str); //false

只有当两个变量或常量内容和类型都相等时,===才会视为相等。但是对于两个对象来说,这一规则将会变得更加严格。

const str1 = new String('novalic');
const str2 = new String('novalic');
console.log(str1 === str2); //false

我们使用new关键字创建了两个String类型的对象,默认值都为'novalic',但是经===比较却返回false。其实这里对象之间并不会比较默认值,而是比较两个对象的引用值,即对应的内存地址。显然,分别创建的两个对象地址不一样,故返回false。

var obj1 = {
    name : 'novalic',
    age : 18,
    dream : 'BAT'
    }
var obj2 = obj1;
console.log(obj1 === obj2); //true

这里我们通过浅拷贝的方式关联了两个对象,其对应内存地址是一致的,故经===比较返回true。

总结

本篇讲了以下知识:

  • JS中的对象基本认知
  • 对象的创建与动态管理
    • 拓展new关键字
  • 对象的简单复制与比较

其中new关键字创建对象时,共享原型对象中属性或方法的底层原理在本篇中并未深入讲解,这些知识涉及到原型链。将与对象的拷贝构造函数一起在后续文章中讲解。如果对你有帮助,请点个赞,这将是我持续创作的动力,我们下篇见!

本人拙见,若有错误,敬请指出。

参考:

MDN Web Docs (mozilla.org)