对于事物的理解,往往需要涵盖三个方面:它是什么样子的?它为何会出现?它的外延是什么?理解了内涵与外延才算真正的掌握。
一、原型是什么
以对象a为例,a内部除了固有的属性外,还有一个隐藏的链接指向另一个对象b,这个b就叫作a的原型(也叫原型对象);同时b也有隐藏链接指向c,以此类推,最终指向的是Object.prototype,以上也解释了什么是原型链。可以通过
Object.getPrototypeOf(a)
来获取a对象的原型。
1.1 令人困惑的prototype
原型之所以使人困惑是因为人们往往把它和prototype属性混在一起,其实这是命名上的失败,对象的原型不是它的属性,而是它的特性。为了方便说明,我将对象的原型称为“原型”使之区别于prototype属性,但两者之间又是紧密相连的。(每个JavaScript对象都有个__proto__
属性指向对象的原型,不过__proto__
是在ECMAScript 2015中被新加入的属性,而该属性的使用也存在争议)。
1.2 prototype属性
每个JavaScript方法都有一个prototype属性;当你定义一个方法var A = function() {}时,实际上它等价于以下代码:
function A() {
}
// obj是一个新对象,且obj.constructor = A,obj的原型为Object.prototype
A.prototype = obj;
也就是说,当你创建了一个方法的同时,JavaScript会创建一个新对象赋值给方法的prototype属性,并将新对象的constructor指向方法A,如下图所示:
1.2.1 Object.prototype
前面已经说过每个JavaScript方法都有一个prototype属性,所以Object也是一个方法,从使用角度看是一个构造方法。Object.prototype是所有JavaScript对象原型链的最末端(除特殊情况:人为的将原型设置为null)。此外需要说明的是:从JavaScript的语法角度看,并没有构造方法的定义,人们只是根据用途将某些方法称为构造方法,本质上构造方法与一般的方法并没有区别。
二、原型的应用
2.1 被人们忽视的原型
如果你一直从事轻前端重后端的web项目,一直在编写意大利面条式的代码,且这种代码还能满足开发与维护的需求,你确实不需要了解原型。当你发觉JavaScript的代码量在项目中急速增长,代码逐渐变的难以阅读、难以维护时,你开始寻找能避免代码重复的方案,首先你想到的应该是---继承。JavaScript通过原型可以达到这个目的。
2.2 原型的特性
原型最基本的作用就是用来检索对象的属性,当检索某个对象的属性时,首先会检索对象本身的属性,如果本身没有该属性,就去检索对象的原型是否存在该属性,如果仍旧没有找到,就去检索原型的原型,以此类推,沿着原型链一路检索,直到原型链的最后一环Object.prototype,如果最终还是没有找到,就会返回undefined。
2.3 创建对象
在JavaScript中创建对象有以下三种方式:
-
var a = {}
; -
var a = Object.create(b)
; -
var a = new A()
;
很显然只有第三种方式才符合OOP的思想,因为它体现了class(类)的概念。在JavaScript的语法层次是没有class的定义的,但是可以通过function来模拟class的行为,通过原型来实现继承。
2.4 JavaScript中的“class”
JavaScript通过function来模拟class,示例代码如下:
var A = function(name, age) {
// init property
this.name = name;
this.age = age;
// default property
this.attr = "attr";
// default function
this.fun = function() {
};
};
var a1 = new A("li", 20);
var a2 = new A("zhang", 21);
通过new操作符,对象a1、a2就拥有了A方法中this所拥有的属性和方法。
2.5 JavaScript中的继承
JavaScript是通过原型来实现继承的,示例代码如下:
// define Parent class
var Parent = function() {
this.pName = "li";
this.pFun = function() {
console.log("parent's fun");
};
};
// define Child class
var Child = function() {
this.cName = "xiaoli";
this.cFun = function() {
console.log("child's fun");
};
};
// Child inherit Parent
Child.prototype = new Parent();
// create an object of Child
var c = new Child();
// print: parent's fun
c.pFun();
new操作的内存分析:
- 当执行
var c = new Child();
时,首先新建了一个空对象
- 将Child方法中的this指向新对象,通过this进行对象初始化
- 将Child的prototype属性(
new Parent()
)作为新对象的原型
2.6 prototype-based(基于原型)的继承 VS class-based(基于类)的继承
class-based语言中,同一个类产生的多个对象之间相互独立、互不干扰;而prototype-based语言中同一个“类”产生的多个对象通过原型捆绑在一起,代码示例如下:
var A = function() {
};
A.prototype = {x : 1};
var a1 = new A();
var a2 = new A();
// change prototype object
A.prototype.x = 2;
// print: 2
console.log(a1.x);
// print: 2
console.log(a2.x);
因为a1、a2的原型为同一个对象,所以当改变原型对象时,a1、a2都受影响;这个例子同时展示了JavaScript作为动态语言的灵活性,在运行时改变“类”的结构,使通过该类产生的对象均受影响。