JavaScript 原型
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。(MDN)
1. 什么是原型
1.1 属性的访问机制
在 JavaScript 中,除了几种基础类型,剩下的几乎都是对象。
var obj = { empty: true };
console.log(obj.toString()); // 输出:[object Object]
这就涉及到了原型。
例子中的变量 obj
的原型可以通过 __proto__
访问。
var obj = { empty: true };
console.log(obj.__proto__);
var obj = { empty: true };
console.log(
obj.toString === obj.__proto__.toString,
); // 输出:true
1.2 原型是怎么出现在一个对象上的
到这里有个问题,到底什么是原型,原型是怎么来的。
首先看一段代码:
function Point(x, y) {
this.x = x;
this.y = y;
}
var point = new Point(, );
console.log(point.__proto__);
这样打印出来的 point
的原型对象,除了 constructor
和 __proto__
属性,就什么都没有了。
接下来做个改写:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.info = function() {
console.log('x: ' + this.x + ', y: ' + this.y);
};
var point = new Point(, );
point.info(); // 输出:"x: 1, y: 2"
console.log(point.__proto__);
使用相等运算符,就可以验证上面这个规则:
console.log(
point.__proto__ === Point.prototype,
); // 输出:true
这就是一个对象原型的由来。
console.log(
point.constructor === point.__proto__.constructor, // 输出:true
point.constructor === Point, // 输出:true
point.__proto__.constructor === Point, // 输出:true
);
事实上对象的 constructor
属性就是直接从原型上继承的。
1.3 原型链
前面有提到访问对象属性的机制。
function Point(x, y) {
this.x = x;
this.y = y;
}
var point = new Point(, );
console.log(point.toString());
然后会再往上一层找,也就是找到了 Point.prototype.__proto__
上 (等同于 point.__proto__.__proto__
),这个时候就找到了 toString
,随后被返回并且调用。
Point.prototype.__proto__
其实就是 Object.prototype
。
console.log(
Point.prototype.__proto__ === Object.prototype,
); // 输出:true
假如检查到 Object.prototype
还没有目标属性,则在往上就找不到了,因为 Object.prototype.__proto__
是 null
。
也就是说原型查找的末端是 null
,碰到 null
就会终止查找。
这些原型环环相扣,就形成了原型链
。
有些同学会有疑问,为什么 Point.prototype
的原型是 Object.prototype
。其实 Point.prototype
也是一个对象,可以理解成这个对象是通过 new Object
创建的,所以原型自然是 Object.prototype
。
2. proto 属性
var date = new Date();
console.log(date.__proto__);
__proto__
具有兼容性问题,因此开发中尽量不要使用到,他不在 ES6
之前的标准中,但是许多旧版浏览器也对他进行了实现。
在 ES6
中 __proto__ 属性
被定制成了规范。
3. Object.getPrototypeOf 方法
var date = new Date();
var dateProto = Object.getPrototypeOf(date);
console.log(dateProto);
console.log(dateProto === date.__proto__); // 输出:true
4. JavaScript 中没有类
在 JavaScript
中是没有类的概念的。
有其他面向对象开发经验的同学可能会被 new
关键字误导。
JavaScript
中采用的是原型的机制,很多文献会称其为 原型代理
,但个人认为对于初学者使用 原型继承
的方式会更好理解一点,日常讨论中其实是一个意思,不需要过多纠正其说法。
类
和原型
是两种不同的机制。
有关于类的内容,篇幅很大,如果不熟悉但又感兴趣,可以尝试着接触一下其他面向对象的语言,如 Python
、Java
、C++
。
ES6 提供了
class
关键字,引入了一些类相关的概念,但其底层运行机制依然是原型这一套,所以即便是有了class
关键字来帮助开发者提升开发体验,但其本质依然不是类,只是一种原型写法的语法糖。
5. 小结
原型的概念至关重要,利用原型的机制可以开发出更加灵活的 JavaScript 应用。