JavaScript原型(上)

工厂模式与构造函数模式

在重复性构建对象时,我们通常会采用工厂模式与构造函数模式

// 工厂模式
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与其他的函数有着什么样的关系呢?让我们一起来探寻下吧

通过一张图来展示

JS原型模式

这里的FooBar都是自定义的构造函数,而Foo.PrototypeBar.Prototype分别是他们对应的原型,而f1f2以及b1b2则是这两个构造函数所new出来的实例对象,他们都通过[[Prototype]]属性与他们各自的原型产生关联,而与其构造函数无直接关联

再看向Foo.PrototypeBar.Prototype这两个原型对象,他们也会通过[[Prototype]]属性指向他们的原型对象,也就是Object的原型对象,也就是说我们自定的构造函数,对于其原型都会默认指向Object.Prototype,而对于Object.Prototype[[Prototype]]属性,最终会指向null

所以我们可以得出总结:

  1. 正常的原型链都会终止于Object的原型对象
  2. Object原型的原型对象是null

既然f1f2这样的实例对象具备[[Prototype]]属性,那FooBar构造函数具有[[Prototype]]属性吗?有的话,应该指向哪里?

同样,我也构建了一张图
构造函数的[[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的惊喜!哦不,这就是原型的动态性!

持续更新,敬请期待…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值