美文网首页
JavaScript | 面向对象编程

JavaScript | 面向对象编程

作者: shawn233 | 来源:发表于2018-09-08 22:59 被阅读0次

Reference : JavaScript教程 - 廖雪峰的官方网站

JavaScript哲学:万物皆对象

JavaScript没有类 (class) 或实例 (instance) 的概念。JavaScript实现面向对象编程的工具是原型 (prototype)。

原型 prototype

以下内容展示JavaScript面向对象编程的原理。

我们定义一个对象robot

var robot = {
    name: 'Robot',
    height: 1.6,
    run: function () {
        console.log (this.name + 'is running ...');
    }
};

我们将这个对象作为模板对象,为了方便理解,重新用Student命名它。

var Student = {
    name: 'Robot',
    height: 1.6,
    run: function () {
        console.log (this.name + 'is running ...');
    }
};

现在想要创建对象xiaoming,同时让这个新的对象获得Student对象相同的属性和方法。

var xiaoming = {
    name: '小明'
};

xiaoming.__proto__ = Student;

最后一行代码把xiaoming的原型指向了对象Student,看上去xiaoming仿佛继承Student对象。

xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running ...

如果现在定义一个新的对象Bird,然后让xiaoming的原型指向Bird

var Bird = {
    fly: function () {
        console.log (this.name + 'is flying ...');   
    }
};

xiaoming.__proto__ = Bird;

这时xiaoming已经无法run()了,他已经变成了一只鸟:

xiaoming.fly(); // 小明 is flying ...

注意上面直接修改obj.__proto__的做法在开发时不可取,而且低版本的IE不支持这种写法。现在我们理解了面向对象编程的原理,接下来介绍推荐的面向对象编程方法。

面向对象编程

原型链

// 原型对象:
var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

Object.create()方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有。

var new_student = Object.create(Student);
new_student; //{}
new_student.height; // 1.6
new_student.name; // 'robot'

可以看出,尽管新的对象没有自己的属性,但实际上具有了Student对象的所有属性。基于此我们可以猜想,在获取对象属性时,先在对象内部查找,然后顺着原型依次向上查找。实际就是这样,而且如果访问到最顶层的Object.prototype对象并且还是找不到这个属性,就会返回undefined

这里引入原型链的概念。正如上面所说,JavaScript的每一个对象,其__proto__属性仍是一个对象,因此可以形成一条原型链。以内置的Array对象为例,我们可以用[]创建一个Array对象。

比如,

var arr = [1, 2, 3];

其原型链是:

arr ----> Array.prototype ----> Object.prototype ----> null

Array.prototype定义了indexOf()shift()等方法,因此我们可以在所有的Array对象上直接调用这些方法。

再举一个例子,我们可以用function关键字创建函数。

function foo () {
    return 0;
}

函数也是一个对象,它的原型链是:

foo ----> Function.prototype ----> Object.prototype ----> null

由于Function.prototype定义了apply()等方法,因此所有函数都可以调用apply()方法。

很容易想到,如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞得太长。

当然,我们可以把Object.create这个方法包装成一个创建新对象的函数。

function createStudent(name) {
    // 基于Student原型创建一个新对象:
    var s = Object.create(Student);
    // 初始化新对象:
    s.name = name;
    return s;
}

var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true

构造函数

除了直接用{...}创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。用法是定义一个构造函数,比如:

function Student (name) {
    this.name = name;
    this.hello = function () {
        alert ('Hello, ' + this.name + '!');
    }
}

这个函数虽然看上去和普通函数一样,但只要用关键字new调用这个函数,它就默认是构造函数,且在函数结束后一定返回this,无论在函数中是否写有return返回语句。

调用的写法如下:

var xiaoming = new Student ('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!

值得注意的是,这个函数如果不写new并调用,则成为了一个返回undefined的普通函数。

这样新建的xiaoming的原型链是:

xiaoming ----> Student.prototype ----> Object.prototype ----> null

此外,用new Student()类似语句创建的对象还从原型上获得了一个constructor属性,它指向Student本身。这段话用代码表示如下。

xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true

Object.getPrototypeOf(xiaoming) === Student.prototype; // true

xiaoming instanceof Student; // true

用图片表示如下,其中红色代表原型链:

对象共享方法

现在这么写,有一个问题:

xiaoming = new Student ('小明');
xiaohong = new Student ('小红');

xiaoming.hello === xiaohong.hello; // false

两个对象的方法不相等,显然浪费了内存空间,因为对于函数而言,我们只需要保存一份就可以了。存在两份的原因是每次调用new Student(),都会在构造时执行var hello一句。对这个问题,一个可行的优化是把hello的定义放在xiaomingxiaohong公共的原型上,而不是构造函数里,这样在调用hello时,就会通过原型链查找到hello。具体的代码如下:

function Student (name) {
    this.name = name;
}

Student.prototype.hello = function () {
   alert ('Hello, ' + this.name + '!');
};

优化的原理通过上面关于原型链的内容很容易理解,即用new Student()语法创建的对象,其原型都指向Student.prototype

当然了,即使有了方便的构造函数工具,我们仍然建议将new的过程封装在函数里完成。原因有二,一是不需要new来调用,避免了漏写new的可能;二是参数更灵活,参数可以不用完整地传递。

原型继承

原文:原型继承 - 廖雪峰的官方网站

function inherits( Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

关于这个函数的内容,请在原文中寻找解释。

这里需要增加的解释是为什么不直接用

PrimaryStudent.prototype = Student.prototype

可以从图中看出,prototype实际指向一个对象,如果用了上面的语句,那么PrimaryStudent就会和Student共享同一个原型对象,这样绑定到PrimaryStudent.prototype上的属性(特别是对象共享的方法)也会被绑定到Student.prototype上,因为它们实际是同一个对象,这样就不符合子类和父类之间的关系。因此可以看出,我们创建的new F()对象,就是为了独立地绑定PrimaryStudent对象共享的方法。

class关键字 [ES6]

在上面的章节中我们看到了JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。

新的关键字class正是为了简化类的定义而引入。对于下面这个用构造函数实现的Student

function Student (name) {
   this.name = name;
}

Student.prototype.hello = function () {
    alert ('Hello, ' + this.name + '!');
};

如果用新的关键字class来实现,可以这样写:

class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('Hello, ' + this.name + '!');
    }
}

显然用class的代码简介明了,既包含了构造函数constructor的定义,也包含了原先定义在原型对象上的函数hello()(注意没有function关键字),这样就避免了代码分散可能造成的理解障碍。

最后,这样定义的Student也是用new关键字调用,得到的对象与之前得到的对象用发法相同。

var xiaoming = new Student ('小明');

class继承 [ES6]

不需要考虑桥接的原型对象,直接用extends关键字完成继承。

class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 记得用super调用父类的构造方法!
        this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

几个要点:

  • class关键字引导
  • extends关键字引出父类
  • constructor定义开头用super()调用父类的构造方法

由于现在很多浏览器还不支持ES6的所有新特性,特别是class,在这里介绍一个小工具,用于将下一代的JavaScript代码转换为同义的较低版本代码:Babel - The compiler for next generation JavaScript。注:这个工具为原文推荐,而本文写作的时间比参考的文章晚3年,现在的主流数浏览器已经支持了ES6标准。

相关文章

  • 构造函数与 new 命令

    JavaScript 语言具有很强的面向对象编程能力,本章介绍 JavaScript 如何进行面向对象编程。 对象...

  • Javascript面向对象编程

    阮一峰文档备忘 Javascript 面向对象编程(一):介绍封装 Javascript 面向对象编程(二):介绍...

  • JS创建对象方案(一)

    5.1 JavaScript的面向对象 JavaScript其实支持多种编程范式的,包括函数式编程和面向对象编程:...

  • JavaScript学习笔记(一)

    Javascript面向对象 1. 面向对象编程介绍 1.1 两大编程思想 面向过程 & 面向对象 1.2 面向过...

  • JavaScript学习

    javascript面向对象 初学javascript,感觉javascript的面向对象编程还是很有意思的,在此...

  • javascript的面向对象

    javascript面向对象 初学javascript,感觉javascript的面向对象编程还是很有意思的,在此...

  • ajax

    1. 面向对象 javascript 具有面向过程,面向对象,函数式编程的特点 javascript 重要 原型/...

  • javascript 面向对象编程

    引自:阮一峰的博客Javascript面向对象编程(一):封装Javascript面向对象编程(二):构造函数的继...

  • javascript面向对象编程

    javascript面向对象编程一(封装) 通俗易懂绝对干货 JS面向对象编程

  • JavaScript学习笔记(五)

    主要源于廖雪峰老师的JavaScript教程 面向对象编程 1. 简介 JavaScript的面向对象编程和大多数...

网友评论

      本文标题:JavaScript | 面向对象编程

      本文链接:https://www.haomeiwen.com/subject/qyjlgftx.html