热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

js温故而知新7(面向对象编程)——进修廖雪峰的js教程

JavaScript不辨别类和实例的观点,而是经由过程原型(prototype)来完成面向对象编程。原型是指当我们想要建立xiaoming这个详细的门生时,我们并没有一个Stude

Javascript不辨别类和实例的观点,而是经由过程原型(prototype)来完成面向对象编程。

原型是指当我们想要建立xiaoming这个详细的门生时,我们并没有一个Student范例可用。那怎么办?恰好有这么一个现成的对象:

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

我们看这个robot对象有名字,有身高,还会跑,有点像小明,痛快就依据它来“建立”小明得了!

因而我们把它改名为Student,然后建立出xiaoming:

var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
var xiaoming = {
name: '小明'
};
xiaoming.__proto__ = Student;

注重末了一行代码把xiaoming的原型指向了对象Student,看上去xiaoming似乎是从Student继承下来的:

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

xiaoming有自身的name属性,但并没有定义run()要领。不过,由于小明是从Student继承而来,只需Student有run()要领,xiaoming也能够挪用:

《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

Javascript的原型链和Java的Class辨别就在,它没有“Class”的观点一切对象都是实例,所谓继承关联不过是把一个对象的原型指向另一个对象罢了

假如你把xiaoming的原型指向其他对象:

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

如今xiaoming已没法run()了,他已变成了一只鸟:

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

在JavaScrip代码运转时代,你能够把xiaoming从Student变成Bird,或许变成任何对象。

请注重,上述代码仅用于演示目标。在编写Javascript代码时,不要直接用obj.__proto__去转变一个对象的原型,而且,低版本的IE也没法运用__proto__。Object.create()要领能够传入一个原型对象,并建立一个基于该原型的新对象,然则新对象什么属性都没有,因而,我们能够编写一个函数来建立xiaoming:

// 原型对象:
var Student = {
name: 'Robot',
height: 1.2,
run: function () {
console.log(this.name + ' is running...');
}
};
function createStudent(name) {
// 基于Student原型建立一个新对象:
var s = Object.create(Student);
// 初始化新对象:
s.name = name;
return s;
}
var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true

建立对象

Javascript对每一个建立的对象都邑设置一个原型,指向它的原型对象

当我们用obj.xxx接见一个对象的属性时,Javascript引擎先在当前对象上查找该属性,假如没有找到,就到其原型对象上找,假如还没有找到,就一向上溯到Object.prototype对象,末了,假如还没有找到,就只能返回undefined。

比方,建立一个Array对象:

var arr = [1, 2, 3];

其原型链是:

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

Array.prototype定义了indexOf()、shift()等要领,因而你能够在一切的Array对象上直接挪用这些要领。

当我们建立一个函数时:

function foo() {
return 0;
}

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

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

由于Function.prototype定义了apply()等要领,因而,一切函数都能够挪用apply()要领。

很轻易想到,假如原型链很长,那末接见一个对象的属性就会由于花更多的时候查找而变得更慢,因而要注重不要把原型链搞得太长。

组织函数

除了直接用{ … }建立一个对象外,Javascript还能够用一种组织函数的要领来建立对象。它的用法是,先定义一个组织函数:

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

这确实是一个一般函数,然则在Javascript中,能够用关键字new来挪用这个函数,并返回一个对象:

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

注重,假如不写new,这就是一个一般函数,它返回undefined。然则,假如写了new,它就变成了一个组织函数,它绑定的this指向新建立的对象,并默许返回this,也就是说,不须要在末了写return this;。

新建立的xiaoming的原型链是:

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

也就是说,xiaoming的原型指向函数Student的原型。假如你又建立了xiaohong、xiaojun,那末这些对象的原型与xiaoming是一样的:

xiaoming ↘
xiaohong -→ Student.prototype ----> Object.prototype ----> null
xiaojun ↗

用new Student()建立的对象还从原型上获得了一个constructor属性,它指向函数Student自身:

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

看晕了吧?用一张图来示意这些杂乱无章的关联就是:
《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

赤色箭头是原型链。注重,Student.prototype指向的对象就是xiaoming、xiaohong的原型对象,这个原型对象自身另有个属性constructor,指向Student函数自身。
别的,函数Student恰好有个属性prototype指向xiaoming、xiaohong的原型对象,然则xiaoming、xiaohong这些对象可没有prototype这个属性,不过能够用__proto__这个非标准用法来检察。

如今我们就以为xiaoming、xiaohong这些对象“继承”自Student。

不过另有一个小题目,注重视察:

xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false

xiaoming和xiaohong各自的name差别,这是对的,不然我们没法辨别谁是谁了。

xiaoming和xiaohong各自的hello是一个函数,但它们是两个差别的函数,虽然函数称号和代码都是雷同的!

假如我们经由过程new Student()建立了许多对象,这些对象的hello函数实际上只须要同享同一个函数就能够了,如许能够节约许多内存。

要让建立的对象同享一个hello函数,依据对象的属性查找准绳,我们只需把hello函数移动到xiaoming、xiaohong这些对象配合的原型上就能够了,也就是Student.prototype:

《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

修正代码以下:

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

用new建立基于原型的Javascript的对象就是这么简朴!

遗忘写new怎么办

假如一个函数被定义为用于建立对象的组织函数,然则挪用时遗忘了写new怎么办?

在strict形式下,this.name =name将报错,由于this绑定为undefined,在非strict形式下,this.name =name不报错,由于this绑定为window,因而无意间建立了全局变量name,而且返回undefined,这个效果更蹩脚。

所以,挪用组织函数万万不要遗忘写new。为了辨别一般函数和组织函数,根据商定,组织函数首字母应该大写,而一般函数首字母应该小写,如许,一些语法搜检东西如jslint将能够帮你检测到漏写的new。
末了,我们还能够编写一个createStudent()函数,在内部封装一切的new操纵。一个经常使用的编程形式像如许:

function Student(props) {
this.name = props.name || '匿名'; // 默许值为'匿名'
this.grade = props.grade || 1; // 默许值为1
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
function createStudent(props) {
return new Student(props || {})
}

这个createStudent()函数有几个庞大的长处:一是不须要new来挪用,二是参数异常天真,能够不传,也能够这么传

var xiaoming = createStudent({
name: '小明'
});
xiaoming.grade; // 1

假如建立的对象有许多属性,我们只须要通报须要的某些属性,剩下的属性能够用默许值。由于参数是一个Object,我们无需影象参数的递次。假如恰好从JSON拿到了一个对象,就能够直接建立出xiaoming。

原型继承

先回忆Student组织函数:

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

如今,我们要基于Student扩展出PrimaryStudent,能够先定义出PrimaryStudent:

function PrimaryStudent(props) {
// 挪用Student组织函数,绑定this变量:
Student.call(this, props);
this.grade = props.grade || 1;
}

然则,挪用了Student组织函数不等于继承了Student,PrimaryStudent建立的对象的原型是:

new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null

必需想办法把原型链修正为:

new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null

如许,原型链对了,继承关联就对了。新的基于PrimaryStudent建立的对象不但能挪用PrimaryStudent.prototype定义的要领,也能够挪用Student.prototype定义的要领。

假如你想用最简朴粗犷的要领这么干:

PrimaryStudent.prototype = Student.prototype;

是不可的!假如如许的话,PrimaryStudent和Student同享一个原型对象,那还要定义PrimaryStudent干啥?
我们必需借助一个中心对象来完成准确的原型链,这个中心对象的原型要指向Student.prototype。为了完成这一点,参考道爷(就是发明JSON的谁人道格拉斯)的代码,中心对象能够用一个空函数F来完成:

// PrimaryStudent组织函数:
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 空函数F:
function F() {
}
// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;
// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型恰好指向Student.prototype:
PrimaryStudent.prototype = new F();
// 把PrimaryStudent原型的组织函数修复为PrimaryStudent:
PrimaryStudent.prototype.cOnstructor= PrimaryStudent;
// 继承在PrimaryStudent原型(就是new F()对象)上定义要领:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// 建立xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2
// 考证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true
// 考证继承关联:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true

instanceof

instanceof 用于推断一个变量是不是是某个对象的实例,如 var a=new Array();alert(a instanceof Array); 会返回 true,同时 alert(a instanceof Object) 也会返回 true;这是由于 Array 是 object 的子类。再如:function test(){};var a=new test();alert(a instanceof test) 会返回true.

谈到 instanceof 我们要多插进去一个题目,就是 function 的 arguments,我们人人或许都以为 arguments 是一个 Array,但假如运用 instaceof 去测试会发明 arguments 不是一个 Array 对象,只管看起来很像。

别的:

测试 var a=new Array();if (a instanceof Object) alert(‘Y’);else alert(‘N’);

得’Y’.

但 if (window instanceof Object) alert(‘Y’);else alert(‘N’);

得’N’.

所以,这里的 instanceof 测试的 object 是指 js 语法中的 object,不是指 dom 模子对象。

运用 typeof 会有些辨别.

alert(typeof(window)) 会得 object.

《js温故而知新7(面向对象编程)——进修廖雪峰的js教程》

注重,函数F仅用于桥接,我们仅建立了一个new F()实例,而且,没有转变原有的Student定义的原型链。

假如把继承这个动作用一个inherits()函数封装起来,还能够隐蔽F的定义,并简化代码:

function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.cOnstructor= Child;
}
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 完成原型继承链:
inherits(PrimaryStudent, Student);
// 绑定其他要领到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};

小结

Javascript的原型继承完成体式格局就是:

定义新的组织函数,并在内部用call()挪用愿望“继承”的组织函数,并绑定this;
借助中心函数F完成原型链继承,最好经由过程封装的inherits函数完成;
继承在新的组织函数的原型上定义新要领。

class继承

在上面的章节中我们看到了Javascript的对象模子是基于原型完成的,特点是简朴,瑕玷是明白起来比传统的类-实例模子要难题,最大的瑕玷是继承的完成须要编写大批代码,而且须要准确完成原型链。
有无更简朴的写法?有!

新的关键字class从ES6最先正式被引入到Javascript中。class的目标就是让定义类更简朴。

我们先回忆用函数完成Student的要领:

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

假如用新的class关键字来编写Student,能够如许写:

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

比较一下就能够发明,class的定义包含了组织函数constructor和定义在原型对象上的函数hello()(注重没有function关键字),如许就避免了Student.prototype.hello = function () {…}如许疏散的代码。

末了,建立一个Student对象代码和前面章节完整一样:

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

class继承

用class定义对象的另一个庞大的优点是继承更方便了。想想我们从Student派生一个PrimaryStudent须要编写的代码量。如今,原型继承的中心对象,原型对象的组织函数等等都不须要斟酌了,直接经由过程extends来完成:

class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super挪用父类的组织要领!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}

注重PrimaryStudent的定义也是class关键字完成的,而extends则示意原型链对象来自Student。子类的组织函数能够会与父类不太雷同,比方,PrimaryStudent须要name和grade两个参数,而且须要经由过程super(name)来挪用父类的组织函数,不然父类的name属性没法一般初始化。

PrimaryStudent已自动获得了父类Student的hello要领,我们又在子类中定义了新的myGrade要领。

ES6引入的class和原有的Javascript原型继承有什么辨别呢?实际上它们没有任何辨别,class的作用就是让Javascript引擎去完成本来须要我们自身编写的原型链代码。简而言之,用class的优点就是极大地简化了原型链代码。

你一定会问,class这么好用,能不能如今就用上?

如今用还早了点,由于不是一切的主流浏览器都支撑ES6的class。假如一定要如今就用上,就须要一个东西把class代码转换为传统的prototype代码,能够尝尝Babel这个东西。


推荐阅读
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
author-avatar
lluuaalulua619
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有