构造函数的“前世今生”——拒绝“八股文”

784 阅读4分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

为什么会出现构造函数

假设我们要统计学校老师的姓名、性别、年龄、科目,可以用对象来表示

const teacher1 = { name: '李雷', gender: 'man', age: 30, subject: 'maths' };
const teacher2 = { name: '韩梅梅', gender: 'woman', age: 20, subject: 'Chinese' };
const teacher3 = { name: 'Peter', gender: 'man', age: 40, subject: 'English' };

虽然可以清楚地记录老师的信息,但是写了很多的重复代码(name、gender、age、subject),构造函数的出现就是为了帮助我们解决此类问题
我们先来了解一下构造函数的定义,再来创建一个构造函数去解决这类问题

什么是构造函数

讨论构造函数与普通函数的区别,其实就是讨论构造函数的定义

  • 构造函数是使用new关键字调用的函数
  • 声明构造函数时,一般首字母大写
function Teacher() {}  // 空的构造函数Teacher

首字母大写并不强制,是程序猿间约定俗成的“浪漫”,非要做一个“直男/女”也是可以的

创建一个构造函数

我们来创建一个构造函数,用来统计上面的老师信息

// 声明构造函数Teacher
function Teacher(name, gender, age, subject) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.subject = subject;
}

// 创建3个teacher对象
const teacher1 = new Teacher('李雷', 'man', 30, 'maths');
const teacher2 = new Teacher('韩梅梅', 'woman', 20, 'Chinese');
const teacher3 = new Teacher('Peter', 'man', 40, 'English');

我们成功省略了重复代码,并清晰地表达出3个老师的信息

new操作符的作用

上面说到除了首字母大写,构造函数和普通函数的区别就是使用new进行调用,那么new关键字帮助我们做了什么?

const teacher = new Teacher();

当我们创建teacher对象时,new帮我们做了下面4件事

// 步骤1:创建空对象
const obj = new Object();
// 步骤2:使空对象的隐式原型指向构造函数的显示原型
obj.__photo__ = Teacher.prototype;
// 步骤3:使指针this指向空对象
Teacher.call(obj);

步骤4有些特殊,有2种情况

// 步骤4:返回值给teacher对象
// 情况1:当构造函数有返回值,且返回值为对象类型,直接返回该对象
function Teacher(name, age) {
    this.name = name;
    this.age = age;
    return {
        hobby: '打篮球'
    }
}
const teacher1 = new Teacher('张三', 19);
console.log(teacher1); // { hobby: '打篮球' }

// 情况2:除情况1外的其他情况,返回this对象
function Student(name, age) {
    this.name = name;
    this.age = age;
}
const student1 = new Student('李四', 36);
console.log(student1); // { name: '李四', age: 36 }

实例对象的属性和方法

实例对象的属性和方法,可以通过2种方式获得:
(1)构造函数内定义的属性和方法,每个实例对象相互独立,不能共享
(2)实例对象继承原型对象中的属性和方法,可以共享

关于原型对象和原型链,不太理解的小伙伴可以看看这篇文章
# 图解原型链的“前世今生”——拒绝“八股文”

构造函数定义

构造函数内定义的属性和方法,每个实例对象相互独立,不能共享

function Teacher(name, age) {
    this.name = name;
    this.age = age;
    this.say = function() {
        console.log(`I am ${this.name}`);
    }
}

const teacher1 = new Teacher('李雷', 35);
const teacher2 = new Teacher('韩梅梅', 40);

console.log(teacher1.name); // 李雷
console.log(teacher2.name); // 韩梅梅
console.log(teacher1.age); // 35
console.log(teacher2.age); // 40
teacher1.say(); // I am 李雷
teacher2.say(); // I am 韩梅梅

继承

实例对象继承原型对象中的属性和方法,可以共享

function Teacher(name, age) {
    this.name = name;
    this.age = age;
}
Teacher.prototype.profession = '老师';
Teacher.prototype.say = function () {
    console.log(`I am ${this.name}`);
}

const teacher1 = new Teacher('李雷', 35);
const teacher2 = new Teacher('韩梅梅', 40);

console.log(teacher1.name); // 李雷
console.log(teacher2.name); // 韩梅梅
console.log(teacher1.age); // 35
console.log(teacher2.age); // 40
console.log(teacher1.profession); // 老师
console.log(teacher2.profession); // 老师
teacher1.say(); // I am 李雷
teacher2.say(); // I am 韩梅梅

我们将say方法从构造函数放到了原型对象中,并新增了一个属性profession,他们都可以被实例对象继承并使用

为什么要将部分属性和方法声明在原型对象上
(1)节省内存空间(每个对象都需要浏览器引擎单独分配内存,我们将对象中的公用属性和方法放到原型中,只需分配一次内存)
(2)符合面向对象的思想(我们将“老师”抽象为teacher对象,name、age是每个老师独有的特征,gender、say是所有老师公有的特征)

后语

为了方便各位小伙伴调试,上面的代码都可以CV大法直接放在浏览器控制台运行,多多尝试才能理解的更透彻

我是前端霸哥,愿你的代码中没有bug
写的不好的地方,欢迎各位小伙伴批评指正