首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript面向对象编程-第三版不完全系统解读

JavaScript面向对象编程-第三版不完全系统解读

作者头像
老九君
发布2021-05-31 16:29:00
4250
发布2021-05-31 16:29:00
举报
文章被收录于专栏:老九学堂老九学堂

1、面向对象JavaScript简介

关于JavaScript的百度说法,我们这里就不贴出来了,因为太多了,也找不到标准。烦请大家自行参考,方便自己理解。我们这里只给针对专业权威书籍《Object-Oriented JavaScript, 3rd Edition》(“JavaScript面向对象编程第三版“--这是本人参考翻译)的解读,希望可以帮助大家深入理解现代的JavaScript编程语言的最大强功能--面向对象编程。书中这样描述:

作为计科系程序员出身的我来参考翻译一下,抛转引玉一下,请不要当成标准翻译。不足之处,请大家原谅和帮助补充:在早期的web应用中,只有一个动态界面和响应界面,我们只需要读取静态HTML文本网页就可以了,如果再加上使用一些CSS技术,那么就可以做出更加漂亮的用户界面,随着越来越多的浏览器应用,比如email、日历、对账、购物、绘图、游戏和文本编辑应用。这些这些应用的实现都得通过JavaScript这种Web编程脚本语言来实现。JavaScript从刚开始嵌入到HTML中的简单脚本代码,到现在已经是一个复杂的生态体系了。开发者人员可以使用该脚本语言的面向对象的特点来构建出可重复使用的代码,从而让应用架构更加具有扩展性。

如果我们回顾一下过程和现在的web开发术语言,比如DHTML、Ajax、 Web 2.0、HTML5等,它们实际上是指HTML、CSS和JavaScript-HTML呈现的内容,而CSS是呈现机制,最后JavaScript是行为。换句话说,JavaScript是一种粘合剂,它把以上所述的内容整合在一起,让我们构建一个内容丰富的web应用。然后,这还不是全部, JavaScript还可以做比Web应用还多的功能。

JavaScript程序在一个实体主机环境中运行,而Web浏览器是最常见的玩意,但是它是JavaScript唯一的运行环境。使用JavaScript时,我们可以各种装饰件、应用程序扩展功能和其它的软件功能代码片断。花时间学习JavaScript是一种聪明的投资行为,因为我们可以使用JavaScript这一种编程脚本语言,就可以编程各种可以运行不同平台上的应用,包括移动应用和服务端应用。也就是说,在当今时代,我们可以使用JavaScript脚本 语言进行安全的编程。

1.1、JavaScrtipt历史简介

这种吹牛皮的历史简介原文,我就不贴出来了,下面是我的参考翻译,我们可当成一种趣闻和谈资来与面试官聊天时使用。建议不在面试时正式场合中使用这个历史简介资料。 最初,web应用只是一些科学出版社使用HTML文档来发布科学文档。那个时候还没有图片,但是随着web应用的不断流行和内容扩展,网站所有者觉得应该放更多的内容,他们希望在浏览器中放更多的、更丰富的用户界面和内容,主要原因是因为网站所有者应用减轻服务器压力,帮助服务器做一些表单验证的工作。这个时候出现了Java Applet小程序和LiveScript脚本语言,1995年网景(Netscape)的Brendan Eich把二者技术融合了,用在Netscape 2.0浏览器使用,并且取名JavaScript。 之后,Java Applet小程序没有出名,但是JavaScript却得到了市场的认可。它被做HTML文档中的一个嵌入代码片断来使用,不久微软推出了IE3.0浏览器使用JScript脚本 ,其实就是JavaScript插件的反解版本,以专门符合IE系列浏览器的编码需求。最终经过多方努力,标准版本的ECMAScript出现了,European Computer Manufacturers Association(简称ECMA)创立了ECMA-62标准,该标准作为JavaScript编程语言的不使用浏览器和web网页的核心规范。

下面部分内容需要贴出原文,以帮助大家理解:

因此,我们可以认为JavaScript术语由下面三个部分组成:

  • ECMAScript:核心语言变量、函数、循环等定义。这部分标准是独立于浏览器的,因此该语言可以被用其它环境中使用。
  • Document Object Model (DOM):该规范提供了JavaScript与HTML和XML文档工作的规范,最初,JavaScript提供了对网页有限的访问权限,主要只是针对表单、超链和图片等,进而扩展到针对所有的脚本元素。这导致World Wide Web Consortium (W3C)组织创建了DOM标准,该标准是语言独立的(不再依赖JavaScript)操作结构文档的规范。
  • Browser Object Model (BOM):它们是一组关于浏览器环境的对象,并且不能被当成标准的一部分,直到HTML5标准出现后,有些通过对象才能够实现跨浏览器访问的对象。

在IE6和其它众多浏览器并存的时代,下面是Ajax出现的原文描述:

在Gmail和Google Maps的正式版发布后,富客户端编程出现,这让JavaScript更加成熟和使用方式的唯一性,以及成为一个强大原型面向对象语言。最好的示例证明就是,我们重新探讨一下被所有函数广泛应用和XMLHttpRequest对象,该对象最初由IE发明并且使用,但是接着被其它浏览器相继模仿和实现来使用。XMLHttpRequest对象允许JavaScript发送HTTP请求,并且从服务器获取信息后刷新显示内容,从而达到可以局部更新网页的效果,而不需要像以前一样需要重新加载一次全部的网页内容。由于XMLHttpRequest对象的广泛应用,一种类桌面应用的Web应用诞生了,这全归功于Ajax应用程序的产生。

1.2、JavaScript当今应用

这种吹牛皮的原文,这里就不贴出来了,下面是我的参考翻译。 一个有趣的现象就是JavaScript总是运行在一个宿主环境,Web浏览器只是一种可以使用的宿主环境。JavaScript还可运行在服务器、桌面应用,以及移动设备上。今天我们使用JavaScript可以做以下事情:

  • 创建内容丰富和功能强大的web应用(各种在运行在web浏览器中的应用),除了HTML5编程外,还可以实现应用缓存、客户端存储和数据库交互功能,这样可以使得浏览器编程更强大,从而创建出在线和离线应用程序(比如,QQ桌面应用和腾讯文档在线编辑)。另外,Chrome的WebKit还提供服务工作站和浏览器消息推送功能。
  • 使用Node.js编程服务端代码,这样就可以在Rhino(一种用JavaScript书写的JavaScript引擎 )引擎上运行了。
  • 创建移动应用,比如使用PhoneGap(手机开发平台)或者Titanium(手机开发平台)来开发跨平台的手机应用。比如Firefox OS针对移动手机的全部都是使用JavaScript、HTML和CSS来实现。React Native是Facebook使用和技术,它使用JavaScript可以用来开发iOS和Android和Windows应用。
  • 创建多媒体应用,比如Flash或者Flex都使用ActionScript脚本来开发,而AS是基于ECMAcript标准的脚本语言。
  • 创建命令行工具,以实现脚本自动化管理任务功能,比如我们桌面管理使用Windows Scripting Host (WSH)或者是WebKit的JavaScriptCore脚本,它可以在Mac系列电脑上使用。
  • 用来书写桌面应用的插件,比如Dreamweaver、Photoshop和其它浏览器软件。
  • 创建跨操作系统桌面应用,比如Mozilla的XULRunner和Electron框架技术。Electron被用来创建一些非常流行的桌面应用,比如Slack、Atom和Visual Studio Code.
  • Emscripten,允许使用C/C++编译成asm.js格式,这样可以直接在浏览器内容直接运行。
  • 使用JavaScript用做测试框架编程,比如PhantomJS
  • 以上列表并没有把JavaScript运用说明完全。因为,我们当今的浏览器供应商正在开发功能更强、运行速度更快的JavaScript引擎,这样可让我们应用开人员开发出功能更强大的应用,比如图片处理、音视频处理和游戏开发等。

1.3、Babel-JavaScript编译器(了解)

Babel支持几乎所有ES6规范定义的功能,它被广泛地运用在构建系统、框架和编程语言的模板引擎,并且它有一个好用的命令行和内容read-eval-print loop(REPL)功能。

2、面向对象编程

先给下原文描述:

我来参考翻译一下,不当之处,请大家指正和补充:

在深入讲解JavaScript之前,我们先看一下通常人们所说的面向对象是什么,以及在编程时的要求。下面给OOP最常见的概念:

  • 对象、方法和属性
  • 封装
  • 聚合
  • 重用/继承
  • 多态

2.1、对象(Objects)

原文描述如下:

类比人们生活用语描述如下:

  • 对象通常是名词、比如书、人等
  • 方法是动作、比如读、跑等
  • 属性的值是形容词

2.2、类(Classes)

原文描述如下:

我来参考翻译一下,不当之处,请大家指正和补充:

在实现生活中,类似的东西我们可以根据一些条件进行分类。蜂鸟和鹰都是鸟,因此它们可以被归为Birds类。在OOP编程思想中,一个类被看成一个创建对象(东西)的蓝图或者一个模板。对象的另外一个名称就是实例,所以我们可以说鹰鸟类的一个实例。我们可以使用类来创建不同的对象,因此一个类就变成了一个模板的效果了,而同时对象就是基于该模板创建出来的实例。

不过,JavaScript与精典的OO编程语言,比如C++和Java不同,因为JavaScript没有类的概念,而理解这个是我们正确掌握JavaScript脚本语言的开始。在JavaScript脚本语言中,所有的东西都是基于对象的概念!JavaScript有一个原型符号(notion of prototypes),这个符号也是对象(稍后讨论)。在精典的OO编程语言中,我们可以说某个东西可创建了一个新的对象 ,比如我们叫做Bob的对象,因为Bob是Person类型。而原型OO脚本语言中,我们只能说该类叫做Bob的爸爸(。。。)然后重和它来作为原型来创建一个新的对象叫做Bob

2.3、封装(Encapsulation)

原文如下:

我来参考翻译一下,不当之处,请大家指正和补充:封装是OOP中一个概念,它描述一个对象包含(封装)下在的内容:

  • 数据(以属性形式保存)
  • 以使用方式来使用这些数据(使用方法)

2.4、聚合(Aggregation)

原文描述如下:

我来参考翻译一下,不当之处,请大家指正和补充:

合成几个对象成为一个新的对象,也就是聚合或者组合的概念。把一个复杂的东西拆分成小的组成部分,这样可以更容易管理的处理,当一个问题复杂的时间,我们一定要尽可能的简化问题,简化问题就是把复杂问题拆分成一个小的部分来研究,然后于对这些被分析的部分进行进一步的细拆分,从而让我们对问题进行几个级别的抽象。

2.5、继承(Inheritance)

原文描述如下:

我来参考翻译一下,不当之处,请大家指正和补充:

继承一个种优雅的重用代码的方式。比如,我们有一个抽象对象Person,该对象有data_of_birth,并且还实现了walk、talk、sleep和eat功能。接着,我们需要一个对象叫做Programmer时,我们需要重新实现Person对象所有的属性的方法,如果我们不使用CV大法,那么可以使用一个比较聪明的办法就是让Programmer对象继承Person对象,这样可以极大节约我们的编码时间。而Programmer对象只需要实现一些特殊的功能即可,比如writeCode方法,而其它属性和方法都直接继承自Person对象即可,即可以达到重用Person对象的功能。

2.6、多态(Polymorphism)

原文描述如下:

我来参考翻译一下,不当之处,请大家指正和补充:

前面示例中,一个Programmer对象继承了Person父对象的所有方法,因此它也可以拥有父类所有的行为。假设在我们的代码中有一个变量Bob,并且如果我们不知道Bob是Person对象,还是Programmer对象,但是我们可以呼叫Bob对象上的talk方法,而代码也会执行的。这种能力叫做呼叫不对象上相同的方法,这样产生不同的行为,这种就叫做多态。

3、JavaScript对象详解

我来翻译一下作为大家的参考(不能作为标准):

JavaScript有选择性地采用了精典的面向对象编程方式。面向对象编程是最流行的编程思想,并且它在广泛使用于大多数的编程语言中,比如Java和C++语言。大多数编程语言都实现精典的OOP编程思想要求的功能,但是JavaScript脚本语言没有完全实现精典的OOP编程思想要求的功能。因此,我们来讨论JavaScript是怎样支持OOP编程思想的。

讨论的方向如下:

  • 怎样创建和使用对象
  • 什么是构造函数
  • JavaScript对象的内置类型,以及这些类型可以帮我们做什么

我们使用JavaScript来进行面向对象编程时,就需要着重思考这三个方式的应用问题。

3.1、对象创建

参见原文中的一个示例:

我来参考翻译一下,不当之处,请大家指正和补充:

我们来书写一个对象创建代码:

var hero = {
  breed: 'Turtle', //品种  
  occupation: 'Ninja' //职业
};

我们从这英雄对象的创建得知下面的信息:

  • 对象名叫做hero--它是一个变量名
  • 不同于数组的方括号[]定义,我们使用一对花括号{}来定义对象
  • 对象中包含的元素(属性)使用逗分隔开
  • 键/值对象是使用冒号来表示key: value

键(属性名)可以引号,也可以没有引用表示。比如面的属性写法,它们的效果是一样:

var hero = {occupation: 1};
var hero = {"occupation": 1}; 
var hero = {'occupation': 1};


一般建议对象的属性不加引号(可以少打字儿),但是下面三种情况要加引号:

  • 如果属性名与JavaScript中的保留字相同
  • 如果包含了空格和特殊字符(只要不是字母的字符、数值或者_和$字符)
  • 如果属性名以数值开头

注意:”空“对象

ver hero = {};这句话意思是,该对象不是空的,只是没有使用的意思!

声明一个”空“对象,我们可以给它赋属性的方法,参见下面的示例代码:

var hero = {}; //创建一个空对象
hero.breed = 'turtle'; //给该对象赋值一个属性
hero.name = 'Leonardo'; //给该对象赋值一个属性
hero.sayName = function(){ //给该对象赋值一个方法
    return hero.name; 
}
//使用对象的方法
heor.sayName();
//删除对象的属性
delete hero.name;

3.2、元素、属性、方法和成员

我来参考翻译一下,不当之处,请大家指正和补充:

一个对象的属性可以是一个函数,因为函数就是数据。指向函数的属性又叫做方法。比如talk是一个方法:

var dog = {
    name: 'Benji',
    talk: function(){
        alert('Woof, woof!');
    }
};

3.3、访问一个对象的属性

在JavaScript脚本语言中,有两种方式来访问对象的属性:

  • 使用方括号访问。比如,hero['occupation']
  • 使用点运算符。比如,hero.occupation

点运算符容易理解和书写,但是不能在任何情况下都能使用。如果一个属性名不是一个合法的变量名,那么我们就不能使用点运算符。

再看一个对象的创建

var book = {
    name: 'Catch-22',
    publish: 1961,
    author: {
        firstname: 'Joseph',
        lastname: 'Heller'
    }
};

再看一个呼叫对象方法的示例:

var hero = {
    breed: 'Turtle',
    occupation: 'Ninja',
    say: function(){
        return 'I am ' + hero.occupation;
    }
}
hero.say();

3.4、使用this值

我来参考翻译一下,不当之处,请大家指正和补充:

在前面的示例中,sayName()方法使用hero.name来访问对象hero的name属性。当我们在方法内容中使用时,也就是使用另外一个方式来访问对象的属性,此时该方法可以使用特殊值this

var hero = {
    name: 'Rafaelo',
    sayName: function(){
        return this.name;  //表示当前对象的成员
    }
};
//呼叫对象的方法
hero.sayName();

当我们使用this值时,表示我们就是说该对象或者说是当前对象的意思。

3.5、构造函数

我来参考翻译一下,不当之处,请大家指正和补充:

还有一种创建对象的方式是使用构造函数,参见下面的示例:

function Hero(){
    this.occupation = 'Ninja';
}

为创建使用构造函数创建一个对象,我们需要使用new运算符来实现,参见下面的示例代码:

//呼叫构造函数创建一hero对象
var hero = new Hero(); 
//呼叫该对象的属性
console.log(hero.occupation);

使用构造函数的好处是,它可以接收参数,我们使用带参的构造函数可以创建一个新对象。下在的带参构造函数使用方式:

function Hero(name){
    this.name = name; //非常类似Java类
    this.occupation = 'Ninja';
    this.whoAreYou = function(){
        return "I'm "+ this.name
          + " and I'm a " +
            this.occupation;
    }
}

然后我们就可以使用构造函数来创建不同的对象了,示例代码如下:

var h1 = new Hero('Michelangelo');
var h2 = new Hero('Donatello');
h1.whoAreYou();
h2.whoAreYou();

3.6、全局对象

我来参考翻译一下,不当之处,请大家指正和补充:

我们已经学过一些全局变量(应该避免使用它们),并且我们也知道JavaScript程序在一个宿主环境(比如浏览器中)内运行的。现在是我们知道全局对象的真像的时候了,因为宿主环境提供了一个全局对象,所有全局变量都是该全局对象的属性。

如果我们宿主环境是一个web浏览器,那么该全局对象叫做window。另外一个访问全局对象的方式是在构造函数的外部使用this值。参见下面的示例代码:

var a = 1; //在函数外声明一个全局变量
window.a; //把这个全局变量赋值window全局对象的属性
this.a;//使用this值引擎该全局变量

3.7、构造方法属性(The constructor property)

我来参考翻译一下,不当之处,请大家指正和补充:

当一个对象被创建后,有一个隐含的特殊属性会存在,它就是constructor属性。它包含了一个构造函数引用,并且创建该对象。

继承使用前面的示例代码:

h2.constructor; //声明一个构造方法属性
function Hero(name){ //声明一个构造方法
    this.name = name;
}
//使用构造函数属性创建一个对象 
var h3 = new h2.constructor('Rafeallo');
//打印对象的属性值
console.log(h3.name);

如果一个对象是使用对象字面符创建的,那么constructor就是内置Object()构造函数:

var o = {};
o.constructor;
function Object(){
    ...
};
console.log(o.constructor);

3.8、instanceof运行符

我来参考翻译一下,不当之处,请大家指正和补充:

我们可以使用instanceof运算符来判断一个对象是否是由特定的构造函数创建的:

function Hero(){};
var h = new Hero();
var o = {};
console.log(h instanceof Hero);
console.log(h instanceof Object);
console.log(o instanceof Object)

注意:不能使用h instanceof Heor(),因为不能调用该函数。

3.8、返回对象函数(Functions that return objects)

我来参考翻译一下,不当之处,请大家指正和补充:

除了使用constructor函数和new运算符来创建一个对象外,我们还可以使用普通函数创建一个对象,并且不需要使用new运算符。我们只需要让一个函数返回一个对象就可以了,参见下面的示例:

function factory(name){
    return {
        name: name
    };
}
//下面是使用对象
var o = factory('one');
console.log(o.name);
console.log(o.constructor);

4、原型属性(Prototype)详解

我来参考翻译一下,不当之处,请大家指正和补充:

理解函数对象的原型属性,以及理解原型的工作原理是学习JavaScript脚本语言最重要的部分。简单而之,JavaScript一般被称为是基于原型的对象模型。。。后面略

4.1、原型属性(The prototype property)

我来参考翻译一下,不当之处,请大家指正和补充:

在JavaScript中函数是对象,并且它们还包含方法和属性。比如有些我们已经熟悉apply()和call()函数,有些是属性是length和constructor。而现在我们来了解另外一个函数对象的属性是prototype.

如果我们定义一个简单的函数foo(),那么我们可以查看一下它的属性,示例代码如下:

function foo(a,b){
    return a * b;
}
console.log(foo.length);
console.log(foo.constructor);

prototype属性是一个可以让我们快速定义函数的属性,它的初始值是空对象:

console.log(typeof foo.prototype);

如果我们需要给自己添加该属性,那么使用下面的语句:

foo.prototype = {};
console.log(foo.prototype);

4.2、使用prototype添加方法和属性

下面是一个构造函数定义,它使用this值来添加两个属性和一个方法,代码如下:

function Gadget(name, color) {
  this.name = name;
  this.color = color;
  this.whatAreYou = function () {
    return 'I am a ' + this.color + ' ' + this.name;
  };
}

使用构造函数的prototype属性来添加属性和方法是另外一种给添加功能的方式。下面是我们使用prototype属性来给对象添加属性和方法的示例:

Gadget.prototype.price = 100;
Gadget.prototype.rating = 3;
Gadget.prototype.getInfo = function () {
  return 'Rating: ' + this.rating +
    ', price: ' + this.price;
}

还有一种方式,我们重写prototype,而不是给一个一个对象添加prototype属性,参见下面的示例:

Gadget.prototype = {
    price:100,
    rating: ... //略
};

4.3、使用prototype的方法和属性

所有向prototype的属性和方法与我们使用constructor创建的新对象一样,如果我们使用Gadget()构造函数创建一个newtoy对象,那么我们可以访问所有的已经定义的属性和方法,参见下面的示例:

var newtoy = new Gadget('webcam', 'black');
console.log(newtoy.name);
console.log(newtoy.color);
console.log(newtoy.whatAreYou());
console.log(newtoy.price);
console.log(newtoy.rating);
console.log(newtoy.getInfo());

重要提示:prototype是一直存在的,在JavaScript中对象是通过引用来传递的,因此prototype不能被新对象实例所复制。这在实际开发有中什么意义?如果我们在任意时候修改prototype属性,那么所有对象都会看到变化。比如下面的示例:

Gadget.prototype.get = function(what){
  return this[what];
}
//newtoy对象可以使用get函数的
console.log(newtoy.get('price'));
console.log(newtoy.get('color'));

4.4、自有属性和prototype属性(JavaScript继承的秘密)

如果getInfo()函数是对象内部属性的访问,那么也可以使得Gadget.prototype属性实现相同的效果,示例代码如下:

Gadget.prototype.getInfo = function(){
  return 'Rating' + Gadget.prototype.rating + 
        ', price: ' + Gadget.prototype.price;
};

这个与使用new构造函数创建的对象有什么不同呢?

var newtoy = new Gadget('webcam', 'black');
//当我们点运算.name时,JavScript引擎查寻所有对象是否有一个叫name的属性,如果找到了,那么返回属性值
console.log(newtoy.name);
//如果我们访问.rating时,如果JavaScript引擎在所有对象中没有找到,那么就在构造函数的prototype中查找
console.log(newtoy.rating);
//每个对象都有constructor属性,该属性可以引用被创建的对象,所以有如下相同的代码
console.log(newtoy.constructor === Gadget);
console.log(newtoy.constructor.prototype.rating);

每个对象都有一个constructor属性,而prototype是一个对象,所以它肯定也有constructor属性!换句话说,它也有一个prototype属性。因此,我们得到一个prototype链,最终我们会查找到内置对象Object()是最顶层的父对象。这个就是JavaScript脚本语言的继承实现秘密了。在实际应用中,当我们呼叫newtoy.toString()方法,实际上不是newtoy没有toString()函数,并且它的prototype属性也没有,实际上是我们得到如下结果:

console.log(newtoy.toString()); //"[object Object]"
console.log(newtoy.hasOwnProperty('toString')); //false
console.log(newtoy.constructor.hasOwnProperty('toString')); //false
console.log(newtoy.constructor.prototype.hasOwnProperty('toString')); //false
console.log(Object.hasOwnProperty('toString')); //false
console.log(Object.prototype.hasOwnProperty('toString')); //true


4.5、使用自有属性重写prototype的属性

function Gadget(name){
    this.name = name
}
Gadget.prototype.name = 'mirror';
var toy = new Gadget('camera');
console.log(toy.name); //"camera"
console.log(toy.hasOwnProperty('name')); //true
delete toy.name;
console.log(toy.name); //“mirror”
console.log(toy.hasOwnProperty('name'));  //false

4.6、使用isPrototypeOf()方法

isPrototyoeOf()方法用来判断一个对象是否是为另外一个对象的原型属性。参见下面的示例:

//使用{}创建一个对象monkey
var monkey = {
  hair: true,
  feeds: 'bananas',
  breathes: 'air'
};
//定义一个构造函数Human
function Human(name){
    this.name = name;
}
//给Human的prototype属性赋值
Human.prototype = monkey;
//创建一个george对象
var george = new Human("George");
//判断一下
console.log(monkey.isPrototypeOf(george)); //true

4.7、__proto__链的秘密

秘密链(proto)是现代大多数JavaScript环境的属性,单词proto的前后有两个下划线:

var monkey = {
    feeds: 'bananas',
    breathes: 'air'
};
function Human(){}
Human.prototype = monkey;
var developer = new Human();
developer.feeds = 'pizza';
developer.hacks = 'JavaScript';
console.log(developer.hacks); //"JavaScript"
console.log(developer.feeds); //"pizza"
console.log(developer.breathes); //"air"
console.log(developer.__proto__ === monkey); //true

注意:__proto__与prototype是不一样的,因为__proto__是对象实例的属性,而prototype是结构方法constructor函数的属性,它是用来创建其它对象的。

console.log(typeof developer.__proto__); // "object"
console.log(typeof developer.prototype); // undefined
console.log(typeof developer.constructor.prototype); //"object"

5、继承(Inheritance)

5.1、原型链(Prototype chaining)

我来参考翻译一下,不当之处,请大家指正和补充:

JavaScript的继承机制是通过原型链来实现的,因为每个函数都有一个prototype属性,而这个属性指向一个对象。当一个函数被new运算符调用时,一个对象被创建后然后返回。这个新的对象有一个秘密链,它指向一个prototype对象。这个秘密链(叫做__proto__)允许原型对象的方法和属性被新的对象使用。

我来参考翻译一下,不当之处,请大家指正和补充:

原型对象是一个正式的对象,所以,它也有一个秘密链指向它的原型,由此,有一个被原型链的体系被创建:

这张图显示,A包含一系列属性,其中有一个隐藏属性叫做__proto__属性,它指向另外一个对象B,是B也有一个隐藏属性指另外一个属性C。这个链的结束是以Object.prototype对象为标识,而它也是所有对象父对象。参见下面的示例:

//声明一个Shape构造函数 
function Shape(){
  this.name = 'Shape';
  this.toString = function () {
    return this.name;
  };
}
//声明一个TwoDShape构造函数
function TwoDShape(){
  this.name = '2D shape';
}
//声明一个Traingle
function Triangle(side, height){
  this.name = 'Triangle';
  this.side = side;
  this.height = height;
  this.getArea = function () {
    return this.side * this.height / 2;
  };
}
//上面三个对象,我们可让它们实际继承
TwoDShape.prototype = new Shape(); //TwoDShape继承Shape
Triangle.prototype = new TwoDShape(); //Triangle继承TwoDShape
//使用对象
var my = new Triangle(5,10);
console.log(my.getArea()); //25
console.log(my.constructor === Triangle); //false, 为什么?
//需要我们重写prototype属性如下:
TwoDShape.prototype.constructor = TwoDShape;
Triangle.prototype.constructor = Triangle;
console.log(my.constructor === Triangle); //true

因此,当我们使用prototype属性实现A继承B之后,一定要重写该原型的constructor属性!

5.2、只继承prototype(Inheriting the prototype only)

如果只继承prototype,那么表示所有代码只能在这里使用。比如,继承Shape.prototype对象会比使用new Shape()创建的继承对象好。因为new Shape()只是给我们拥有shape的属性,但是并不表示可以被重用。使用prototype继承有如下好处:

我来参考翻译一下,不当之处,请大家指正和补充:

  • 不需要使用new对象来创建
  • 运行效率主(比如当我们要让对象使用toString()方法,JavaScript引擎查询速度快)

这里我们给一个使用的示例代码如下:

function Shape() {}
// 参数原型
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
       return this.name;
};
function TwoDShape() {}
// 使用原型继承
TwoDShape.prototype = Shape.prototype;
TwoDShape.prototype.constructor = TwoDShape;
// 参数原型
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
    this.side = side;
    this.height = height;
}
// 实现原型继承
Triangle.prototype = TwoDShape.prototype;
Triangle.prototype.constructor = Triangle;
// augment prototype
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function () {
    return this.side * this.height / 2;
};
// 使用构造函数创建对象
var my = new Triangle(5, 10);
console.log(my.getArea());
console.log(my.toString());

此时,同样的对象使用代码my.toString()函数呼叫,在JavaScript引擎首先会从my对象自身找该方法,如果没有,它会从prototype中自省查找。而原型会返回一个指向TwoShape的原型对象来查找,如果没有就指向Shape.prototype原型来查找,直到Object.prototyoe为止。

我们简单的复制原型是非常有效的,但是只是单方向的有效,因为所有子对象的原型和父对象的原型都是指向相同的对象,因此当有一个子对象修改原型属性时,那么父对象也会变化,并且它的兄弟对象也会改变。参见下面的示例代码:

Triangle.prototype.name = 'Triangle';

这句话修改了原来的名称,所以也修改了Shape.prototype.name的值,如果,我们使得new创建一个对象,参见下面的代码:

var s = new Shape();
console.log(s.name); //"Triangle"

这种效果可能会满足我们大多数的使用场景,但是有一些场景是不能满足的。

5.3、临时构造函数(A temporary constructor - new F())

我们通过创建一个空函数F()可以打断原来继承的原型链,这样被创建的对象就不会拥自身的属性了,但是会拥有父对象prototype的所有属性和方法。参见下面的示例代码:

function Shape() {}
// 参数原型
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
    return this.name;
};
function TwoDShape() {}
// 继承
var F = function () {};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
// 参数原型
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height) {
    this.side = side;
    this.height = height;
}
// 继承
var F = function () {};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
// 参数原型
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function () {
    return this.side * this.height / 2;
};

var my = new Triangle(5, 10);
console.log(my.getArea());
console.log(my.toString());

console.log(my.__proto__ === Triangle.prototype); //true
console.log(my.__proto__.constructor === Triangle); //true
console.log(my.__proto__.__proto__ === TwoDShape.prototype);//true
console.log(my.__proto__.__proto__.__proto__.constructor === Shape);//true
console.log(my.__proto__ === Shape); //false
console.log(my.__proto__ === TwoDShape);//false

//使用构造函数来创建对象--这个就是临时构造函数优化的效果
var s = new Shape();
console.log(s.name); //Shape
console.log("I am a " + new TwoDShape()); //I am a 2D shape

5.4、对象继承对象(Objects inherit from objects)(了解)

除了上面讲解可以创建对象的方法外(使用new 构造函数的方式),我们还可以让对象继承其它的对象,只使用对象语法的方式来创建。参见下面的格式:

//声明一个继承函数
function extendCopy(p) {
    var c = {};
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p; //uber是超级的意思
    return c;
}
//声明一个被继承的对象shape
var shape = {
    name: 'Shape',
    toString: function () {
        return this.name;
    }
};

//声明一个twoDee对象,然后继承shape对象
var twoDee = extendCopy(shape);
twoDee.name = '2D shape';
twoDee.toString = function () {
    return this.uber.toString() + ', ' + this.name;
};
//声明一个对象triangle继承twoDee对象
var triangle = extendCopy(twoDee);
triangle.name = 'Triangle';
triangle.getArea = function () {
    return this.side * this.height / 2;
};

//使用对象
triangle.side = 5;
triangle.height = 10;
console.log(triangle.getArea()); //25
console.log(triangle.toString());//"Shape, 2D shape, Triangle"

6、综合示例

综合示例是本大黍根据对该书描述的理解,为大家制作的应用级别示例,希望能够给大家带来学以致用的帮助效果。不足之处,请大家补充。

6.1、class关键字声明类

使用class关键字来声明一个类

class Person {

    //声明类的构造方法
    constructor(name) {
        this.name = name;
    }

    // 等于同于PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
}
//使用一个类语法创建一个该类的实例
let person = new Person("CSS");
person.sayName();   
//使用控制台来判断该对象的类型
console.log(person instanceof Person);     // true
console.log(person instanceof Object);          // true
console.log(typeof Person);                    // "function"
console.log(typeof Person.prototype.sayName);  // "function"

6.2、使用extends继承父类

//定义一个普通类(父类)
    class Book {
        //书写一个带参数的构造方法
        constructor (title, pages, isbn) {
            this.title = title;
            this.pages = pages;
            this.isbn = isbn;
        }
        //定义一个成员方法
        printIsbn(){
            console.log(this.isbn);
        }
    }

    //定义一个子类,它继承Book父类
    class ITBook extends Book {
        //重写构造方法
        constructor (title, pages, isbn, technology) {
            super(title, pages, isbn);
            this.technology = technology;
        }
        //定义一个子类的成员方法
        printTechnology(){
            console.log(this.technology);
        }
    }
    //使用子类来创建一个该类的实例
    let jsBook = new ITBook('Learning JS Algorithms', '200', '1234567890', 'JavaScript');
    //控制台打印一下该类实例的成员变小量的方法
    console.log(jsBook.title);
    console.log(jsBook.printTechnology());

6.3、getter和setter方法使用

 
   //声明一个类来演示getter and setters方法的声明与使用
    class Person {
        //定义该类的构造方法
        constructor (name) {
            this._name = name;
        }
        //使用get关键字来定义一个getter方法
        get name() {
            return this._name;
        }
        //使用set关键字来定义一个setter方法
        set name(value) {
            this._name = value;
        }
    }
    //使用Person创建一个该类的实例
    let lotrChar = new Person('Frodo');
    console.log(lotrChar.name);
    //给setter方法
    lotrChar.name = 'Gandalf';
    //呼叫getter方法
    console.log(lotrChar.name);
    //直接呼叫成员变量来赋值
    lotrChar._name = 'Sam';
    //呼叫getter方法
    console.log(lotrChar.name);

6.4、使用super关键

//使用class声明一个Ship类,它作为父类来使用
    class Ship{
        //声明该类的构造方法
        constructor(name, type, color){
            this.name = name;
            this.type = type;
            this.color = color;
        }
        //分别定义三个成员方法
        shipName(){
            return 'I am ' + this.name;
        }
        shipType(){
            return   'I am type: ' + this.type;
        }
        shipColor(){
            return   'My color is ' + this.color;
        }
    }

    //声明一个子类,它继承Ship父类
    class SpaceShip extends Ship{
        //使用关键字super重写该类的构造方法
        constructor(type, name, color){
            super(type, name, color)
        }
        //声明三个自己的成员方法
        spaceShipName(){
            return super.shipName();
        }
        spaceShipType(){
            return super.shipType();
        }
        spaceShipColor(){
            return super.shipColor();
        }
    }
    //使用子类声明一个该类的实例
    let planetExpress = new SpaceShip('Planet Express Ship', 'Delivery Ship' ,'Green');

    console.log(planetExpress.spaceShipName()); // returns I am Planet Express Ship
    console.log(planetExpress.shipType());  // return I am type: 'Delivery Ship
    console.log(planetExpress.spaceShipColor()); // returns My color is Green

6.5、迭代器

迭代器(Iterator)并非JavaScript的特性,而是一种概念,需要我们自己实现。

6.5.1、数组遍历与迭代器语法对比

 //声明一个类
    class ArrayIterator {
        //声明该类的构造方法
        constructor(array) {
            this.array = array.map(item => item).sort();
            this.index = 0;
        }
        //声明一个next迭代方法
        next() {
            //声明一个返回对象
            let result = { value: undefined, done: true };
            //使用数组方法迭代数组中的元素
            if (this.index < this.array.length) {
                result.value = this.array[this.index];
                result.done = false;
                this.index++;
            }
            return result;
        }
    }

    //声明另外一个类
    class TaskList {
        constructor() {
            this.tasks = [];
        }
        //使用不定参数定义一个addTask方法
        addTasks(...tasks) {
            this.tasks = this.tasks.concat(tasks);
        }
        //使用iterator语法来创建一个迭代器对象
        [Symbol.iterator]() {
            return new ArrayIterator(this.tasks);
        }
    }
    //使用TaskList类创建一个它的实例
    let taskList = new TaskList();
    //添加集合添加元素
    taskList.addTasks('Learn JS', 'Learn ES6', 'Buy products');
    //使用foreach语法迭代该集合对象
    for (let task of taskList) {
        console.log(task);
    }

6.5.2、Fibonacci数列迭代实现

  
 //声明一个单例对象
    let fibObj = {
        one: 0,
        two: 1,
        temp: 0,
        //声明一个迭代器方法
        [Symbol.iterator](){
            return this;
        },
        //声明一个next方法实现成员遍历
        next(){
            this.temp = this.two;
            this.two = this.temp + this.one;
            this.one = this.temp;
            return {value: this.two}
        }
    }
    //使用foreach语法实现迭代遍历
    for(let I = 0 ; I < 1000; I++){
        console.log(fibObj.next().value) //1,2,3,5,8.....
    }

6.5.3、自定义迭代器

    
//声明一个单例对象
    let countdown = {
        max: 3,
        //声明它的成员为迭代器方法
        [Symbol.iterator]() {
            return this;
        },
        //声明next迭代方法
        next() {
            if(this.max == undefined){
                this.max = max;
            }else if(this.max > -1){
                return {value: this.max --};
            }else{
                return {done: true};
            }
        }
    };
    //使用foreach语法实现迭代效果
    for (let i of countdown) {
        console.log(i);
    }

6.6、JSON内置对象使用

6.6.1、把类转换成JSON对象

  
  //声明一个Person类
    class Person {
        //定义该类的构造方法
        constructor(firstName, lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.kind = "Person";
        }
        name() { return this.firstName + " " + this.lastName; }
    }
    var person1 = new Person("CSS", "HTML");
    console.log(person1);
    //使用JSON对象转换person1实例为字符串
    json = JSON.stringify(person1);
    console.log(json);
    console.log(typeof json);
    //解析json对象
    obj = JSON.parse(json);
    console.log(obj);
    console.log(typeof obj); // 注意:Person类型被解析后,它的类型会丢失

    // 使用解析后对象做为构造方法的参数
    var person2 = new Person(obj.firstName, obj.lastName);
    console.log(person2);

    // 对象析构造的语法
    var {firstName, lastName} = JSON.parse(json);
    console.log(firstName + " " + lastName);
    var person3 = new Person(firstName, lastName);
    console.log(person3);

6.6.2、把Object对象转换成字符串

   
 //声明一个单例对象
    var fred1 = {
        firstName: "CSS",
        lastName: "HTML",
        kind: "Language",
        name: function() { return this.firstName + " " + this.lastName; }
    };

    console.log(fred1);
    //把Object对象转换成json字符串
    let json = JSON.stringify(fred1);
    console.log(json);
    console.log(typeof json);
    //把JSON字符串转换成对象
    let obj = JSON.parse(json);
    console.log(obj);
    console.log(typeof obj);

6.7、Generator函数

简单来说,生成器(Generator)就是一个自带迭代器(Iterator)的函数。该函数可以使用function关键字与*号一起定义,也可以使用GeneratorFunction构造方法来定义。

6.7.1、*定义generator函数


    //使用functiont *来定义一个generator函数
    function *myGen(){
        yield 'generator function' //yield一下函数
    }
    let iterator = myGen();
    console.log(iterator.next()); //Object {value: "generator function", done: false}
    console.log(iterator.next()); //Object {value: undefined, done: true}

可以看到,我们每调用一次it.next()语句,就会运行一个yield表达式,直到所有的yield表达示全部运行完毕。如此一来,就具有了分段执行一个函数的能力。

6.7.2、使用 GeneratorFunction函数

   
 //声明一个GeneratorFunction函数模板类
    let GeneratorFunction = Object.getPrototypeOf(function*(){}).constructor
    //使用该函数模板类创建一个该模板类的实例
    let myGenFunction = new GeneratorFunction('value',   'yield value');
    //声明一个函数对象,并且调用该函数
    let myGenIterator = myGenFunction();
    //呼叫它的next迭代方法
    console.log(myGenIterator.next()); //Object {value: undefined, done: false}
    console.log(myGenIterator.next()); //Object {value: undefined, done: true}

7、总结

面向对象编程技术是现代编程技术的核心,每一种编程语言都会秉承这种编程思想进行演化。

我们JavaScript编程语言从曾经不起眼的一个浏览器客户端脚本技术,发展成当今最热门的全栈编程语言之一,它也从最初的使用对象的编程思想演变到面向对象编程的思想。

因此,作为我们开发人员或者未来想成为开发人员的来说,它是非常重要的技术。我们应用JavaScript的面向对象编程思想,大黍也觉得大家必须熟悉掌握这种编程技术,当我们通过一门编程语言掌握了面向对象编程之后,就可以触类旁通,在未来的实际开发中快速掌握其他的编程语言。

<END>

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老九学堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、面向对象JavaScript简介
    • 1.1、JavaScrtipt历史简介
      • 1.2、JavaScript当今应用
        • 1.3、Babel-JavaScript编译器(了解)
        • 2、面向对象编程
          • 2.1、对象(Objects)
            • 2.2、类(Classes)
              • 2.3、封装(Encapsulation)
                • 2.4、聚合(Aggregation)
                  • 2.5、继承(Inheritance)
                    • 2.6、多态(Polymorphism)
                    • 3、JavaScript对象详解
                      • 3.1、对象创建
                        • 3.2、元素、属性、方法和成员
                          • 3.3、访问一个对象的属性
                            • 3.4、使用this值
                              • 3.5、构造函数
                                • 3.6、全局对象
                                  • 3.7、构造方法属性(The constructor property)
                                    • 3.8、instanceof运行符
                                      • 3.8、返回对象函数(Functions that return objects)
                                      • 4、原型属性(Prototype)详解
                                        • 4.1、原型属性(The prototype property)
                                          • 4.2、使用prototype添加方法和属性
                                            • 4.3、使用prototype的方法和属性
                                              • 4.4、自有属性和prototype属性(JavaScript继承的秘密)
                                                • 4.5、使用自有属性重写prototype的属性
                                                  • 4.6、使用isPrototypeOf()方法
                                                    • 4.7、__proto__链的秘密
                                                    • 5、继承(Inheritance)
                                                      • 5.1、原型链(Prototype chaining)
                                                        • 5.2、只继承prototype(Inheriting the prototype only)
                                                          • 5.3、临时构造函数(A temporary constructor - new F())
                                                            • 5.4、对象继承对象(Objects inherit from objects)(了解)
                                                            • 6、综合示例
                                                              • 6.1、class关键字声明类
                                                                • 6.2、使用extends继承父类
                                                                  • 6.3、getter和setter方法使用
                                                                    • 6.4、使用super关键
                                                                      • 6.5、迭代器
                                                                        • 6.5.1、数组遍历与迭代器语法对比
                                                                        • 6.5.2、Fibonacci数列迭代实现
                                                                        • 6.5.3、自定义迭代器
                                                                      • 6.6、JSON内置对象使用
                                                                        • 6.6.1、把类转换成JSON对象
                                                                        • 6.6.2、把Object对象转换成字符串
                                                                      • 6.7、Generator函数
                                                                        • 6.7.1、*定义generator函数
                                                                        • 6.7.2、使用 GeneratorFunction函数
                                                                    • 7、总结
                                                                    相关产品与服务
                                                                    云开发 CloudBase
                                                                    云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
                                                                    领券
                                                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档