前言:本人表述能力不太好,如果有不清晰的地方请一定回复,我会改。
每个人都知道js有原型继承。但js只默认提供了new运算符实现特定的原型继承。所以,大多数的说明都很难读懂。这篇文章目的就是说清楚什么是原型继承,和怎么在js中使用原型继承。
原型继承的定义
当你读到关于js的原型继承,经常能看到如下定义:
当访问对象的属性时,JavaScript将向上遍历原型链,直到找到具有对应的属性---Javascript Garden
大多数js的实现使用了__proto__属性来表示原型链中的下一个对象。在本文中将看到__proto__与prototype的区别。
📒__proto__非标准的,代码中不应该使用。他在这篇文章中用来解释js原型是怎么工作的。
下面的代码展示了js引擎怎么检索属性
function getProperty(obj, prop) {
if (obj.hasOwnProperty(prop))
return obj[prop]
else if (obj.__proto__!=null)
return getProperty(obj.__proto__, prop)
else return undefinded
}我们用一个经典例子:一个二维点。一个点有两个坐标x,y和一个方法print
用前面写到的原型链的定义,我们将写一个对象Point,他有三个属性x,y,print。为了创建一个新的二维点,我们写了一个对象,并设置了__proto__
var Point = {
x: 0,
y: 0,
print: function () {console.log(this.x, this.y);}
};
var p = {x: 10, y: 20, __proto__:Point};
p.print(); // 10 20诡异的js原型继承
知道什么最让人迷惑吗?每个这样定义原型继承的人不写这样的代码,他们代码是这样的:
function Point(x,y){
this.x = x;
this.y = y;
}
Point.prototype = {
print: function () { console.log(this.x, this.y); }
}
var p = new Point(10, 20);
p.point(); // 10 20这个就和上面的那段代码完全不一样了。Point现在是个function,有个prototype属性,还有new运算符。都什么鬼?
new是怎么工作的?
Brendan Eich 希望js是个传统的面向对象的编程语言,就像Java和C++一样。在这些语言中,使用new运算符创建类的实例。所以他给js写了个new运算符。
C++有构造函数的概念,初始化实例属性。所以,new操作目标必须是函数。- 我们需要要把对象的方法放在某个地方。既然js是个原型语言(
prototype language),那就放在方法的prototype属性上吧。
new运算符有方法F和参数:new F(arguments)。做了单个简单的步骤:
- 创建类的实例。一个空对象将
__proto__属性设置为F.prototype - 实例化。
F及其参数集被调用,并把this设置上去 - 返回该实例
现在我们理解了new做了什么,可以用js实现它
function New(f){
/*1*/ var n = {__proto__:f.prototype};
return function(){
/*2*/ f.apply(n, arguments);
/*3*/ return n;
};
}测试下
function Point(x,y) {
this.x= x;
this.y=y
}
Point.prototype = {
print: function(){console.log(this.x,this.y)}
};
var p1 = new Point(10, 20);
p1.print();// 10 20
console.log(p1 instanceof Point);// true
var p2 = New(Point)(10,20);
p2.print();
console.log(p2 instanceof Point);// truejs里真实的原型继承
JavaScript规范只提供了new运算符。但Douglas Crockford找到了利用new运算符实现真正的原型继承的方法。我们现在写Object.create方法。
Object.create = function(parent) {
function F() {};
F.prototype = parent;
return new F();
};这看上去很奇怪,但是它做的事情却很简单。它只是创建了一个新对象,并把prototype设置为任何你想设置的。如果可以使用__proto__,他就可以写成这样
Object.create = function(parent) { return {__proto__: parent};
};下面的代码是用真正的原型继承实现的Point
var Point = {
x: 0,
y: 0,
print: function () { console.log(this.x, this.y); }
};
var p = Object.create(Point);
p.x = 10;
p.y = 20;
p.print(); // 10 20总结
我们知道了原型继承是什么,以及js是怎么实现它的。
但是,原型继承的使用(Object.create and __proto__)有几个缺陷:
- 不标准:
__proto__是非标准的甚至反对这样做。而Object.create和Douglas Crockford的实现不完全等同。 - 没优化:
Object.create还没有像new构造一样优化。要慢了10倍
一些深入阅读:
- Douglas Crockford: Prototypal Inheritance
- MDC Documentation: __proto__
- John Resig: getPrototypeOf
- Javascript Garden: Object.prototype
- Dmitry Shoshnikov: OOP: ECMAScript Implementation
- Angus Croll: Understanding Javascript prototypes
- Yehuda Katz: Understanding JavaScript Function Invocation and “this”