From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../learn/javascript/objects/basics/index.html | 243 +++++++++++ files/zh-cn/learn/javascript/objects/index.html | 51 +++ .../javascript/objects/inheritance/index.html | 251 +++++++++++ .../zh-cn/learn/javascript/objects/json/index.html | 327 ++++++++++++++ .../objects/object-oriented_js/index.html | 267 ++++++++++++ .../objects/object_building_practice/index.html | 454 ++++++++++++++++++++ .../objects/object_prototypes/index.html | 357 ++++++++++++++++ .../index.html" | 468 +++++++++++++++++++++ .../index.html" | 95 +++++ 9 files changed, 2513 insertions(+) create mode 100644 files/zh-cn/learn/javascript/objects/basics/index.html create mode 100644 files/zh-cn/learn/javascript/objects/index.html create mode 100644 files/zh-cn/learn/javascript/objects/inheritance/index.html create mode 100644 files/zh-cn/learn/javascript/objects/json/index.html create mode 100644 files/zh-cn/learn/javascript/objects/object-oriented_js/index.html create mode 100644 files/zh-cn/learn/javascript/objects/object_building_practice/index.html create mode 100644 files/zh-cn/learn/javascript/objects/object_prototypes/index.html create mode 100644 "files/zh-cn/learn/javascript/objects/\345\220\221\342\200\234\345\274\271\350\267\263\347\220\203\342\200\235\346\274\224\347\244\272\347\250\213\345\272\217\346\267\273\345\212\240\346\226\260\345\212\237\350\203\275/index.html" create mode 100644 "files/zh-cn/learn/javascript/objects/\346\265\213\350\257\225\344\275\240\347\232\204\346\212\200\350\203\275_colon_\351\235\242\345\220\221\345\257\271\350\261\241\347\232\204javascript/index.html" (limited to 'files/zh-cn/learn/javascript/objects') diff --git a/files/zh-cn/learn/javascript/objects/basics/index.html b/files/zh-cn/learn/javascript/objects/basics/index.html new file mode 100644 index 0000000000..48c8646a07 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/basics/index.html @@ -0,0 +1,243 @@ +--- +title: JavaScript 对象基础 +slug: Learn/JavaScript/Objects/Basics +translation_of: Learn/JavaScript/Objects/Basics +--- +
{{LearnSidebar}}
+ +
{{NextMenu("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects")}}
+ +

在这学习JavaScript的对象的首篇文章中,我们将会学习有关对象基础的语法,并且回顾一些之前学过的JavaScript的一些特点,使你明白你所使用过的一些功能实际上是由对象提供的。

+ + + + + + + + + + + + +
前提:基础计算机基础, 了解基础的HTML 和 CSS, 熟悉 JavaScript 基础 (基础知识看这里 First steps 和这里 Building blocks).
目标:理解面向对象编程背后的基础理论, 怎样理解 JavaScript ("一切皆对象most things are objects"), 如何开始使用JavaScript对象.
+ +

对象基础

+ +

对象是一个包含相关数据和方法的集合(通常由一些变量和函数组成,我们称之为对象里面的属性和方法),让我们通过一个例子来了解它们。

+ +

首先, 将 oojs.html 文件复制到本地. 此文件包含非常少 — 一个供我们写源代码的 {{HTMLElement("script")}} 标签, 一个供我们输入示例指令的 {{HTMLElement("input")}} 标签,当页面被渲染时, 一些变量定义, 一个输出任何输入到{{HTMLElement("input")}}的内容输出到{{HTMLElement("p")}}标签的函数。我们用这个文件做为基础探索对象的基础语法.

+ +

如同Javascript中的很多东西一样,创建一个对象通常先定义初始化变量。 尝试在您已有的文件中JavaScript代码下面输入以下内容, 保存刷新页面:

+ +
var person = {};
+ +

如果你在浏览器控制台输入person,然后按下Enter(确认)键,你会得到如下结果:

+ +
[object Object]
+ +

恭喜, 你刚创建了你的第一个对象. 干的漂亮! 但这是一个空对象,所以我们做不了更多的事情。像下面一样更新下我们的对象:

+ +
var person = {
+  name : ['Bob', 'Smith'],
+  age : 32,
+  gender : 'male',
+  interests : ['music', 'skiing'],
+  bio : function() {
+    alert(this.name[0] + ' ' + this.name[1] + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
+  },
+  greeting: function() {
+    alert('Hi! I\'m ' + this.name[0] + '.');
+  }
+};
+
+ +

保存刷新后, 尝试在你的浏览器控制台输入下面的内容:

+ +
person.name[0]
+person.age
+person.interests[1]
+person.bio()
+person.greeting()
+ +

现在在你的对象里得到了一些数据和功能(functionality),现在可以通过简单的语法访问他们了!

+ +
+

Note:如果做上面的东西遇到了麻烦,尝试拿你的代码与我们的版本做对比——对比 oojs-finished.html (也可以 看实际效果)。一个对于初学者很常见的错误是在最后一个成员后面多了一个逗号,这会引发错误。

+
+ +

所以发生了什么?一个对象由许多的成员组成,每一个成员都拥有一个名字(像上面的name、age),和一个值(如['Bob', 'Smith']、32)。每一个名字/值(name/value)对被逗号分隔开,并且名字和值之间由冒号(:)分隔,语法规则如下所示:

+ +
var objectName = {
+  member1Name : member1Value,
+  member2Name : member2Value,
+  member3Name : member3Value
+}
+ +

对象成员的值可以是任意的,在我们的person对象里有字符串(string),数字(number),两个数组(array),两个函数(function)。前4个成员是资料项目,被称为对象的属性(property),后两个成员是函数,允许对象对资料做一些操作,被称为对象的方法(method)

+ +

一个如上所示的对象被称之为对象的字面量(literal)——手动的写出对象的内容来创建一个对象。不同于从类实例化一个对象,我们会在后面学习这种方式。

+ +

当你想要传输一些有结构和关联的资料时常见的方式是使用字面量来创建一个对象,举例来说,发起一个请求到服务器以存储一些数据到数据库,发送一个对象要比分别发送这些数据更有效率,而且比起数组更为易用,因为你使用名字(name)来标识这些资料。

+ +

点表示法

+ +

在上面的例子中,你使用了点表示法(dot notation)来访问对象的属性和方法。对象的名字表现为一个命名空间(namespace),它必须写在第一位——当你想访问对象内部的属性或方法时,然后是一个点(.),紧接着是你想要访问的项目,标识可以是简单属性的名字(name),或者是数组属性的一个子元素,又或者是对象的方法调用。如下所示:

+ +
person.age
+person.interests[1]
+person.bio()
+ +

子命名空间

+ +

可以用一个对象来做另一个对象成员的值。例如将name成员

+ +
name : ['Bob', 'Smith'],
+ +

改成

+ +
name : {
+  first : 'Bob',
+  last : 'Smith'
+},
+ +

这样,我们实际上创建了一个子命名空间,听起来有点复杂,但用起来很简单,你只需要链式的再使用一次点表示法,像这样:

+ +
person.name.first
+person.name.last
+ +

注意:你需要改变你之前的代码,从

+ +
name[0]
+name[1]
+ +

改成

+ +
name.first
+name.last
+ +

否则,你的方法不再有效。

+ +

括号表示法

+ +

另外一种访问属性的方式是使用括号表示法(bracket notation),替代这样的代码

+ +
person.age
+person.name.first
+ +

使用如下所示的代码:

+ +
person['age']
+person['name']['first']
+ +

这看起来很像访问一个数组的元素,从根本上来说是一回事儿,你使用了关联了值的名字,而不是索引去选择元素。难怪对象有时被称之为关联数组(associative array)了——对象做了字符串到值的映射,而数组做的是数字到值的映射。

+ +

设置对象成员

+ +

目前我们仅仅看到了如何访问对象的成员,而你其实也可以设置对象成员的值,通过声明你要设置的成员,像这样:

+ +
person.age = 45
+person['name']['last'] = 'Cratchit'
+ +

尝试这些代码,然后再查看这些成员是否已经被改变了

+ +
person.age
+person['name']['last']
+ +

设置成员并不意味着你只能更新已经存在的属性的值,你完全可以创建新的成员,尝试以下代码:

+ +
person['eyes'] = 'hazel'
+person.farewell = function() { alert("Bye everybody!") }
+ +

现在你可以测试你新创建的成员

+ +
person['eyes']
+person.farewell()
+ +

括号表示法一个有用的地方是它不仅可以动态的去设置对象成员的值,还可以动态的去设置成员的名字。

+ +

比如说,我们想让用户能够在他们的数据里存储自己定义的值类型,通过两个input框来输入成员的名字和值,通过以下代码获取用户输入的值:

+ +
var myDataName = nameInput.value
+var myDataValue = nameValue.value
+ +

我们可以这样把这个新的成员的名字和值加到person对象里:

+ +
person[myDataName] = myDataValue
+ +

为了测试这个功能,尝试在你的代码里添加以下几行,就在person对象的右花括号的下面:

+ +
var myDataName = 'height'
+var myDataValue = '1.75m'
+person[myDataName] = myDataValue
+ +

现在,保存并刷新,在输入框里输入以下代码:

+ +
person.height
+ +

这是使用点表示法无法做到的,点表示法只能接受字面量的成员的名字,不接受变量作为名字。

+ +

"this"的含义

+ +

你也许在我们的方法里注意到了一些奇怪的地方,看这个例子:

+ +
greeting: function() {
+  alert('Hi! I\'m ' + this.name.first + '.');
+}
+ +

你也许想知道"this"是什么,关键字"this"指向了当前代码运行时的对象( 原文:the current object the code is being written inside )——这里即指person对象,为什么不直接写person呢?当你学到下一篇Object-oriented JavaScript for beginners文章时,我们开始使用构造器(constructor)时,"this"是非常有用的——它保证了当代码的上下文(context)改变时变量的值的正确性(比如:不同的person对象拥有不同的name这个属性,很明显greeting这个方法需要使用的是它们自己的name)。

+ +

让我们以两个简单的person对象来说明:

+ +
var person1 = {
+  name : 'Chris',
+  greeting: function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  }
+}
+
+var person2 = {
+  name : 'Brian',
+  greeting: function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  }
+}
+ +

在这里,person1.greeting()会输出:"Hi! I'm Chris.";person2.greeting()会输出:"Hi! I'm Brain.",即使greeting这个方法的代码是一样的。就像我们之前说的,this 指向了代码所在的对象(其实代码运行时所在的对象)。在字面量的对象里this看起来不是很有用,但是当你动态创建一个对象(例如使用构造器)时它是非常有用的,之后你会更清楚它的用途。

+ +

你一直在使用对象

+ +

当你使用过这些例子之后,你可能会发现你对点表示法并不陌生,这是因为我们在这个课程里一直在使用它,每次我们学习的示例使用浏览器内建的API和JavaScript的一些对象时,我们就在使用对象,因为,这些功能是由跟我们所看到的对象同样的结构来构建的,虽然比我们自己定义的要复杂许多。

+ +

所以当我们这样使用字符串的方法时:

+ +
myString.split(',');
+ +

你正在使用一个字符串实例上可用的方法,你随时都可以在代码里使用字面量创建一个字符串,字符串会自动的被创建为字符串(String)的实例,因此会有一些常见的方法和属性可用。

+ +

当你这样访问document对象时:

+ +
var myDiv = document.createElement('div');
+var myVideo = document.querySelector('video');
+ +

你正在使用Document实例上可用的方法。每个页面在加载完毕后,会有一个Document的实例被创建,叫做document,它代表了整个页面的结构,内容和一些功能,比如页面的URL。同样的,这意味document有一些可用的方法和属性。

+ +

这同样适用许多其他内建的对象或API,你使用过有—— ArrayMath, 等。

+ +

请注意内建的对象或API不会总是自动地创建对象的实例,举例来说,这个 Notifications API——允许浏览器发起系统通知,需要你为每一个你想发起的通知都使用构造器进行实例化。尝试在JavaScript终端里输入以下代码

+ +
var myNotification = new Notification('Hello!');
+ +

我们会在之后的文章里学习到构造器。

+ +
+

Note: 这样来理解对象之间通过消息传递来通信是很有用的——当一个对象想要另一个执行某种动作时,它通常会通过那个对象的方法给其发送一些信息,并且等待回应,即我们所知的返回值。

+
+ +

总结

+ +

恭喜,你已经阅读到了我们有关JavaScript对象的第一篇文章的末尾,你现在应该对如何在JavaScript中使用对象有了很好的认识,包括你自己创建一个简单的对象。你应该清楚对象有利于存储一些相关联的数据和函数,如果你尝试以分开的方式去保存person对象包含的所有的属性和方法,这是令人沮丧且效率低下的,而且会有很多的变量和函数之间同名的风险。对象使我们将一些信息安全地锁在了它们自己的包内,防止它们被损坏。

+ +

在下一篇文章,我们将会了解面对对象编程(OOP)理论,和许多在JavaScript中使用的技巧。

+ +

{{NextMenu("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects")}}

diff --git a/files/zh-cn/learn/javascript/objects/index.html b/files/zh-cn/learn/javascript/objects/index.html new file mode 100644 index 0000000000..cb1c75af18 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/index.html @@ -0,0 +1,51 @@ +--- +title: JavaScript 对象入门 +slug: Learn/JavaScript/Objects +tags: + - CodingScripting + - JavaScript + - 初学者 + - 学习 + - 对象 + - 指南 + - 教程 + - 评估 +translation_of: Learn/JavaScript/Objects +--- +
{{LearnSidebar}}
+ +

在 JavaScript 中,大多数事物都是对象, 从作为核心功能的字符串和数组,到建立在 JavaScript 之上的浏览器 {{Glossary("API", "API")}} 。你甚至可以自己创建对象,将相关的函数和变量高效地封装打包成便捷的数据容器。对于进一步学习 JavaScript 语言知识而言,理解这种面向对象(object-oriented, OO)的特性是必不可少的,所以,我们提供了这个模块来帮助你了解这一切。这里我们会先详细介绍对象的理论和语法,再介绍如何创建对象。

+ +

预备知识

+ +

开始这个模块之前,你应当已经对 HTML 和 CSS 有所了解。我们建议你通读 HTML 入门CSS 入门模块,再开始了解 JavaScript。

+ +

详细了解 JavaScript 对象之前,你应当已经对 JavaScript 基础有所熟悉。尝试这个模块之前,请通读 JavaScript 第一步 和 JavaScript基础要件

+ +
+

注意:如果您无法在当前使用的电脑/平板/其他设备上创建自己的文件,可以使用在线编程网站如 JSBinThimble,来试验文章中的(大多数)代码。

+
+ +

指南

+ +
+
对象基础
+
在了解 JavaScript 对象的第一篇文章中,我们将介绍 JavaScript 对象的语法,并回顾先前课程中讲过的某些 JavaScript 功能。你会发现,你已经在使用的很多功能本质上都是对象。
+
适合初学者的面向对象 JavaScript
+
了解基础后,我们将关注面向对象 JavaScript (OOJS)。本文将介绍面向对象编程 (OOP) 的基本理论,然后讲解 JavaScript 如何通过构造器 (constructor) 函数模拟对象类别 (class)、如何创建对象实例 (instance)。
+
对象原型
+
通过原型 (prototype) 这种机制,JavaScript 中的对象从其他对象继承功能特性;这种继承机制与经典的面向对象编程语言不同。本文将探讨这些差别,解释原型链如何工作,并了解如何通过 prototype 属性向已有的构造器添加方法。
+
JavaScript 中的继承
+
了解了 OOJS 的大多数细节之后,本文将介绍如何创建“子”对象类别(构造器)并从“父”类别中继承功能。此外,我们还会针对何时何处使用 OOJS 给出建议。
+
使用 JSON 数据
+
JavaScript Object Notation (JSON) 是一种将结构化数据表达为 JavaScript 对象的标准格式,其常用于在网站上表达或传输数据(比如:从服务器向客户端发送数据,使之显示在网页上)。你会经常遇到它,因此本文将告诉你如何在 JavaScript 中使用 JSON 数据,包括访问 JSON 对象中的数据条目、编写自己的 JSON 数据等等。
+
构建对象实战
+
在前面的文章中我们了解了 JavaScript 对象基本理论和语法,为你打下坚实的基础。本文中你需要进行实战练习,通过构建自定义 JavaScript 对象的实践过程,编写一个有趣而又多彩的程序——“彩色弹跳球”。
+
+ +

学习评估

+ +
+
向“弹跳球”演示程序添加新功能
+
在这个评估中,你需要以上一篇文章中的“弹跳球”演示为起点,向这个演示程序新增一些有趣的功能。
+
diff --git a/files/zh-cn/learn/javascript/objects/inheritance/index.html b/files/zh-cn/learn/javascript/objects/inheritance/index.html new file mode 100644 index 0000000000..c7b564d978 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/inheritance/index.html @@ -0,0 +1,251 @@ +--- +title: JavaScript 中的继承 +slug: Learn/JavaScript/Objects/Inheritance +tags: + - JavaScript + - OOJS + - 原型 + - 对象 + - 继承 + - 面向对象JS +translation_of: Learn/JavaScript/Objects/Inheritance +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}
+ +

了解了 OOJS 的大多数细节之后,本文将介绍如何创建“子”对象类别(构造器)并从“父”类别中继承功能。此外,我们还会针对何时何处使用 OOJS 给出建议。

+ + + + + + + + + + + + +
预备知识:基本的计算机素养,对 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基础(参见 First stepsBuilding blocks)以及面向对象的JavaScript (OOJS) 基础(参见 Introduction to objects)。
目标:理解在 JavaScript 中如何实现继承。
+ +

原型式的继承

+ +

到目前为止我们已经了解了一些关于原型链的实现方式以及成员变量是如何通过它来实现继承,但是之前涉及到的大部分都是浏览器内置函数(比如 StringDateNumber 和 Array),那么我们如何创建一个继承自另一对象的JavaScript对象呢?

+ +

正如前面课程所提到的,有些人认为JavaScript并不是真正的面向对象语言,在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类(参考C++ inheritance里的一些简单的例子),JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承(通常被称为 原型式继承 —— prototypal inheritance

+ +

让我们通过具体的例子来解释上述概念

+ +

开始

+ +

首先,将oojs-class-inheritance-start.html文件复制到您本地(也可以 在线运行 ),其中您能看到一个只定义了一些属性的Person()构造器,与之前通过模块来实现所有功能的Person的构造器类似。

+ +
function Person(first, last, age, gender, interests) {
+  this.name = {
+    first,
+    last
+  };
+  this.age = age;
+  this.gender = gender;
+  this.interests = interests;
+};
+
+ +

所有的方法都定义在构造器的原型上,比如:

+ +
Person.prototype.greeting = function() {
+  alert('Hi! I\'m ' + this.name.first + '.');
+};
+
+ +
+

注意:在源代码中,你可以看到已定义的bio()farewell()方法。随后,你将看到它们被其他的构造器所继承。

+
+ +

比如我们想要创建一个Teacher类,就像我们前面在面向对象概念解释时用的那个一样。这个类会继承Person的所有成员,同时也包括:

+ +
    +
  1. 一个新的属性,subject——这个属性包含了教师教授的学科。
  2. +
  3. 一个被更新的greeting()方法,这个方法打招呼听起来比一般的greeting()方法更正式一点——对于一个教授一些学生的老师来说。
  4. +
+ +

定义 Teacher() 构造器函数

+ +

我们要做的第一件事是创建一个Teacher()构造器——将下面的代码加入到现有代码之下:

+ +
function Teacher(first, last, age, gender, interests, subject) {
+  Person.call(this, first, last, age, gender, interests);
+
+  this.subject = subject;
+}
+
+ +

这在很多方面看起来都和Person的构造器很像,但是这里有一些我们从没见过的奇怪玩意——call()函数。基本上,这个函数允许您调用一个在这个文件里别处定义的函数。第一个参数指明了在您运行这个函数时想对“this”指定的值,也就是说,您可以重新指定您调用的函数里所有“this”指向的对象。其他的变量指明了所有目标函数运行时接受的参数。

+ +
+

注:在这个例子里我们在创建一个新的对象实例时同时指派了继承的所有属性,但是注意您需要在构造器里将它们作为参数来指派,即使实例不要求它们被作为参数指派(比如也许您在创建对象的时候已经得到了一个设置为任意值的属性)

+
+ +

所以在这个例子里,我们很有效的在Teacher()构造函数里运行了Person()构造函数(见上文),得到了和在Teacher()里定义的一样的属性,但是用的是传送给Teacher(),而不是Person()的值(我们简单使用这里的this作为传给call()this,意味着this指向Teacher()函数)。

+ +

在构造器里的最后一行代码简单地定义了一个新的subject属性,这将是教师会有的,而一般人没有的属性。

+ +

顺便提一下,我们本也可以这么做:

+ +
function Teacher(first, last, age, gender, interests, subject) {
+  this.name = {
+    first,
+    last
+  };
+  this.age = age;
+  this.gender = gender;
+  this.interests = interests;
+  this.subject = subject;
+}
+
+ +

但是这只是重新定义了一遍属性,并不是将他们从Person()中继承过来的,所以这违背了我们的初衷。这样写也会需要更长的代码。

+ +

从无参构造函数继承

+ +

请注意,如果您继承的构造函数不从传入的参数中获取其属性值,则不需要在call()中为其指定其他参数。所以,例如,如果您有一些相当简单的东西:

+ +
function Brick() {
+  this.width = 10;
+  this.height = 20;
+}
+ +

您可以这样继承widthheight属性(以及下面描述的其他步骤):

+ +
function BlueGlassBrick() {
+  Brick.call(this);
+
+  this.opacity = 0.5;
+  this.color = 'blue';
+}
+ +

请注意,我们仅传入了thiscall()中 - 不需要其他参数,因为我们不会继承通过参数设置的父级的任何属性。

+ +

设置 Teacher() 的原型和构造器引用

+ +

到目前为止一切看起来都还行,但是我们遇到问题了。我们已经定义了一个新的构造器,这个构造器默认有一个空的原型属性。我们需要让Teacher()Person()的原型对象里继承方法。我们要怎么做呢?

+ +
    +
  1. 在您先前添加的代码的下面增加以下这一行: +
    Teacher.prototype = Object.create(Person.prototype);
    + 这里我们的老朋友create()又来帮忙了——在这个例子里我们用这个函数来创建一个和Person.prototype一样的新的原型属性值(这个属性指向一个包括属性和方法的对象),然后将其作为Teacher.prototype的属性值。这意味着Teacher.prototype现在会继承Person.prototype的所有属性和方法。
  2. +
  3. 接下来,在我们动工之前,还需要完成一件事 — 现在Teacher()prototypeconstructor属性指向的是Person(), 这是由我们生成Teacher()的方式决定的。(这篇 Stack Overflow post 文章会告诉您详细的原理) — 将您写的页面在浏览器中打开,进入JavaScript控制台,输入以下代码来确认: +
    Teacher.prototype.constructor
    +
  4. +
  5. 这或许会成为很大的问题,所以我们需要将其正确设置——您可以回到源代码,在底下加上这一行代码来解决: +
    Teacher.prototype.constructor = Teacher;
    +
  6. +
  7. 当您保存并刷新页面以后,输入Teacher.prototype.constructor就会得到Teacher()
  8. +
+ +
+

注:每一个函数对象(Function)都有一个prototype属性,并且只有函数对象有prototype属性,因为prototype本身就是定义在Function对象下的属性。当我们输入类似var person1=new Person(...)来构造对象时,JavaScript实际上参考的是Person.prototype指向的对象来生成person1。另一方面,Person()函数是Person.prototype的构造函数,也就是说Person===Person.prototype.constructor(不信的话可以试试)。

+ +

在定义新的构造函数Teacher时,我们通过function.call来调用父类的构造函数,但是这样无法自动指定Teacher.prototype的值,这样Teacher.prototype就只能包含在构造函数里构造的属性,而没有方法。因此我们利用Object.create()方法将Person.prototype作为Teacher.prototype的原型对象,并改变其构造器指向,使之与Teacher关联。

+ +

任何您想要被继承的方法都应该定义在构造函数的prototype对象里,并且永远使用父类的prototype来创造子类的prototype,这样才不会打乱类继承结构。

+
+ +

向 Teacher() 添加一个新的greeting()函数

+ +

为了完善代码,您还需在构造函数Teacher()上定义一个新的函数greeting()。最简单的方法是在Teacher的原型上定义它—把以下代码添加到您代码的底部:

+ +
Teacher.prototype.greeting = function() {
+  var prefix;
+
+  if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
+    prefix = 'Mr.';
+  } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
+    prefix = 'Mrs.';
+  } else {
+    prefix = 'Mx.';
+  }
+
+  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
+};
+ +

这样就会出现老师打招呼的弹窗,老师打招呼会使用条件结构判断性别从而使用正确的称呼。

+ +

范例尝试

+ +

现在我们来键入代码,将下面的代码放到您的 JavaScript 代码下面从而来创建一个 Teacher() 对象实例。

+ +
var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');
+ +

当您保存代码并刷新的时候,试一下您的老师实例的属性和方法:

+ +
teacher1.name.first;
+teacher1.interests[0];
+teacher1.bio();
+teacher1.subject;
+teacher1.greeting();
+ +

前面三个进入到从Person()的构造器 继承的属性和方法,后面两个则是只有Teacher()的构造器才有的属性和方法。

+ +
+

注:如果您在这里遇到了问题,请对比您的代码与我们的完成版本(或查看可运行的在线示例)。

+
+ +

我们在这里讲述的技巧并不是 JavaScript 中创建继承类的唯一方式,但是这个技巧也还不错,非常好地告诉了您如何在 JavaScript 中实行继承操作。

+ +

您可能对在 JavaScript中使用其他方法来实行继承会感兴趣(参见 Classes)。我们没有覆盖那些内容,因为并不是每种浏览器都会支持这些方法。我们在这一系列文章中介绍的所有其他方法都会被 IE9 支持或者更老的浏览器支持,也有一些方法可以支持更老的浏览器。

+ +

一个常用的方法是使用 JavaScript 语言库——最热门的一些库提供一些方法让我们更快更好地实行继承。比如 CoffeeScript 就提供一些类和扩展。

+ +

更多练习

+ +

在我们的 OOP theory section 模块中, 我们也将学生类作为一个概念,继承了 Person 所有的属性和方法,也有一个不同的打招呼的方法(比老师的打招呼轻松随意一些)。您可以自己尝试一下如何实现。

+ +
+

注:如果你编写时遇到困难,代码无法运行,那么可以查看我们的完成版本(也可查看 可运行的在线示例)。

+
+ +

对象成员总结

+ +

总结一下,您应该基本了解了以下三种属性或者方法:

+ +
    +
  1. 那些定义在构造器函数中的、用于给予对象实例的。这些都很容易发现 - 在您自己的代码中,它们是构造函数中使用this.x = x类型的行;在内置的浏览器代码中,它们是可用于对象实例的成员(通常通过使用new关键字调用构造函数来创建,例如var myInstance = new myConstructor())。
  2. +
  3. 那些直接在构造函数上定义、仅在构造函数上可用的。这些通常仅在内置的浏览器对象中可用,并通过被直接链接到构造函数而不是实例来识别。 例如Object.keys()
  4. +
  5. 那些在构造函数原型上定义、由所有实例和对象类继承的。这些包括在构造函数的原型属性上定义的任何成员,如myConstructor.prototype.x()
  6. +
+ +

如果您现在觉得一团浆糊,别担心——您现在还处于学习阶段,不断练习才会慢慢熟悉这些知识。

+ +

何时在 JavaScript 中使用继承?

+ +

特别是在读完这段文章内容之后,您也许会想 "天啊,这实在是太复杂了". 是的,您是对的,原型和继承代表了JavaScript这门语言里最复杂的一些方面,但是JavaScript的强大和灵活性正是来自于它的对象体系和继承方式,这很值得花时间去好好理解下它是如何工作的。

+ +

在某种程度上来说,您一直都在使用继承 - 无论您是使用WebAPI的不同特性还是调用字符串、数组等浏览器内置对象的方法和属性的时候,您都在隐式地使用继承。

+ +

就在自己代码中使用继承而言,您可能不会使用的非常频繁,特别是在小型项目中或者刚开始学习时 - 因为当您不需要对象和继承的时候,仅仅为了使用而使用它们只是在浪费时间而已。但是随着您的代码量的增大,您会越来越发现它的必要性。如果您开始创建一系列拥有相似特性的对象时,那么创建一个包含所有共有功能的通用对象,然后在更特殊的对象类型中继承这些特性,将会变得更加方便有用。

+ +
+

注: 考虑到JavaScript的工作方式,由于原型链等特性的存在,在不同对象之间功能的共享通常被叫做 委托 - 特殊的对象将功能委托给通用的对象类型完成。这也许比将其称之为继承更为贴切,因为“被继承”了的功能并没有被拷贝到正在“进行继承”的对象中,相反它仍存在于通用的对象中。

+
+ +

在使用继承时,建议您不要使用过多层次的继承,并仔细追踪定义方法和属性的位置。很有可能您的代码会临时修改了浏览器内置对象的原型,但您不应该这么做,除非您有足够充分的理由。过多的继承会在调试代码时给您带来无尽的混乱和痛苦。

+ +

总之,对象是另一种形式的代码重用,就像函数和循环一样,有他们特定的角色和优点。如果您发现自己创建了一堆相关的变量和函数,还想一起追踪它们并将其灵活打包的话,对象是个不错的主意。对象在您打算把一个数据集合从一个地方传递到另一个地方的时候非常有用。这些都可以在不使用构造器和继承的情况下完成。如果您只是需要一个单一的对象实例,也许使用对象常量会好些,您当然不需要使用继承。

+ +

总结

+ +

这篇文章覆盖了剩余的 OOJS 理论的核心知识和我们认为您应该知道的语法,这个时候您应该理解了 JavaScript 中的对象和 OOP 基础,原型和原型继承机制,如何创建类(constructors)和对象实例,为类增加功能,通过从其他类继承而创建新的子类。

+ +

下一篇文章我们将学习如何运用 JavaScript Object Notation (JSON), 一种使用 JavaScript 对象写的数据传输格式。

+ +

参见

+ + + +

{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}

diff --git a/files/zh-cn/learn/javascript/objects/json/index.html b/files/zh-cn/learn/javascript/objects/json/index.html new file mode 100644 index 0000000000..c6963d261f --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/json/index.html @@ -0,0 +1,327 @@ +--- +title: 使用JSON +slug: Learn/JavaScript/Objects/JSON +tags: + - Working with JSON data +translation_of: Learn/JavaScript/Objects/JSON +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects/Object_building_practice", "Learn/JavaScript/Objects")}}
+ +

JavaScript对象表示法(JSON)是用于将结构化数据表示为JavaScript对象的标准格式,通常用于在网站上表示和传输数据(例如从服务器向客户端发送一些数据,因此可以将其显示在网页上)。您会经常遇到它,所以在本文中,我们向您提供使用JavaScript处理JSON的所有工作,包括访问JSON对象中的数据项并编写自己的JSON。

+ + + + + + + + + + + + +
前提:计算机基础知识,HTML 和 CSS 基础 (see First steps and Building blocks) 和 JS 面向对象基础(see Introduction to objects)。
目标:理解 JSON 的数据储存工作原理,创建您的 JSON 对象。
+ +

什么是 JSON?

+ +

{{glossary("JSON")}} 是一种按照JavaScript对象语法的数据格式,这是 Douglas Crockford 推广的。虽然它是基于 JavaScript 语法,但它独立于JavaScript,这也是为什么许多程序环境能够读取(解读)和生成 JSON。 

+ +

JSON可以作为一个对象或者字符串存在,前者用于解读 JSON 中的数据,后者用于通过网络传输 JSON 数据。 这不是一个大事件——JavaScript 提供一个全局的 可访问的 JSON 对象来对这两种数据进行转换。

+ +

一个 JSON 对象可以被储存在它自己的文件中,这基本上就是一个文本文件,扩展名为 .json, 还有 {{glossary("MIME type")}} 用于 application/json.

+ +

JSON 结构

+ +

我们已经可以推测出 JSON 对象就是基于 JavaScript 对象,而且这几乎是正确的。您可以把 JavaScript 对象原原本本的写入 JSON 数据——字符串,数字,数组,布尔还有其它的字面值对象。这允许您构造出一个对象树,如下:

+ +
{
+  "squadName" : "Super hero squad",
+  "homeTown" : "Metro City",
+  "formed" : 2016,
+  "secretBase" : "Super tower",
+  "active" : true,
+  "members" : [
+    {
+      "name" : "Molecule Man",
+      "age" : 29,
+      "secretIdentity" : "Dan Jukes",
+      "powers" : [
+        "Radiation resistance",
+        "Turning tiny",
+        "Radiation blast"
+      ]
+    },
+    {
+      "name" : "Madame Uppercut",
+      "age" : 39,
+      "secretIdentity" : "Jane Wilson",
+      "powers" : [
+        "Million tonne punch",
+        "Damage resistance",
+        "Superhuman reflexes"
+      ]
+    },
+    {
+      "name" : "Eternal Flame",
+      "age" : 1000000,
+      "secretIdentity" : "Unknown",
+      "powers" : [
+        "Immortality",
+        "Heat Immunity",
+        "Inferno",
+        "Teleportation",
+        "Interdimensional travel"
+      ]
+    }
+  ]
+}
+ +

如果我们要加载对象进入 JavaScript 程序,以保存为一个名为 superHeroes 对象为例,我们使用 . 或 [] 访问对象内的数据(关于. 和 []概念,见 对象基础 )。如:

+ +
superHeroes.hometown
+superHeroes["active"]
+ +

为了访问对象中的对象,您只需简单地链式访问(通过属性名和数组索引)。例如,访问 superHeroes 对象中的 members 数组对象的第二个元素的 powers 数组对象的第三个元素,您可以这样做:

+ +
superHeroes["members"][1]["powers"][2]
+ +
    +
  1. 首先我们有变量名 superHeroes,储存对象 。
  2. +
  3. 在对象中我们想访问 members 属性,所以我们使用 ["members"]
  4. +
  5. members 包含有对象数组,我们想要访问第二个元素,所以我们使用[1]
  6. +
  7. 在对象内,我们想访问 powers 属性,所以我们使用 ["powers"]
  8. +
  9. powers 属性是一个包含英雄技能的数组。我们想要第三个,所以我们使用[2]
  10. +
+ +
+

注:我们已经在 JSONText.html 实例中让JSON 对象进入变量中使其可访问(见源代码)。尝试加载它并且在您的浏览器上访问对象数据。

+
+ +

JSON 数组

+ +

前面我们已经说过,”我们已经可以推测出 JSON 对象就是基于 JavaScript 对象,而且这几乎是正确的“——我们说几乎正确的原因是数组对象也是一种合法的 JSON 对象,例如:

+ +
[
+  {
+    "name" : "Molecule Man",
+    "age" : 29,
+    "secretIdentity" : "Dan Jukes",
+    "powers" : [
+      "Radiation resistance",
+      "Turning tiny",
+      "Radiation blast"
+    ]
+  },
+  {
+    "name" : "Madame Uppercut",
+    "age" : 39,
+    "secretIdentity" : "Jane Wilson",
+    "powers" : [
+      "Million tonne punch",
+      "Damage resistance",
+      "Superhuman reflexes"
+    ]
+  }
+]
+ +

上面是完全合法的 JSON。您只需要通过数组索引就可以访问数组元素,如[0]["powers"][0]。

+ +

其他注意事项

+ + + +

主动学习 : 一个JSON 示例

+ +

好了,让我们通过运行这个示例来展示我们如何利用JSON数据。

+ +

开始吧

+ +

首先,拷贝我们的 heroes.html 和 style.css 文件。后者包含了用于页面的简单的 CSS ,前者包含了简单的 HTML  body。

+ +
<header>
+</header>
+
+<section>
+</section>
+ +

添加 <script>元素来包含我们的 JavaScript 代码。当前它只有两行,获得了<header><section>的引用,保存在变量中。

+ +
var header = document.querySelector('header');
+var section = document.querySelector('section');
+
+ +

我们已经把 JSON 数据放在了GitHub 上面:https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json

+ +

我们准备把它加载到我们的页面中,然后使用漂亮的 DOM 操作来展示它,就像这样:

+ +

+ +

加载我们的JSON

+ +

为了载入 JSON 到页面中,我们将使用 一个名为XMLHTTPRequest的API(常称为XHR)。这是一个非常有用的 JavaScript 对象,使我们能够通过代码来向服务器请求资源文件(如:图片,文本,JSON,甚至HTML片段),意味着我们可以更新小段内容而不用重新加载整个页面。这将有更多响应页面,听起来让人兴奋,但是这部分超出我们本部分的文章,所以就不多详述了。

+ +
    +
  1. 首先,我们将保存一个即将访问的 URL 作为变量。在您的 JavaScript 代码的底部添加下面的代码: +
    var requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json';
    +
  2. +
  3. 为了创建一个HTTP请求,我们需要创建一个HTTP请求对象,通过 new 构造函数的形式。在您最下面的代码中写入: +
    var request = new XMLHttpRequest();
    +
  4. +
  5. 现在我们需要使用 open() 函数打开一个新的请求,添加如下代码: +
    request.open('GET', requestURL);
    + +

    这个函数至少含有两个参数,其它的是可选参数。对于示例我们只需要两个强制参数

    + +
      +
    • HTTP 方法,网络连接时使用。这个示例中 GET 就可以了,因为我们只要获得简单的数据。
    • +
    • URL,用于指向请求的地址。我们使用之前保存的变量。
    • +
    +
  6. +
  7. 接下来,添加,两行代码,我们设定 responseType 为 JSON,所以服务器将知道我们想要返回一个 JSON 对象,然后发送请求 : +
    request.responseType = 'json';
    +request.send();
    +
  8. +
  9. 最后一点内容涉及相应来自服务器的返回数据,然后处理它,添加如下代码在您先前的代码下方: +
    request.onload = function() {
    +  var superHeroes = request.response;
    +  populateHeader(superHeroes);
    +  showHeroes(superHeroes);
    +}
    +
  10. +
+ +

这儿我们保存了相应我们请求的数据(访问 response 属性) 于变量 superHeroes ;这个变量现在含有 JSON!我们现在把superHeroes传给两个函数,第一个函数将会用正确的数据填充<header>,同时第二个函数将创建一个信息卡片,然后把它插入<section>中。

+ +

我们把代码包在事件处理函数中,当请求对象load事件触发时执行代码(onload),这是因为请求对象load事件只有在请求成功时触发;这种方式可以保证事件触发时 request.response 是绝对可以访问的。

+ +

定位 header

+ +

现在我们已经获得我们的JSON数据,让我们利用它来写两个我们使用的函数。首先,添加下面的代码于之前的代码下方:

+ +
function populateHeader(jsonObj) {
+  var myH1 = document.createElement('h1');
+  myH1.textContent = jsonObj['squadName'];
+  header.appendChild(myH1);
+
+  var myPara = document.createElement('p');
+  myPara.textContent = 'Hometown: ' + jsonObj['homeTown'] + ' // Formed: ' + jsonObj['formed'];
+  header.appendChild(myPara);
+}
+ +

我们称参数为 jsonObj,那也是为什么我们要在其中调用 JSON 对象。这儿我们首先使用 createElement() 创建了一个 <h1> 节点,将它的 textContent 设为 JSON 对象的 squadName 属性,然后通过 appendChild() 把它加入 <header>中。然后我们对段落做了相同的一件事情:创建,设置内容,追加到 <header>。唯一的不同在于它的内容设为一个与 JSON 内属性 homeTown formed相关联的字符串。

+ +

创建英雄信息卡片

+ +

接下来,添加如下的函数到脚本代码底部,这个函数创建和展示了superhero cards

+ +
function showHeroes(jsonObj) {
+  var heroes = jsonObj['members'];
+
+  for(i = 0; i < heroes.length; i++) {
+    var myArticle = document.createElement('article');
+    var myH2 = document.createElement('h2');
+    var myPara1 = document.createElement('p');
+    var myPara2 = document.createElement('p');
+    var myPara3 = document.createElement('p');
+    var myList = document.createElement('ul');
+
+    myH2.textContent = heroes[i].name;
+    myPara1.textContent = 'Secret identity: ' + heroes[i].secretIdentity;
+    myPara2.textContent = 'Age: ' + heroes[i].age;
+    myPara3.textContent = 'Superpowers:';
+
+    var superPowers = heroes[i].powers;
+    for(j = 0; j < superPowers.length; j++) {
+      var listItem = document.createElement('li');
+      listItem.textContent = superPowers[j];
+      myList.appendChild(listItem);
+    }
+
+    myArticle.appendChild(myH2);
+    myArticle.appendChild(myPara1);
+    myArticle.appendChild(myPara2);
+    myArticle.appendChild(myPara3);
+    myArticle.appendChild(myList);
+
+    section.appendChild(myArticle);
+  }
+}
+ +

首先,我们保存了 JSON 的 members 属性作为一个变量。这个数组含有多个带有英雄信息的对象。

+ +

接下来,我们使用一个循环来,遍历每个元素。对于每一个元素,我们:

+ +
    +
  1. 创建几个元素: 一个 <article>,一个 <h2>, 三个 <p>s, 和一个 <ul>。
  2. +
  3. 设置 <h2> 为当前英雄的 name
  4. +
  5. 使用他们的secretIdentity, age, "Superpowers:" 介绍信息列表 填充三个段落来。
  6. +
  7. 保存 powers 属性于另一个变量 superPowers,包含英雄的superpowers列表。
  8. +
  9. 使用另一个循环来遍历当前的英雄的 superpowers ,对于每一个元素我们创建<li>元素,把superpower放进去,然后使用appendChild()把 listItem 放入<ul> 元素中。
  10. +
  11. 最后一件事情是追加<h2>,<p>,还有<ul>进入 <article> (myArticle)。然后将<article> 追加到 <section>。追加的顺序很重要,因为他们将被展示在 HTML 中。
  12. +
+ +
+

Note: 如有疑难,试试引用我们的 heroes-finished.html 代码(也可见 running live )。

+
+ +
+

Note: 如果您对访问 JSON对象的 点/括号标记 有困扰。获得文件 superheroes.json 并在您的编辑器中打开参考我们的 JS 代码将会有帮助。您还应该参考我们的 JavaScript object basics文章,了解关于点和括号符号的更多信息。

+
+ +

对象和文本间的转换

+ +

上述示例就访问 JSON 而言是简单的,因为我们设置了 XHR 来访问 JSON 格式数据: 

+ +
request.responseType = 'json';
+ +

但是有时候我们没有那么幸运,我们接收到一些 字符串作为 JSON 数据,然后我们想要将它转换为对象。当我们想要发送 JSON 数据作为信息,我们将需要转换它为字符串,我们经常需要正确的转换数据,幸运的是,这两个问题在web环境中是那么普遍以至于浏览器拥有一个内建的 JSON,包含以下两个方法。

+ + + +

您可以看看我们 heroes-finished-json-parse.html 示例的第一个操作 (见 source code) ,除了返回的是 text,这做了一件与我们之前一模一样的事情,然后使用 parse() 来将他转换成为 JavaScript 对象。关键片段如下:

+ +
request.open('GET', requestURL);
+request.responseType = 'text'; // now we're getting a string!
+request.send();
+
+request.onload = function() {
+  var superHeroesText = request.response; // get the string from the response
+  var superHeroes = JSON.parse(superHeroesText); // convert it to an object
+  populateHeader(superHeroes);
+  showHeroes(superHeroes);
+}
+ +

正如您所想, stringify() 做相反的事情. 尝试将下面的代码输入您的浏览器 JS 控制台来看看会发生什么:

+ +
var myJSON = { "name" : "Chris", "age" : "38" };
+myJSON
+var myString = JSON.stringify(myJSON);
+myString
+ +

这儿我们创建了一个JavaScript 对象,然后检查了它包含了什么,然后用stringify() 将它转换成JSON字符串,最后保存返回值作为变量。然后再一次检查。

+ +

总结

+ +

在这个文章中,我们给了您一个简单的示例来在自己的程序中使用 JSON,包括创建和处理 JSON,还有如何访问 JSON 内的数据。在下一篇文章中我们将开始关注JS中的面向对象内容。

+ +

参见

+ + + +

{{PreviousMenuNext("Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects/Object_building_practice", "Learn/JavaScript/Objects")}}

diff --git a/files/zh-cn/learn/javascript/objects/object-oriented_js/index.html b/files/zh-cn/learn/javascript/objects/object-oriented_js/index.html new file mode 100644 index 0000000000..e20c345337 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/object-oriented_js/index.html @@ -0,0 +1,267 @@ +--- +title: 适合初学者的JavaScript面向对象 +slug: Learn/JavaScript/Objects/Object-oriented_JS +translation_of: Learn/JavaScript/Objects/Object-oriented_JS +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}
+ +

学完基础后, 现在我们集中于面向对象的 JavaScript (OOJS) — 本文首先提出了面向对象编程(OOP) 理论的基本观点,然后探索如何通过构造函数模拟对象类,以及如何创建对象.

+ + + + + + + + + + + + +
预备知识:计算机基础知识, 了解 HTML 和 CSS, 熟悉 JavaScrpit 基础知识 (查看 First stepsBuilding blocks) 和 OOJS 基础 (查看 Introduction to objects).
目标:掌握面向对象程序的基本理论, 这涉及到 JavaScript  的("万物皆对象"), 以及如何创建构造器和对象.
+ +

从零开始面向对象的程序设计

+ +

首先,我们从高维度且简化的角度看看 面向对象的程序(Object-oriented programming ,OOP)是什么。我们将简单描述OOP,因为OOP该概念已变得很复杂,如果完整地描述OOP将使读者难以理解。OOP 的基本思想是:在程序里,我们通过使用对象去构建现实世界的模型,把原本很难(或不可)能被使用的功能,简单化并提供出来,以供访问。

+ +

对象可以包含相关的数据和代码,这些数据和代码用于表示 你所建造的模型是什么样子,以及拥有什么样的行为或功能。对象包(object package,或者叫命名空间 namespace)存储(官方用语:封装)着对象的数据(常常还包括函数),使数据的组织和访问变得更容易了;对象也常用作 数据存储体(data stores),用于在网络上运输数据,十分便捷。

+ +

定义一个对象模板

+ +

让我们来考虑一个简单的程序,它可以显示一个学校的学生和老师的信息.在这里我们不讨论任何程序语言,我们只讨论 OOP 思想.

+ +

首先,我们可以回到上一节拿到定义好属性和方法的Person对象。对于一个人(person)来说,我们能在他们身上获取到很多信息(他们的住址,身高,鞋码,基因图谱,护照信息,显著的性格特征等等),然而,我们仅仅需要他们的名字,年龄,性别,兴趣 这些信息,然后,我们会基于他们的这些信息写一个简短的介绍关于他们自己,在最后我们还需要教会他们打招呼。以上的方式被称为抽象-为了我们编程的目标而利用事物的一些重要特性去把复杂的事物简单化

+ +

+ +

在一些面向对象的语言中,我们用类(class)的概念去描述一个对象(您在下面就能看到JavaScript使用了一个完全不同的术语)-类并不完全是一个对象,它更像是一个定义对象特质的模板。

+ +

创造一个真正的对象

+ +

从上面我们创建的class中, 我们能够基于它创建出一些对象 - 一些拥有class中属性及方法的对象。基于我们的Person类,我们可以创建出许许多多的真实的人:

+ +

+ +

当一个对象需要从类中创建出来时,类的构造函数就会运行来创建这个实例。这种创建对象的过程我们称之为实例化-实例对象被类实例化。

+ +

具体的对象

+ +

在这个例子里,我们不想要泛指的人,我们想要像老师和学生这样类型更为具体的人。在 OOP 里,我们可以创建基于其它类的新类,这些新的子类可以继承它们父类的数据和功能。比起复制来说这样能够使用父对象共有的功能。如果类之间的功能不同,你可以根据需要定义专用的特征。

+ +

+ +

这是非常有用的,老师和学生具有一些相同的特征比如姓名、性别、年龄,因此只需要定义这些特征一次就可以了。您可以在不同的类里分开定义这些相同的特征,这样该特征会有一个不同的命名空间。比如,一个学生的 greeting 可以是 "Yo, I'm [firstName]" (例子 Yo, I'm Sam),老师的可能会正式一些,比如"Hello, my name is [Prefix] [lastName]" (例子 Hello, My name is Mr Griffiths)。

+ +
+

注:多态——这个高大上的词正是用来描述多个对象拥有实现共同方法的能力。

+
+ +

现在可以根据子类创建对象。如:

+ +

下面我们来看看 OOP 理论如何应用到 JavaScript 实践中去的。

+ +

构建函数和对象

+ +

有些人认为 JavaScript 不是真正的面向对象的语言,比如它没有像许多面向对象的语言一样有用于创建class类的声明。JavaScript 用一种称为构建函数的特殊函数来定义对象和它们的特征。构建函数非常有用,因为很多情况下您不知道实际需要多少个对象(实例)。构建函数提供了创建您所需对象(实例)的有效方法,将对象的数据和特征函数按需联结至相应对象。

+ +

不像“经典”的面向对象的语言,从构建函数创建的新实例的特征并非全盘复制,而是通过一个叫做原形链的参考链链接过去的。(参见 Object prototypes),所以这并非真正的实例,严格的讲, JavaScript 在对象间使用和其它语言的共享机制不同。

+ +
+

注: “经典”的面向对象的语言并非好事,就像上面提到的,OOP 可能很快就变得非常复杂,JavaScript 找到了在不变的特别复杂的情况下利用面向对象的优点的方法。

+
+ +

让我们来看看 JavaScript 如何通过构建函数对象来创建类。首先,请先复制一个新的前文提到的oojs.html

+ +

一个简单的例子

+ +
    +
  1. 让我们看看如何通过一个普通的函数定义一个”人“。在您的文件中添加以下代码: +
    function createNewPerson(name) {
    +  var obj = {};
    +  obj.name = name;
    +  obj.greeting = function () {
    +    alert('Hi! I\'m ' + this.name + '.');
    +  }
    +  return obj;
    +}
    +
  2. +
  3. 您现在可以通过调用这个函数创建一个新的叫 salva 的人,在您浏览器的JavaScript console 试试 : +
    var salva = createNewPerson('salva');
    +salva.name;
    +salva.greeting();
    + 上述代码运行良好,但是有点冗长;如果我们知道如何创建一个对象,就没有必要创建一个新的空对象并且返回它。幸好 JavaScript 通过构建函数提供了一个便捷的方法,方法如下:
  4. +
  5. 将之前的代码用如下代码代替: +
    function Person(name) {
    +  this.name = name;
    +  this.greeting = function() {
    +    alert('Hi! I\'m ' + this.name + '.');
    +  };
    +}
    +
  6. +
+ +

这个构建函数是 JavaScript 版本的类。您会发现,它只定义了对象的属性和方法,除了没有明确创建一个对象和返回任何值和之外,它有了您期待的函数所拥有的全部功能。这里使用了this关键词,即无论是该对象的哪个实例被这个构建函数创建,它的 name 属性就是传递到构建函数形参name的值,它的 greeting() 方法中也将使用相同的传递到构建函数形参name的值。

+ +
+

注: 一个构建函数通常是大写字母开头,这样便于区分构建函数和普通函数。

+
+ +

那如何调用构建函数创建新的实例呢?

+ +
    +
  1. 将下面的代码加在您之前的代码下面: +
    var person1 = new Person('Bob');
    +var person2 = new Person('Sarah');
    +
  2. +
  3. 保存并刷新浏览器,在 console 里输入如下代码: +
    person1.name
    +person1.greeting()
    +person2.name
    +person2.greeting()
    +
  4. +
+ +

酷!您现在看到页面上有两个对象,每一个保存在不同的命名空间里,当您访问它们的属性和方法时,您需要使用person1或者person2来调用它们。尽管它们有着相同的name属性和 greeting()方法它们是各自独立的,所以相互的功能不会冲突。注意它们使用的是自己的 name 值,这也是使用 this 关键字的原因,它们使用的从实参传入形参的自己的值,而不是其它的什么值。

+ +

再看看这个构造对象的语法:

+ +
var person1 = new Person('Bob');
+var person2 = new Person('Sarah');
+ +

上述代码中,关键字 new 跟着一个含参函数,用于告知浏览器我们想要创建一个对象,非常类似函数调用,并把结果保存到变量中。每个示例类都是根据下面的方式定义的。

+ +
function Person(name) {
+  this.name = name;
+  this.greeting = function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  };
+}
+
+ +

当新的对象被创立, 变量person1person2有效地包含了以下值:

+ +
{
+  name : 'Bob',
+  greeting : function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  }
+}
+
+{
+  name : 'Sarah',
+  greeting : function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  }
+}
+ +

值得注意的是每次当我们调用构造函数时,我们都会重新定义一遍 greeting(),这不是个理想的方法。为了避免这样,我们可以在原型里定义函数,接下来我们会讲到。

+ +

创建我们最终的构造函数

+ +

上面的例子仅仅是简单地介绍如何开始。让我们现在开始创建Person()构造函数。

+ +
    +
  1. 移除掉您之前写的所有代码, 用如下构造函数替代 —— 实现原理上,这与我们之前的例子并无二致, 只是变得稍稍复杂了些: +
    function Person(first, last, age, gender, interests) {
    +  this.name = {
    +    'first': first,
    +    'last': last
    +  };
    +  this.age = age;
    +  this.gender = gender;
    +  this.interests = interests;
    +  this.bio = function() {
    +    alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
    +  };
    +  this.greeting = function() {
    +    alert('Hi! I\'m ' + this.name.first + '.');
    +  };
    +};
    +
  2. +
  3. 接下来加上这样一行代码, 用来创建它的一个对象: +
    var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
    +
  4. +
+ +

这样,您就可以像我们定义第一个对象一样访问它的属性和方法了:

+ +
person1['age']
+person1.interests[1]
+person1.bio()
+// etc.
+ +
+

注: 如果您对这一部分有疑问, 尝试将您的代码与我们的版本做比较 —— 戳链接: oojs-class-finished.html (或者: 查看它的实现).

+
+ +

进一步的练习

+ +

首先, 尝试着写几行代码创建您自己的对象, 接着,尝试getting与setting对象中的成员。

+ +

此外, 我们的bio()方法里仍有一些问题 —— 尽管您创建的Person是女性,或者是些别的性别类型,输出里的代词都总是 "He"。 而且, 纵然您有更多的兴趣列举在interests数组中, bio只会展示您的两个兴趣。 您能想出如何在类型定义(构造函数)中解决这个问题吗? 您可以按照您喜欢的方式编写构造函数(您可能需要一些条件判断和循环)。 考虑下语句如何根据性别、兴趣列表中兴趣的数目异构。

+ +
+

注:如果您觉得困难, 我们在我们的GitHub仓库里作了回答(查看它的实现) ——但首先请您尝试着自己写出来。

+
+ +

创建对象的其他方式

+ +

到现在为止,我们了解到了两种不同的创建对象的方式 —— 声明一个对象的语法, 与使用构造函数(回顾上面)。

+ +

这些方法都是很有用的, 但仍有其他的方法 —— 我们希望您能熟悉这些,以免您在Web世界的旅行中碰到它们。

+ +

Object()构造函数

+ +

首先, 您能使用Object()构造函数来创建一个新对象。 是的, 一般对象都有构造函数,它创建了一个空的对象。

+ +
    +
  1. 尝试在您浏览器中的Javascript控制台中输入以下代码: +
    var person1 = new Object();
    +
  2. +
  3. 这样就在person1变量中存储了一个空对象。然后, 可以根据需要, 使用点或括号表示法向此对象添加属性和方法;试试这个例子: +
    person1.name = 'Chris';
    +person1['age'] = 38;
    +person1.greeting = function() {
    +  alert('Hi! I\'m ' + this.name + '.');
    +}
    +
  4. +
  5. 还可以将对象文本传递给Object() 构造函数作为参数, 以便用属性/方法填充它。请尝试以下操作: +
    var person1 = new Object({
    +  name : 'Chris',
    +  age : 38,
    +  greeting : function() {
    +    alert('Hi! I\'m ' + this.name + '.');
    +  }
    +});
    +
  6. +
+ +

使用create()方法

+ +

JavaScript有个内嵌的方法create(), 它允许您基于现有对象创建新的对象。

+ +
    +
  1. 在 JavaScript 控制台中尝试此操作: +
    var person2 = Object.create(person1);
    +
  2. +
  3. 现在尝试这个: +
    person2.name
    +person2.greeting()
    +
  4. +
+ +

您可以看到,person2是基于person1创建的, 它们具有相同的属性和方法。这非常有用, 因为它允许您创建新的对象而无需定义构造函数。缺点是比起构造函数,浏览器在更晚的时候才支持create()方法(IE9,  IE8 或甚至以前相比), 加上一些人认为构造函数让您的代码看上去更整洁 —— 您可以在一个地方创建您的构造函数, 然后根据需要创建实例, 这让您能很清楚地知道它们来自哪里。

+ +

但是, 如果您不太担心对旧浏览器的支持, 并且您只需要一个对象的一些副本, 那么创建一个构造函数可能会让您的代码显得过度繁杂。这取决于您的个人爱好。有些人发现create() 更容易理解和使用。

+ +

稍后我们将更详细地探讨create() 的效果。

+ +

总结

+ +

这篇文章简单地介绍了一些面向对象原理 —— 这些描述还不够完整, 但它让您知道我们在这里处理什么。此外, 我们已经开始研究 javascript与 "经典 OOP"的关联与区别, 如何使用构造函数实现 javascript 中的类, 以及生成对象的不同方法。

+ +

在下一篇文章中, 我们将探讨 JavaScript 对象原型。

+ +

{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}

diff --git a/files/zh-cn/learn/javascript/objects/object_building_practice/index.html b/files/zh-cn/learn/javascript/objects/object_building_practice/index.html new file mode 100644 index 0000000000..a5e19db541 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/object_building_practice/index.html @@ -0,0 +1,454 @@ +--- +title: 实践对象构造 +slug: Learn/JavaScript/Objects/Object_building_practice +tags: + - JavaScript + - 初学者 + - 学习 + - 对象 + - 指南 + - 画布 +translation_of: Learn/JavaScript/Objects/Object_building_practice +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}
+ +

在前面的文章中,我们学习了 JavaScript 的面向对象理论和基本的语法知识,为之后的学习建立了良好的基础。这篇文章中我们将进行一次实战演练,通过构造 JavaScript 对象得到生动有趣的成果!

+ + + + + + + + + + + + +
预备知识:基本的计算机知识,了解HTML与CSS的基本概念,熟悉JavaScript基本知识 (请参阅 入门 和 构建块结构)和面向对象的JavaScript (OOJS) 基础 (请参阅 对象基础)。
目标:练习使用对象,在真实环境中应用面向对象开发技术。
+ +

弹跳吧!小彩球!

+ +

本文通过编写一个弹球 demo 来展示 JavaScript 中对象的重要性。我们的小球会在屏幕上弹跳,当它们碰到彼此时会变色。最终会像这样:

+ + + +
    +
+ +

{{ EmbedLiveSample('Bouncing_balls', '100%', 480, "", "", "hide-codepen-jsfiddle") }}

+ +

这个实例将会利用 Canvas API 来在屏幕上画小球, 还会用到 requestAnimationFrame API 来使整个画面动起来 —— 我们并不要求你事先学习过这些 API 的相关知识,希望你完成这个练习之后会想去探索更多。这个过程中我们会用到一些漂亮的小东西并向你展示一些技巧,比如小球从墙上反弹,检查它们是否撞到了对方 (也就是碰撞检测)。

+ +

让我们开始吧

+ +

首先下载 bouncing-balls-start.zip,其中包含以下三个文件:index.html、style.css 和 main.js。它们分别包含以下内容:

+ +
    +
  1. 一个非常简单的 HTML 文档,包括一个 <h1> 元素、一个{{HTMLElement("canvas")}} 元素来画小球,还有一些元素将 CSS 和 JavaScript 运用到我们的 HTML 中。
  2. +
  3. 一些非常简单的样式,主要是 <h1> 元素的样式和定位,另外还能使画面填充整个页面从而摆脱滚动条和边缘的空白(这样看起来非常简洁)
  4. +
  5. 一些 JavaScript 用来设置 <canvas> 元素,并提供我们要用到的基本函数。
  6. +
+ +

脚本的第一部分是这样的:

+ +
const canvas = document.querySelector('canvas');
+
+const ctx = canvas.getContext('2d');
+
+const width = canvas.width = window.innerWidth;
+const height = canvas.height = window.innerHeight;
+ +

这个脚本使用变量代指了 <canvas> 元素, 然后对其调用 getContext() 从而我们获得一个开始画画的环境。存储以上操作结果的变量(ctx)是一个对象,直接代指画布上的一块允许我们绘制 2D 图形的区域。

+ +

接下来,我们设置 widthheight 变量,并且让画布元素的宽和高(分别使用 canvas.widthcanvas.height 表示)等于浏览器的宽和高(也就是网页显示的区域 — 可以从 {{domxref("Window.innerWidth")}} 和 {{domxref("Window.innerHeight")}}参数获得)。

+ +

你会看到我们在这里串联了多个赋值表达式在一起,这样能更快地设置变量——这是完全正确的。

+ +

原始脚本最后的部分如下:

+ +
function random(min,max) {
+  return Math.floor(Math.random()*(max-min)) + min;
+}
+
+function randomColor() {
+  return 'rgb(' +
+         random(0, 255) + ', ' +
+         random(0, 255) + ', ' +
+         random(0, 255) + ')';
+}
+ +

第一个函数为我们生成一个 minmax 之间的随机整数,第二个函数为我们生成一个随机的颜色值。

+ +

为程序中的小球建立模型

+ +

我们的项目中会有很多小球在屏幕上跳来跳去。因此这些小球会以相同的方式运作,从而我们可以通过一个对象实例化它们。首先,我们将下面的构造器加入到代码的底部。

+ +
function Ball(x, y, velX, velY, color, size) {
+  this.x = x;
+  this.y = y;
+  this.velX = velX;
+  this.velY = velY;
+  this.color = color;
+  this.size = size;
+}
+ +

这个构造器中定义了每个小球需要的参数:

+ + + +

这里说明了小球的属性,那么方法呢?别忘了我们要让小球动起来。

+ +

画小球

+ +

首先给小球的原型加上 draw() 方法:

+ +
Ball.prototype.draw = function() {
+  ctx.beginPath();
+  ctx.fillStyle = this.color;
+  ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
+  ctx.fill();
+}
+ +

通过使用这个函数,通过使用我们之前定义的 ctx对象 的方法,我们就可以让在屏幕上画出小球了。ctx 的内容区域就像是一张纸,现在我们就可以命令我们的笔画一点东西。

+ + + +

现在你已经可以测试你的对象了。

+ +
    +
  1. 保存代码,将 HTML 加载到浏览器中。
  2. +
  3. 打开浏览器中的 JavaScript 控制台,刷新页面,从而画布可以根据可视的区域调整自己的大小。
  4. +
  5. 通过下面的代码创建一个小球实例。 +
    let testBall = new Ball(50, 100, 4, 4, 'blue', 10);
    +
  6. +
  7. 你可以调用实例的这些属性。 +
    testBall.x
    +testBall.size
    +testBall.color
    +testBall.draw()
    +
  8. +
  9. 当你键入最后一行的时候,你会在你的画布上看到一个小球被画出来了。
  10. +
+ +

更新小球的数据

+ +

我们可以在一个固定位置画出小球,但是他们不会动,我们需要一个函数来更新一些东西。在 JavaScript 文件底部加上下面的代码,也就是给小球原型加上一个 update() 方法。

+ +
Ball.prototype.update = function() {
+  if ((this.x + this.size) >= width) {
+    this.velX = -(this.velX);
+  }
+
+  if ((this.x - this.size) <= 0) {
+    this.velX = -(this.velX);
+  }
+
+  if ((this.y + this.size) >= height) {
+    this.velY = -(this.velY);
+  }
+
+  if ((this.y - this.size) <= 0) {
+    this.velY = -(this.velY);
+  }
+
+  this.x += this.velX;
+  this.y += this.velY;
+}
+ +

函数的前四个部分用来检查小球是否碰到画布的边缘。如果碰到,我们反转小球的速度方向来让它向反方向移动。就比如说,如果小球正向上移动 (正 velY),然后垂直速度发生改变,小球就向下移动 (负 velY)。

+ +

在这四部分中,我们:

+ + + +

在每种情况下,我们都会加上小球的半径,因为 x/y 坐标是小球中心的坐标,我们希望小球在其边界接触浏览器窗口的边界时反弹,而不是小球的一部分都不见了再返回。

+ +

最后两行,我们将 velX 的值加到 x 的坐标上,将 velY 的值加到 y 坐标上 —— 每次调用这个方法的时候小球就移动这么多。

+ +

暂时先这样做; 让我们继续做一些动画!

+ +

让球动起来 

+ +

现在就变得非常有趣了。我们在画布上加上一些小球,并且让他们动起来。

+ +
    +
  1. 首先我们需要一个地方储存小球,下面的数组会干这件事 —— 现在将它添加到你的代码底部: +
    let balls = [];
    +
    +while (balls.length < 25) {
    +    let size = random(10, 20);
    +    let ball = new Ball(
    +      // 为避免绘制错误,球至少离画布边缘球本身一倍宽度的距离
    +      random(0 + size, width - size),
    +      random(0 + size, height - size),
    +      random(-7, 7),
    +      random(-7, 7),
    +      randomColor(),
    +      size
    +    );
    +    balls.push(ball);
    +  }
    +
    +
  2. +
  3. +

    几乎所有的动画效果都会用到一个运动循环,也就是每一帧都自动更新视图。这是大多数游戏或者其他类似项目的基础。

    +
  4. +
  5. 现在将它添加到你的代码底部: +
    function loop() {
    +  ctx.fillStyle = 'rgba(0, 0, 0, 0.25)';
    +  ctx.fillRect(0, 0, width, height);
    +
    +  for (let i = 0; i < balls.length; i++) {
    +    balls[i].draw();
    +    balls[i].update();
    +  }
    +
    +  requestAnimationFrame(loop);
    +}
    + +

    loop() 函数做了下面的事情:

    + +
      +
    • 将整个画布的颜色设置成半透明的黑色。然后使用  fillRect()(这四个参数分别是起始的坐标、绘制的矩形的宽和高)画出一个填充满整个画布的矩形。这是在下一个视图画出来时用来遮住之前的视图的。如果不这样做得话,你就会在屏幕上看到一条蛇的形状而不是小球的运动了。用来填充的颜色设置成半透明的rgba(0,0,0,0.25),也就是让之前的视图留下来一点点,从而你可以看到小球运动时的轨迹。如果你将 0.25 设置成 1 时,你就完全看不到了。试着改变其中的值查看造成的影响。
    • +
    • 当且仅当小球数量小于 25 时,将 random() 函数产生的数字传入新的小球实例从而创建一个新的小球,并且加入到数组中。因此当屏幕上有 25 个小球时,不会再出现更多小球。你可以改变这个值,从而看到不同小球个数造成的影响。如果你的电脑或者浏览器性能不怎么样的话,几千个小球的速度就会明显慢下来。
    • +
    • 遍历数组中的所有小球,并且让每个小球都调用 draw()update() 函数来将自己画出来,并且再接下来的每一帧都按照其速度进行位置的更新。
    • +
    • 使用 requestAnimationFrame() 方法再运行一次函数 —— 当一个函数正在运行时传递相同的函数名,从而每隔一小段时间都会运行一次这个函数,这样我们可以得到一个平滑的动画效果。这主要是通过递归完成的 —— 也就是说函数每次运行的时候都会调用自己,从而可以一遍又一遍得运行。
    • +
    +
  6. +
  7. 最后但是非常重要的是,加上下面这一行 —— 让动画开始运行的话我们需要调用这个函数。 +
    loop();
    +
  8. +
+ +

完成这些基础的之后在浏览器打开测试一下!

+ +

添加碰撞检测

+ +

现在会更加有趣,给我们的项目加上碰撞检测,从而小球会知道他们正在撞击其他的球。

+ +
    +
  1. 首先在 update() 方法后添加以下方法 (即 Ball.prototype.update 的下面)。 + +
    Ball.prototype.collisionDetect = function() {
    +  for (let j = 0; j < balls.length; j++) {
    +    if (this !== balls[j]) {
    +      const dx = this.x - balls[j].x;
    +      const dy = this.y - balls[j].y;
    +      const distance = Math.sqrt(dx * dx + dy * dy);
    +
    +      if (distance < this.size + balls[j].size) {
    +        balls[j].color = this.color = randomColor();
    +      }
    +    }
    +  }
    +}
    + +

    这个方法有一点点复杂,如果不理解的话不必过分担心,下面是对它的解释:

    + +
      +
    • 对于每个小球,我们都要检查其他的小球是否和当前这个小球相撞了。为了达到此目的,我们构造另外一个 for 循环来遍历 balls[] 数组中的小球。
    • +
    • 在循环里面,我们使用一个 if 语句来检查遍历的小球是否是当前的小球。我们不希望检测到一个小球撞到了自己!为了达到这个目的,我们需要检查当前小球 (即正在调用 collisionDetect 方法的球) 是否和被循环到的小球 (for 循环检测中的当前遍历所引用的球) 是不是同一个。我们使用 ! 来否定判断,因此只有两个小球不是同一个时,条件判断中的代码才会运行。
    • +
    • 我们使用了一个常见的算法来检测两个小球是否相撞了,两个小球中心的距离是否小于两个小球的半径之和。这些会在 2D 碰撞检测 介绍地更加详细。
    • +
    • 如果检测到了碰撞,会运行 if 语句中的代码。我们会将两个小球的颜色都设置成随机的一种。我们也可以将这步操作变得复杂一点,比如让两个小球弹开,那样需要植入更加复杂的代码。像这样的物理场景,有以下专门的库比如 PhysicsJSmatter.jsPhaser 等。
    • +
    +
  2. +
  3. 我们也需要在每一帧动画中都调用这个函数,因此在 balls[i].update() 加上下面的代码: +
    balls[i].collisionDetect();
    +
  4. +
  5. 保存文件,刷新浏览器,你就会看到小球在撞击时会变色!
  6. +
+ +
+

注:如果示例无法顺利执行,可参考我们的 最终版本,或者 在线试用

+
+ +

概要

+ +

我们希望你玩得开心,编写出你自己的随机弹跳球的例子,在整个程序中使用各种对象和面向对象的技术! 在你实际运用对象中能提供一些有用的帮助。

+ +

对象文章就到这里了。现在剩下的就是在下一节的对象评估中测试你的技能。

+ +

另请参阅

+ + + +

{{PreviousMenuNext("Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects/Adding_bouncing_balls_features", "Learn/JavaScript/Objects")}}

+ +

本章目录

+ + diff --git a/files/zh-cn/learn/javascript/objects/object_prototypes/index.html b/files/zh-cn/learn/javascript/objects/object_prototypes/index.html new file mode 100644 index 0000000000..89028e5e17 --- /dev/null +++ b/files/zh-cn/learn/javascript/objects/object_prototypes/index.html @@ -0,0 +1,357 @@ +--- +title: 对象原型 +slug: Learn/JavaScript/Objects/Object_prototypes +tags: + - JavaScript + - 初学者 + - 原型 + - 对象 +translation_of: Learn/JavaScript/Objects/Object_prototypes +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects")}}
+ +

通过原型这种机制,JavaScript 中的对象从其他对象继承功能特性;这种继承机制与经典的面向对象编程语言的继承机制不同。本文将探讨这些差别,解释原型链如何工作,并了解如何通过 prototype 属性向已有的构造器添加方法

+ + + + + + + + + + + + +
预备知识:基本的计算机素养,对 HTML 和 CSS 有基本的理解,熟悉 JavaScript 基础(参见 First stepsBuilding blocks)以及面向对象的JavaScript (OOJS) 基础(参见 Introduction to objects)。
目标:理解 JavaScript 对象原型、原型链如何工作、如何向 prototype 属性添加新的方法。
+ +

基于原型的语言?

+ +

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

+ +

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。

+ +

在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

+ +
+

注意: 理解对象的原型(可以通过Object.getPrototypeOf(obj)或者已被弃用的__proto__属性获得)与构造函数的prototype属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())Foobar.prototype指向着同一个对象。

+
+ +

以上描述很抽象;我们先看一个例子。

+ +

使用Javascript中的原型

+ +

在javascript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype) ,正如下面所展示的。请注意,下面的代码是独立的一段(在网页中没有其他代码的情况下,这段代码是安全的)。为了最好的学习体验,你最好打开一个控制台 (在Chrome和Firefox中,可以按Ctrl+Shift+I来打开)切换到"控制台" 选项卡, 复制粘贴下面的JavaScript代码,然后按回车来运行.

+ +
function doSomething(){}
+console.log( doSomething.prototype );
+// It does not matter how you declare the function, a
+//  function in javascript will always have a default
+//  prototype property.
+var doSomething = function(){};
+console.log( doSomething.prototype );
+
+ +

正如上面所看到的, doSomething 函数有一个默认的原型属性,它在控制台上面呈现了出来. 运行这段代码之后,控制台上面应该出现了像这样的一个对象.

+ +
{
+    constructor: ƒ doSomething(),
+    __proto__: {
+        constructor: ƒ Object(),
+        hasOwnProperty: ƒ hasOwnProperty(),
+        isPrototypeOf: ƒ isPrototypeOf(),
+        propertyIsEnumerable: ƒ propertyIsEnumerable(),
+        toLocaleString: ƒ toLocaleString(),
+        toString: ƒ toString(),
+        valueOf: ƒ valueOf()
+    }
+}
+ +

现在,我们可以添加一些属性到 doSomething 的原型上面,如下所示.

+ +
function doSomething(){}
+doSomething.prototype.foo = "bar";
+console.log( doSomething.prototype );
+
+ +

结果:

+ +
{
+    foo: "bar",
+    constructor: ƒ doSomething(),
+    __proto__: {
+        constructor: ƒ Object(),
+        hasOwnProperty: ƒ hasOwnProperty(),
+        isPrototypeOf: ƒ isPrototypeOf(),
+        propertyIsEnumerable: ƒ propertyIsEnumerable(),
+        toLocaleString: ƒ toLocaleString(),
+        toString: ƒ toString(),
+        valueOf: ƒ valueOf()
+    }
+}
+
+ +

 

+ +

然后,我们可以使用 new 运算符来在现在的这个原型基础之上,创建一个 doSomething 的实例。正确使用 new 运算符的方法就是在正常调用函数时,在函数名的前面加上一个 new 前缀. 通过这种方法,在调用函数前加一个 new ,它就会返回一个这个函数的实例化对象. 然后,就可以在这个对象上面添加一些属性. 看.

+ +
function doSomething(){}
+doSomething.prototype.foo = "bar"; // add a property onto the prototype
+var doSomeInstancing = new doSomething();
+doSomeInstancing.prop = "some value"; // add a property onto the object
+console.log( doSomeInstancing );
+ +

 

+ +

结果:

+ +

 

+ +
{
+    prop: "some value",
+    __proto__: {
+        foo: "bar",
+        constructor: ƒ doSomething(),
+        __proto__: {
+            constructor: ƒ Object(),
+            hasOwnProperty: ƒ hasOwnProperty(),
+            isPrototypeOf: ƒ isPrototypeOf(),
+            propertyIsEnumerable: ƒ propertyIsEnumerable(),
+            toLocaleString: ƒ toLocaleString(),
+            toString: ƒ toString(),
+            valueOf: ƒ valueOf()
+        }
+    }
+}
+ +

就像上面看到的, doSomeInstancing__proto__ 属性就是doSomething.prototype. 但是这又有什么用呢? 好吧,当你访问 doSomeInstancing 的一个属性, 浏览器首先查找 doSomeInstancing 是否有这个属性. 如果 doSomeInstancing 没有这个属性, 然后浏览器就会在 doSomeInstancing__proto__ 中查找这个属性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 __proto__ 有这个属性, 那么 doSomeInstancing 的 __proto__ 上的这个属性就会被使用. 否则, 如果 doSomeInstancing 的 __proto__ 没有这个属性, 浏览器就会去查找 doSomeInstancing 的 __proto____proto__ ,看它是否有这个属性. 默认情况下, 所有函数的原型属性的 __proto__ 就是 window.Object.prototype. 所以 doSomeInstancing 的 __proto____proto__ (也就是 doSomething.prototype 的 __proto__ (也就是 Object.prototype)) 会被查找是否有这个属性. 如果没有在它里面找到这个属性, 然后就会在 doSomeInstancing 的 __proto____proto____proto__ 里面查找. 然而这有一个问题: doSomeInstancing 的 __proto____proto____proto__ 不存在. 最后, 原型链上面的所有的 __proto__ 都被找完了, 浏览器所有已经声明了的 __proto__ 上都不存在这个属性,然后就得出结论,这个属性是 undefined.

+ +
function doSomething(){}
+doSomething.prototype.foo = "bar";
+var doSomeInstancing = new doSomething();
+doSomeInstancing.prop = "some value";
+console.log("doSomeInstancing.prop:      " + doSomeInstancing.prop);
+console.log("doSomeInstancing.foo:       " + doSomeInstancing.foo);
+console.log("doSomething.prop:           " + doSomething.prop);
+console.log("doSomething.foo:            " + doSomething.foo);
+console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
+console.log("doSomething.prototype.foo:  " + doSomething.prototype.foo);
+ +

结果:

+ +
doSomeInstancing.prop:      some value
+doSomeInstancing.foo:       bar
+doSomething.prop:           undefined
+doSomething.foo:            undefined
+doSomething.prototype.prop: undefined
+doSomething.prototype.foo:  bar
+ +

理解原型对象

+ +

让我们回到 Person() 构造器的例子。请把这个例子载入浏览器。如果你还没有看完上一篇文章并写好这个例子,也可以使用 oojs-class-further-exercises.html 中的例子(亦可参考源代码)。

+ +

本例中我们将定义一个构造器函数:

+ +
function Person(first, last, age, gender, interests) {
+
+  // 属性与方法定义
+
+};
+ +

然后创建一个对象实例:

+ +
var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
+ +

在 JavaScript 控制台输入 "person1.",你会看到,浏览器将根据这个对象的可用的成员名称进行自动补全:

+ +

+ +

在这个列表中,你可以看到定义在 person1 的原型对象、即 Person() 构造器中的成员—— nameagegenderinterestsbiogreeting。同时也有一些其他成员—— watchvalueOf 等等——这些成员定义在 Person() 构造器的原型对象、即 Object 之上。下图展示了原型链的运作机制。

+ +

+ +

那么,调用 person1 的“实际定义在 Object 上”的方法时,会发生什么?比如:

+ +
person1.valueOf()
+ +

这个方法仅仅返回了被调用对象的值。在这个例子中发生了如下过程:

+ + + +
+

注意:必须重申,原型链中的方法和属性没有被复制到其他对象——它们被访问需要通过前面所说的“原型链”的方式。

+
+ +
+

注意:没有官方的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 [[prototype]] 表示(参见 {{glossary("ECMAScript")}})。然而,大多数现代浏览器还是提供了一个名为 __proto__ (前后各有2个下划线)的属性,其包含了对象的原型。你可以尝试输入 person1.__proto__person1.__proto__.__proto__,看看代码中的原型链是什么样的!

+
+ +

prototype 属性:继承成员被定义的地方

+ +

那么,那些继承的属性和方法在哪儿定义呢?如果你查看 Object 参考页,会发现左侧列出许多属性和方法——大大超过我们在 person1 对象中看到的继承成员的数量。某些属性或方法被继承了,而另一些没有——为什么呢?

+ +

原因在于,继承的属性和方法是定义在 prototype 属性之上的(你可以称之为子命名空间 (sub namespace) )——那些以 Object.prototype. 开头的属性,而非仅仅以 Object. 开头的属性。prototype 属性的值是一个对象,我们希望被原型链下游的对象继承的属性和方法,都被储存在其中。

+ +

于是 Object.prototype.watch()Object.prototype.valueOf() 等等成员,适用于任何继承自 Object() 的对象类型,包括使用构造器创建的新的对象实例。

+ +

Object.is()Object.keys(),以及其他不在 prototype 对象内的成员,不会被“对象实例”或“继承自 Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。

+ +
+

注意:这看起来很奇怪——构造器本身就是函数,你怎么可能在构造器这个函数中定义一个方法呢?其实函数也是一个对象类型,你可以查阅 Function() 构造器的参考文档以确认这一点。

+
+ +
    +
  1. 你可以检查已有的 prototype 属性。回到先前的例子,在 JavaScript 控制台输入: + +
    Person.prototype
    +
  2. +
  3. 输出并不多,毕竟我们没有为自定义构造器的原型定义任何成员。缺省状态下,构造器的 prototype 属性初始为空白。现在尝试: +
    Object.prototype
    +
  4. +
+ +

你会看到 Objectprototype 属性上定义了大量的方法;如前所示,继承自 Object 的对象都可以使用这些方法。

+ +

JavaScript 中到处都是通过原型链继承的例子。比如,你可以尝试从 StringDateNumberArray 全局对象的原型中寻找方法和属性。它们都在原型上定义了一些方法,因此当你创建一个字符串时:

+ +
var myString = 'This is my string.';
+ +

myString 立即具有了一些有用的方法,如 split()indexOf()replace() 等。

+ +
+

重要prototype 属性大概是 JavaScript 中最容易混淆的名称之一。你可能会认为,this 关键字指向当前对象的原型对象,其实不是(还记得么?原型对象是一个内部对象,应当使用 __proto__ 访问)。prototype 属性包含(指向)一个对象,你在这个对象中定义需要被继承的成员。

+
+ +

create()

+ +

我们曾经讲过如何用 Object.create() 方法创建新的对象实例。

+ +
    +
  1. 例如,在上个例子的 JavaScript 控制台中输入: +
    var person2 = Object.create(person1);
    +
  2. +
  3. create() 实际做的是从指定原型对象创建一个新的对象。这里以 person1 为原型对象创建了 person2 对象。在控制台输入: +
    person2.__proto__
    +
  4. +
+ +

结果返回对象person1

+ +

constructor 属性

+ +

每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。

+ +
    +
  1. 例如,在控制台中尝试下面的指令: +
    person1.constructor
    +person2.constructor
    + +

    都将返回 Person() 构造器,因为该构造器包含这些实例的原始定义。

    + +

    一个小技巧是,你可以在 constructor 属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new 关键字,便能将此函数作为构造器使用。

    +
  2. +
  3. 在控制台中输入: +
    var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
    +
  4. +
  5. 现在尝试访问新建对象的属性,例如: +
    person3.name.first
    +person3.age
    +person3.bio()
    +
  6. +
+ +

正常工作。通常你不会去用这种方法创建新的实例;但如果你刚好因为某些原因没有原始构造器的引用,那么这种方法就很有用了。

+ +

此外,constructor 属性还有其他用途。比如,想要获得某个对象实例的构造器的名字,可以这么用:

+ +
instanceName.constructor.name
+ +

具体地,像这样:

+ +
person1.constructor.name
+ +

修改原型

+ +

从我们从下面这个例子来看一下如何修改构造器的 prototype 属性。

+ +
    +
  1. 回到 oojs-class-further-exercises.html 的例子,在本地为源代码创建一个副本。在已有的 JavaScript 的末尾添加如下代码,这段代码将为构造器的 prototype 属性添加一个新的方法: + +
    Person.prototype.farewell = function() {
    +  alert(this.name.first + ' has left the building. Bye for now!');
    +}
    +
  2. +
  3. 保存代码,在浏览器中加载页面,然后在控制台输入: +
    person1.farewell();
    +
  4. +
+ +

你会看到一条警告信息,其中还显示了构造器中定义的人名;这很有用。但更关键的是,整条继承链动态地更新了,任何由此构造器创建的对象实例都自动获得了这个方法。

+ +

再想一想这个过程。我们的代码中定义了构造器,然后用这个构造器创建了一个对象实例,此后向构造器的 prototype 添加了一个新的方法:

+ +
function Person(first, last, age, gender, interests) {
+
+  // 属性与方法定义
+
+};
+
+var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);
+
+Person.prototype.farewell = function() {
+  alert(this.name.first + ' has left the building. Bye for now!');
+}
+ +

但是 farewell() 方法仍然可用于 person1 对象实例——旧有对象实例的可用功能被自动更新了。这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

+ +
+

注意:如果运行样例时遇到问题,请参阅 oojs-class-prototype.html 样例(也可查看即时运行)。

+
+ +

你很少看到属性定义在 prototype 属性中,因为如此定义不够灵活。比如,你可以添加一个属性:

+ +
Person.prototype.fullName = 'Bob Smith';
+ +

但这不够灵活,因为人们可能不叫这个名字。用 name.firstname.last 组成 fullName 会好很多:

+ +
Person.prototype.fullName = this.name.first + ' ' + this.name.last;
+ +

然而,这么做是无效的,因为本例中 this 引用全局范围,而非函数范围。访问这个属性只会得到 undefined undefined。但这个语句若放在 先前定义在 prototype 上的方法中则有效,因为此时语句位于函数范围内,从而能够成功地转换为对象实例范围。你可能会在 prototype 上定义常属性 (constant property) (指那些你永远无需改变的属性),但一般来说,在构造器内定义属性更好。

+ +
+

译者注:关于 this 关键字指代(引用)什么范围/哪个对象,这个问题超出了本文讨论范围。事实上,这个问题有点复杂,如果现在你没能理解,也不用担心。

+
+ +

事实上,一种极其常见的对象定义模式是,在构造器(函数体)中定义属性、在 prototype 属性上定义方法。如此,构造器只包含属性定义,而方法则分装在不同的代码块,代码更具可读性。例如:

+ +
// 构造器及其属性定义
+
+function Test(a,b,c,d) {
+  // 属性定义
+};
+
+// 定义第一个方法
+
+Test.prototype.x = function () { ... }
+
+// 定义第二个方法
+
+Test.prototype.y = function () { ... }
+
+// 等等……
+ +

在 Piotr Zalewa 的 school plan app 样例中可以看到这种模式。

+ +

总结

+ +

本文介绍了 JavaScript 对象原型,包括原型链如何允许对象之间继承特性、prototype 属性、如何通过它来向构造器添加方法,以及其他有关主题。

+ +

下一篇文章中,我们将了解如何在两个自定义的对象间实现功能的继承。

+ +

{{PreviousMenuNext("Learn/JavaScript/Objects/Object-oriented_JS", "Learn/JavaScript/Objects/Inheritance", "Learn/JavaScript/Objects")}}

diff --git "a/files/zh-cn/learn/javascript/objects/\345\220\221\342\200\234\345\274\271\350\267\263\347\220\203\342\200\235\346\274\224\347\244\272\347\250\213\345\272\217\346\267\273\345\212\240\346\226\260\345\212\237\350\203\275/index.html" "b/files/zh-cn/learn/javascript/objects/\345\220\221\342\200\234\345\274\271\350\267\263\347\220\203\342\200\235\346\274\224\347\244\272\347\250\213\345\272\217\346\267\273\345\212\240\346\226\260\345\212\237\350\203\275/index.html" new file mode 100644 index 0000000000..2730489d15 --- /dev/null +++ "b/files/zh-cn/learn/javascript/objects/\345\220\221\342\200\234\345\274\271\350\267\263\347\220\203\342\200\235\346\274\224\347\244\272\347\250\213\345\272\217\346\267\273\345\212\240\346\226\260\345\212\237\350\203\275/index.html" @@ -0,0 +1,468 @@ +--- +title: 为“弹球”示例添加新功能 +slug: Learn/JavaScript/Objects/向“弹跳球”演示程序添加新功能 +tags: + - JavaScript + - 初学者 + - 对象 + - 测验 + - 面向对象 +translation_of: Learn/JavaScript/Objects/Adding_bouncing_balls_features +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/Object_building_practice", "", "Learn/JavaScript/Objects")}}
+ +

在此次测验中, 你需要将上一节中的“弹球”演示程序作为模板,添加一些新的有趣的功能。

+ + + + + + + + + + + + +
预备知识:请确保完整学习本章所有内容后再开始测验。
目标:测试你对 JavaScript 对象和面向对象结构的理解。
+ +

开始

+ +

请先下载 index.htmlstyle.css 和 main.js 三个文件。

+ +
+

注:也可以使用 JSBinThimble 这样的网站来进行测验。 你可以选择其中一个将HTML,CSS 和JavaScript 粘贴过去。 如果你的版本没有单独的 JavaScript / CSS 板块,可以把它们嵌入 HTML 页面内的 <script>/<style> 元素。

+
+ +

项目简介

+ +

我们的弹球 demo 很有趣, 但是现在我们想让它更具有互动性,我们为它添加一个由玩家控制的“恶魔圈”,如果恶魔圈抓到弹球会把它会吃掉。我们还想测验你面向对象的水平,首先创建一个通用 Shape() 对象,然后由它派生出弹球和恶魔圈。最后,我们为 demo 添加一个计分器来记录剩下的球数。

+ +

程序最终会像这样:

+ + + + + +

{{ EmbedLiveSample('Evil_circle', '100%', 480, "", "", "hide-codepen-jsfiddle") }}

+ +

可以 查看完成版本 来获得更全面的体验。(别偷看源代码哦。)

+ +

步骤

+ +

以下各节介绍你需要完成的步骤。

+ +

创建我们的新对象

+ +

首先, 改变你现有的构造器 Ball() 使其成为构造器 Shape() 并添加一个新的构造器 Ball()

+ +
    +
  1. 构造器 Shape() 应该像构造器 Ball() 那样的方式定义 x, y, velX, 和 velY 属性,但不包括 colorsize 。
  2. +
  3. 还应该定义一个叫 exists 的新属性,用来标记球是否存在于程序中 (没有被恶魔圈吃掉)。这应该是一个布尔型((true/false)。
  4. +
  5. 构造器 Ball() 应该从构造器 Shape() 继承 x, y, velX, velY,和 exists 属性。
  6. +
  7. 构造器 Ball() 还应该像最初的构造器 Ball() 那样定义一个 color 和一个size 属性。
  8. +
  9. 请记得给构造器 Ball() 的prototypeconstructor 属性设置适当的值。
  10. +
+ +

draw(), update(), 和collisionDetect() 方法定义应保持不变。

+ +

你还需要为 new Ball() { ... } 构造器添加第五个参数—— exists, 且值为 true

+ +

到这里, 尝试重新加载代码(运行程序),程序以及重新设计的对象都应该像之前那样工作。

+ +

定义恶魔圈 EvilCircle()

+ +

现在是时候来看看那个坏蛋了——恶魔圈 EvilCircle()! 我们的游戏中只会有一个恶魔圈,但我们仍然要使用继承自 Shape() 的构造器来定义它,这是为让你得到锻炼。 之后你可能会想再添加一个由另一个玩家控制的恶魔圈到程序中,或者有几个电脑控制的恶魔圈。你可没法通过一个恶魔圈来掌管程序中的这个世界,但这个评估中就先只这么做吧。

+ +

EvilCircle() 构造器应该从Shape() 继承 x, y, 和 exists ,velXvelY 要恒为 20。

+ +

可以这样做:Shape.call(this, x, y, 20, 20, exists);

+ +

它还应该定义自己的一些属性,如:

+ + + +

再次记得给你的 EvilCircle() 构造器的传递的参数中定义你继承的属性,并且给prototypeconstructor 属性设置适当的值。

+ +

定义 EvilCircle() 的方法

+ +

EvilCircle() 应该有以下四个方法:

+ +

draw()

+ +

这个方法和 Ball()'s draw() 方法有着相同的目的:它们把都是对象的实例画在画布上(canvas) 。它们实现的方式差不多,所以你可以先复制 Ball.prototype.draw 的定义。然后你需要做下面的修改:

+ + + +

checkBounds()

+ +

这个方法和 Ball() 的 update() 函数做相同的事情—— 查看恶魔圈是否将要超出屏幕的边界, 并且禁止它超出。 同样,你可以直接复制 Ball.prototype.update 的定义, 但是你需要做一些修改:

+ + + +

setControls()

+ +

这个方法将会一个 onkeydown 的事件监听器给 window 对象,这样当特定的键盘按键按下的时候,我们就可以移动恶魔圈。下面的代码块应该放在方法的定义里:

+ +
window.onkeydown = e => {
+  switch(e.key) {
+    case 'a':
+      this.x -= this.velX;
+      break;
+    case 'd':
+      this.x += this.velX;
+      break;
+    case 'w':
+      this.y -= this.velY;
+      break;
+    case 's':
+      this.y += this.velY;
+      break;
+  }
+};
+ +

所以当一个按键按下时, 事件对象的 key 属性 就可以请求到按下的按键值。如果是代码中那四个指定的键值之一, 那么恶魔圈将会左右上下的移动。

+ +
+

译注:英文页面中使用了事件对象的 keyCode 属性,不推荐在新代码中使用该属性,应使用标准 key 属性代替。(详见介绍页面)

+
+ +
译注:这里的 window.onkeydown 用一个 箭头函数 代替了英文页面中的匿名函数,从而无需 var _this = this
+ +

collisionDetect()

+ +

这个方法和 Ball()'s collisionDetect() 方法很相似,所以你可以从它那里复制过来作为新方法的基础。但有一些不同之处:

+ + + +

把恶魔圈带到程序中

+ +

现在我们已经定义了恶魔圈,我们需要让它显示到我们的屏幕中。为了做这件事,你需要修改一下 loop() 函数:

+ + + +

计算得分

+ +

为了计算得分,需按照以下步骤:

+ +
    +
  1. 在你的HTML文件中添加一个{{HTMLElement("p")}} 元素到 {{HTMLElement("h1")}} 元素的下面,其中包含文本 "还剩多少个球"。
  2. +
  3. 在你的CSS文件中,添加下面的代码到底部: +
    p {
    +  position: absolute;
    +  margin: 0;
    +  top: 35px;
    +  right: 5px;
    +  color: #aaa;
    +}
    +
  4. +
  5. 在你的 JavaScript 文件中,做下列的修改: +
      +
    • 创建一个变量存储段落的引用。
    • +
    • 以同样的方式在屏幕上显示小球的数量。
    • +
    • 增加球数并在每次将球添加到屏幕里时显示更新的球数量。
    • +
    • 减少球数并在每次恶魔吃球时显示更新的球数(因为被吃掉的球不存在了)
    • +
    +
  6. +
+ +

提示

+ + + +

评定

+ +

如果你将此评估作为有组织的课程的一部分,你可以将你的成果交给您的老师/导师进行评分。 如果你是自学的,通过在 Learning Area Discourse thread, 或者在 Mozilla IRC 的 #mdn IRC 频道上申请,你可以十分容易地得到评分指南。首先先尝试这个练习,作弊不会有任何收获。

+ +

{{PreviousMenuNext("Learn/JavaScript/Objects/Object_building_practice", "", "Learn/JavaScript/Objects")}}

+ +

本章目录

+ + diff --git "a/files/zh-cn/learn/javascript/objects/\346\265\213\350\257\225\344\275\240\347\232\204\346\212\200\350\203\275_colon_\351\235\242\345\220\221\345\257\271\350\261\241\347\232\204javascript/index.html" "b/files/zh-cn/learn/javascript/objects/\346\265\213\350\257\225\344\275\240\347\232\204\346\212\200\350\203\275_colon_\351\235\242\345\220\221\345\257\271\350\261\241\347\232\204javascript/index.html" new file mode 100644 index 0000000000..8fd0cc3256 --- /dev/null +++ "b/files/zh-cn/learn/javascript/objects/\346\265\213\350\257\225\344\275\240\347\232\204\346\212\200\350\203\275_colon_\351\235\242\345\220\221\345\257\271\350\261\241\347\232\204javascript/index.html" @@ -0,0 +1,95 @@ +--- +title: 测试你的技能:面向对象的Javascript +slug: 'Learn/JavaScript/Objects/测试你的技能:面向对象的Javascript' +tags: + - JavaScript + - OOJS + - 初学者 + - 学习 + - 对象 + - 测试你的技能 +translation_of: 'Learn/JavaScript/Objects/Test_your_skills:_Object-oriented_JavaScript' +--- +
{{learnsidebar}}
+ +
这个测试的目的是为了评估你是否已经理解了我们的适合初学者的JavaScript面向对象对象原型,和 JavaScript 中的继承文章。
+ +
+ +
+

注意: 你可以尝试在下方的交互编辑器,但是若你下载源码或是使用在线工具例如 CodePen, jsFiddle, 或 Glitch 来进行这些项目的话,会更有帮助。

+ +

如果你在过程中想不出解决方案,你可以向我们寻求帮助——查看在本页的底部章节 {{anch("Assessment or further help")}}。

+
+ +
+

注意: 在下方的例子中,如果在你的代码中有错误内容的话,错误内容将在页面的结果面板进行显示,以此来帮助你想出解决方案(若是下载的版本,请进入浏览器的 JavaScript 控制台)。

+
+ +

OOJS 1

+ +

In this task we provide you with a constructor. We want you to:

+ + + +

Try updating the live code below to recreate the finished example:

+ +

{{EmbedGHLiveSample("learning-area/javascript/oojs/tasks/oojs/oojs1.html", '100%', 400)}}

+ +
+

Download the starting point for this task to work in your own editor or in an online editor.

+
+ +

OOJS 2

+ +

Next up we want you to take the Shape class you saw in Task #1 (including the calcPerimeter() method) and recreate it using ES class syntax instead.

+ +

Test that it works by creating the square and triangle object instances as before (using new Shape() for both), and then calling their calcPerimeter() methods.

+ +

Try updating the live code below to recreate the finished example:

+ +

{{EmbedGHLiveSample("learning-area/javascript/oojs/tasks/oojs/oojs2.html", '100%', 400)}}

+ +
+

Download the starting point for this task to work in your own editor or in an online editor.

+
+ +

OOJS 3

+ +

Finally, we'd like you to start with the ES Shape class you created in the last task.

+ +

We'd like you to create a Square class that inherits from Shape, and adds a calcArea() method that calculates the square's area. Also set up the constructor so that the name property of Square object instances is automatically set to square, and the sides property is automatically set to 4. When invoking the constructor, you should therefore just need to provide the sideLength property.

+ +

Create an instance of the Square class called square with appropriate property values, and call its calcPerimeter() and calcArea() methods to show that it works ok.

+ +

Try updating the live code below to recreate the finished example:

+ +

{{EmbedGHLiveSample("learning-area/javascript/oojs/tasks/oojs/oojs3.html", '100%', 400)}}

+ +
+

Download the starting point for this task to work in your own editor or in an online editor.

+
+ +

Assessment or further help

+ +

You can practice these examples in the Interactive Editors above.

+ +

If you would like your work assessed, or are stuck and want to ask for help:

+ +
    +
  1. Put your work into an online shareable editor such as CodePen, jsFiddle, or Glitch. You can write the code yourself, or use the starting point files linked to in the above sections.
  2. +
  3. Write a post asking for assessment and/or help at the MDN Discourse forum Learning category. Your post should include: +
      +
    • A descriptive title such as "Assessment wanted for OOJS 1 skill test".
    • +
    • Details of what you have already tried, and what you would like us to do, e.g. if you are stuck and need help, or want an assessment.
    • +
    • A link to the example you want assessed or need help with, in an online shareable editor (as mentioned in step 1 above). This is a good practice to get into — it's very hard to help someone with a coding problem if you can't see their code.
    • +
    • A link to the actual task or assessment page, so we can find the question you want help with.
    • +
    +
  4. +
-- cgit v1.2.3-54-g00ecf