本文主要针对JS中原型结合自己的理解进行总结,如有不正确之处,欢迎指正。
既然说到原型,那么函数是不可避免的,下面我们先看看函数中的工厂函数和构造函数
关于工厂函数
工厂函数或者工厂模式重点在于工厂,所谓“工厂”在于批量创造同一类的东西。JS中的工厂函数吗目的是在于创造相同特性的对象,这里的特性指对象的属性和方法。
比如在实际开发中,我们要操作多个对象,这些对象都有相同的属性和方法,如果两个我们可以声明两个对象,并依次为两个对象添加属性和方法。比如要为某个班级所有的学生创造档案,每个学生都有编号ID和成绩排名rank以及学习study的刻苦成都描述。以及和同学和老师打招呼的sayHello方法,如果这个班级只有两个学生,那么我们可能会这样做。
var studentA = {}
studentA.name = "A";
studentA.ID = 01;
studentA.study = "hard";
studentA.sayHello = function() {
console.log("hello, my name is" + studentA.name);
}
var studentB = {}
studentB .name = "B";
studentB .ID = 02;
studentB .study = "lazy";
studentB .sayHello = function() {
console.log("hello, my name is" + studentB.name);
}
studentA.sayHello() // hello, my name isA
studentB.sayHello() // hello, my name isB
上面仅仅只有创建两个对象,但代码重复就很多,如果这个班级有几十学生那么创建这些学生对象并添加属性和方法就是一件非常麻烦的工作,为了解决这类问题,也就是重复创建具有相同属性和方法的对象,JS提供了一种工厂函数模式。
创建工厂函数步骤
- 创建一个函数,并指定参数
- 函数中创建一个对象
- 在函数体内给对象添加属性和方法
- 返回这个对象
// step01:声明一个函数
function student(name, id, study) {
// step02:创建对象obj
var obj = {};
//step03: 给obj对象添加属性和方法
obj.name = name;
obj.ID = id;
obj.study = study;
obj.sayHello = function() {
console.log("hello, my name is" +
this.name +
" " +
"I am study" +
" " +
this.study)
}
// step04:返回这个对象
return obj;
}
student("A", 01, "hard").sayHello() // hello, my name isA I am study hard
student("B", 02, "lazy").sayHello() // hello, my name isB I am study lazy
从上面代码可以看到,通过工厂函数可以避免为多个对象重复添加属性和方法的,直接通过传参的方式来创建多个不同的对象。
工厂函数的缺点
不能确定创建出的对象的具体类型
var sA = student("A", 01, "hard")
var sB =student("B", 02, "lazy")
typeof sA //"object"
typeof sB //"object"
sA instanceof student // false
sB instanceof student // false
在用typeof检查对象类型时只会返回"object", sA和sB也不是student的实例。
构造函数模式
js中构造函数约定大写字母开头(约定),但一个函数时候是构造函数取决于函数的使用方式,如果new 一个函数,那么就可以认为这个函数是构造函数。
function getDescByAge(age) {
var desc = age > 18 ? "I am young" : "I am old";
return desc;
}
console.log(new getDescByAge()) // getDescByAge {}
getDescByAge是一个通过age获取desc的方法,通过new操作,这个函数实际上就是一个构造函数,尽管上面这样操作没有什么意义。构造函数的出现弥补了工厂函数不能确定创建出来的对象是何种具体类型的缺陷。构造函数是为了创建具有同种属性和方法的对象的一种模式或者一种函数的使用方式。
使用构造函数的步骤
- 创建一个构造函数,函数以大写字母开头,并指定参数
- 在构造函数体内使用参数给this添加属性和方法
这就是使用构造函数的模板,用构造函数重写上面的工厂函数
function Student(name, id, study) {
this.name = name;
this.ID = id;
this.study = study;
this.sayHello = function() {
console.log("hello, my name is" + " "
+ this.name + " " +
"I am study" +
this.study );
}
}
let sA = new Student("A", 01, "hard");
let sB = new Student("B", 02, "lazy");
sA.sayHello(); // hello, my name is A I am studyhard
sB.sayHello(); // hello, my name is B I am studylazy
// instanceof 方法
sA instanceof Student // true
sB instanceof Student // true
// Object.getPrototypeOf 方法 这个方法后面会讲到,可略过
Object.getPrototypeOf(sA) == Student.prototype // true
Object.getPrototypeOf(sB) == Student.prototype // true
上面声明一个构造函数Student,函数体内使用this添加属性和方法。
new一个构造函数发生了什么?
- 创建一个对象
- 将构造函数的作用域赋给新对象(因此this指向这个对象)
- 执行构造函数中的代码(为这个对象添加属性)
- 返回这个对象
相比工厂函数,使用构造函数
- 没有显式的创建对象
- 没有返回这个对象
- 能够确定对象的类型(对象是哪个类型的实例)
为什么要确定对象的类型呢?
function Teacher(skill) {
this.skill = skill;
}
var sC = new Teacher();
// 班级数组class
var class = [sA, sB, sC]
//遍历class数组,给老师添加一个属性教学时长teachingTime
for(let i = 0; i < class.length; i++) {
class[i] instanceof Teacher && class[i].teachingTime = '15 years'
}
上述代码中添加了一个Teacher构造函数,班级数组class,对班级中的老师类型添加一个属性teachingTime,所以在实际项目开发中确定对象的类型是有时是需要的。
原型
- 什么是原型
- 关于原型的误解
- 为什么用原型
- 原型链
什么是原型
首先要区分原型和原型对象是两个不同的概念。
- 任何对象都有指针__proto__,指向他的构造函数的原型对象。是构造函数的原型对象,而不是实例自身的原型对象。
- 构造函数具有prototype属性,这个属性是一个指针,指向构造函数的原型对象,prototype仅仅是一个指针,而不是对象,在我初次接触原型的时候,会有这种错误的想法
// 伪代码 function P() { // code here } let p = new P() p.prototype = p的原型对象 typeof p == "object" // true p.prototype //undefined
- 实例没有prototype属性,只有构造函数才有prototype属性,这个属性是一个指针,通过Fnc.prototype可以取到构造函数Fnc的原型对象
- 实例有__prototype__属性,也是一个指针,指向构造函数的原型对象。
- 原型对象具有constuctor属性,这个属性同样可以理解为一个指针,指回他的构造函数。构造函数实例的constructor不是本身所具有的,而是继承自构造函数的原型对象。
关于__proto__,prototype,constructor三者之间的图示关系网上有很多,这里就不额外附图了。请看下面代码。
function Person() {
// some code here
}
let p1 = new Person();
p1.__proto__ == Person.prototype; // true
Person.prototype.constructor == Person; // true
p1.constructor === Person // true
]