理解 JavaScript 中的原型

prototype 与 __proto__ 的联系与区别

重要的话放前面讲。首先,理解一个函数的原型属性 prototype 其实和实际的原型 __proto__ 没有关系至关重要。

prototype 只有在 Function 中有,而 __proto__ 在 Function 和 Object 中都有。

关于Function

在 JavaScript 中,除了五种原始类型(BooleanNumberStringNullUnderfined),引用类型包括 ArrayObjectRegExpFunctionDateError等,但从根本上只有两种,即 Function 和 Object,其他的引用类型都是从 Object 衍生出来。

对于这两种类型的判断,前者(原始类型)判断用 typeof,后者(引用类型)用 instanceof

1
2
3
4
5
6
7
8
9
10
11
var a = true;
typeof a; // boolean

var b = {};
typeof b; // object
b instanceof Object; // true

var c = [];
typeof c; // object
b instanceof Array; // false
c instanceof Array; // true

在 js 中,Function 和 Object 是平级的,而且 Function 能做但 Object 做不了的事情:

第一,Fuction 可以被执行。

第二,Fuction 可以被当做 Object 的构造函数。当使用 new 操作符后面跟着一个 Function 类型的变量时,这个 Function 变量会被当成构造函数返回一个 Object 对象。

1
2
3
4
function Foo(){
...
}
var foo = new Foo();

第三,Function 有内置的 prototype 属性,而 Object 没有。正是因为有了把 Function 当做构造函数的功能,才需要 prototype 属性。

关于继承(__proto__)

继承仅限于引用类型,即 Function 和 Object。JavaScript中的继承是基于原型链来实现的,用来明确继承关系的就是 __proto__ ,毕竟只有它同时存在于两者。

用最简单的话来描述javascript中继承的本质:一个对象A的 __proto__ 属性所指向的那个对象B就是它的原型对象(或者叫上级对象、父对象),对象A可以使用对象B中定义的属性和方法,同时也可以使用对象B的原型对象C的属性与方法,以此递归,这也就是所谓的原型链。

下面是一个对象继承的栗子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var A = {
name: 'huangtengfei'
}
var B = {
github: 'github.com/huangtengfei'
}
var C = {
blog: 'huangtengfei.com'
}

B.__proto__ = C;
A.__proto__ = B;

console.log(A.name); // huangtengfei
console.log(A.github); // github.com/huangtengfei
console.log(A.blog); // huangtengfei.com

prototype的作用

既然 __proto__ 一个东西把继承问题就都解决了,那要 prototype 做甚?

并不,因为 __proto__ 并非官方标准中定义的属性,当把一个Function当做构造函数使用时,继承关系需要借助 prototype 实现。

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Foo(name, github, blog){
this.name = name;
this.github = github;
this.blog = blog;
}

Foo.prototype.aboutMe = function(){
var info = 'my name is ' + this.name +
'. my github is ' + this.github +
'. my blog is ' + this.blog;
console.log(info);
}

var foo = new Foo('htf', 'github.com/huangtengfei', 'huangtengfei.com');
foo.aboutMe();
// my name is htf. my github is github.com/huangtengfei.
// my blog is huangtengfei.com

这是个典型的构建构造函数,然后创建对象的过程。那么,在 var foo = new Foo() 这一步发生了什么呢?

第一步,Foo 函数被执行。Foo 函数在 foo 的作用域下被执行,所以这里 this 指代的就是 foo,这样 name、github、blog 三个属性才会被当做 foo 的属性被创建。

第二步,将 foo.__proto__ 指向 Foo.prototype 。这才是 javascript 构造函数的精髓所在,之后的原理和 关于继承 部分的代码一样,foo 就继承了 Foo.prototype 中(以及其原型链上)的属性与方法。

1
console.log(foo.__proto__ === Foo.prototype)    // true

对象的 __proto__ 指向的是创建它的函数的 prototype,因此:

1
Object.__proto__ === Function.prototype

结论

__proto is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the object that is used to build \proto__ when you create an object with new.

意思就是:

__proto__ 是真正用来查找原型链去获取方法的对象。

prototype 是在用 new 创建对象时用来构建 __proto__ 的对象。

更详细的区别可参考:prototype与__proto__的联系与区别

获取对象原型的方法

ECMA引入了标准对象原型访问器 Object.getPrototype(object),到目前为止只有 Firefox 和 Chrome 实现了此访问器。除了 IE,其他的浏览器支持非标准的访问器 __proto__,如果这两者都不起作用的,我们需要从对象的构造函数中找到的它原型属性。

1
2
3
4
5
6
7
8
9
10
var a = {}; 

//Firefox 3.6 and Chrome 5
Object.getPrototypeOf(a); // [object Object]

//Firefox 3.6, Chrome 5 and Safari 4
a.__proto__; // [object Object]

//all browsers
a.constructor.prototype; // [object Object]

任何时候,a.__proto__a.constructor.prototype 是等价的。

constructor 与 prototype

1
2
3
4
5
var A = function() {};
A.prototype.constructor == A; // true
var a = new A();
a.constructor == A; // true
// (a's constructor property inherited from it's prototype)

图解可参考:js原型链原理看图说明prototype原型

instance of 和原型的关系

如果 a 的原型属于 A 的原型链(即 a.__proto__ === A.prototype),表达式 a instance of A 值为 true。

1
2
3
4
5
6
7
8
9
var A = function() {}   
var a = new A();

a.__proto__ == A.prototype; // true
a instanceof A; // true

// 改变a的原型指向
a.__proto__ = Function.prototype;
a instanceof A; // false

原型的作用

原型的作用当然是继承了。体现在以下两种情况:

一、当多个实例共用一个通用原型的时候,原型对象的属性一旦定义,就可以被多个引用它的实例所继承。栗子可以看 JavaScript 面向对象编程 一文

二、扩展原生构造函数 Function、String 等的功能,使其所有实例都拥有此扩展功能。

比如下面一个栗子,对字符串本身进行指定数目的复制:

1
2
3
4
5
6
String.prototype.times = function(count) {
// 构造空元素中间用当前字符串连接
return count < 1 ? '' : new Array(count + 1).join(this);
}

"hello!".times(3); // "hello!hello!hello!";

参考

理解JavaScript原型
JavaScript探秘:强大的原型和原型链

坚持原创技术分享,您的支持将鼓励我继续创作!