Web程序员应该知道的Javascript prototype原理

时间:2023-12-24 19:28:37

有同事问了我几个和Javascript的类继承的小问题,我在也不太理解的情况下,胡诌了一通. 回来以后有些内疚, 反省一下, 整理整理Javascript的prototype的原理, 自己清楚点, 也希望对别人也有帮助.

首先js里面没有类这回子事情, 虽然class是js的保留字, 但是到现在也没派上任何用场. 在js里面, 几乎所有的东西都是对象, 函数也是对象, 所以函数可以赋值给别的变量. 另外js对象也可以用数组的方式访问, 也就是说访问obj1.something, 也可以写成obj1['something'].

在js里虽然没有类, 但是有一个在类编程里常用的的概念, Constructor构造器, 或者叫构造函数. 语法上Constructor跟普通的js函数没有区别, 但是Constructor前面加上new关键字就可以产生新的对象.

那么”new Constructor()”这样调用时到底做了什么事情呢?
1. js运行环境首先创建一个空对象
2. 把this变量指向这个对象
3. 把__proto__指向这个构造器的prototype属性
4. 通过this把属性和方法加在这个对象上
5. 最后会把this指向的对象return出来(当然你也可以显示的return别的对象,这是情况跟我讨论的有点不一样)

这里有2个概念比较重要, 第一是Constructor的prototype属性, 它是Constructor的一个保持共享属性和方法的对象. 二通过该Constructor生成的对象的__proto__属性, 它指向了那个prototype对象. 对象的Constructor可以通过该对象的constructor属性获得.

我们定义一个简单的Constructor Demo(), 然后通过他生成一个aDemo对象, 在Chrome的console里把aDemo打出来看到的结果是这样的,这是我们能很清楚的看出这种关系.

01
02
03
04
05
06
function DemoA(){
  this.title = "this is a demo constructor";
  this.f1 = function(){}
}
var aDemo = new DemoA();
console.log(aDemo);

Web程序员应该知道的Javascript prototype原理

换一种写法,

01
02
03
04
05
06
function DemoB(){
  this.title = "this is a demo constructor";
}
DemoB.prototype.f2 = function () {};
var bDemo = new DemoB();
console.log(bDemo);

Web程序员应该知道的Javascript prototype原理
其实跟前面没多大区别, 不过这一次f2()不在创建出来的对象上, 而是在对象的__proto__指向的构造器DemoB上. 这样的好处是当使用DemoB创建很多对象时, 比较节省内存.

js的prototype搜索链 prototype Chain
js的运行环境是这样使用构造器的prototype的. 当我们对第一个对象调用其方法时, js运行环境首先看, 这个对象本身有没有这个方法, 如果有就直接调用. 如果没找到, 就查找这个对象的__proto__属性指向的对象(就是前面说的该对象的构造器的prototype属性), 如果有该方法就调用, 没有继续查找__proto__属性指向对象的__proto__属性指向的对象, 以此类推. 直到最后, 找到js所有的对象都会指向默认指向的Object(算是js的根对象吧). 所以如果我们给Object上增加个方法, 所以的js对象都能使用.

由于js有对对象函数的prototype chain搜索的特性, 我们可以利用这点来模拟类似其他语言里类(class)的效果. 如:

01
02
03
04
05
06
07
08
09
10
11
12
13
function DemoA(){
  this.title = "this is a demo constructor";
  this.f1 = function(){}
}
function DemoB(){
  this.title = "this is b demo constructor";
  this.f2 = function(){}
}
DemoB.prototype = new DemoA();
var bDemo = new DemoB();
console.log(bDemo);

Web程序员应该知道的Javascript prototype原理
这时我们通过bDemo对象, 不但能调用bDemo上的DemoB的f2()方法, 也能__proto__同对对prototype的搜索, 在bDemo上直接调用DemoA的f1()方法.

由于这种写法在语法上太啰嗦, 不容易理解, 我可以定义一个这样的函数.

01
02
03
function inherit(Child, Parent) {
  Child.prototype = new Parent();
}

这样我们前面的例子可以写成这样, 效果是一样的:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function DemoA(){
  this.title = "this is a demo constructor";
  this.f1 = function(){}
}
function DemoB(){
  this.title = "this is b demo constructor";
  this.f2 = function(){}
}
inherit(DemoB, DemoA);
var bDemo = new DemoB();
console.log(bDemo);