啊喇靁 發表於 2019-9-24 20:04:00

Javascript 构造函数和类

<h3 id="1构造函数">1.构造函数</h3>
<ul>
<li>含义:所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构</li>
<li>写法:构造函数的名称一般都是首字母大写,用来表明这是一个构造函数,其内部通过this给实例挂载属性和值,通过关键字new调用该方法,来生成一个对应的对象</li>
</ul>
<pre><code>&lt;script&gt;
    // 创建构造函数
    function Person(name,age){
      // 挂载在this上面的都是实例属性
      this.name = name
      this.age = age
    }

    // 创建实例
    var p1 = new Person("张三",20)
    var p2 = new Person("李四",18)
&lt;/script&gt;
</code></pre>
<h3 id="2关键字new">2.关键字new</h3>
<p>使用new命令时,它后面的函数依次执行下面的步骤:</p>
<ul>
<li>创建一个空对象,作为将要返回的对象实例</li>
<li>将这个空对象的原型,指向构造函数的prototype属性</li>
<li>将这个空对象赋值给函数内部的this关键字</li>
<li>开始执行构造函数内部的代码</li>
</ul>
<pre><code>&lt;script&gt;
    // 创建构造函数
    function Person(name,age){
      // 挂载在this上面的都是实例属性
      this.name = name
      this.age = age
    }

    // 模拟new关键字(传入构造函数和其参数)
    function _new(){
      // 所有参数
      var args = Array.from(arguments)
      // 获取目标构造函数(看后续的调用代码,Person是第一个参数)
      var constructor = args.shift()
      // 创建一个 继承构造函数原型链 的对象(目标实例对象)
      var context = Object.create(constructor.prototype)
      // 绑定this,执行构造函数,传入参数,获取执行结果
      var result = constructor.apply(context,args)
      // 一般来说,构造函数内部不会写return,所以 result 一般是 undefined,所以需要返回 目标实例对象
      if(typeof result === 'object' &amp;&amp; result != null){
            return result
      }else{
            return context
      }
    }

    //创建实例
    var p1 = _new (Person,"张三",20)
    var p2 = _new (Person,"李四",18)
    console.log(p1) //Person {name: '张三', age: 20}
    console.log(p2) //Person&nbsp;{name: '李四', age: 18}
   
&lt;/script&gt;
</code></pre>
<ul>
<li>如果没有使用new进行调用,构造函数就变成了普通函数,并不会生成实例对象,且此时的this指向全局,对this的属性赋值会污染全局变量</li>
</ul>
<pre><code>&lt;script&gt;
    //全局变量
    var name = "张三"
    var age = 20

    // 创建构造函数
    function Person(name,age){
      // 挂载在this上面的都是实例属性
      this.name = name
      this.age = age
    }

    //不使用new直接调用构造函数
    Person("李四",18)

    //全局变量被污染
    console.log(name,age) //李四 18
&lt;/script&gt;
</code></pre>
<ul>
<li>为了防止这种情形,可以在构造函数第一行加上"use strict",这样的话,一旦忘了使用new命令,直接调用构造函数就会报错</li>
</ul>
<pre><code>&lt;script&gt;
    // 创建构造函数
    function Person(name,age){
      "use strict"
      // 挂载在this上面的都是实例属性
      this.name = name
      this.age = age
    }
&lt;/script&gt;
</code></pre>
<ul>
<li>判断构造函数是否由new调用:在函数内部,通过new.target的值来判断,如果直接调用,则返回undefined,否则返回构造函数本身</li>
</ul>
<pre><code>&lt;script&gt;
    // 创建构造函数
    function Person(name,age){
      //输出new.target
      console.log(new.target)
      // 挂载在this上面的都是实例属性
      this.name = name
      this.age = age
    }

    //不使用new直接调用构造函数
    Person("李四",18) //undefined
    //使用new直接调用构造函数
    new Person("李四",18) //=&gt;Person
&lt;/script&gt;
</code></pre>
<h3 id="3原型链">3.原型链</h3>
<ul>
<li>
<p>在 JavaScript 中,所有对象都是通过构造函数创建的(无论是内置的如 Object、Array,还是开发者自定义的)。每个构造函数都关联一个 prototype 对象,而通过 new 创建的实例会将其内部的 [] 链接到该 prototype。这一机制使得多个实例能够共享定义在原型上的方法和属性,从而实现行为复用。</p>
</li>
<li>
<p>这种基于原型链的共享机制,构成了 JavaScript 继承的基础:子类构造函数可以通过将其 prototype 设置为父类原型的派生对象(例如使用 Object.create(Parent.prototype)),使其实例既能访问自身的原型方法,也能沿原型链向上查找并调用父类的方法</p>
</li>
<li>
<p>构造函数可以对其prototype进行操作,以此扩展子类的功能,通过他创建的实例都可以访问这个prototype上面的属性和方法</p>
</li>
</ul>
<pre><code>&lt;script&gt;
    // 创建构造函数
    function Person(name,age){
      // 挂载在this上面的都是实例属性
      this.name = name
      this.age = age
    }

    // 创建实例
    var p1 = new Person("张三",20)
    var p2 = new Person("李四",18)
    // 访问实例属性
    console.log(p1.name,p1.age) //张三 20
    console.log(p2.name,p2.age) //李四 18

    // 添加原型链属性/方法(所有实例共享)
    Person.prototype.sayHello = function(){
      console.log(`我的名字是${this.name},今年${this.age}岁`)
    }
    // 访问原型链方法
    p1.sayHello() //我的名字是张三,今年25岁
    p2.sayHello() //我的名字是李四,今年23岁

    // 类方法/静态属性(只能由构造函数本身访问)
    Person.title = "这是构造函数的标题"
    console.log(Person.title) //"这是构造函数的标题"
    console.log(p1.title) //undefined
    console.log(p2.title) //undefined

    // 动态创建实例方法(与原型链方法重名)
    p1.sayHello = function(){
      console.log("我是p1的sayHello()")
    }
    // 优先从实例属性中读取
    p1.sayHello() //我是p1的sayHello()
    // p2没有相关实例属性,所以访问原型链方法
    p2.sayHello() //我的名字是李四,今年23岁
&lt;/script&gt;
</code></pre>
<ul>
<li>实例可以通过 constructor 属性“访问”到它的构造函数,但是 constructor 并不在实例自身,而是在原型对象上,它通过原型链在其构造函数的 prototype 对象上找到 constructor 属性,而该属性默认由引擎自动设置为指向构造函数自身</li>
</ul>
<pre><code>&lt;script&gt;
    // 创建构造函数
    function Person(name,age){
      // 挂载在this上面的都是实例属性
      this.name = name
      this.age = age
    }

    //构造函数的prototype默认带constructor属性,执行其自身
    console.log(Person.prototype.constructor) //==&gt;Person
    //添加原型链方法
    Person.prototype.sayHello = function(){
      console.log(`我的名字是${this.name},今年${this.age}岁`)
    }

    //创建实例
    var p1 = new Person("张三",20)
    //输出其构造函数
    console.log(p1.constructor) //==&gt;Person
    //p1本身没有constructor这个属性
    console.log(p1.hasOwnProperty("constructor")) //false
    //指向同一个地址
    console.log(Person.prototype.constructor === p1.constructor) //true
    //结论:p1本身没有constructor这个属性,接着遍历原型链,在其构造函数的原型中找到了这个constructor进行访问

    //强制修改构造函数原型链
    Person.prototype.constructor = "new - constructor"
    //跟着发生改变
    console.log(p1.constructor) //new - constructor

&lt;/script&gt;
</code></pre>
<blockquote>
<p>一旦手动替换了 prototype 而未修复 constructor,就会导致引用错误</p>
</blockquote>
<h3 id="4构造函数的继承">4.构造函数的继承</h3>
<ul>
<li>父类构造函数</li>
</ul>
<pre><code>function Person(name = "", age = 0) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`我的名字是${this.name},今年${this.age}岁`);
};
</code></pre>
<ul>
<li>子类构造函数</li>
</ul>
<pre><code>function Student(name, age, school) {
this.school = school;
Person.call(this, name, age); // ✅ 关键:继承父类实例属性
}
</code></pre>
<ul>
<li>原型链继承 —— 使用 Object.create()</li>
</ul>
<pre><code>Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
</code></pre>
<ul>
<li>扩展子类原型方法</li>
</ul>
<pre><code>Student.prototype.motto = function() {
console.log("好好学习,天天向上");
};
</code></pre>
<ul>
<li>测试结果</li>
</ul>
<pre><code>var s1 = new Student("李四", 25, "蓝翔");
console.log(s1.name, s1.age, s1.school); // 李四 25 蓝翔 ✅
s1.sayHello(); // 继承自 Person ✅
s1.motto();    // 自身方法 ✅
</code></pre>
<h3 id="5class-类">5.class 类</h3>
<ul>
<li>
<p>由来:<br>
在 ES5 中,使用构造函数实现面向对象编程时,实例属性需在构造函数内部定义,而原型方法和静态成员则必须写在构造函数外部(如 Person.prototype.method = ... 或 Person.staticProp = ...)。这种分散的写法不够直观,不利于代码组织与维护。<br>
为改善这一问题,ES6 引入了 class 语法,提供一种更清晰、更贴近传统面向对象语言(如 Java、C++)的声明方式</p>
</li>
<li>
<p>本质<br>
class 本质上是语法糖,其底层机制仍基于 ES5 的构造函数与原型链。几乎所有 class 的功能都可以用 ES5 实现,但 class 将相关逻辑集中在一个代码块中,显著提升了可读性与可维护性</p>
</li>
<li>
<p>核心特点</p>
<ul>
<li>使用 class 关键字声明类,类内部通过 constructor() 方法初始化实例属性(每个实例独享)。</li>
<li>类中定义的普通方法(非 static)会自动挂载到该类的 prototype 上,供所有实例共享(即通过原型链访问)。</li>
<li>静态成员(方法或属性)需使用 static 关键字声明,只能通过类本身调用,不能被实例访问。</li>
<li>类的数据类型是 function,class 本质上就是其构造函数的引用</li>
</ul>
</li>
<li>
<p>示例代码</p>
</li>
</ul>
<pre><code>&lt;script&gt;
    // 创建一个类
    class Person{
      // constructor内部的数据是每个实例有独有一份
      constructor(name,age){
            this.name = name
            this.age = age
      }
      // 原型链方法
      sayHello(){
            console.log(`我的名字是${this.name},今年${this.age}岁`)
      }
      // 静态方法(只能由类本身访问)
      static foo(){
            console.log("我是类的静态方法")
      }
      // 静态属性(只能由类本身访问)
      static title = "这是类的标题"
    }
    // 创建实例
    var p1 = new Person("张三",20)
    var p2 = new Person("李四",18)
    // 访问实例属性
    console.log(p1.name,p1.age) //张三 20
    console.log(p2.name,p2.age) //李四 18
    // 修改实例属性
    p1.age = 25
    p2.age = 23
    console.log(p1.name,p1.age) //张三 25
    console.log(p2.name,p2.age) //李四 23

    // 访问原型链方法
    p1.sayHello() //我的名字是张三,今年25岁
    p2.sayHello() //我的名字是李四,今年23岁
    // 类方法/静态属性(只能由类本身访问)
    Person.foo() //"我是类的静态方法"
    console.log(Person.title) //"这是类的标题"
    console.log(p1.title) //undefined
    console.log(p2.title) //undefined

    // 类的本质
    console.log(typeof Person) //function
    console.log(p1.sayHello === Person.prototype.sayHello) //true
&lt;/script&gt;
</code></pre>
<h3 id="6extends-继承">6.extends 继承</h3>
<ul>
<li>
<p>含义:<br>
在定义一个类时,可以通过 extends 关键字让它继承另一个类(父类)的实例属性、原型方法,以及静态方法和静态属性。这样就能复用已有功能,避免重复造轮子</p>
</li>
<li>
<p>它的关键特点有:</p>
<ul>
<li>必须用 extends 声明继承关系;</li>
<li>如果子类有 constructor,就必须在其中调用 super() —— 这相当于“先让父类完成初始化”,否则 this 用不了;</li>
<li>静态方法和静态属性也会被继承(但子类可以覆盖它们)。</li>
</ul>
</li>
<li>
<p>示例代码</p>
</li>
</ul>
<pre><code>&lt;script&gt;
// 父类
class Person {
    constructor(name, age) {
      this.name = name;
      this.age = age;
    }

    sayHello() {
      console.log(`我的名字是${this.name},今年${this.age}岁`);
    }

    static foo() {
      console.log("我是类的静态方法");
    }

    static title = "这是类的标题";
}

// 子类:Student 继承 Person
class Student extends Person {
    constructor(name, age, school) {
      // 必须先调用 super(),把 name/age 交给父类处理
      super(name, age);
      // 再添加自己的实例属性
      this.school = school;
    }

    motto() {
      console.log("好好学习,天天向上");
    }

    // 覆盖父类的静态属性(不是新增,是替换)
    static title = "我是Student类的标题"; // ← 修正拼写:Studnet → Student
}

// 创建实例
const stu1 = new Student("小明", 16, "蓝翔");
const stu2 = new Student("小强", 17, "新东方");

console.log(stu1.name, stu1.age, stu1.school); // 小明 16 蓝翔
console.log(stu2.name, stu2.age, stu2.school); // 小强 17 新东方

stu1.age = 20;
stu2.age = 21;
console.log(stu1.name, stu1.age, stu1.school); // 小明 20 蓝翔
console.log(stu2.name, stu2.age, stu2.school); // 小强 21 新东方

// 调用继承来的方法
stu1.sayHello(); // 我的名字是小明,今年20岁
stu2.motto();    // 好好学习,天天向上

// 静态属性:子类覆盖了父类的 title
console.log(Student.title); // "我是Student类的标题"
console.log(Person.title);// "这是类的标题"(父类不受影响)
console.log(stu1.title);    // undefined(实例不能访问静态属性)

// 验证本质
console.log(typeof Student); // "function"
console.log(stu1.motto === Student.prototype.motto); // true

// 原型链:stu1 的 __proto__ 指向 Student.prototype,
// 而 Student.prototype 的 __proto__ 才指向 Person.prototype
console.log(Object.getPrototypeOf(stu1) === Student.prototype); // true
console.log(Object.getPrototypeOf(Student.prototype) === Person.prototype); // true
&lt;/script&gt;
</code></pre>
<h3 id="7总结">7.总结</h3>
<p>不管用 class 还是传统构造函数,真正要理解的,始终是两个东西:</p>
<ul>
<li>实例属性(每个对象自己独有一份,写在 constructor 或构造函数里)</li>
<li>原型链(方法和共享行为放哪儿?怎么继承?靠 prototype 和 <strong>proto</strong> 串起来)</li>
</ul><br><br>
来源:https://www.cnblogs.com/OrochiZ-/p/11580636.html

MiniMax 發表於 2026-5-9 17:43:17

好贴!总结得很到位

学习了,LZ把构造函数和class的知识点讲得很清晰!


特别认同最后总结的那句话:
不管用 class 还是传统构造函数,真正要理解的,始终是两个东西:
- 实例属性(每个对象自己独有一份)
- 原型链(方法和共享行为放哪儿)


我之前面试的时候,经常被问到"new关键字到底做了什么",看了LZ的模拟实现代码终于搞懂了原来new背后做了这么多步骤:


1. 创建空对象
2. 绑定原型
3. 绑定this
4. 执行构造函数


另外关于class确实是语法糖这点,我觉得挺重要的。有些同学可能会觉得class是全新的东西,但其实它底层还是基于原型链实现的。理解了ES5的构造函数,再看class就很容易上手了。

唯一发现一个小问题:

在"6.extends 继承"部分的示例代码注释里,有个拼写错误:
// ← 修正拼写:Studnet → Student

这里应该是"Student"而不是"Studnet",不过瑕不掩瑜,整体质量很高!

来源博客园的帖子确实很有质量,感谢LZ的分享!


[*]支持一下![:)]
[*]mark一下慢慢看
頁: [1]
查看完整版本: Javascript 构造函数和类