JavaScript构造函数与普通函数的区别及原型优化

作者:半夏时间:2026-10-24 14:37:39浏览:491

本文深入解析JavaScript中构造函数与普通函数的核心区别,包括命名规范、this指向及new操作符的作用。详细阐述new关键字执行的四步流程,并针对构造函数导致的方法重复创建与内存浪费问题,探讨从全局函数封装到原型对象优化的演进过程,帮助开发者理解对象创建机制并提升代码性能。

首先需要明确一个核心概念:构造函数本质上仍然是函数。

在开发中,我们常利用构造函数来创建实例对象,例如通过调用Object()和Array()构造函数来实例化对象和数组。

尽管语法定义上构造函数与普通函数完全一致,但它们在行为上存在三个主要差异。

第一,命名规范不同。按照惯例,构造函数的首字母必须大写,而普通函数首字母小写。这一约定源自面向对象编程传统,旨在ECMAScript中帮助开发者区分两者。

第二,内部this指向与返回值不同。构造函数内部使用this关键字指向即将生成的实例,且无需显式使用return语句,默认返回this。例如Person()函数虽未显式返回,但person1变量仍获得了包含name和age属性的Person实例。


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
      //1.定义构造函数
      function Person(name,age){
        this.age = age;
        this.name = name;
        this.sayName = function(){
            console.log(this.name);
        } 
      }
      // 2.生成实例对象
      var person1 = new Person('小苏','18');
      // 3.打印实例对象
      console.log(person1);
  </script>
</body>
</html>

运行结果如下所示。

1、构造函数的定义与调用

第三,调用方式不同。构造函数必须配合new操作符使用。任何函数只要通过new调用即视为构造函数,否则仅为普通函数。

1、构造函数的定义与调用

构造函数,亦称自定义构造函数,用于以函数形式为自定义对象类型定义属性和方法。

具体示例如下。


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
      //1.定义构造函数
      function Person(name,age){
        this.age = age;
        this.name = name;
        this.sayName = function(){
            console.log(this.name);
        } 
      }
      // 2.生成实例对象
      var person1 = new Person('小苏','18');
      // 3.调用方法
      person1.sayName();
  </script>
</body>
</html>

2、new关键字的用途

当使用new关键字执行构造函数时,会依次执行以下四个步骤:

  • 第一步,通过new操作符在内存中开辟新地址,创建实例对象。
  • 第二步,确定构造函数内部this的指向,使其指向新创建的实例本身。
  • 第三步,执行构造函数体内的代码,为新实例添加属性。
  • 第四步,隐式返回该新创建的对象。

以前文生成person1实例的代码为例进行解析。

第一步:在内存中为person1实例分配新地址。

第二步:将person1实例的this指向绑定至person对象本身。

第三步:为person1实例添加name、age属性,以及sayName方法(其值为一个函数)。

第四步:返回该person1实例。

具体实例演示。


var person1 = new Person("Bob", 18);

这行代码的逻辑等价于以下手动实现new功能的代码。


// 演示 new 的功能
function Person(name,age) {
  // 1.创建一个新对象
  var instance = new Object();
  // 2.将函数内部的 this 指向了这个新对象
  this = instance;
  // 3.执行构造函数内部的代码
  this.name = name;
  this.age = age;
  this.sayName = function () {
    console.log(this.name);
  };
  // 4.将新对象作为返回值
  return instance;
}

这意味着将instance赋值给person1,即执行person1 = instance。其实际执行过程如下。


// 演示 new 的功能
function Person(name,age) {
  // 1.创建一个新对象
  var person1 = new Object();
  // 2.将函数内部的 this 指向了这个新对象
  this = person1;
  // 3.执行构造函数内部的代码
  this.name = name;
  this.age = age;
  this.sayName = function () {
    console.log(this.name);
  };
  // 4.将新对象作为返回值
  return person1;
}

3、构造函数的问题:内存浪费

基本事实是:不同实例中的sayName属性并非同一个函数。例如。


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // 1.自定义构造函数
    function Person(name,age) {
      this.name = name;
      this.age = age;
      // this 内部的 type 属性值是不变的
      this.type = "human";
      // 每个对象的 sayName 方法也是一样的
      this.sayName = function () {
        console.log(this.name);
      };
    }
    
    var person1 = new Person('Bob',18);
    var person2 = new Person('Mike',20);
    // 2,判断各自的方法是否是同一个函数
    console.log(person1.sayName === person2.sayName); // false
  </script>
</body>
</html>

解释:通过console.log(person1.sayName === person2.sayName)判断可知,两个实例的方法并不相等。这意味着每个实例都拥有独立的函数副本,占用多个内存空间,造成资源浪费。

解决函数占用内存的方法是提取对象内部函数,将其作为公共函数处理。


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // 解决方法1: 将公共的函数提取到构造函数之外
    function sayName() {
      console.log(this.name);
    }
    function sayAge() {
      console.log(this.age);
    }
    //1.自定义对象
    function Person(name,age) {
      this.name = name;
      this.age = age;
      // this 内部的 type 属性值是不变的
      this.type = "human";
      // 每个对象的 sayName 方法也是一样的
      this.sayName = sayName;
      this.sayAge = sayAge;
    }
    //2.实例化对象
    var person1 = new Person('Bob',18);
    var person2 = new Person('Mike',20);
    console.log(person1.sayName === person2.sayName); // true
    </script>
</body>
</html>

然而,若存在多个公共函数,需在外部创建多个函数,这可能导致命名冲突。

解决方案是将多个公共函数封装到一个对象中。


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // 3.将多个公共的函数封装到一个对象
    var fns = {
      sayName : function () {
        console.log(this.name);
      },
      sayAge : function () {
        console.log(this.age);
      }      
    };    
    // 1.自定义对象
    function Person(name,age) {
      this.name = name;
      this.age = age;
      this.type = "human";
      this.sayName = fns.sayName;
      this.sayAge = fns.sayAge;
    }
    // 2.生成对象实例
    var person1 = new Person("Bob",18);
    var person2 = new Person("Mike",20);

    // person1.sayName();
    console.log(person1.sayName === person2.sayName);
    console.log(person1.sayAge === person2.sayAge);
  </script>
</body>
</html>

解释:将多个函数封装至单一对象,可有效解决函数名冲突问题。

通过设置全局可访问的函数,所有实例均可共享访问,无需重复创建。

但此方案存在缺陷:若将所有函数处理为全局函数,无法实现自定义类型对象的属性封装。因为属性需定义在函数外部作为全局变量才能调用,这反而污染了全局变量空间,故非理想方案。

为此,引入原型概念。利用原型机制可完美解决上述问题,实现良好的封装与共享。

总结

本文到此结束,希望以上内容对您有所帮助,欢迎持续关注本站获取更多技术干货。

作者最新文章