在 JavaScript 的编程世界里,对象是极为关键的概念。让我们一同深入探索对象的奥秘。
一、对象的初识
(一)对象的定义
在现实生活中,世间万物皆可看作对象。它是具体的事物,是那些我们能亲眼看到、亲手触摸到的实体。像一本书、一辆汽车、一个人,无疑都是对象;而在数字化领域,一个数据库、一张网页,甚至是与远程服务器的连接,同样也属于对象的范畴 。
比如,明星、女朋友、班主任、苹果、手机这些都是对象的示例,进一步具体化,周星驰、小明的女朋友、这个班的班主任、这个被咬了一口的苹果、小王的手机,分别对应着不同的特定对象。
在 JavaScript 中,对象被定义为一组无序的相关属性和方法的集合。值得注意的是,几乎所有的事物在 JavaScript 里都可被视为对象,像常见的字符串、数值、数组、函数等。
属性,用于描述事物的特征,在对象中以属性的形式呈现,通常用名词来表示。以手机对象为例,其属性包括大小、颜色、重量、屏幕尺寸、厚度等。
方法,则体现了事物的行为,在对象中用方法来表示,多为动词。例如手机具有打电话、发短信、聊微信、玩游戏等方法 。
再比如桌子,其属性有长宽、材质、颜色;方法包括放东西、写字、吃饭。电脑的属性有 cpu、显卡、固态、颜色、尺寸;方法有写代码、看视频、开会议、聊天 。
(二)对象的意义
当我们仅需保存一个值时,使用变量就足够了。若要保存多个值(即一组值),数组是不错的选择。然而,数组存在一定的局限性,数组元素之间的信息缺乏连贯性和紧密连接,表达不够清晰。
设想一下,若要完整保存一个人的信息,数组可能无法清晰地展现各信息之间的关系。而 JavaScript 中的对象,其表达结构更加清晰,功能也更为强大,能够很好地解决这个问题。
(三)对象的分类
对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性,这使得信息与信息之间建立起联系,关系明确,操作起来也更加便捷。
二、创建对象:利用 new Object 创建对象
(一)对象的基本操作
- 创建对象:使用 new 关键字调用的函数,被称为构建函数 constructor。构建函数的使命就是专门用来创建对象。当我们使用 typeof 检查一个对象时,它会返回 object。
例如:
- 向对象添加属性:采用对象.属性名=属性值的方式向对象添加属性。
| obj.name = "张三";//向obj中添加一个name属性,name属性值为'张三' obj.gender = "男";//向obj中添加一个gender属性 obj.age = 18;//向obj中添加一个age属性 |
需要注意的是,我们通过=赋值运算符来添加对象的属性和方法,并且每个属性和方法之间要用分号;结束。
- 修改对象属性:按照对象.属性名=新值的形式修改对象属性。
- 删除对象的属性:利用delete 对象.属性名来删除对象的属性。
| delete obj.name;//删除name属性 console.log(obj.name);//undefined |
(二)属性名与属性值
- 属性名:
- 对象的属性名并不强制要求遵守标识符号的规范,也就是说可以使用任何名字。但为了代码的规范性和可读性,我们在使用时最好还是遵循标识符的规范。
- 倘若要使用特殊的属性名,比如数字,不能通过对象.属性名的方式来操作,而需要使用对象名['属性名']来读取。使用[]这种形式操作属性更为灵活,因为在[]中可以直接传递一个变量,如此一来,变量值是多少就会读取对应的属性。
| var obj = new Object(); obj.name = "tom"; //向对象中添加属性 obj["123"] = "你好"; var n = "123"; //把'123'属性赋值给n console.log(obj["123"]); //读取obj的'123'属性 console.log(obj[n]); //读取obj的'123'属性 |
- 属性值:JavaScript 对象的属性值可以是任意的数据类型,甚至可以是对象。
| obj.test = true; obj.test = undefined; obj.test = "true"; obj.test = 123; // 创建一个对象 var obj2 = new Object(); obj2.name = "猪八戒"; obj.test = obj2; |
- in 运算符:借助该运算符能够检查一个对象中是否含有指定的属性,如果存在则返回 true,不存在则返回 false。其语法为"属性名" in 对象。
| //检查obj中是否含有test2属性 console.log("test2" in obj);//false console.log("test" in obj);//true |
(三)基本和引用数据类型
- 基本数据类型:包括 String、Number、Boolean、Null、Undefined。
- 引用数据类型:主要是 Object。
- 在 JavaScript 中,变量都是存储在栈内存中的。基本数据类型的值直接在栈内存中存储,每个值都是存在的,修改一个变量的值不会对其他变量造成影响。而引用数据类型的值是保存在堆内存中的,每创建一个新的对象,都会在堆内存中开辟一个新的空间。变量保存的是对象的内存地址(即对象的引用),若两个变量保存的是同一个对象引用,那么当通过其中一个变量修改对象属性时,另一个变量也会受到影响 。
- 比较两个基本属性类型的值时,比较的是值本身;而比较两个引用数据类型时,比较的是对象的内存地址。即便两个对象的内容完全相同,但只要地址不同,比较结果就会返回 false。
| //基本类型值存储在栈内存中 var a = 123; var b = a; a++; console.log("a=" + a);//124 console.log("b=" + b);//123 ,因为b的变化不会影响到a var obj = new Object; obj.name = 'java'; var obj2 = obj // 修改obj的name值 obj.name = 'web' console.log(obj.name);//web console.log(obj2.name);//web //因为obj和obj2指向同一个对象 |
三、创建对象方式二
(一)利用字面量创建对象
对象字面量,就是在花括号{}里面包含表达这个具体事物(对象)的属性和方法。
| var obj = {};//创建一个空对象 var obj2 = { name: "猪八戒", age: 28, gender: "男", sayHi: function () { console.log('hi~'); } }; |
需要留意的是,对象内的属性或者方法以键值对的形式呈现,即键:值,也就是属性名:属性值。多个属性或者方法中间用逗号隔开,最后一个属性或者方法的逗号可以省略。方法冒号后跟的是一个匿名函数。
- 读取对象:
- 调用对象的属性:可以使用对象名.属性名或者对象名['属性名']。
- 调用对象的方法:采用对象名.方法名()。
| //读取对象属性 console.log(obj2.name); console.log(obj2['age']); //读取对象方法 obj2.sayHi(); |
(二)对象的方法
函数也能够成为对象的属性。当一个函数作为对象的属性保存时,我们将这个函数称作这个对象(obj)的方法。调用函数也就意味着调用对象(obj)的方法(method),实际上它和函数在本质上并无太大区别,只是名称有所不同。
| /* 创建对象一*/ var obj = new Object(); //向对象中添加属性 obj.name = "孙悟空"; obj.age = 18; //对象的属性值可以是任何的数据类型,也可以是个函数 obj.sayName = function () { console.log(obj.name); }; console.log(obj.sayName);//打印函数属性 obj.sayName();//调用obj.sayName()函数。也叫调用obj的sayName方法 /* 创建对象二 */ // function sayHi(){ // console.log('hallo') // } var obj2 = { name: '小明', age: 18, sex: '男', job: 123, // 第一种写法 // sayHi:sayHi,(之前定义好的函数) //方法:函数名 // 第二种写法 // sayHi:function(){ //方法:匿名函数 console.log(obj2.name) // 第三种写法es6 sayHi() { console.log('hallo') } }; console.log(obj2.name) console.log(obj2['sex']) console.log(obj2.sayHi) obj2.sayHi() |
(三)枚举(遍历)对象属性
使用for...in语句可以实现对对象属性的枚举。其语法为for(var 变量 in 对象){ }。for...in语句会根据对象中属性的数量来决定循环体的执行次数。每次执行循环时,会将对象中的一个属性的名字赋值给变量。
| var obj = { name: "孙悟空", age: 18, gender: "男", address: "花果山" }; console.log(obj.name) for (var n in obj) { console.log('属性名:' + n)//对象中每个属性的名字赋值给n console.log('属性值:' + obj[n]);//通过变量n读取属性值, []读取变量 } |
(四)区分变量和属性,函数和方法
- 变量和属性:变量和属性都用于存储数据。变量需要单独声明并赋值,使用时直接写变量名,它是存在的;而属性存在于对象中,无需单独声明,使用时通过对象.属性的方式,依托对象而存在。
- 函数和方法:函数和方法都用于实现某种功能,都可以用来做某件事。函数是存在的,而方法则是依托对象存在的。
(五)this 的初印象
解析器在调用函数时,每次都会向函数内部传递一个隐含的参数,这个参数就是 this。this 是由浏览器传递的,我们可以直接使用。this 指向的是一个对象,这个对象被称为函数执行的上下文对象。
根据函数调用方式的不同,this 会指向不同的对象:
- 当以函数的形式调用时,this 指向 window。
- 当以方法的形式调用时,谁调用方法,this 就指向谁。
- 当以构建函数的形式调用时,this 指向新创建的那个对象,即当前对象 。
| //需求:可以输出各自obj的名字 //创建一个fun()函数 function fun() { console.log(this.name); } //创建两个对象 var obj = { name: "孙悟空", sayName: fun, }; var obj2 = { name: "沙和尚", sayName: fun, }; var name = "全局的name属性"; fun(); //==window.fun() 以函数的形式调用,this就是window obj.sayName(); //孙悟空 以方法的形式调用,this是调用方法的对象 obj2.sayName(); //沙和尚 |
四、创建对象方式三
使用工厂方法创建对象,能够实现大批量地创建对象。
| //需求:创建一个对象 var obj = { name: "孙悟空", age: 18, gender: "男", sayName: function () { alert(this.name); }, }; /* 需求:批量的创建对象 使用工厂方法创建对象 */ //函数里面加对象,靠参数生产 function createPerson(name, age, gender) { //创建一个新的对象 var obj = new Object(); //向对象中添加属性 obj.name = name; obj.age = age; obj.gender = gender; obj.sayName = function () { alert(this.name); }; //将新的对象返回 return obj; } var obj2 = createPerson("猪八戒", 28, "男"); var obj3 = createPerson("沙和尚", 38, "男"); var obj4 = createPerson("白骨精", 18, "女"); |
然而,使用工厂方法创建的对象,其构造函数都是 Object,这就导致创建的对象均为 Object 类型,使得我们难以区分多种不同类型的对象。
五、创建对象方式四
(一)利用构造函数创建对象
构造函数是一种特殊的函数,主要用于初始化对象,即为对象成员变量赋予初始值,它通常与 new 运算符一起使用。我们可以将对象中一些公共属性和方法抽取出来,封装到这个函数里面。
构造函数语法规范如下:
| function 构造函数名() { this.属性 = 值; this.方法 = function () {} } new 构造函数名(); |
示例:
| function Person(name, age, gender) { //alert(this);//this就是新建的对象,然后赋值给per this.name = name; this.age = age; this.gender = gender; this.sayName = function () { alert(this.name) }; } var per = new Person("孙悟空", 18, "男"); var per2 = new Person("玉兔精", 16, "女"); var per3 = new Person("沙和尚", 28, "男"); console.log(per); console.log(per2); console.log(per3); |
需要注意的是,构造函数本质上就是一个普通的函数,其创建方式与普通函数并无差异,不同之处在于构造函数习惯上首字母大写。构造函数和普通函数的区别主要体现在调用方式上,普通函数直接调用,而构造函数需要使用 new 关键字来调用。在构造函数中创建属性和方法时,必须结合 this 使用,此时的 this 指向的就是新创建的那个对象。
(二)构造函数(new 关键字)的执行流程
- 立刻创建一个新的对象。
- 将新建的对象设置为函数中 this,在构造函数中可以使用 this 来引用新建的对象。
- 逐行执行函数中的代码。
- 将新建的对象作为返回值返回。
当使用new创建一个对象时,会在堆中开辟一片区域,例如new Person()会在堆中开辟一块区域。
(三)对象的实例化
使用同一个构造函数创建的对象,我们将其视为一类对象,同时也将这个构造函数称为一个类。例如明星汽车设计图纸,就如同一个类。通过一个构造函数创建的对象,则被称为该类的实例,比如刘德华、某一辆宝马车。利用构造函数创建对象的过程,我们称之为对象的实例化。
(四)instanceof
instanceof的作用是检查一个对象是否是一个类的实例,就像是进行 “亲子鉴定”。其语法为对象 instanceof 构造函数,如果是,则返回 true,否则返回 false。需要注意的是,所有的对象都是 Object(对象源头)的后代,所以任何对象与instanceof进行检查时,都会与 Object 存在这种关系。
| console.log(dog instanceof Person); |
(五)构造函数优化
在前面的示例中,我们创建的 Person 构造函数,为每一个对象都添加了一个 sayName 的方法。当前的实现方式是在构造函数内部创建方法,这就意味着构造函数每执行一次,就会创建一个新的 sayName 方法,所有实例的 sayName 方法都是唯一的。这会导致构造函数执行多次时,创建大量重复的方法,造成不必要的资源浪费。实际上,完全可以让所有的对象共享同一个方法。
| function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; // 向对象中添加一个方法 this.sayNam = fun; } //将sayName方法在 |