JavaSript系列:浅析原型

  • 白小霁
  • 11 Minutes
  • May 3, 2017

为什么要有原型对象

看到了阮老师的Javascript继承机制的设计思想,明白了当初设计原型对象的历史原因:

  1. 为了降低JavaScript的学习难度,所以不引入类(class)的概念
  2. 构造函数使用new运算符实例化对象后,并不能共享属性和方法,这一点不利于之后的继承
    大概基于以上两点,Brendan Eich决定给构造函数设置一个prototype属性。

原型对象

每一个构造函数都有一个prototype的属性,这个属性指向的一个对象,而这个对象就是原型对象。对于每一个实例化的对象而言,可以通过__proto__属性访问到其构造函数的prototype对象。按自己理解来说:每一个Function都有一个prototype属性,而该属性指向的就是原型对象,而原型对象上的作为一个公共区域,实例化的对象就可以访问到其构造函数的原型上的属性和方法。

举例说明

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name)
}
}
let p1 = new Person("baiji",10)
let p2 = new Person("baixiaoji",20)
console.log( baiji.sayName === baixiaoji.sayName ) //logs false

如果按照上述的代码,每一次实例化对象的时候,都要创建一个sayName的函数,如果要构造1000个Person的实例,那么内存一定会吃紧,所以prototype属性就可以将构造函数的公共的属性和方法都放上去。改写上述的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function(){
console.log(this.name)
}
let p1 = new Person("baiji",10)
let p2 = new Person("baixiaoji",20)
console.log( p1.sayName === p2.sayName ) //logs true
// ES 6
// 其中的class是一个语法糖 通过bable转化后和上述代码大概一致
class Person{
// constructor 就是构造函数
constructor(name,age){
this.name = name
this.age = age
}
sayName(){
console.log(this.name)
}
}

将公共的方法抽取出来,挂在prototype上,可以供实例化的对象调用公共方法。

文字说明

Person是一个构造函数,通过new可以构造出Person的一个实例对象(p1、p2),每一个对象都会有一个__proto__的属性指向构造函数的原型对象(Person.prototype)。每一个函数都会一个prototype的方法,指向一个对象;而每一个原型对象(Person.prototype)都有一个constructor指向这个函数本身(Person())。

大概关系如下图:

来自简书

这也就可以解释了,为什么每一个对象上都有一个toString的方法,见下图:
来自简书
这样指向一层又一层的原型对象(会有终止),就是JavaScript的原型链,而我们可以基于原型链的特性,实现之后的继承

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.sayName = function(){
console.log(this.name)
}
// 构建一个工人的类 继承原来人的特性
function Worker(name,age,workYear){
Person.call(this,name,age)
this.workYear = workYear
}
inherit(Person,Worker)
Work.prototype.sayWork = function(){
console.log(this.work)
}
// 构造一个函数来控制原型对象
function inherit(superType, subType){
/*
Object.create 创建一个拥有指定原型和若干指定属性的对象 有兼容问题
这个实现的好处:防止对子类的原型对象修改的时候,修改到了父类的原型对象上
*/
let __prototype = Object.create(superType.prototype)
__prototype.constructor = subType
subType.prototype = __prototype
}
let p1 = new Work("baiji",10,"学生")

其实JavaScript中的继承是通过原型链的方式,实现了继承父类的方法和属性。由此,就是原型拓展的全部,知道了上面这些知识点,我们就可以去根据面向对象的方式去封装一些组件。

总结

  1. 每一个构造函数都有一个prototype属性,指向其原型对象,一般将公共的方法和属性挂在上面
  2. 由new运算符实例化的对象,通过__proto__属性访问其构造函数的原型对象
  3. JavaScript通过原型链的方式实现继承