JavaScript-对象(上)【适合初学者,有案例结合】

1. 面向过程与面向对象

  • 以完成一件事来说明什么是面向过程与面向对象。
    面向过程的解决办法:注重的是具体的步骤,只有按照步骤一步一步的执行,才能够完成这件事情。
    面向对象的解决办法:注重的是一个个对象,这些对象各司其职,我们只要发号施令,即可指挥这些对象帮我们完成任务。

在这里插入图片描述

总结:

  • 对于面向过程思想,我们扮演的是执行者,凡事都要靠自己完成。
  • 对于面向对象思想,我们扮演的是指挥官,只要找到相应的对象,让它们帮我
    们做具体的事情即可。
  • 面向过程思想的劣势,编写的代码都是一些变量和函数,随着程序功能的不断增加,变量和函数就会越来越多,此时容易遇到命名冲突的问题,由于各种功能的代码交织在一起,导致代码结构混乱,变得难以理解、维护和复用。
  • 面向对象思想的优势,可以将同一类事物的操作代码封装成对象,将用到的变量和函数作为对象的属性和方法,然后通过对象去调用,这样可以使代码结构清晰、层次分明。

2. 面向对象的特征


2.1 封装性

封装: 指的是隐藏内部的实现细节,只对外开放操作接口。
接口: 就是对象的方法,无论对象的内部多么复杂,用户只需知道这些接口怎么使用即可。
举例: 电脑是非常高精密的电子设备,其实现原理也非常复杂,而用户在使用时并不需要知道这些细节,只要操作键盘和鼠标就可以使用。

优势:无论一个对象内部的代码经过了多少次修改,只要不改变接口,就不会影响到使用这个对象时编写的代码。


2.2 继承性

继承: 是指一个对象继承另一个对象的成员,从而在不改变另一个对象的前提下进行扩展。
举例1: 动物与猫和狗的关系,人类的繁衍等。
举例2: String对象就是对所有字符串的抽象,所有字符串都具有toUpperCase()方法,用来将字符串转换为大写,这个方法其实就是继承自String对象。

优势:

  • 可在保持接口兼容的前提下对功能进行扩展。
  • 增强了代码的复用性,为程序的修改和补充提供便利。

2.3 多态性

多态: 指的是同一个操作作用于不同的对象,会产生不同的执行结果。
理解: 实际上JavaScript被设计成一种弱类型语言(即一个变量可以存储任意类型的数据),就是多态性的体现。

  • 例如,数字、数组、函数都具有toString()方法,当使用不同的对象调用该方法时,执行结果不同。
<script>
  var obj = 123;
  console.log(obj.toString());    // 输出结果:123
  obj = [1, 2, 3];
  console.log(obj.toString());    // 输出结果:1,2,3
  obj = function() {
    
    };
  console.log(obj.toString());    // 输出结果:function () {}
</script>

提示:在面向对象中,多态性的实现往往离不开继承,这是因为当多个对象继承了同一个对象后,就获得了相同的方法,然后根据每个对象的不同来改变同名方法的执行结果。


3. 自定义对象

3.1 对象的定义

语法: 对象的定义是通过“{ }”语法实现的。
组成: 对象以对象成员(属性和方法)构成,多个成员之间使用逗号分隔。
成员: 对象的成员以键值对的形式存放在中。

对象也是一个变量,但对象可以包含多个值(多个变量)。

<script>
  var o1 = {
    
    };                                      // 定义空对象
  var o2 = {
    
    name: 'Jim'};                           // 定义含有name属性的对象
  var o3 = {
    
    name: 'Jim', age: 19, gender: '男'};    // 定义含有3个属性的对象
</script>

定义 JavaScript 对象可以跨越多行,空格跟换行不是必须的:

<script>
  var o4 = {
    
    
    name: 'Jim',                // 成员属性o4.name
    age: 19,                    // 成员属性o4.age
    gender: '男',               // 成员属性o4.gender
    sayHello: function() {
    
          // 成员方法o4.sayHello()
      console.log('你好');
    }
  }
</script>
  • 实例:
<!DOCTYPE html>
<html>
<head> 
<meta charset="utf-8"> 
<title>text</title> 
</head>
<body>

<p>创建 JavaScript 对象。</p>
<p id="demo"></p>
<script>
var person = {
     
     firstName:"Tom", lastName:"Jack", age:20, eyeColor:"black"};
document.getElementById("demo").innerHTML=person.firstName + " 现在是" + person.age + " 岁.";
</script>

</body>
</html>

在这里插入图片描述

  • JSON: JavaScript Object Notation,JavaScript对象符号。
    用途: 应用于数据存储和交互。
    语法: JSON是一个字符串,使用双引号包裹对象的成员名和字符串型的值。

JSON与对象的区别: JSON是一个字符串。
JSON不仅可以用来保存对象,还可以保存数字、字符串、数组等其他类型的数据。

<script>
  var json = {
    
    "name":"Tom","age":24,"work":true,"arr":[1,2]};
</script>

<script>
  var json = [{
    
    "name":"Tom","age":24},{
    
    "name":"Jim","age":25}];
</script>

3.2 访问对象成员

  • 语法: 对象.成员。
<script>
  var o5 = {
    
    };                           // 创建一个空对象
  o5.name = 'Jack';                      // 为对象增加属性
  o5.introduce = function () {
    
               // 为对象增加方法
    alert('My name is ' + this.name);    // 在方法中使用this代表当前对象
  };
  alert(o5.name);           // 访问name属性,输出结果:Jack
  o5.introduce();           // 调用introduce()方法,输出结果:My name is Jack 
</script>

在这里插入图片描述
在这里插入图片描述

<title>text</title> 
</head>
<body>

<p>创建和使用对象方法。</p>
<p>对象方法作为一个函数定义存储在对象属性中。</p>
<p id="demo"></p>
<script>
var person = {
     
     
    firstName: "Tom",
    lastName : "Jack",
    id : 5566,
    fullName : function() 
	{
     
     
       return this.firstName + " " + this.lastName;
    }
};
document.getElementById("demo").innerHTML = person.fullName();
</script>
	
</body>
</html>

  • 可变成员名语法:对象[变量名]=值。
<script>
  var o6 = {
    
    };              // 创建一个空对象
  var key = 'id';           // 通过变量保存要操作的属性名
  o6[key] = 123;            // 相当于“o6['id'] = 123”或“o6.id = 123”
</script>

3.3 对象成员遍历

  • 语法: for…in
<script>
  var obj = {
    
    name: 'Tom', age: 16};
  for (var k in obj) {
    
    
    console.log(k + '-' + obj[k]);
  }
</script>
  • 变量k保存了每个对象成员的名称。
  • obj[k]访问成员属性的值。
  • obj[k] ()调用成员方法。
  • 判断对象成员是否存在
    当需要判断一个对象中的某个成员是否存在时,可以使用in运算符。当对象的成员存在时返回true,不存在时返回false。
<script>
  var obj = {
    
    name: 'Tom', age: 16};
  console.log('name' in obj);      // 输出结果:true
  console.log('gender' in obj);    // 输出结果:false
</script>

3.4 深拷贝与浅拷贝

拷贝:是指将一个目标数据复制一份,形成两个个体。
深拷贝:参与拷贝的两个目标,改变其中一个目标的值,不会影响另一个目标的值。
浅拷贝:参与拷贝的两个目标,一个目标的值改变,另一个目标的值也会随之改变.

<script>
  var p1 = {
    
    name: 'Jim', age: 19};
  var p2 = p1;
  p2.name = 'Tom';
  console.log(p1);           // 输出结果:Object {name: "Tom", age: 19}
  console.log(p2);           // 输出结果:Object {name: "Tom", age: 19}
  console.log(p1 === p2);    // 输出结果:true
</script>

从运行结果可以看出,在将变量p1赋值给p2后,更改p2的成员,p1的成员也会发生改变。这种情况在JavaScript 中称之为浅拷贝( shallow copy )
例如,将上述代码中的对象“{name:‘Jim’ , age: 19})”想象成一个文件夹,该文件夹中保存了name和age两个文件,而变量p1是链接到这个文件夹的快捷方式。在执行“var p2 = p1;”操作时,是将快捷方式复制了一份,此时两个快捷方式指向了同一个文件夹,而不是对文件夹进行复制操作。


与浅拷贝相对的是深拷贝( deep copy ),即真正创建一个对象的副本。若要实现深拷贝的效果,可以编写代码复制对象里的成员到另一个对象,具体如例所示。

<script>
  function deepCopy(obj) {
    
    
    var o = {
    
    };
    for (var k in obj) {
    
    
      o[k] = (typeof obj[k] === 'object') ? deepCopy(obj[k]) : obj[k];
    }
    return o;
  }
  var p1 = {
    
    name: 'Jim', subject: {
    
    name: ['HTML', 'CSS']} };
  var p2 = deepCopy(p1);
  p2.subject.name[0] = 'JavaScript';
  console.log(p1.subject.name[0]);    // 输出结果:HTML
  console.log(p2.subject.name[0]);    // 输出结果:JavaScript
  console.log(p1 === p2);			  // 输出结果:false
</script>

在上述代码中,第3行创建了一个新对象o用来保存成员,第4~6行遍历了 obj对象的每一个成员,在遍历时,通过“o[k] = obj[k]”实现成员的复制。由于传入的对象 obj的成员有可能还是一个对象,第5行代码通过typeof进行了判断,如果typeof检测的类型为object(数组、对象的类型都是object ),则递归调用deepCopy()函数,进行完整的复制。

实现深拷贝:
基本类型(如数值、字符型):通过变量赋值即可实现。
引用类型(如数组、对象):复制对象里的成员到另一个对象。

实现浅拷贝
引用类型(如数组、对象):通过变量赋值即可实现。提示:浅拷贝是引用类型中才有的概念。
浅拷贝的优势
浅拷贝可以节省内存开销。


4.构造函数

4.1为什么使用构造函数

构造函数:是JavaScript创建对象的另外一种方式。
与字面量方式创建对象对比:构造函数可以创建出一些具有相同特征的对象。

举例:通过水果构造函数创建苹果、香蕉、橘子对象。其特点在于这些对象都基于同一个模板创建,同时每个对象又有自己的特征。

JavaScript在设计之初并没有class关键字,但可以通过函数来实现相同的目的。我们可以将创建对象的过程封装成函数,通过调用函数来创建对象,具体示例如下。

<script>
  function factory(name, age) {
    
    
    var o = {
    
    };             // 创建一个空对象
    o.name = name;          // 添加name属性
    o.age = age;            // 添加age属性
    return o;               // 将对象返回
  }
  var o1 = factory('Jack', 18);
  var o2 = factory('Alice', 19);
  console.log(o1);          // 输出结果: Object {name: "Jack", age: 18}
  console.log(o2);          // 输出结果: Object {name: "Alice", age: 19}
</script>

字面量的方式创建对象的特点

  • 优势: 简单灵活。
  • 劣势:当需要创建一组具有相同特征的对象时,无法通过代码指定这些对象应该具有哪些相同的成员。

面向对象编程语言的实现模板的方式:
利用类(class)创建模板,根据模板实现不同的对象(类的实例)。

  • Javascript实现模板的方式1:通过工厂函数,在其内部通过字面量“{}”的方式创建对象来实现,缺点是无法区分对象的类型。
  • Javascript实现模板的方式2:通过构造函数创建对象。

4.2 JavaScript内置的构造函数

在学习如何自定义构造函数之前,先来看一下JavaScript内置的构造函数如何使用。

  • 常见的内置构造函数: Object、String、Number等构造函数。
  • 构造函数如何创建对象: new构造函数名()。
  • 什么是实例化与实例:人们习惯将使用new关键字创建对象的过程称为实例化,实例化后得到的对象称为构造函数的实例。
<script>
  // 通过构造函数创建对象
  var obj = new Object();          // 创建Object对象
  var str = new String('123');     // 创建String对象
  // 查看对象是由哪个构造函数创建的
  console.log(obj.constructor);    // 输出结果:function Object() { [native code] }
  console.log(str.constructor);    // 输出结果:function String() { [native code] }
</script>

在上述示例中,obj和str对象的constructor属性指向了该对象的构造函数,通过console.log()输出时,[native code]表示该函数的代码是内置的,因此,此函数为JavaScript 的内置构造函数。

<script>
  console.log({
    
    }.constructor);    // 输出结果:function Object() { [native code] }
</script>

通过字面量“”创建的对象是Object对象的实例,具体示例如上。


4.3 自定义构造函数

  • 构造函数的命名采用帕斯卡命名规则,即所有的单词首字母大写。
  • 在构造函数内部,使用this来表示刚刚创建的对象。
<script>
//自定义构造函数
  function Person(name, age) {
    
    
    this.name = name;                // 添加name属性
    this.age = age;                  // 添加age属性
    this.sayHello = function () {
    
        // 添加sayHello()方法
      console.log('Hello, my name is ' + this.name);
    };
  }
  
  //构造函数
  var p1 = new Person('Jack', 18);
  var p2 = new Person('Alice', 19);
  console.log(p1);                  // 输出结果:Person {name: "Jack", age: 18}
  console.log(p2);                  // 输出结果:Person {name: "Alice", age: 19}
  p1.sayHello();                    // 输出结果:Hello, my name is Jack
  console.log(p1.constructor);      // 输出结果:function Person(name, age) ……
</script>

注意:在学习JavaScript时,初学者经常会对一些相近的名词感到困惑,如函数、方法、构造函数、构造方法、构造器等。
实际上,它们都可以统称为函数,只不过在不同使用场景下的称呼不同。根据习惯,对象中定义的函数称为对象的方法。
而对于构造函数,也有一部分人习惯将其称为构造方法或构造器,我们只需明白这些称呼所指的是同一个事物即可。


补充:
在各种面向对象编程语言中,class关键字的使用较为普遍,而JavaScript为了简化难度并没有这样设计。不过,随着Web前端技术的发展,一部分原本从事后端开发的人员转向了前端。为了让JavaScript更接近一些后端语言(如 Java、PHP等)的语法从而使开发人员更快地适应,ES6增加了class关键字,用来定义一个类。在类中可以定义constructor构造方法。具体示例如下。

<script>
  // 定义类
  class Person {
    
    
    constructor (name, age, gender) {
    
        // 构造方法
      this.name = name;                  // 添加name属性
      this.age = age;                    // 添加age属性
      this.gender = gender;              // 添加gender属性
    }
    introduce() {
    
                            // 定义introduce()方法
      console.log('我是' + this.name + ',今年' + this.age + '岁。');
    }
  }
  // 实例化时会自动调用constructor()构造方法
  var p = new Person('Jim', 19, '男');
  p.introduce();    // 输出结果:我是Jim,今年19岁。
</script>

class语法本质上是语法糖,只是方便用户使用而设计的,不使用该语法同样可以达到相同的效果,如前面学过的构造函数。为了避免用户的浏览器不支持此语法,因此不推荐使用此方式。


4.4 私有成员

概念: 在构造函数中,使用var关键字定义的变量称为私有成员。
特点: 在实例对象后无法通过“对象.成员”的方式进行访问,但是私有成员可以在对象的成员方法中访问。
特性: 私有成员name体现了面向对象的封装性。

在构造函数中,使用var关键字定义的变量称为私有成员,在实例对象后无法通过“对象.成员”的方式进行访问,但是私有成员可以在对象的成员方法中访问。具体示例如下。

<script>
  function Person() {
    
    
    var name = 'Jim';
    this.getName = function () {
    
    
      return name;
    };
  }
  var p = new Person();    // 创建实例对象p
  console.log(p.name);     // 访问私有成员,输出结果:undefined
  p.getName();             // 访问对外开放的成员,输出结果:Jim
</script>

从上述代码可知,私有成员name体现了面向对象的封装性,即隐藏程序内部的细节,仅对外开放接口getName(),防止内部的成员被外界随意访问。

补充:构造函数中的return关键字
构造函数的本质是函数,因此构造函数中也可以使用return关键字。
构造函数在使用时与普通函数有一定的区别:

  • 若用return返回一个数组或对象等引用类型数据,则构造函数直接返回该数据,而不会返回原来创建的对象。
  • 若返回的是基本类型数据,则返回的数据无效,依然会返回原来创建的对象。
<script>
  // 返回基本类型数据
  function Person() {
    
    
    obj = this;
    return 123;
  }
  var obj, p = new Person();
  console.log(p === obj);    // true
</script>

<script>
  // 返回复合类型数据
  function Person() {
    
    
    obj = this;
    return {
    
    };
  }
  var obj, p = new Person();
  console.log(p === obj);    // false
</script>

4.5 函数中的this指向

this的特点: 根据函数不同的调用方式,函数中的this指向会发生改变。

1. 分析this指向
在JavaScript中,函数内的 this指向通常与以下3种情况有关。

  • 使用new关键字将函数作为构造函数调用时,构造函数内部的this 指向新创建的对象。
  • 直接通过函数名调用函数时,this指向的是全局对象(在浏览器中表示 window对象)。
  • 如果将函数作为对象的方法调用,this将会指向该对象。

演示第2、3种情况,具体示例如下。

<script>
  function foo() {
    
    
    return this;
  }
  var o = {
    
    name: 'Jim', func: foo};
  console.log(foo() === window);    // 输出结果:true
  console.log(o.func() === o);      // 输出结果:true
</script>

从上述代码可以看出,对于同一个函数foo(),当直接调用时,this指向window对象,而作为o对象的方法调用时,this指向的是o对象。

2.更改this 指向
除了遵循默认的 this指向规则,函数的调用者还可以利用JavaScript 提供的两种方式手动控制this 的指向。
一种是通过apply()方法,另一种是通过call()方法。
具体示例如下:

apply()和 call()方法的区别在于第2个参数,apply()的第2个参数表示调用函数时传入的参数,通过数组的形式传递;而call()则使用第2~N个参数来表示调用函数时传入的函数。具体示例如下。

<script>
  function method(a, b) {
    
    
    console.log(a + b);
  }
  method.apply({
    
    }, ['1', '2']);     // 数组方式传参,输出结果:12
  method.call({
    
    }, '3', '4');        // 参数方式传参,输出结果:34
</script>

<script>
  function method() {
    
    
    console.log(this.name);
  }
  method.apply({
    
    name: '张三'});     // 输出结果:张三
  method.call({
    
    name: '李四'});      // 输出结果:李四
</script>

通过上述代码可以看出,当直接调用method()函数时,this指向的是全局对象,因此调用method()时this.name相当于window.name,输出结果为“张三”。而通过bind()绑定后,其返回值test用来代替method()函数,在调用test()时this 指向绑定时传入的对象,因此 this.name输出结果为“李四”。

猜你喜欢

转载自blog.csdn.net/m0_49095721/article/details/109173908
今日推荐