构造函数模式
-
没有显式地创建对象
-
属性和方法直接赋值给了this
-
没有return
functionPerson(name, age, info) {
this.name = name;
this.age = age;
this.running = function () {
console.log(info);
};
}
const person = newPerson("moment", 18, "我会跑步");
const student = newPerson("supper", 16, "我会跑步,我比你还年轻呢");
person.running(); // 我会跑步
student.running(); // 我会跑步,我比你还年轻呢复制代码
- 以上代码实际上执行的是这样的操作
person.name = "moment";
person.age = 18;
person.running = function () {
console.log("我会跑步");
};
student.name = "moment";
student.age = 16;
student.running = function () {
console.log("我会跑步,我比你还年轻呢");
};
复制代码
- 要创建Person实例,应使用new操作符。
-
在内存创建一个新对象。
-
这个新对象内部的[[Prototype]] (proto)特性被赋值为构造函数的prototype属性, 即 person.proto=Person.prototype。
-
构造函数内部的this被赋值为这个新对象(即this指向新对象)。
-
执行构造函数内部的代码. 如 person.proto.name=‘moment’。
-
如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
- new的过程可以参考以下代码
functionmyNew(func, ...args) {
// 判断方法体if (typeof func !== "function") {
throw"第一个参数必须是方法体";
}
// 创建新对象// 这个对象的[[prototype]](隐式原型 __proto__)指向 func 这个类的原型对象 prototype// 即实例可以访问构造函数原型 obj.constructor === Personconst object = Object.create(func.prototype);
// 构造函数内部的this被赋值为这个新对象const result = func.apply(object, args);
// 如果构造函数返回的结果是引用数据类型,则返回运行后的结果// 否则返回新创建的 objconst isObject = typeof result === "object" && result !== null;
const isFunction = typeof result === "function";
return isObject || isFunction ? result : object;
}
复制代码
- 通过控制台打印不难发现,前面两个对象都是Object的实例,同时也是Person的实例
// instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上// person和student的隐式原型都指向Person的显式原型;console.log(person.__proto__ === student.__proto__); //trueconsole.log(person instanceofObject); //trueconsole.log(person instanceofPerson); //trueconsole.log(student instanceofObject); //trueconsole.log(student instanceofPerson); //true// 以上代码的代码实际上执行的该例子console.log(student.__proto__.__proto__.constructor.prototype === Object.prototype);
console.log(student.__proto__.constructor.prototype === Person.prototype);
复制代码
- 构造方法虽然有用,但是也存在问题,虽然person和student都有一个方法running,但这两个方法不是同一个Function的实例,指向的内存也各自不同。
console.log(student.running === person.running); //false复制代码
- 都是做着同样的事情,但是每个实例都创建出一个方法,这就造成了没必要的内存损耗,那么有没有什么办法可以很好的解决这个问题呢,这时候,原型模式就出现了。
原型模式
-
每个函数都会创建一个prototype属性,这个属性是一个对象,该对象可以给每个实例共享属性和方法。
-
我们对之前的代码进行改造。
functionPerson(name, age) {
this.name = name;
this.age = age;
}
// 在这里,把方法running定义在了Person的prototype属性上// person和student共享同一个原型方法running,指向的是同一快内存空间Person.prototype.running = function (info) {
console.log(info);
};
const person = newPerson("moment", 18);
const student = newPerson("supper", 16);
// 在构造函数中这里输出的是falseconsole.log(person.running === student.running); //true
person.running("我会跑步"); // 我会跑步
student.running("我会跑步,我比你还年轻呢"); //我会跑步,我比你还年轻呢复制代码
- 在前面的new操作符可以知道,person和student的隐式原型等于Perosn的显式原型
-
首先person和student现在自己身上查找有没有running方法,没有找到。
-
去原型里查找,也就是通过person.__proto__或者student.proto,该方法,由于person.proto=Person.prototype,所以调用person.running()实际上调用的是Person.prototype.running()方法
- 具体的内存表现形式如下图所示。
- 通过上图,一下代码的输出就能理解了。
console.log(Person.prototype.constructor === Person); // trueconsole.log(student.__proto__.constructor === Person); // true复制代码
原型层级
- 在讲之前先祭出两张神图
- 在讲原型之前,我们先来认识一下认识一下Function和Object的关系!
-
在JavaScript中,每个JavaScript函数实际上都是一个Function对象。
-
在 JavaScript 中,几乎所有的对象都是 Object 类型的实例,它们都会从 Object.prototype 继承属性和方法。
-
从原型链上讲,Function继承了Object。
-
从构造器上讲,Function构造了Object。
-
好了,看到这里,晕了没?
-
接下来通过代码的展示,应该能更清楚的说明了上面两张图中的所讲的意思了。
functionPerson(name, age) {
this.name = name;
this.age = age;
}
// 在这里,把方法running定义在了Person的prototype属性上了Person.prototype.running = function (info) {
console.log(info);
};
const obj = {};
const person = newPerson("moment", 18);
const student = newPerson("supper", 16);
console.log(obj.__proto__ === Object.prototype); // trueconsole.log(Object.constructor === Function); // trueconsole.log(Function.prototype.__proto__ === Object.prototype); // trueconsole.log(Function.constructor === Function); // trueconsole.log(Person.__proto__.constructor.__proto__ === Function.prototype); // trueconsole.log(student.__proto__.__proto__.constructor.__proto__ === Function.prototype); // trueconsole.log(Function.constructor.__proto__ === Function.prototype); // trueconsole.log(Object.constructor === Function.constructor); // trueconsole.log(ObjectinstanceofFunction); // trueconsole.log(FunctioninstanceofObject); // trueconsole.log(Function.prototype.__proto__ === Object.prototype); // true复制代码
- 原型查找机制,首先在实例上查找,有则返回,没有则往上查找,这时候查找到了原型上了,如果能找到有该属性或方法,则返回,如果仍然没找打,就在Object的原型上查找,找到了则返回,没有就返回undefined或者报错。
functionPerson(name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
console.log("我是在实例身上的");
};
this.memory = "我是属于实例的";
}
// 在这里,把方法running定义在了Person的prototype属性上了Person.prototype.running = function () {
console.log("我是原型上的方法");
};
Object.prototype.牛逼 = "这是真的";
Person.prototype.memory = "我是属于原型的";
const person = newPerson("moment", 18);
console.log(person.name); // 来自实例console.log(person.memory); // 来自实例
person.sayName(); // 来自实例
person.running(); // 来自原型console.log(person.牛逼); // 这是真的console.log(person.六六六); // undefined复制代码
原型模式的弊端
- 在继承之前,我们每个类都要给其定义属于它自己的属性和方法,但是如果出现相同的方法,我们都要给他重新定义一遍,这样未免出现过多重复代码。
functionStudent() {}
functionTeacher() {}
Student.prototype.running = function () {
console.log("学生");
};
Student.prototype.eating = function () {
console.log("吃");
};
Student.prototype.study = function () {
console.log("学习");
};
Teacher.prototype.running = function () {
console.log("老师
};
Teacher.prototype.teach = function () {
console.log("吃");
};
复制代码
原型链继承
- 父类的原型直接赋值给子类的原型
-
父类和子类共享同一个原型对象,修改了任意一个,另外一个也被修改。
-
这是一个错误的做法。
functionStudent() {}
functionTeacher() {}
Teacher.prototype.running = function () {
console.log("老师");
};
Teacher.prototype.teach = function () {
console.log("吃");
};
Student.prototype = Teacher.prototype;
const student = newStudent();
student.running(); // 老师
student.__proto__.running = function () {
console.log("我被修改了");
};
const teacher = newTeacher();
teacher.running();// 我被修改了复制代码
- 正确的原型链继承
functionStudent() {}
functionTeacher() {}
Teacher.prototype.running = function () {
console.log("老师");
};
Teacher.prototype.teach = function () {
console.log("教");
};
const teach = newTeacher();
Student.prototype = teach;
const student = newStudent();
student.running = function () {
console.log("我被修改了");
};
const smallStudent = newStudent();
smallStudent.running(); // 老师 继承于Teacher原型
student.running(); // 我被修改了 来自于实例本身复制代码
- 通过一张图来展示子类的两个实例和两个构造函数及其对应的原型之间的关系
- 原型链继承的问题
-
在使用原型实现继承时,原型实际上变成了另一个类型的实例。这意味着原先的实例属性和方法摇身一变成了原型属性和方法。
-
子类型在实例化时不能给父类型的构造函数传参。
盗用构造函数
- 通过call或者apply改变this指向
functionTeacher(nickname, age, height) {
this.nickname = nickname;
this.age = age;
this.height = height;
}
functionStudent(nickname, age, height) {
Teacher.call(this, nickname, age, height);
this.hobby = ["唱", "跳", "rap"];
}
Teacher.prototype.running = function () {
console.log("老师");
};
Teacher.prototype.teach = function () {
console.log("教");
};
const student = newStudent("moment", "18", "1米59");
console.log(student.height); // 1米59console.log(student.hobby); // ["唱", "跳", "rap"]复制代码
- 借用构造函数存在问题
-
必须在构造函数中定义方法,因此函数不能重用。
-
子类也不能访问父类原型上定义的方法。
组合继承
- 综合了原型链和盗用构造函数,将两者的优点集中了起来。
functionTeacher(nickname, age, height) {
this.nickname = nickname;
this.age = age;
this.height = height;
}
functionStudent(nickname, age, height) {
Teacher.call(this, nickname, age, height);
this.hobby = ["唱", "跳", "rap"];
}
Teacher.prototype.running = function () {
console.log("老师");
};
Teacher.prototype.teach = function () {
console.log("教");
};
Student.prototype = newTeacher();