工厂模式与构造函数模式
在重复性构建对象时,我们通常会采用工厂模式与构造函数模式
// 工厂模式
function createPerson(name,age) {
let o = new Object();
o.name = name;
o.age = age;
o.sayName = function() {
console.log(this.name);
}
return o;
}
let person1 = createPerson('leo',19);
let person2 = createPerson('kang',20);
console.log(person1); // {name: "leo", age: 19, sayName: ƒ}
console.log(person2); // {name: "kang", age: 20, sayName: ƒ}
person1.sayName(); // leo
工厂模式的问题
通过工厂模式创建的对象,没有解决对象标识问题(即新创建的对象是什么类型)
// 构造函数模式
function Person(name,age) {
this.name = name;
this.age = age;
this.sayName = function() {
console.log(this.name);
}
}
// 通过new构造函数创建实例对象
let person1 = new Person('Sheng',21);
let person2 = new Person('Sao',22);
console.log(person1); // Person {name: "Sheng", age: 21, sayName: ƒ}
console.log(person2); // Person {name: "Sao", age: 22, sayName: ƒ}
person1.sayName(); // Sheng
构造函数的问题
上面的Person()函数可等价于
function Person(name,age) {
this.name = name;
this.age = age;
// 逻辑等价于上面的方式
this.sayName = new Function("console.log(this.name)");
}
所以每次实例化对象都会创建一次sayName方法
console.log(person1.sayName == person2.sayName); // false
但是没必要在每次创建实例对象时,都创建一次sayName
原型模式
为解决上面陈诉的问题,我们可以采用JS中一个很重要的概念,那就是原型模式!
// 原型模式
function Person(name, age) {
this.name = name;
this.age = age;
}
// 共享方法定义在原型对象
Person.prototype.sayName = function() {
console.log(this.name);
}
let person1 = new Person('Leo', 20);
let person2 = new Person('John', 22);
console.log(person1); // Person {name: "Leo", age: 20}
console.log(person2); // Person {name: "John", age: 22}
person1.sayName(); // Leo
person2.sayName(); // John
console.log(person1.sayName === person2.sayName); // true
这样就可以解决工厂模式与构造函数模式存在的问题!
图解:
图中的
[[Prototype]]就是每个对象上暴露的__proto__属性
说到原型,我们就不得不提一提我们常见的
Object构造函数,在每种语言中都有属于自己的超类与“超类”(在JS中没有真正的类的概念,用委托更加贴合,所以我打上了引号),那我们这里的Object与其他的函数有着什么样的关系呢?让我们一起来探寻下吧
通过一张图来展示
这里的Foo与Bar都是自定义的构造函数,而Foo.Prototype与Bar.Prototype分别是他们对应的原型,而f1与f2以及b1和b2则是这两个构造函数所new出来的实例对象,他们都通过[[Prototype]]属性与他们各自的原型产生关联,而与其构造函数无直接关联
再看向Foo.Prototype与Bar.Prototype这两个原型对象,他们也会通过[[Prototype]]属性指向他们的原型对象,也就是Object的原型对象,也就是说我们自定的构造函数,对于其原型都会默认指向Object.Prototype,而对于Object.Prototype的[[Prototype]]属性,最终会指向null
所以我们可以得出总结:
- ==正常的原型链都会终止于
Object的原型对象==- ==
Object原型的原型对象是null==
既然
f1与f2这样的实例对象具备[[Prototype]]属性,那Foo与Bar构造函数具有[[Prototype]]属性吗?有的话,应该指向哪里?
同样,我也构建了一张图
通过图解,我们可以发现,我们所有的自定义构造函数(也是对象),他们都是通过
Function这个构造函数所创建的实例,那就好理解了,既然是它的实例,那就肯定有[[Prototype]]属性来指向他们的原型咯,那问题是不是就清晰了呢?
我们仔细再看,图中的构造函数Function也具有一个[[Prototype]]属性,并且这个属性还是指向了其原型对象?
其实很好理解,Function也是一个函数,函数是一种对象,也有[[Prototype]]属性。既然是函数,那么它一定是被Function创建。==所以Function(构造函数)是被自身创建的,所以它的隐式[[Prototype]]属性指向了自身的原型对象==
原型的动态性
首先来看一段简单的代码
function Person() {};
let friend = new Person();
Person.prototype.sayHi = function() {
console.log('Hello Man');
};
friend.sayHi(); // Hello Man
此时未重写原型对象,可以通过实例访问到原型中的方法,请分析下面的代码为何报错?
function Person() {};
let friend = new Person();
Person.prototype = {
constructor: Person,
name: 'leo',
age: 20,
sayName() {
console.log(this.name);
}
}
friend.sayName(); // 报错
通过模型图来解释更为直观
通过图我们可以看到当Person的原型对象被重写时,==实例的[[Prototype]]属性任然是指向原来的原型对象,因为实例的[[Prototype]]指针是在new时自动指向的(关于new的原理会在后续章节更新)==,所以此时通过实例friend调用不到重写原型对象中的sayName方法
这就是TM的惊喜!哦不,这就是原型的动态性!
持续更新,敬请期待...