JavaScript中的构造函数及原型模式

  今天详细了解了一下JavaScript中构造函数和原型模式的相关知识,做了一个总结,以便日后做一个参考。

构造函数

  构造函数可以用来创建特定类型的对象。先来看一个构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) {
this.name = name
this.age = age
this.sayName = function() {
console.log(this.name)
}
}

var xm = new Person("小明", 20) // 这句代码干了四件事
// 1.创建了一个新对象 2.将Person的作用域赋值给新对象 3.执行Person内的代码 4.将这个新对象返回给xm
xm.name // 小明
xm.age // 20
xm.sayName() // 控制台打印小明的名字

  声明构造函数按照惯例通常以一个大写字母来开头,构造函数的新实例用new操作符创建。任何函数用new操作符来调用就是构造函数,而构造函数不通过new操作符调用也就和普通函数没什么两样。

  使用构造函数的缺点是每个方法都需要在每个实例上重新创建一遍。看下面代码:

1
2
var xf = new Person("小芳", 20)
console.log(xm.sayName === xf.sayName) // false

  我在这里新建了一个名为xf的实例,这时xm和xf都有一个sayName()方法,但这两个方法如果去比较的话却是不一样的。这就说明两个方法是独立的,每个方法都会为其分配一定的内存用来存放,这样显然是浪费资源的。这时就需要提到另一种方法,原型模式。

原型模式

1
2
3
4
5
6
7
8
9
10
11
12
13
// 构造函数
function Person(name) {
this.name = name
}
// 原型模式
Person.prototype.age = 20
Person.sayName = function() {
console.log(this.name)
}

var xm = new Person("小明")
var xf = new Person("小芳")
console.log(xm.sayName === xf.sayName) // true

  上面我使用了构造函数和原型模式混合的方法,其中name属性是构造函数创建的,age属性和sayName()方法是用原型模式(prototype属性)创建的。原型模式创建的两个方法均为所有实例共享,所以在最后比较两个实例的sayName()方法时返回了true,表示两者是调用的同一个sayName()方法。

  只要创建了一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针,也就是指向构造函数。在上面的例子里Person.prototype.constructor指向构造函数Person

1
2
3
4
5
6
7
xf.age = 25  // 重写了小芳的年龄
xf.age // 25
xm.age // 20
/* 重写已经存在于原型的值后,用该实例访问该属性会返回重写后的值,这种情况会把原型中的该值屏蔽掉,但是不会影响其他实例调用原型中的属性。
如果需要删除实例属性,可以使用"delete"操作符完全删除实例属性。*/
delete xf.age
xf.age // 20

原型链

  也就是说,当访问实例的属性或方法时,首先会按照名称查找实例中有没有存在的属性或方法,有的话则屏蔽原型内的同名属性或方法(原型里有的话),直接调用实例中的。如果没有,则向上查找该实例的原型,原型里有的话就调用,都没有的话就返回undefined。这个步骤是沿着原型链查找的,也就涉及到了原型链的概念。创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,至于其他方法,则都是从Object继承而来的。
  这么来理解的话,刚刚创建的xm实例的原型链是:

1
xm → Person.prototype → Object.prototype → null

0%