继承链(原型链)以及构造函数

JavaScript 对象都有一个 prototype 属性,这个属性的作用就是为了方便的进行 继承。一个对象的原型属性,可以被设置为另外一个对象创建的继承链的实例,也就是说,对象 B在创建的时候,可以继承对象 A 的 prototype 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Shape(name) {
  this.x = 0;
  this.y = 0;
  this.name = name;
  console.log('Shape constructor called');
}
 
Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;
  },
 
  toString: function() {
    return 'name: ' + this.name + ', at x: ' + this.x + ', y:' + this.y;
  }
};
 
// Rectangle
function Rectangle(name) {
  this.name = name;
  console.log('Rectangle constructor called');
}
 
Rectangle.prototype = new Shape();
 
var rect = new Rectangle('Player 1');
rect.move(1, 1);
console.log(rect.toString());
console.log(rect instanceof Rectangle);

此代码执行后会得到:

1
2
3
4
Shape constructor called
Rectangle constructor called
name: Player 1, at x: 1, y:1
true

结果表明,Shape 和 Rectangle 这两个构造函数( constructor )都被调用了。这是因为这句 Rectangle.prototype = new Shape();--new Rectangle() 并不会使其父对象的构造函数自动被调用,这也就是为什么两个构造函数都有 this.name = name; 了。

这里的 Shape.prototype 的方法 rect.move 和 rect.toString,当代码执行到这里的时候,如果构造函数 Rectangle 中有此方法,则就执行此方法,如果构造函数 Rectangle 中 没有此方法,则会向上寻找到 Rectangle.prototype ,如果找到,则执行,否则则返回 undefined 。如果构造函数 Rectangle 中和 Rectangle.prototype 都有此方法的话,则优先使用构造函数 Rectangle 中的方法,而不是 Rectangle.prototype 的。

调用父方法

但是,如果想要 Rectangle 使用一个不同的 move 方法,而又想重用原始的 Shape 方法的话,这时最好的方法就是使用 Function.prototype.apply:

1
2
3
4
Rectangle.prototype.move = function(x, y) {
  console.log('Super method called');
  Shape.prototype.move.apply(this, arguments);
};

虽然 Shape.prototype.move.apply 看起来很复杂, 但是如果把它拆解开来看的话,其实也比较简单。

1. 想要使用 Shape 调用 move 方法。
2. 此方法被存储在 Shape.prototype.move 中。
3. 由于是一个函数,所以有很多方法可以调用。
4. apply 方法可以调用一个函数而不用创建一个新实例。
5. 可以使用一些参数。

当函数执行的时候,arguments 对象,会被解释器所创建,其实就是传入的参数列表,类似参数的数组。而 this 则又是两一个深奥的东西了,那么下次再说。

这是一系列对于初学者的 javascript 文章,包括很多的基础知识,由于我也是初学者,所以,翻译过来,大家一起学习,这一系列就叫做 javascript 101 吧,嘿嘿,当然,一般情况下,每篇文章,基本不会超过十分钟。
那么,就先从基本的“值”来说吧。
“值”的类型
在 javascript 中,与其他语言对比,很多不起眼的或者说对其他语言来说是缺点的东西,却恰恰是 javascript 的魅力所在。
看一下下面的例子。有几种数据类型。

1
2
3
var one = 1, oneObject = new Number(1);
log(one === oneObject);
log(typeof(one), typeof(oneObject));

也可以在这里直接进行调试:http://jsfiddle.net/uWup3/.
在这个例子中,使用了“恒等号”,也即是“===”,来对比 值 “1” 和 对象(object)“new Number(1)” 是否相等,肯定的,返回了 “false” ,原因就是 “1” 是原始值,而 “new Number(1)” 的到的是一个对象,可以通过 typeof 来验证。
其他的原始值:"undefined","Null","Boolean","Number"以及 "string",对于涉及到未定义的数据的时候,其值为 “undefined”。

创建对象(object)
在 javascript 中,我们主要关注的还是对象,这也就是为什么 JSON 这么的流行和有用。其中的原因就是对象的灵活性。对于对象和原始值的主要区别就是“对象由多个项目组成”。

1
2
3
4
5
var javascript = {
  influences: ['C', 'Java', 'Scheme']
, designer: 'Brendan Eich'
, created: new Date(1995, 0, 1)
};

在此对象(javascript)中的项目(influences/designer/created)被称作“属性”,而且对于属性的值,可以是不同类型的,例如原始值,对象,数组,等。这也就是对象的灵活之处。
还有一种创建对象的方式:

1
2
3
4
5
var javascript = new Object({
  influences: ['C', 'Java', 'Scheme']
, designer: 'Brendan Eich'
, created: new Date(1995, 0, 1)
});

此种方式使用了 new Object 创建了一个对象的实例(instance),但是,虽然创建方式不一样,但是根据语言规范,这两种方式创建的对象是一样的。但是此种方式一般不推荐使用。

但是这里唯一需要注意的是,对于 constructor 函数的使用,很多种情况下 constructor 函数都是对象,另外,还有一些系统内置函数,例如 Date();

Constructors 函数
上一段代买,看起来其实不太像代码,而像一些数据的集合,那么这里我们将会创建一个 Constructor 函数(如何能使同一种功能的代码初始化),来使上段代码可以被重复利用。

1
2
3
4
5
6
7
8
9
10
11
function Language(designer, created, influences) {
  this.designer = designer;
  this.created = created;
  this.influences = influences;
}
 
var javascript = new Language(
  'Brendan Eich'
, new Date(1995, 0, 1)
, ['C', 'Java', 'Scheme']
);

这时候,同样的可以在创建一个实例:

1
2
3
4
5
var c = new Language(
  'Dennis Ritchie'
, new Date(1972, 0, 1)
, ['B', 'ALGOL']
);

此构造函数可以使用 constructor 属性来访问,例如,此时,c.constructor 将返回:

1
2
3
4
5
function Language(designer, created, influences) {
  this.designer = designer;
  this.created = created;
  this.influences = influences;
}

而 javascript.constructor 也将返回同样的代码。

在 ES5 的规范下,还有一些其他的原型对象的属性,例如,toString,还有比较陌生的 hasOwnProperty 方法。

这篇文章就讲到这里,下一篇将会介绍 prototypes (原型),javascript 中非常重要的概念。