一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

JavaScript面向对象精要

时间:2015-09-30 编辑:简简单单 来源:一聚教程网

1. 作为函数调用,指代的是全局对象

如果我们定义以下函数:

function f() {
    console.log(this);
}

然后将其作为普通对象调用,即f(),此时this指代的是全局对象window。

2. 作为对象的方法调用,指代是调用对象本身

如果我们将f作为一个对象的方法,也就是说作如下改造:

var a = {};
a.f = f;

然后通过对象a调用方法f,即a.f(),此时this指代的就是调用者a。

3. 作为构造器函数使用,指代的隐含的新建对象

如果我们用new语句调用函数f,即new f(), 则此时this指代的是新建的对象。

4. call和apply的方式

除了上述几种方式外,我们还可以任意指定this的指代,这就是call和apply发挥作用的地方了。call和apply是每个函数对象都拥有的方法,其第一个参数就是要指定的this值,后面是函数正常的参数值。其细微的差别在于参见下面的示例:

function g(name, age) {
    this.name = name;
    this.age = age
}

var a = {};

g.call(a, 'xiaoming', 18);
g.apply(a, ['xiaoming', 18]);

即call的非this参数只需在后面列出就可以了,apply要把它们封装到一个数组里面去。

从某种程度上说,前面的三种函数调用形式都是call方式的一种语法糖:

    f() === f.call(window)
    a.f() === f.call(a)
    new f() === var _a = {}; f.call(_a)

call有很强大的能力,我们常常使用的函数借用就是利用call可以动态指定this这一特性的。例如Array.prototype.slice.call(a)可以将看起来像数组的对象a转化为实际的数组对象。

var a={length:2,0:'first',1:'second'};
Array.prototype.slice.call(a);//  ["first", "second"]
 
var a={length:2};
Array.prototype.slice.call(a);//  [undefined, undefined]

构造函数的prototype属性和对象的__proto__隐式链接

我在这里不说原型和基于原型的面向对象模式了。诚然,JavaScript确实是一门基于原型的面向对象的语言,它确实不是一门基于类模板的面向对象语言。但在这里我不对这两种面向对象模式进行讨论来。老实说,对于思想的东西,我讨论不好。我只能列出在JavaScript已经有的东西,以及这个东西能干什么。

JavaScript确实不能定义类,但却有构造函数的概念。在关于this的讨论中也提到这一点了。对于一个普通的函数,如果通过new语句的方式调用,它就变成了构造函数了。在这里我给出一个具体的例子:

function Person(name, age) {
    this.name = name;
    this.age = age;
}

new Person('xiaoming', 18); //return {name: 'xiaoming', age: 18}

一般来说,构造函数会首字母大写。这会让我们产生醒目的感觉,防止我们忘记了加入new。特别注意,new Person(name, age)跟Person(name, age)的含义是截然不同的。它们中的主要区别在于this的指代不同。对于new方式,this指代的是隐含新建的对象,是我们的意愿;对于忘记new的方式,this指代的是全局window对象,此时我们在修改window对象,是非常危险的。这也是一般不推荐new语句新建对象的原因。

每个对象都有一个神秘的链接__proto__,我们可以称之为原型(原型就是这么来的,PS:我瞎说的)。原型的用途在于,如果属性在当前的对象找不到,则一律抛到原型中去找。给个具体的示例:

var a = {};

var proto = {};
proto.b = 'hello';

a.__proto__ = proto;
a.b //=> 'hello'

(注:直接操作__proto__绝不是最佳实践)

每个函数都有一个prototype属性。一般说来,只有这个函数被当成构造函数调用时才有意义。如果一个函数通过new语句调用了,新建的对象的__proto__链接指向构造函数的prototype属性。具体的示例如下:

function F() {
    //这是构造函数
}

var a = new F();
a.__proto__ === F.prototype; //=> true

这其实给出了一种JavaScript实现面向对象的一种模式,尽管这种模式并不是最佳实践。但如果小心谨慎,也是一种不错的选择。

1. 在构造函数里定义对象的私有属性:

function Person(name, age) {
    this.name = name;
    this.age = age;
}

2. 在构造函数的prototype中定义对象的公有属性和方法:

Person.prototype.show = function() {
    return this.name + ', ' + this.age;
};

3. 通过new语句创建对象:

new Person('xiaoming', 18);

JavaScript的继承模式有很多,归根结底,要么是通过原型链的方式,要么就是属性复制的方式。


原型链

之前说过,如果某个属性在对象中找不到,就会抛到原型中去继续找。实际上这个过程能够递归进行,如果在原型中仍然找不到,就会抛到原型的原型里面继续找……直到在某个原型中找到或者原型链终止为之。这个过程给了继承的一个暗示,继承的过程就是构造原型链的过程。

如果我们有一个基类Person如下:

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.sayHello = function() {
    return 'Hello, ' + this.name;
}

现在希望新建一个Student函数,它继承自Person函数。这意味着Student对象可以调用Person原型中的方法,如sayHello;并且可以在自己的原型中定义额外的方法。

function Student(name, age, grade) {
    Person.call(this, name, age);
    this.grade = grade;
}

Student.prototype = new Person();
Student.prototype.upgrade = function() {
    this.age += 1;
    this.grade += 1;
}

在上面的示例中,我们定义了构造函数Student。它通过以下的步骤实现了继承:

1. 首先它有自己的私有属性name, age, grade. 其中我们借用了构造函数Person来初始化name和age属性(`Person.call(this, name, age)`),并且初始化了Student独有的属性grade。

2. 然后`Student.prototype = new Person()`这一步是实现继承的魔法。如果我们生成一个Student对象,它的原型关系如下(箭头指示原型方向):

Student() -> Person() -> Person.prototype

如果我们生成一个Student对象,即通过new Student()的方式,则它的原型是构造函数Student的prototype,在这里是new Person();而new Person()的原型自然就是Person的prototype属性了。这些是上面的箭头关系的解释了。由于原型链最后能够到达Person.prototype,所以Student对象可以调用Person在prototype上定义的方法,也就是继承了Person的方法。

```javascript
var s = new Student();
s.sayHello(); //没问题
```

3. 最后在Student的prototype属性即Person()对象上定义新方法upgrade。另外在Student的prototype可以覆盖Person的同名方法,因为在原型链上,Student.prototype比Person.prototype靠前。

```javascript
Student.prototype.sayHello = function() {
    alert(Person.prototype.sayHello.call(this));
}
```

一个比较经典的例子如下,这实现了继承链。

function Shape() {}

function TwoDShape() {}

function Triangle() {}

TwoDShape.prototype = new Shape();
Triangle.prototype = new TwoDShape();

//原型链是:Triangle() -> TwoDShape() -> Shape() -> Shape.prototype

拷贝继承

JavaScript作为基于原型的面向对象语言,最直接的方式就是把父对象的属性直接拷贝到子对象中去。这个地方不便展开说了,大致就是下面这个样子。

forr(name in Person.prototype) {
    Student.prototype[name] = Person.prototype[name];
}

ES5中的方式

ES5中Object对象新加入了一个方法create,可以实现继承链。如果用这种方式,上面的代码可以改写为:

//原方式为:Student.prototype = new Person();
Student.prototype = Object.create(Person.prototype);

热门栏目