图片来源于 DigitalOcean

1. 什么是类

在说 JavaScript 的面向对象的实现方法之前,我们先来看面向对象编程的一个核心概念——类(class)。类是对拥有同样属性(property)和行为的一系列对象(object)的抽象。 这里说的“行为”,在基于类的面向对象的语言中通常叫做类的方法(method)。而在 JavaScript 里,函数也是“一等公民”,可以被直接赋值给一个变量或一个对象的属性,因此在本文后续的讨论中,把“行为”也归入“属性”的范畴。

2. JavaScript 对“类”的实现

JavaScript 一开始是被设计成在网页上对表单进行校验或者对网页上的元素进行操纵的一种脚本语言,没有像 C++ 和 Java 那样用 classprivateprotected 等关键字来定义类的语法。JavaScript 采用的是一种更简单的实现方式:既然类就是拥有同样属性的一系列对象,那么只要通过一种方式能使某一些对象拥有同样的属性就行了。

JavaScript 规定每一个对象都可以有一个原型([[prototype]] 内部属性)。(在实现 ECMAScript 5.1 规范以前,除了 Object.prototype 以外的对象都必须有一个原型。)每个对象都“共享”其原型的属性:在访问一个对象的属性时,如果该对象本身没有这个属性,则 JavaScript 会继续试图访问其原型的属性。这样,就可以通过指定一些对象的原型来使这些对象都拥有同样的属性。从而我们可以这样认为,在 JavaScript 中,以同一个对象为原型的对象就是属于同一个类的对象

2.1 JavaScript 中对象的原型的指定方式

那么 JavaScript 中的对象与其原型是怎样被关联起来的呢?或者说,JavaScript 中的对象的原型是怎样被指定的呢?

2.1.1 new 操作符

JavaScript 有一个 new 操作符(operator),它基于一个函数来创建对象。这个用 new 操作符创建出来的对象的原型就是 new 操作符后面的函数(称为“构造函数”)的 prototype 属性。例如:

var a = {"aa": 1};function B() {}B.prototype = a;var b = new B();

此时 b 对象的原型就是 a 对象。我在另一篇文章中介绍了 new 操作符的具体实现逻辑,供大家参考。

2.1.2 Object.create 方法

Object.create 方法直接以给定的对象作为原型创建对象。一个代码例子:

var a = {"aa": 1};var b = Object.create(a);

此时 b 对象的原型就是 a 对象。关于 Object.create 方法的实现细节,大家可参考我的这篇文章。

2.1.3 Object.setPrototypeOf 方法

new 操作符和 Object.create 方法都是在创建一个对象的同时就指定其原型。而 Object.setPrototypeOf 方法则是指定一个已被创建的对象的原型。代码例子:

var a = {"aa": 1};var b = Object.create(a);// 此时 b 的原型是 avar c = {"cc": 2};Object.setPrototypeOf(b, c);// 此时 b 的原型变为 c 了

2.1.4 隐式指定

数字、布尔值、字符串、数组和函数在 JavaScript 中也是对象,而它们的原型是被 JavaScript 隐式指定的:

1. 数字(例如 11.1NaNInfinity)的原型是 Number.prototype

2. 布尔值(true 和 false)的原型为 Boolean.prototype

3. 字符串(例如 """abc")的原型为 String.prototype

4. 函数(例如 function () {}function (a) { return a + '1'; }) 的原型为 Function.prototype

5. 数组(如 [][1, '2'])的原型是 Array.prototype

6. 用花括号直接定义的对象(如 {}{"a": 1})的原型是 Object.prototype

2.2 JavaScript 中定义类的代码示例

下面给出定义一个类的一段 JavaScript 代码的示例。它定义一个名为 Person 的类,它的构造函数接受一个字符串的名称,还一个方法 introduceSelf 会输出自己的名字。

// ----==== 类定义开始 ====----function Person(name) {    this.name = name;}Person.prototype.introduceSelf = function () {    console.log("My name is " + this.name);};// ----==== 类定义结束 ====----// 下面实例化一个 Person 类的对象var someone = new Person("Tom");// 此时 someone 的原型为 Person.prototypesomeone.introduceSelf(); // 输出 My name is Tom

如果转换为 ECMAScript 6 引入的类声明(class declaration)语法,则上述 Person 类的定义等同于:

class Person {    constructor(name) {        this.name = name;    }    introduceSelf() {        console.log("My name is " + this.name);    }}

2.3 对“构造函数”的再思考

在上面的例子中,假如我们不通过 Person.prototype 来定义 introduceSelf 方法,而是在构造函数中给对象指定一个 introduceSelf 属性:

function Person(name) {    this.name = name;    this.introduceSelf = function () {        console.log("My name is " + this.name);    };}var someone = new Person("Tom");someone.introduceSelf(); // 也会输出 My name is Tom

虽然这种方法中,通过 Person 构造函数 new 出来的对象也都有 introduceSelf 属性,但这里 introduceSelf 变成了 someone 自身的一个属性而不是 Person 类的共有的属性:

function Person1(name) {    this.name = name;}Person1.prototype.introduceSelf = function () {    console.log("My name is " + this.name);};var a = new Person1("Tom");var b = new Person1("Jerry");console.log(a.introduceSelf === b.introduceSelf); // 输出 truedelete a.introduceSelf;a.introduceSelf(); // 仍然会输出 My name is Tom,因为 introduceSelf 不是 a 自身的属性,不会被 delete 删除b.introduceSelf = function () {    console.log("I am a pig");};Person1.prototype.introduceSelf.call(b); // 输出 My name is Jerry// 即使 b 的 introduceSelf 属性被覆盖,我们仍然可以通过 `Person1.prototype` 来让 b 执行 Person1 类规定的行为。
function Person2(name) {    this.name = name;    this.introduceSelf = function () {        console.log("My name is " + this.name);    };}a = new Person2("Tom");b = new Person2("Jerry");console.log(a.introduceSelf === b.introduceSelf); // 输出 false// a 的 introduceSelf 属性与 b 的 introduceSelf 属性是不同的对象,分别占用不同的内存空间。// 因此这种方法会造成内存空间的浪费。delete a.introduceSelf;a.introduceSelf(); // 会抛 TypeErrorb.introduceSelf = function () {    console.log("I am a pig");};// 此时 b 的行为已经与 Person2 类规定的脱节,对象 a 和对象 b 看起来已经不像是同一个类的对象了

但是这种方法也不是一无是处。例如我们需要利用闭包来实现对 name 属性的封装时:

function Person(name) {    this.introduceSelf = function () {        console.log("My name is " + name);    };}var someone = new Person("Tom");someone.name = "Jerry";someone.introduceSelf(); // 输出 My name is Tom// introduceSelf 实际用到的 name 属性已经被封装起来,在 Person 构造函数以外的地方无法访问// name 相当于 Person 类的一个私有(private)成员属性

3. JavaScript 的类继承

类的继承实际上只需要实现:

1. 子类的对象拥有父类定义的所有成员属性;

2. 子类的任何一个构造函数都必须在开头调用父类的构造函数。

实现第 2 点的方式比较直观。而怎样实现第 1 点呢?其实我们只需要让子类的构造函数的 prototype 属性 (子类的实例对象的原型) 的原型是父类的构造函数的 prototype 属性 (父类的实例对象的原型),简而言之就是:把父类实例的原型作为子类实例的原型的原型。这样在访问子类的实例对象的属性时,JavaScript 会沿着原型链找到子类规定的成员属性,再找到父类规定的成员属性。而且子类可在子类构造函数的 prototype 属性中重载(override)父类的成员属性

3.1 代码示例

下面给出一个代码示例,定义一个 ChinesePerson 类继承上文中定义的 Person 类:

function ChinesePerson(name) {    Person.apply(this, name); // 调用父类的构造函数}ChinesePerson.prototype.greet = function (other) {    console.log(other + "你好");};Object.setPrototypeOf(ChinesePerson.prototype, Person.prototype); // 将 Person.prototype 设为 ChinesePerson.prototype 的原型

var someone = new ChinesePerson("张三");someone.introduceSelf(); // 输出“My name is 张三”someone.greet("李四"); // 输出“李四你好”

上述定义 ChinesePerson 类的代码改用 ECMAScript 6 的类声明语法的话,就变成:

class ChinesePerson extends Person {    constructor(name) {        super(name);    }

    greet(other) {        console.log(other + "你好");    }}

3.1.1 重载父类成员属性的代码示例

你会不会觉得上面代码示例中,introduceSelf 输出半英文半中文挺别扭的?那我们让 ChinesePerson 类重载 introduceSelf 方法就好了:

ChinesePerson.prototype.introduceSelf = function () {    console.log("我叫" + this.name);};var someone = new ChinesePerson("张三");someone.introduceSelf(); // 输出“我叫张三”

var other = new Person("Ba Wang");other.introduceSelf(); // 输出 My name is Ba Wang// ChinesePerson 的重载并不会影响父类的实例对象

文中的兔纸图片由作者提供,


推荐阅读:

前端项目框架搭建随笔 --- Webpack 踩坑记

freeCodeCamp 高级算法

[译文] 如何在 JavaScript 中更好地使用数组

javascript 本地对象和内置对象_JavaScript 的面向对象相关推荐

  1. JavaScript中本地对象、内置对象和宿主对象

    http://www.jianshu.com/p/a52e6e183427 http://blog.csdn.net/weiyastory/article/details/52837466 http: ...

  2. JavaScript对象与内置对象——内置对象(二)

    内置对象 JavaScript中的对象分为3种:自定义对象.内置对象,浏览器对象 前两种对象是js基础内容,属于ECMAScript;的三个浏览器对象属于我们JS独有的. JavaScript提供多个 ...

  3. JavaScript内置对象(内置对象、查文档(MDN)、Math对象、日期对象、数组对象、字符串对象)

    目录 JavaScript内置对象 内置对象 查文档 MDN Math对象 Math概述 案例一:封装自己的对象 随机数方法 random() 案例一:猜数字游戏 日期对象 Date 概述 Date( ...

  4. JS基础 -- 大复习(阶段六:对象和内置对象及预解析)

    1.概念 现实生活:对象就是东西,是一个具体的事物.万物皆对象 程序角度: 对象是一组无序的数据的集合.包含属性与方法 2.对象的创建 方式1:字面量 var person = {      name ...

  5. 【 js基础 Day4】面向过程,面向对象,自定义对象,内置对象

    01 复习 函数:把一些重复的代码封装在一个地方,在需要的时候直接调用这个地方的代码就可以了 函数作用:代码重用 函数的参数: 1.形参:函数定义的时候,函数名字后面的小括号里的变量 2.实参:函数调 ...

  6. javascript对象分类(原生对象,内置对象)

    <!-- javascript对象分类 javascript的对象大致可以分为以下几种: --原生对象:又名本地对象.native object.独立于宿主环境的ECMAScript实现提供的对 ...

  7. 【融职培训】Web前端学习 第3章 JavaScript基础教程9 内置对象

    一.内置对象概述 javascript为我们提供了很多内置对象,这些内置对象为我们提供了很多语言的基本功能.我们之前学过的数组就是JavaScript的内置对象,除了数组我们还应该了解的内置对象有:M ...

  8. JavaScript 的自定义对象 、内置对象、 浏览器对象(window对象以及子对象)、Dom对象

    JS 中的 Object从本质上看,Object 是一个构造函数,用于创建对象. console.dir(Object); //ƒ Object() var obj = new Object(1); ...

  9. JS的对象与内置对象详细介绍

    感谢内容提供者:金牛区吴迪软件开发工作室 文章目录 前言 一.基本对象Object: 二.内置对象之Array: 三.内置对象之Math: 四.内置对象之Number: 五.内置对象之String: ...

  10. 对象、内置对象、数组对象、字符串对象的使用

    概念:无序的相关属性和方法的集合 组成:属性.方法 属性:特征 方法:行为 创建对象三种方式: 1.利用对象字面量创建对象{} var obj={};//创建一个空对象 var obj ={ unam ...

最新文章

  1. linux6 yum安装mysql_linux CentOS6.5 yum安装mysql5.6
  2. 解决SqlTransaction用尽的问题
  3. android 6.0 自定义application,Android6.0之App中的资源管理对象创建
  4. mysql 2048kb限制_如何解决phpmyadmin导入数据库文件最大限制2048KB
  5. android 弹起键盘把ui顶上去的解决办法
  6. c语言酒店管理系统设计目的,C语言酒店管理系统设计.doc
  7. Java中String类中compareTo( )方法
  8. Java 统计字母个数
  9. 第1章 神经网络的思想
  10. [菜鸟自学过程] ASP.NET2.0 ClientCallback脚本回调
  11. 50个技巧提高你的PHP网站程序执行效率
  12. javascript(JS)混淆工具
  13. Java二叉树的最大宽度详解版
  14. matlab eemd输出,MATLAB信号处理EEMD工具箱
  15. C++中endl、ends和flush作用:
  16. libusb-win32介绍
  17. xss漏洞学习心得(泪目)
  18. Python---Excel文件xls格式转为xlsx格式
  19. gcc posix sjij for MSYS 9.2.1+
  20. 2010年06月《安全天下事之安全的证书与证书体系的安全》

热门文章

  1. ASP.NET MVC分页实现
  2. AWS - EC2 Micro Instance 添加 SWAP 分区
  3. Spread for Windows Forms 7新功能使用指南
  4. C#是面向对象编程语言
  5. 关于SharePoint部署Webpart的十个必读链接(downmoon)
  6. pytorch中数组维度的理解
  7. reactjs高阶函数和函数柯里化
  8. scala语法注意点小结
  9. notepad++安装
  10. linux随机数示例:随机产生以139开头的电话号码