热门标签 | 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这个东西。


推荐阅读
  • 云函数与数据库API实现增删查改的对比
    本文将深入探讨使用云函数和数据库API实现数据操作(增删查改)的不同方法,通过详细的代码示例帮助读者更好地理解和掌握这些技术。文章不仅提供代码实现,还解释了每种方法的特点和适用场景。 ... [详细]
  • 探讨如何修复Visual Studio Code中JavaScript的智能感知和自动完成功能在特定场景下无法正常工作的问题,包括配置检查、语言模式选择以及类型注释的使用。 ... [详细]
  • 本文介绍了如何在React和React Native项目中使用JavaScript进行日期格式化,提供了获取近7天、近半年及近一年日期的具体实现方法。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 本文详细介绍了如何在Kendo UI for jQuery的数据管理组件中,将行标题字段呈现为锚点(即可点击链接),帮助开发人员更高效地实现这一功能。通过具体的代码示例和解释,即使是新手也能轻松掌握。 ... [详细]
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • This post discusses an issue encountered while using the @name annotation in documentation generation, specifically regarding nested class processing and unexpected output. ... [详细]
  • JavaScript中的数组是数据集合的核心结构之一,内置了多种实用的方法。掌握这些方法不仅能提高开发效率,还能显著提升代码的质量和可读性。本文将详细介绍数组的创建方式及常见操作方法。 ... [详细]
  • 本文探讨了如何在Classic ASP中实现与PHP的hash_hmac('SHA256', $message, pack('H*', $secret))函数等效的哈希生成方法。通过分析不同实现方式及其产生的差异,提供了一种使用Microsoft .NET Framework的解决方案。 ... [详细]
  • 鼠标悬停出现提示信息怎么做
    概述–提示:指启示,提起注意或给予提醒和解释。在excel中会经常用到给某个格子增加提醒信息,比如金额提示输入数值或最大长度值等等。设置方式也有多种,简单的,仅为单元格插入批注就可 ... [详细]
  • 本文详细介绍了一种通过MySQL弱口令漏洞在Windows操作系统上获取SYSTEM权限的方法。该方法涉及使用自定义UDF DLL文件来执行任意命令,从而实现对远程服务器的完全控制。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • ElasticSearch 集群监控与优化
    本文详细介绍了如何有效地监控 ElasticSearch 集群,涵盖了关键性能指标、集群健康状况、统计信息以及内存和垃圾回收的监控方法。 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • 在编译BSP包过程中,遇到了一个与 'gets' 函数相关的编译错误。该问题通常发生在较新的编译环境中,由于 'gets' 函数已被弃用并视为安全漏洞。本文将详细介绍如何通过修改源代码和配置文件来解决这一问题。 ... [详细]
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社区 版权所有