[TOC] # 对象 ## 概念 &emsp;&emsp;JavaScript的对象是无序属性的集合。其属性可以包含基本值,对象或者函数。可以包含基本类型值和复杂类型值。js中对象就是一组键值对 &emsp;&emsp;对象是一种复合值,它将很多值(原始值或其他对象)聚合在一起,可通过属性名访问这些值。。而属性名可以是包含空字符串在内的任意字符串。 &emsp;&emsp;JavaScript中的对象是现实生活中对象的抽象。即显示生活中的对象使用JavaScript来描述。 * 事物的特征在对象中用**属性**来表示。 * 事物的行为在对象中用**方法**来表示。 <br> JavaScript对象也可以称作一种数据结构。 <br> ## **属性和方法** 1. 如果一个变量属于一个对象所有,那么该变量就可以称之为该对象的一个属性,属性一般是名词,用来描述对象的特征。 2. 如果一个函数属于一个对象所有,那么该函数就可以称之为该对象的一个方法,方法是动词,描述对象的行为和功能。 3. 属性和方法统称为对象的成员(成员变量) ## **创建对象** 创建对象有四种方式 <br> ### **一、通过字面量创建对象** &emsp;&emsp;对象也可以用字面量方式表示,而且也可以对象的字面量值赋值给一个变量,此时这个变量就代表这个对象。 &emsp;&emsp;使用字面量方式表示对象,又叫创建字面量对象,如下: ``` var person = { name: '冯宝宝', age: 18 }; ``` <br> #### **语法格式** >[success]var 对象名 = {键:值, 键:值...} ; &emsp;&emsp;语法格式分析 1. 对象名 -> 本质是变量名; 2. {} -> 固定格式,表示封装一段代码,类似于函数的大括号; 3. 键:值 -> 键-值对,固定格式; &emsp;&emsp;键 -> 表示 属性名,类似于变量名; &emsp;&emsp;值 -> 表示 属性值,类似于变量值,可以是基本数据类型(String,Number...)或引用数据类型(数组,对象); 6. 键 和 值之间使用 ":" 隔开,键值对和键值对之间用 "," 隔开; 注意:对象名类似于变量名,键也类似于变量名,对象中存在键,好比变量中存在变量。 &emsp;&emsp;根据以上语法格式就可以创建对象了。我们知道,JavaScript对象是由属性和方法组成的,所以只要确定了一个对象的属性和方法,就可以使用代码创建出这个对象了。 <br> **例:** 1、创建一个 "人" 对象 属性:name(冯宝宝),age(18),gender(true) 方法:sayHi(打招呼-瓜娃子) ``` var person = { name: '冯宝宝', age: 18, gender: true, sayHi: function () { console.log('你好,瓜娃子,我叫 '+this.name); } }; ``` <br> ### **二、 通过Object构造函数创建对象(内置构造函数)** &emsp;&emsp;构造函数实质是函数,Object()是JavaScript内置的一个构造函数,可以用来创建对象 <br> #### **语法格式** >[success]var 对象名 = new 内置构造函数名(); 语法格式分析 new -> 用于调用内置构造函数的关键字 步骤: 1. 创建出一个空对象 ``` var obj = new Object(); console.log(obj); ``` 2. 给对象添加属性和方法并赋值 **语法格式** >[success]对象名.属性名 = 属性值; 对象名.方法名 = function(){...} 区别于字面量方式的 键值对! { 属性名:属性值,方法名:function(){...} } 利用内置构造函数的方式,创建一个完整的hero对象,如下 ``` //使用new关键字来调用Object()构造函数,创建出一个空的对象 var obj = new Object(); console.log(obj); //动态地给对象设置属性和方法 obj.name = '孙尚香'; obj.gender = false; obj.weapon = '弩炮'; obj.equipment = ['急速战靴', '宗师之力', '无尽战刃', '泣血之刃', '闪电匕首', '破晓']; obj.attack = function () { console.log(this.name + ':收割人头'); } obj.run = function () { console.log(this.name + ':加速奔跑'); } console.log(obj.name); console.log(obj.equipment); obj.attack(); obj.run(); ``` **注意** &emsp;&emsp;创建字面量对象的方式本质上是对内置构造函数方式缩写,字面量方式的底层也是使用new调用内置构造函数来完成的。两者比较,内置构造函数方式比字面量的方式更灵活。 <br> ### **三、自定义构造函数** &emsp;&emsp;自定义构造函数和内置构造函数类似,自定义构造函数是由自己定义,而内置构造函数是JavaScript库内置(已经定义好的)的。 **语法格式** ``` //定义一个构造函数,帕斯卡命名:所有单词首字母都大写。 function Hero(name) { //this 表示当前构造函数创建的对象 //this 用来设置对象的属性和方法 this.name = name; } ``` **注意:** >[info]1、使用帕斯卡命名:函数名首字母都大写,如果函数由多个单词组成,每个单词首字母都要大写。 2、使用this表示当前构造函数创建的对象 3、使用this设置对象的属性和方法 &emsp;&emsp;自定义构造函数 跟 内置构造函数 的调用方式一样! 都是使用**new**关键字来调用。 ``` var hero = new Hero('孙尚香');//调用构造函数时,传入实参 ``` &emsp;&emsp;使用自定义构造函数创建一个Hero的完整代码如下。 ``` //定义一个构造函数,帕斯卡命名:所有单词首字母都大写。 function Hero(name, gender, weapon, equipment, blood) { //this 表示当前构造函数创建的对象 //this 用来设置对象的属性和方法 this.name = name; this.gender = gender; this.weapon = weapon; this.equipment = equipment; this.blood = blood; this.attack = function () { console.log(this.name + ':收割人头'); } this.run = function () { console.log(this.name + ':加速奔跑'); } } var hero1 = new Hero('孙尚香', false, '弩炮', ['急速战靴', '宗师之力', '无尽战刃', '泣血之刃', '闪电匕首', '破晓'], 100); var hero2 = new Hero('刘备', true, '剑', ['头盔', '铠甲'], 100); console.log(hero1, hero2); ``` >[info]**构造函数是这几种创建对象的方式中,最灵活,最常用的一种。** <br> ### **四、工厂模式** 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。 工厂模式:使用创建并返回特定类型的对象的**工厂函数**(其实就是普通函数,没啥区别,只是叫法不同),可以创建多个相似的对象。 工厂函数: &emsp;&emsp;用函数来封装特定接口创建对象的细节。 &emsp;&emsp;把实现同一事情的相同代码,放到一个函数中,以后如果再想实现这个功能,就不需要重新编写这些代码了,只要执行当前的函数即可,这就是函数的封装,体现了**高内聚、低耦合**的思想:减少页面的中的冗余代码,提高代码的重复利用率。 ``` ~~~ //在函数内创建一个对象,给对象赋予属性及方法再将对象返回即可。 function createPerson(name) { var o = new Object(); o.name = name; o.sayName = function () { alert(this.name); }; return o; } var obj = createPerson("张三");//将'张三'作为实参调用函数,此时对象o的name属性的值为张三。 var obj2 = createPerson("李四"); alert(obj instanceof Object); alert(obj instanceof createPerson) ~~~ ``` <br> ### 各种创建函数的方法的优劣 >[info] > >**1、字面量方式** &emsp;&emsp;优点:方便快捷,即写即用 &emsp;&emsp;缺点:每个对象都要写一堆代码 >**2、内置构造函数方式** &emsp;&emsp;优点:没什么优点 &emsp;&emsp;缺点:每个对象都要写一堆代码 >**3、自定义构造函数** &emsp;&emsp;优点:较前三种方式灵活,函数只需创建一次,可重复使用,提高开发效率;能确定当前被创建的对象的类型。 &emsp;&emsp;缺点:如果对象只使用一次,要比字面量方式多些几行代码 >**4、工厂函数** &emsp;&emsp;优点:大量创建相似对象时,可以节省大量代码。 &emsp;&emsp;缺点:不能确定不能确定对象类型 <br> ## 对象的访问 **访问对象的成员** 访问属性的格式: &emsp;&emsp;对象名.属性名;(点语法) dog.name;或dog['name'];(中括号语法) 访问方法的格式: &emsp;&emsp;对象名.方法名(); dog.bark(); 或 dog.['bark'] (同上,有点语法和中括号语法) ``` var dog = { name: '花卷', age: 2, gender: true, bark: function () { console.log(this.name + ':汪~汪'); } } console.log(dog.name);// 访问属性,又叫调用属性 dog.bark();// 调用方法 ``` 函数和方法的区别,从定义和调用的角度来看 >[info]1、定义 函数:独立存在 方法:依赖于对象存在,语法跟匿名函数语法相似 2、调用 函数:函数名(); 方法:对象名.方法名(); <br> ## **new关键字和this关键字详解** <br> ### **new关键字** 构造函数 ,是一种特殊的函数。它总是与new关键字一起使用,完成对象的创建。 **举例:** ~~~ // 定义构造函数 function Person(name, age, gender) {    // 1、在内存中创建一个新的空对象;    // var obj = new Object();    // 2、将this指向创建出来的新对象    // this = obj; ​    // 3、执行构造函数的代码(设置属性和方法)    // 添加属性    this.name = name;    this.age = age;    this.gender = gender; ​    // 添加方法    this.sayHi = function () {        console.log('你好,瓜娃子~,我是' + this.name);   }    // 4、返回这个新对象;    // return this; } var per = new Person('冯宝宝', 18, true); console.log(per); ~~~ new在调用构造函数中所执行的步骤 >[info]1. 在内存中创建一个新的空对象; var obj = new Object(); >2. 将this指向创建出来的新对象; this = obj; >3. 执行构造函数的代码(设置属性和方法) >4. 返回这个新对象; return this; <br> ### **this关键字** this关键字总是指向一个对象(引用类型),this所指向的对象跟 **函数的调用方式** 有关 ``` // this关键字总是指向一个对象(引用类型),this所指向的对象跟 函数的调用方式 有关 console.log(this);// this --> window function person() { console.log(this); } person();// this --> window // person 作为构造函数来使用 var per = new person();// this --> person per.sayHello = function () { console.log(this); } per.sayHello();// this --> person ``` this指向的对象跟 函数的调用方式 有关,如下 >[info]1. 函数作为普通函数的调用中,this --> window >2. 函数作为构造函数的调用中,this --> 当前被创建出来的对象 >3. 函数作为对象的方法调用中,this --> 当前调用该方法的对象 <br> ## 对象的使用 <br> ### 遍历对象的成员 **语法格式** >[success]通过for..in语法可以遍历一个对象。格式: >for(var key in obj) { > >} >obj是要遍历的对象。 key是从obj对象中遍历出的属性名, (obj[key])是从obj对象中遍历出的。 注意:这里只能用中括号语法。 **代码案例** ``` var obj = {}; for (var i = 0; i < 10; i++) { obj[i] = i * 2; } for(var key in obj) { console.log(key); //对象的属性名, console.log(obj[key]); //对象的属性值。 console.log(key + "==" + obj[key]); } ``` ### 删除对象的成员 **语法格式** >[success]利用delete关键字 >delete 成员名; **代码案例** ``` function fun() { this.name = 'mm'; } var obj = new fun(); console.log(obj.name); // mm delete obj.name; console.log(obj.name); // undefined ``` ## 对象的存储 ### 基本类型和引用类型的区别 &emsp;&emsp;基本数据类型简称基本类型,又叫简单类型或值类型,引用数据类型简称引用类型,又叫复杂类型或对象类型 &emsp;&emsp;基本类型的存储特点:在存储数据时,变量中存储的是值本身,因此基本类型又称&emsp;&emsp;值类型。引用类型的存储特点:在存储数据时,变量中存储的仅仅是数据的地址(地址又称引用)。 #### 堆和栈的概念 堆栈空间分配区别:   1、栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;   2、堆(操作系统): 存储引用类型(对象),一般由程序员分配释放, 若程序员不释放,由垃圾回收机制回收,分配方式类似于链表。 内存中的堆栈可简化为下图 ![](https://box.kancloud.cn/5802cbe0164460dc6ab999887adcb1a9_697x488.png) ``` //定义基本数据类型变量 var a = 5; var b = a; a = 6; console.log(a, b); ``` ### 基本类型的数据存储图示如下 ![](https://box.kancloud.cn/61e760358bda8ef588f854c1957467a2_731x479.png) 由上图可以知道,基本类型数据只存储在占内存中,跟堆内存块无关。 ### 引用类型在内存中的存储 &emsp;&emsp;引用类型又称对象类型,对象是通过new关键字创建出来的,所以引用类型的数据存储图跟new关键字紧密相关。 &emsp;&emsp;当执行new关键字时,JS解析器就在堆中开辟一块内存,并给这块内存分配一个地址,比如 0xaabb,用于存储对象的数据(属性和方法),同时在栈中开辟一块内存,用于存储堆内存的地址(0xaabb)。此时栈中存储的数据就是堆内存块的地址,所以我们称 “ 栈上的地址指向了这块堆内存 ” 。 分析以下代码在执行过程中的变量的值的变化,并画出内存图作进一步分析。 ``` // 定义构造函数 function Person(name, age) { this.name=name; this.age=age; this.sayHi=function () { console.log('你好,我是' + this.name); } } // 调用构造函数 var p1 = new Person('冯宝宝', 100); //将 '冯宝宝', 100 传入函数Person中,得到对象{name='冯宝宝';age=100;},此时对象的属性和方法都存储在堆内存中, var p2=p1; //将对象p1赋值给对象p2,两个对象名都指向同一个堆内存。 p2.name='张楚岚'; //将对象p2中的name属性的值改为'张楚岚',即将堆内存中的name='冯宝宝'改为name='张楚岚',此时对象p1的属性值也随之给为name='张楚岚'。 console.log(p1);// 输出结果:Person {name: "张楚岚", age: 100, sayHi: ƒ} console.log(p2);// 输出结果:Person {name: "张楚岚", age: 100, sayHi: ƒ} ``` ### 基本类型作为函数的参数 ``` function fn(a, b) { a = a + 2; b = b + 2; console.log(a, b); } // 在函数外部的x,y是否受影响? var x = 2; var y = 3; fn(x, y); console.log(x, y); //注意:函数的参数是局部变量 ``` 基本类型的数据作为函数参数时的存储图示如下 ![](https://box.kancloud.cn/df0cbde0bf1d4b27f891da9a025763df_916x408.png) 根据以下存储图示可知,在**函数内部修改了变量的值,不影响外部变量原有的值。**我们可以画出内存图作进一步分析,就很容易看出不影响的原因。 基本数据类型的数据都是存放在栈内存中,遵循先进后出的原则,当在函数内部修改了变量的值时,实际是新开辟了给内存空间存储改变后的数据。 ### 引用类型作为函数的参数 ``` // 定义构造函数 function Person(name, age) { this.name=name; this.age=age; this.sayHi=function () { console.log('你好,我是' + this.name); } } ``` 方式一、调用自定义构造函数,分析调用前和调用后数据有什么不同。在函数内部修改变量的值。 ``` // 定义一个函数,参数用于接收一个person对象 function fn(person) { person.name='张楚岚'; console.log(person); } var p1 = new Person('冯宝宝', 100); fn(p1); console.log(p1); ``` 引用类型的数据作为函数参数时的存储图示如下 ![](https://box.kancloud.cn/bbc3e41266aa3e60d2891ada7190003d_990x404.png) 方式二、调用自定义构造函数,分析调用前和调用后数据有什么不同。在函数内部修改变量的值。 ``` // 定义一个函数,参数用于接收一个person对象 function fn(person) { person.name='张楚岚'; person = new Person('王也',22); console.log(person); } var p1 = new Person('冯宝宝', 100); fn(p1); console.log(p1); ``` 引用类型的数据作为函数参数时的存储图示如下 ![](https://box.kancloud.cn/34853185f00fe23690512f8269c28145_986x391.png) ``` // 定义函数 function fn(array) { array[0] = -1; console.log(array);// array[0] -> -1 } //定义一个数组 var arr = [9, 5, 2, 7, 1, 3, 6]; // 调用函数 fn(arr); console.log(arr);// arr[0] -> -1 ``` ![](https://box.kancloud.cn/34205c90582d426cc1539515ccc39728_929x414.png)