工厂模式与构造函数模式
在重复性构建对象时,我们通常会采用工厂模式与构造函数模式
// 工厂模式
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的惊喜!哦不,这就是原型的动态性!
持续更新,敬请期待…