前端面向对象编程:ES5与ES6核心语法及原型链详解

作者:小括号

时间:2026-10-26

浏览:207

本文深入解析前端面向对象编程的核心机制,涵盖ES5的工厂模式与构造函数,以及ES6的class语法。重点讲解对象属性的封装、函数方法的内存优化,并详细剖析prototype与__proto__构成的原型链访问规则,阐明对象赋值与属性查找的实际行为。

文章目录

一、面向对象编程

1. 概念

  • 面向对象编程是对具有相同属性和函数方法的一类事物进行描述,从而实现特定的代码功能。
  • 它通过抽象具有相同属性和函数方法的类来实现代码功能。

2. 理解

首先需要定义一个对象。

利用对象的属性来存储所需的数据。

将程序执行所需的数据全部以属性的形式存储在对象中。

利用函数方法来存储程序的执行代码。

将执行代码所需的程序逻辑进行封装。

以函数方法的形式存储这些代码。

当调用并执行函数方法时。

所使用的数据来源于对象属性中预先存储的数据。

二、语法形式

工厂模式

function 函数(形参,形参...){
    const obj= {};
    //创建一个空对象
    obj.属性 = 属性;
    obj.属性 = 属性;
    ...
    设定对象的属性和属性值
    obj.函数方法 = function(){}
    obj.函数方法 = function(){}
    ...
        设定对象的函数方法
    return obj;
        返回值是这个创建的对象
}
/*
工厂模式 是 创建 属性和函数方法相同
属性值不同的 对象 
创建的对象 都 相同的属性 不同的属性值
还有 完全相同的函数方法
每一个对象都存储了完全相同的函数方法
 造成有 冗余的代码程序
*/

ES5语法

function 构造函数(形参,形参...){
//在构造函数中通过this指向设定操作对象的属性
   this.属性 = 形参;
   this.属性 = 形参;
}
//在构造函数外在构造函数的prototype中定义函数
构造函数.prototype.函数方法 = function(){}
构造函数.prototype.函数方法 = function(){}
//通过new关键词 调用构造函数创建实例化对象

ES6语法

使用class关键词定义构造函数
class 构造函数{
    //在构造函数中定义对象的属性属性值
    constructor(形参,形参...){
    this.属性 = 形参;
    this.属性 = 形参;
    }
    //在构造函数外定义函数方法 js程序自动定义在prototype 中
    函数方法(){}
    函数方法(){}
}
const 实例化对象 = new 构造函数(实参,实参...);
    通过new关键词调用构造函数创建实例化对象

三、批量生产对象

字面量方式

       const obj = {}
        obj.a = 1
        obj.b = 2
        console.log(obj)
        const obj1 = {}
        obj1.a = 1
        obj1.b = 2
        console.log(obj1)
        //不能批量生产

内置构造函数

        const obj = new Object()
        obj.a = 1
        obj.b = 2
        console.log(obj)
        const obj1 = new Object()
        obj1.a = 1
        obj1.b = 2
        console.log(obj1)
        //不能批量生产

工厂函数

        function createObj (name, age) {
            // 1. 手动创建一个对象
            const obj = {}
            // 2. 手动向对象内部添加属性
            obj.name = name
            obj.age = age
            // 3. 手动返回一个对象
            return obj
        }
//这个方法能够创建对象, 并且批量生产也可以

自定义构造函数

  function createObj () {
            // 1. 自动创建出来一个对象
            // 2. 手动向对象添加属性
            // 3. 自动返回一个对象
        }
        const o1 = new createObj()  // 这种调用方式, 才是 构造函数
        const o2 = createObj()     // 这就是一个普通函数的调用
        console.log(o1)
        console.log(o2)
/*
 自定义构造函数, 本质是就是一个函数
          当他和 new 关键字连用的时候, 就叫做构造函数
            自动创建一个对象
            手动向创建的对象添加属性
             自动返回一个对象
*/

四、自定义构造函数的书写

1. 构造函数必须与 new 关键字连用。

如果没有和 new 连用,那么它仅仅是一个普通函数。

2. 当一个函数和 new 关键字连用时,该函数被称为自定义构造函数。此时,函数内部的 this 指向返回出来的对象。

3. 构造函数不能使用箭头函数。

因为箭头函数内部没有 this 绑定。

4. 构造函数内部通常不需要 return。

  • 如果 return 基本数据类型,写了和没写一样。
  • 如果 return 引用数据类型,则构造函数将失效。

5. 书写构造函数时,首字母建议大写。

目的仅仅是为了和普通函数进行区分。

6. 我们构造函数通过 new 关键字创建出来的对象,叫做实例化对象。本质上它还是一个对象,只不过在命名上称为实例化对象(或实例对象)。

我们把构造函数通过 new 关键字创建对象的过程叫实例化。

和普通函数一样,只不过调用的时候要和 new 连用,否则就是一个普通函数调用。

1. 不写 new 的时候就是普通函数调用,没有创造对象的能力。

function Person () {}
var o1 = new Person()   // 一个对象
var o2 = Person()   // 什么也得不到, 就是一个普通函数的调用

2. 首字母不大写,只要和 new 连用,就有创造对象的能力。

function person () {}
var o1 = new person()   // 一个对象

3. 当调用的时候,如果不需要传递参数可以不写 (),但是建议写上。

function person () {}
var o1 = new person   // 一个对象

5. 构造函数内部的 this,由于和 new 连用的关系,是指向当前实例对象的。

function Person () {
    console.log(this)
}
var o1 = new Person()   // this ---> o1
var o2 = new Person()   // this ---> o2

五、使用构造函数创建一个对象

function Person() {
    this.name = 'Jack'
    this.age = 18
}
var o1 = new Person()
var o2 = new Person()
function Person() {
    this.name = 'Jack'
    this.age = 18
    this.sayHi = function () {
        console.log('hello word')
    }
}
var o1 = new Person()
var o2 = new Person()
// 有缺点
function Person() {
    this.name = 'Jack'
    this.age = 18
    this.sayHi = function () {
        console.log('hello word')
    }
}
/**
 *  第一次 new 的时候, Person 这个函数要执行一遍
 *  执行一边就会创造一个新的函数, 并且把函数地址赋值给 this.sayHi
*/
var o1 = new Person()
/**
 *  第二次 new 的时候, Person 这个函数要执行一遍
 *  执行一遍就会创造一个新的函数, 并且把函数地址赋值给 this.sayHi
*/
var o2 = new Person()
  • 我们在使用构造函数的时候,可以通过一些参数和内容来向当前的对象中添加一些内容。
  • 我们得到的两个对象里面都有自己的成员 name 和 age。
  • 我们在写构造函数的时候其实也可以添加一些方法进去。

这样也是可以的,但是有一点小缺点。

  • 第一次 new 的时候,Person 这个函数要执行一遍,执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi。
  • 第二次 new 的时候,Person 这个函数要执行一遍,执行一遍就会创造一个新的函数,并且把函数地址赋值给 this.sayHi。
  • 这样写的话,我们两个对象内的 sayHi 函数就是一个代码一模一样、功能一模一样。
  • 但却占用了两个内存空间。
  • 也就是说 o1.sayHi 是一个地址,o2.sayHi 是一个地址。
  • 所以我们执行 console.log(o1.sayHi === o2.sayHi) 得到的结果是 false。
  • 缺点:一模一样的函数出现了两次,占用了两个空间地址。
  • 要想解决的话,就需要一个东西,叫做原型。

原型

  • 原型的出现,就是为了解决构造函数的缺点。
  • 也就是给我们提供了一个给对象添加函数的方法。
  • 不然构造函数只能给对象添加属性,不能合理地添加函数就太 low 了。

prototype

  • 每一个函数天生自带一个成员,叫做 prototype,是一个对象空间。
  • 既然每一个函数都有,构造函数也是函数,构造函数也有这个对象空间。
  • 这个 prototype 对象空间可以由函数名来访问。
function Person () {}
console.log(Person.prototype)   // 是一个对象
  • 既然是一个对象,那么我们就可以向里面放入一些东西。
function Person() {}
Person.prototype.name = 'prototype'
Person.prototype.sayHi = function () {}
  • 重点:在函数的 prototype 里存储的内容,不是给函数使用的,是给函数的每一个实例化对象使用的。

__proto__

每一个对象都天生自带一个成员,叫做 __proto__,是一个对象空间。

既然每一个对象都有,实例化对象也是对象,那么每一个实例化对象也有这个成员。

这个 __proto__ 对象空间是给每一个对象使用的。

当你访问一个对象中的成员的时候。

  • 如果这个对象自己本身有这个成员,那么就会直接给你结果。
  • 如果没有,就回去 __proto__ 这个对象空间里面找,里面有的话就会有结果。
  • 未完待续...

那么这个 __proto__ 又指向哪里呢?

  • 这个对象是由那个构造函数 new 出来的。
  • 那么这个对象的 __proto__ 就指向这个构造函数的 prototype。
function Person() {}
var p1 = new Person()
console.log(p1.__proto__ === Person.prototype)  // true

实例化对象的 __proto__ 和所属构造函数的 prototype 是一个对象空间。

我们可以通过构造函数名称来向 prototype 中添加成员。

对象在访问的时候自己没有,可以自动去自己的 __proto__ 中查找。

那么,我们之前构造函数的缺点就可以解决了。

  • 我们可以把函数放在构造函数的 prototype 中。
  • 实例化对象访问的时候,自己没有,就会自动去 __proto__ 中找。
  • 那么也可以使用了。
function Person() {}
Person.prototype.sayHi = function () {
    console.log('hello Person')
}
var p1 = new Person()
p1.sayHi()
  • p1 自己没有 sayHi 方法,就会去自己的 __proto__ 中查找。
  • p1.__proto__ 就是 Person.prototype。
  • 我们又向 Person.prototype 中添加了 sayHi 方法。
  • 所以 p1.sayHi 就可以执行了。
  • 到这里,当我们实例化多个对象的时候,每个对象里面都没有方法。
  • 都是去所属的构造函数的 prototype 中查找。
  • 那么每一个对象使用的函数,其实都是同一个函数。
  • 那么就解决了我们构造函数的缺点。
function Person() {}
Person.prototype.sayHi = function () {
    console.log('hello')
}
var p1 = new Person()
var p2 = new Person()
console.log(p1.sayHi === p2.sayHi)
  • p1 是 Person 的一个实例。
  • p2 是 Person 的一个实例。
  • 也就是说 p1.__proto__ 和 p2.__proto__ 指向的都是 Person.prototype。
  • 当 p1 去调用 sayHi 方法的时候是去 Person.prototype 中找。
  • 当 p2 去调用 sayHi 方法的时候是去 Person.prototype 中找。
  • 那么两个实例化对象就是找到的一个方法,也是执行的一个方法。

结论

  • 当我们写构造函数的时候。
  • 属性我们直接写在构造函数体内。
  • 方法我们写在原型上。

原型链

  • 构造函数的 prototype 是一个对象,每一个对象又自带 __proto__ 属性。
  • 构造函数的 prototype 里面的 proto 属性又指向哪里?

一个对象所属的构造函数。

每一个对象都有一个自己所属的构造函数。

比如:数组。

// 数组本身也是一个对象
var arr = []
var arr1 = new Array()
  • 以上两种方式都是创造一个数组。
  • 我们就说数组所属的构造函数就是 Array。

比如:函数。

// 函数本身也是一个对象
var fn = function () {}
var fun = new Function()
  • 以上两种方式都是创造一个函数。
  • 我们就说函数所属的构造函数就是 Function。

constructor

  • 对象的 __proto__ 里面也有一个成员叫做 constructor。
  • 这个属性就是指向当前这个对象所属的构造函数。

链状结构

  • 当一个对象我们不知道准确的是谁构造的时候,我们就把他看成 Object 的实例化对象。
  • 也就是说,我们的 构造函数 的 prototype 的 proto 指向的是 Object.prototype。
  • Object.prototype 也是个对象,那么它的 __proto__ 又指向谁呢?
  • 因为 Object 的 js 中的顶级构造函数,我们有一句话叫 万物皆对象。
  • 所以 Object.prototype 就到顶了,Object.prototype 的 __proto__ 就是 null。

原型链的访问原则

  • 访问一个对象的成员时,自己没有就会去 __proto__ 中找。
  • 接下来就是,如果 __proto__ 里没有就再去 __proto__ 里面找。
  • 一直找到 Object.prototype 里面都没有,那么就会返回 undefined。

对象的赋值

到这里,我们就会觉得,如果是赋值的话,那么也会按照原型链的规则来。

但是:并不是!并不是!并不是!重要的事情说三遍。

赋值的时候就是直接给对象本身赋值。

  • 如果原先有就是修改。
  • 如果原先没有就是添加。
  • 不会和 __proto__ 有关系。

总结

到了这里,我们就发现了面向对象的思想模式。

  • 当我想完成一个功能的时候。
  • 先看内置构造函数有没有能给我提供一个完整功能对象的能力。
  • 如果没有,我们就自己写一个构造函数,能创造出一个完成功能的对象。
  • 然后再用我们写的构造函数 new 一个对象出来,帮助我们完成功能就行了。

以上就是前端面向对象编程之ES5语法ES6语法详解的详细内容,更多关于前端面向对象编程ES5ES6语法的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章