JS 中的构造函数和类

59 阅读3分钟

建议:忘掉关于所有构造函数的知识,看构造函数的代码逻辑是如何一步一步构建的

一、自制一个构造函数

要求:它可以创建一个含有 name 和 age 的对象

    function Person(name, age) {
        name = name || '匿名'
        age = age || 0
        const obj = {
            name: name,
            age: age
        }
        return obj
    }
  • 创建了一个构造函数,传入name和age两个参数,返回了一个带有name和age属性的对象
  • 简易写法, 如下:
    function Person(name = '匿名', age = 0) {
        return {name, age}
    }
const f = Person('fang', 18)
console.log(f)
// f就是由Person函数构造出来的对象

二、第二步

新要求: 所有 Person 构造出来的对象都能 有 sayHi

 function Person(name = '匿名', age = 0) {
    return {
        name, 
        age,
        syaHi() {
            console.log(`你好,我是${name}`)
        }
   }
}

执行:

const f1 = Person('fang', 18)
f1.sayHi()
const f2 = Person('xin', 19)
f1.sayHi()

画个内存图吧!!

构造函数内存图.jpg

  • 从上面的内存图可以发现,每执行一次Person就会创建一个SayHi函数,这显然是不合理的

三、解决sayHi的问题

为什么要把sayHi写在Person里呢,我们把它拆出来单独写在一个变量里

    const sayHi = function() {
        console.log(`你好,我是${this.name}`)
    }
    function Person(name = '匿名', age = 0) {
        return { name, age, syaHi }
    }

四、现在要求所有的Person构造出来的对象都能有 run函数

    const sayHi = function() {
        console.log(`你好,我是${this.name}`)
    }
    const run = function() {
        console.log(`${this.name}在跑步`)
    }
    function Person(name = '匿名', age = 0) {
        return { name, age, syaHi, run }
    }

问题:那么是不是Person返回的对象每多返回一个函数,就要多增加一个全局变量。那么是不是占用了太多全局变量了呢

五、解决一个Person占用太多全局变量的问题

那就创建一个对象吧,这个对象里面放着Person所需要的所有共有属性。 注: Person共有属性 只是为了方便起的存放共有属性的变量名

const Person共有属性 = {
    sayHi: function() {
        console.log(`你好,我是${this.name}`)
    },
    run: function() {
        console.log(`${this,name}正在跑步`)
    }
}
function Person(name='匿名', age=0) {
    return {
        name,
        age,
        sayHi: Person共有属性.sayHi,
        run: Person共有属性.run
    }
}

六、那为什么要创建两个全局变量呢,只用一个不行么

提示: 构造函数既是一个函数,同时也是一个对象。那我们为什么不把共有属性写出Person的属性

function Person(name='匿名', age=0) {
    return {
        name,
        age,
        sayHi: Person.共有属性.sayHi,
        run: Person.共有属性.run
    }
}
Person.共有属性 = {
    sayHi() {
        console.log(`你好,我是${this.name}`)
    },
    run() {
        console.log(`${this,name}正在跑步`)
    }
}

问题:既然共有属性对象写在Person的属性上,那为什么不把它写在隐藏属性上呢。 注::::此处写的隐藏属性指的是原型 -> prototype,我们统一用隐藏属性表示

七、把共有属性写在隐藏属性上

function Person(name='匿名', age=0) {
    const obj = Object.create(Person.共有属性)
    // 相当于 obj = { 隐藏属性: Person.共有属性 }
    obj.name = name
    obj.age = age
    return obj
}
Person.共有属性 = {
    sayHi() {
        console.log(`你好,我是${this.name}`)
    },
    run() {
        console.log(`${this,name}正在跑步`)
    }
}

屏幕截图 2023-05-05 231215.png

  • 创建f1,可以看到有name,age属性,隐藏属性里面有run,sayHi函数

八、新的问题:f1是谁构造出来的

console.log(f1.constructor)
// 得到 Object

因为Person函数返回的是一个对象赋值给f1,对象是Objec类创造的实例,对象的__proto__指向的所属类的prototype,所以f1的__proto__指向了Object类的prototype,而Object的prototype的constructor指向了prototype所在的类

问题:别人怎么不看源码知道f1是谁构造的呢,所以在共有属性上加个constructor

Person.共有属性 = {
    constructor: Person,
    sayHi() {
        console.log(`你好,我是${this.name}`)
    },
    run() {
        console.log(`${this,name}正在跑步`)
    }
}

九、构造函数的写法

function Person(name='匿名', age=0) {
    this.name = name
    this.age = age
}
Person.prototype = {
    constructor: Person,
    sayHi() {
        console.log(`你好,我是${this.name}`)
    },
    run() {
        console.log(`${this,name}正在跑步`)
    }
}
const f1 = new Person('xin', 19)
f1.sayHi()
f1.run()
  • 用 new 执行的好处
  1. 创建出一个类的实例(对象)
  2. 把函数体中的this指向指这个实例
  3. 如果没有返回值,返回当前的this(this指向的对象)

class的写法

class Person {
    constructor(name='匿名', age=0) {
        this.name = name
        this.age = age
    }
    sayHi() {
        console.log(`你好,我是${this.name}`)
    }
    run() {
        console.log(`${this,name}正在跑步`)
    }
}

const f1 = new Person('xin', 19)
f1.sayHi()
f1.run()