【懿瑾】JS的学习笔记④-对象(Object)

106 阅读13分钟

不是5种基本数据类型,就都是对象

4.1 对象的分类

  1. 内建对象

    由ES标准定义的对象,在任何ES的实现中都可以使用

    比如Math,String,Number,Funtion……

  2. 宿主对象

    由JS的运行环境提供的对象,目前来讲主要指浏览器提供的对象

    比如DOM,BOM

  3. 自定义对象

    由开发人员自己创建的对象

4.1.1对象基本操作

  1. 创建对象:

    使用new关键字调用的函数,是构造函数constructor

    构造函数是专门用来创建对象的函数

    使用typeof检查一个对象时,会返回object

     var obj = new Object();
    

    在对象中保存的值称为属性

  2. 向对象中添加属性

    语法:对象.属性名 = 属性值;

     obj.name = "Tom";
    
  3. 特殊属性名

    属性名不强制要求遵守标识符规范,但是在使用中尽量按照标识符规范写

    如果采用特殊的属性名,不能采用”.”的方式来操作

    语法:对象[“属性名”] = 属性名

     obj["123"] = 789;
     console.log(obj["123"]);
     ​
     let n = "123";
     console.log(obj[n]);    //输出789
    

    属性值可以是任意数据类型,可以是基本数据类型,可以是个对象,即套娃

  4. 读取对象中的属性

    语法:对象.属性;

    如果读取对象中没有的属性,不会报错而是会返回undefined

  5. 改对象中的属性

    语法:对象.属性 = 新值;

  6. 删除对象中的属性

    语法:delete 对象.属性;

     delete obj.name;
    

4.2 遍历

4.2.1 In 运算符

检查一个对象中是否含有该属性

语法: “属性名” in 对象

 console.log("name" in obj);
 // 有返回true,没有就返回false  

JS中的变量都是保存到栈内存中的

基本数据类型的值直接在栈内存中存储

值与值之间是独立存在的,修改一个变量不会影响到其他的变量

对象时保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟一个新的空间

跟指针差不多

而变量保存的是对象的地址(对象的引用),如果两个变量保存的是同一个对象引用

当其中一个通过一个变量修改属性时,另一个也会受到影响

1668070084832.png

当两个基本数据类型比较时,就是比较值

当两个引用数据类型比较时,它是比较对象的内存地址

如果两个对象内容一模一样,但是地址不同,还是返回false

4.2.2 使用字面量创建对象

使用字面量创建对象时,可以直接指定对象中的属性

语法: var 对象名 = {属性名:属性值,属性名:属性值……}

属性名可以加引号,一般不加,特殊的名字必须加引号

 let obj = {
     name:"孙悟空",
     gender:"男"
 };

4.3 函数 Function

4.3.1函数基本操作

  1. 创建函数

    使用函数声明创建函数

语法:function 函数名([形参1,形参2……]){

//函数体

}

 // 函数声明创建函数
 function first(num1,num2){
     // 函数体
 }
 ​
 // 函数表达式创建函数
 var fun = function(num1,num2){
     // 函数体
 };  

可以使用构造函数创建函数,但是一般不用

1668072258547.png

  1. 函数参数传递

    调用函数时,解析器不会检查实参的类型和数量

    函数的实参可以是任意数据类型。

     function first(num1,num2){
         console.log(num1 + num2);
     }
     ​
     first(123,"hello");     //123hello
     first(true,false);     //1
     first(123,"hello",456);     //123hello
     first(23);     //NaN
    

    如果实参的数量多于形参数量,多于的实参将不会被赋值

    如果实参的数量少于形参数量,没有对应实参的形参将是undefinde

    可以将一个匿名函数当做实参传输给另一个函数

    当传输的参数过多时,可以将参数打包成一个对象

     // 创建对象
     var obj = {
         name:"孙悟空",
         gender:"男",
         age:18,
         address:"花果山"
     };
     // 函数声明创建函数
     function first(o){
         console.log("我叫" + o.name + ",我是个" + o.gender +"人,我今年" + o.age + "岁了,我来自" + o.address);
     }
     ​
     first(obj);  // 我叫孙悟空,我是个男,我今年18岁了,我来自花果山 
    
  2. 函数返回值

    函数中使用:可以跟任意类型的值

    return 值

    不跟任何值和不写return 返回undefined

  3. 立即执行函数

     (function(a,b){
         console.log("a=" +a);
         console.log("b=" +b);
     })(12,34)
    

方法

函数也可以称为对象的属性,

如果一个函数作为一个对象的属性保存

那么我们称这个函数是这个对象的方法

调用这个函数就说调用对象的方法(method)

它只是名称上的区别,没有任何其他的区别。

4.3.2 枚举对象中的属性

  1. For…in语句

    语法:

    For(var 变量名 in 对象名){

    //代码块

    }

    For…in语句,对象中有几个属性就执行几次

    每次执行时,会将对象中一个属性的名字赋值给变量

     for(var n in obj){
         console.log("属性名:" + n);
         console.log(n +":" + obj[n]);
     }
    

    用中括号传一个变量就可以取对象里面的值

4.3.3 作用域

  1. 全局作用域

    直接编写在script标签中的JS代码,都在全局作用域

    全局作用域在页面打开时创建,在页面关闭时销毁

    在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口

    它由浏览器创建,我们可以直接使用它

    在全局作用域中:

    创建的变量都会作为window对象的属性保存

    创建的函数都会作为window对象的方法保存

  2. 变量的声明提前:变量提升

    使用var关键字声明的变量,会在所有代码执行之前被声明(但是不会赋值,执行到变量的时候才赋值)

    如果变量声明不使用var关键字,则变量不会被声明提前

    在函数作用域中也会有变量声明提前

  3. 函数的声明提前(会有这样的面试题)

    使用函数声明创建函数 function 函数名(){} 会在所有代码执行之前声明

    这种函数写到哪都行

    使用函数表达式创建函数 var 变量 = function(){} 只有变量提前声明,但是没有函数声明提前

  4. 函数作用域(局部作用域)

    调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁

    每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的

    在函数作用域中可以访问全局变量

    全局作用域无法访问函数作用域的变量

    当在函数作用域操作一个变量时,它会现在自身作用域中寻找,如果有就直接使用

    如果没有则向上一级作用域中寻找,上一级未必是全局作用域

    全局作用域中也没有就会报错

    在函数作用域中使用全局作用域的变量,就需要使用window对象

     var a = "全局作用域的a";
     function fun() {
         var a = "fun的a";
         function fun2() {
             console.log("a=" +a);           //a=fun的a
             console.log("a=" + window.a);   //a=全局作用域的a
         }
         fun2();
     }
     fun();
    

    在函数作用域中也会有变量声明提前

    同样的,函数声明也会提前

    相当于小的全局作用域,就是生命周期比较短

     function test() {   //函数作用域中也存在变量声明提前
         console.log("a=" + a);      //a=undefined
         var a = 35;
     }
     ​
     // 相当于
     function test() {
         var a;
         console.log("a=" + a);      //a=undefined
         a = 35;
     }
    

    在函数中,不使用var声明的变量都会成为全局变量

     function test() {    //函数中不加var的变量就会被认为是全局变量
         d = 35;
         // 相当于
         window.d = 35;
     }
    

4.3.4 this关键字

解析器在调用函数每次都会向函数内部传递一个隐含的参数,(还有一个隐含的参数,arguments)

这个隐含的参数就是this,this指向的是一个对象,

这个对象我们称为函数执行的上下文对象

根据函数调用方式的不同,this会指向不同的对象

  1. 以函数的形式调用时,this永远都是window

  2. 以对象的形式调用时,this就是调用方法的那个对象

     var obj = {
         name:"孙悟空",
         sayName:fun
     }
     function fun() {
         console.log(this);
     }
     // 以函数形式调用
     fun(); //返回window
     // 以对象形式调用
     obj.sayName(); //返回obj
    

    使用工厂方法创建对象

    将创建对象的重复语句封装到函数中

    创建的对象都是Object类型,有时候无法区分

     function createPerson(name, age, gender) {
         var obj = new Object();
         obj.name = name;
         obj.age = age;
         obj.gender = gender;
         console.log(obj);
         return obj;
     }
     ​
     var obj2 = createPerson("孙悟空",24,"男");
     var obj3 = createPerson("蜘蛛精",18,"女");
     var obj4 = createPerson("白骨精",16,"女");
    

(1)构造函数

构造函数就是一个普通的函数,创建方式和普通的函数没有区别

不同的是构造函数习惯上首字母大写

构造函数和普通函数的区别就是调用方式的不同

普通函数是直接调用,而构造函数需要使用new关键字来调用

(2)构造函数执行过程:

  1. 立刻创建一个新的对象
  2. 将新建的对象设置为函数中的this,在构造函数中可以使用this来引用新建的对象
  3. 逐行执行函数中的代码
  4. 将新建的对象做饭返回值返回

使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类

我们将通过一个构造函数创建的对象,称为是该类的实例

所以per2,per3,per4都是Person类的实例

(3)This的4种情况

  1. 当以函数的形式调用时,this是window

  2. 当以方法的形式调用时,谁调用方法this就是谁

  3. 当以构造函数的形式调用时,this就是新创建的那个对象

  4. 使用call()和apply()调用时,this是指定的那个对象

     function Person(name, age, gender) {
         this.name = name;
         this.age = age;
         this.gender = gender;
         this.sayName = function () {
             alert(this.name);
         }
         console.log(this);
     }
     ​
     var per2 = new Person("孙悟空",24,"男");
     var per3 = new Person("蜘蛛精",18,"女");
     var per4 = new Person("白骨精",16,"女");
    

    运行结果:

1668083885150.png

上面构造函数中的sayName方法还是有缺陷的,详情见4.3.5

使用instanceof 可以检查一个对象是否是一个类的实例

语法:

对象 instanceof 构造函数

如果是,则返回true,否则返回false

```
 console.log(per2 instanceof Person);
```

> 所有对象都是Object的后代
>
> 所以任何对象和Object做insranceof检查时都会返回true

(4)使用call(),apply()调用函数

他们两个都可以指定this指向,区别就是传递实参的形式不同

 var obj = {name:"Tom"};
 var obj2 = {name:"Jack"};
 function fun(a,b) {
     console.log("a=" +a);   //2
     console.log("b=" +b);   //3
     console.log(this.name);
 }
 ​
 fun.call(obj,2,3);              //obj
 fun.apply(obj2,[2,3]);        //obj2

(5)封装实参的对象arguments

它也是在调用函数时,浏览器传递的隐含参数,和this一样

是一个类数组对象,也可以通过索引操作数据,也可以获取长度 (arguments.length就是实参的个数)

在调用函数时,我们所传递的实参都会在arguments中保存

我们即使不定义形参,也可以通过arguments调用实参

arguments[0] 表示第一个实参,以此类推

它里面有个属性叫callee,

这个属性对应一个函数对象,就是当前正在指向的函数的对象。

4.3.5 原型prototype

上面构造函数中的sayName方法还是有缺陷的

  • 每创建一个对象就会在内部执行创建方法,将sayName写在外部的话,会污染全局作用域的命名空间

原型上的方法是给实例化对象用的,

实例对象的隐式原型 = 构造函数的显式原型

  • 每个函数function都有一个prototype,即显式原型(属性)

    他的默认值是一个空的Object对象,(但是Object实例对象不满足)

  • 当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,也叫隐式原型(属性)

    指向该构造函数的原型对象,我们可以通过 __proto__来访问该属性

    函数也是通过Funtion构造函数实现的对象,所以函数的隐式原型__proto__属性都一样

  • 对象的隐式原型的值为其对应构造函数的显式原型的值,原型是一个地址值

原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象

我们可以将对象中的共有的内容,统一设置到原型对象中。

原型链:

当我们访问对象的一个属性或者方法是,它会现在对象自身中寻找,如果有则直接使用,

如果没有,则会去原型对象中寻找,找到再使用

实例的原型对象永远指向自己缔造者的原型对象

 //向原型中添加属性
 Person.prototype.a = 123;
 console.log(per2.a);        //123

我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型中

这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以是每个对象都具有这些属性和方法了

原型和对象示意图:

1668084578797.png

完善后的创建对象代码:

 function Person(name, age, gender) {
     this.name = name;
     this.age = age;
     this.gender = gender;
 }
 ​
 var per2 = new Person("孙悟空",24,"男");
 var per3 = new Person("蜘蛛精",18,"女");
 var per4 = new Person("白骨精",16,"女");
 ​
 //向原型中添加sayName方法
 // 这样即保证这个函数只有一个,也不会影响到全局作用域
 Person.prototype.sayName = function(){
     alert(this.name);
 };
 ​
 console.log(per2.sayName());        
 console.log(per2.__proto__ == Person.prototype); //true

原型对象也是对象,所以它也有原型

当我们使用一个对象的属性或者方法时,会先在自身中查找

自身如果有,直接使用

如果自身中没有就会去原型总查找,有就使用

如果没有就去原型的原型中查找,直到Object对象的原型

Object对象的原型没有原型,如果在Object中依然没有找到,则返回undefined

  • 使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true

  • 可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性

    使用该方法只有当对象自身中含有属性时,才会返回true

     console.log(per2.hasOwnProperty("name"));   //true
    

这个方法存放在原型对象的原型中

就是Object对象的原型

 console.log(Person.__proto__.__proto__.hasOwnProperty);//显示对象
 console.log(Person.__proto__.__proto__.__proto__);    //null

4.3.6 toString方法

当我们直接在页面中打印一个对象时,事件上是输出对象的toString()方法的返回值

这个方法在Object对象的原型中

现在浏览器调试工具中都自带了

4.3.7 垃圾回收(GC)

软件或者系统运行过程中会产生垃圾

当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,垃圾就是这样产生的

在JS中有自己的自动垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作

我们需要做的就是将不再用的对象设置为null即可